Mobile UI pass: responsive tables, deck-view drill-down, dialog fixes

- Library/Card Search/Deck tables stack into dense card-style rows below
  720px instead of scrolling horizontally (floated thumbnail, wrapping
  chips for MV/Owned/color pips).
- Deck view: fix desktop min-widths and a non-wrapping button row that
  forced horizontal scroll on phones; split the deck list and the
  deck-building pane into separate drill-down screens with a back button
  so a long deck list doesn't bury the builder.
- Suggested Cards modal: fix action buttons crushing the card name down to
  one word per line by giving them their own full-width row.
- Add a close (X) button to the shared dialog title bar — mobile has no
  Escape key and wide dialogs leave little backdrop to tap.
- Info tab, bottom-nav loading/safe-area fixes, and Gradle NDK_HOME
  inheritance fix carried over from the Android bring-up work.
This commit is contained in:
2026-08-02 16:10:25 -05:00
parent 94e4f25ec4
commit 663af32562
10 changed files with 646 additions and 99 deletions

View File

@@ -54,6 +54,10 @@ export function LibraryBrowser({
const [inDeck, setInDeck] = useState<number | "">("");
const [location, setLocation] = useState("");
const [oracleText, setOracleText] = useState("");
// Collapsed by default (relevant on narrow/mobile screens only — see
// .filters-more's CSS, which is always expanded above that breakpoint
// regardless of this).
const [filtersExpanded, setFiltersExpanded] = useState(false);
const [facets, setFacets] = useState<LibraryFacets>(NO_FACETS);
const [decks, setDecks] = useState<Deck[]>([]);
@@ -321,14 +325,14 @@ export function LibraryBrowser({
<tbody>
{cards.map((c) => (
<tr key={c.oracle_id}>
<td>
<td className="cell-check">
<input
type="checkbox"
checked={selected.has(c.oracle_id)}
onChange={() => toggleOne(c.oracle_id)}
/>
</td>
<td>
<td className="cell-thumb">
<CardThumb card={c} />
</td>
<td>
@@ -336,8 +340,11 @@ export function LibraryBrowser({
</td>
<td className="card-name">{c.name}</td>
<td className="card-type">{c.type_line}</td>
<td>{c.mana_value}</td>
<td className={c.quantity_assigned > c.quantity_owned ? "over-assigned" : ""}>
<td data-label="MV">{c.mana_value}</td>
<td
data-label="Owned"
className={c.quantity_assigned > c.quantity_owned ? "over-assigned" : ""}
>
<span className="row" style={{ gap: 8 }}>
<QtyStepper
value={c.quantity_owned}
@@ -353,7 +360,7 @@ export function LibraryBrowser({
)}
</span>
</td>
<td>{deckOrLocation(c)}</td>
<td data-label="Decks / Location">{deckOrLocation(c)}</td>
</tr>
))}
</tbody>
@@ -522,6 +529,7 @@ export function LibraryBrowser({
<label>Name contains</label>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="e.g. bolt" />
</div>
<div className={`filters-more ${filtersExpanded ? "expanded" : ""}`}>
<div className="field">
<label>Playable in commander (color identity ⊆)</label>
<ColorToggles value={commander} onChange={setCommander} />
@@ -600,6 +608,13 @@ export function LibraryBrowser({
/>
</div>
</div>
</div>
<button
className="filters-toggle"
onClick={() => setFiltersExpanded((v) => !v)}
>
{filtersExpanded ? "▴ Fewer filters" : "▾ More filters"}
</button>
<div className="workspace-bar">
<label className="workspace-bar-check" title="Select all filtered cards">