diff --git a/src/components.tsx b/src/components.tsx index 1692235..01e663c 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -1261,9 +1261,33 @@ function PrintingsSection({ ); } +/** Below this many printings the picker skips its filter box. */ +const PRINTING_FILTER_MIN = 7; + +/** Whether a printing matches a filter query: every word must start one of + * the things the picker shows for it — set code, collector number, year, + * rarity, language, finish. Prefixes rather than substrings, so "foil" + * doesn't also match every nonfoil printing. */ +function printingMatches(p: PrintingRef, query: string): boolean { + const fields = [ + p.set_code, + p.collector_number, + `#${p.collector_number}`, + p.released_at, + p.rarity, + p.lang, + ...p.finishes.map((f) => FINISH_LABELS[f] ?? f), + ].map(fold); + return fold(query) + .split(/\s+/) + .filter(Boolean) + .every((word) => fields.some((f) => f.startsWith(word))); +} + /** * A grid of a card's printings to pick from. With `chooseFinish`, each * printing also offers its finishes (clicking the art takes the first). + * Long lists (basic lands run to hundreds) get a filter box. * * Opens above the click preview, which closes on any Escape it hears on * `window` — so this listens in the capture phase and stops the event there, @@ -1294,6 +1318,8 @@ function PrintingPicker({ return () => window.removeEventListener("keydown", onKey, true); }, [onCancel]); useBackButton(onCancel); + const [query, setQuery] = useState(""); + const shown = printings.filter((p) => printingMatches(p, query)); return createPortal(
{chooseFinish ? `Add a printing of ${name}` : `Show ${name} as`}
+ {printings.length >= PRINTING_FILTER_MIN && ( +
+ setQuery(e.target.value)} + placeholder="Filter by set, number, year, rarity, finish…" + autoFocus + /> + + {shown.length === printings.length ? printings.length : `${shown.length} of ${printings.length}`} + +
+ )} + {shown.length === 0 &&

No printings match.

}
- {printings.map((p) => { + {shown.map((p) => { const finishes = p.finishes.length > 0 ? p.finishes : ["nonfoil"]; const meta = [ p.released_at.slice(0, 4), diff --git a/src/styles.css b/src/styles.css index b8bc8ec..33930ee 100644 --- a/src/styles.css +++ b/src/styles.css @@ -3949,6 +3949,16 @@ button.pull-action { .printing-picker-backdrop { z-index: 500; } +.printing-filter { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-3); +} +.printing-filter input { + flex: 1; + min-width: 0; +} .printing-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(130px, 40vw), 1fr));