Blocked by #196.
A list/grid switch on /competitions. List is the dense deadline-scanning view; grid is the browsing view.
Files
src/components/competitions/view-toggle.tsx - new, 'use client'
src/components/competitions/competitions-browser.tsx - consume it
src/components/competitions/competition-card.tsx - support both variants
Nothing like this exists in the repo yet
Grep for viewMode|view_mode|LayoutGrid|toggle.*view across src/ returns zero hits. The nearest relatives:
src/components/docs/docs-index-list.tsx:8-12 - its comment records that a vertical list replaced an icon-card grid
src/components/map/results-list.tsx:29 - a generic toggle?: { label, onClick } prop used for "Show all"
Build it once here properly, because #205 needs the same control for the docs index and should reuse this component rather than growing a second one.
Behaviour
- Two-state segmented control, list and grid, with
aria-pressed and a visible label or a title, not an unlabelled icon pair
- Default is list: deadlines are the reason people come to this page and a list scans faster
- Persist in
localStorage under a namespaced key, read it in an effect so the server render is deterministic and hydration stays clean
- Wrap the read in
try/catch; localStorage throws outright in some privacy modes
Card variants
List: full width, description clamped to three lines, four meta pills in a row, countdown on the right.
Grid: sm:grid-cols-2 lg:grid-cols-3, description clamped to two lines, meta pills wrapping, countdown under the title. Cards in a row must be equal height, so the save row pins to the bottom.
Same component with a variant prop, not two components that drift apart.
Acceptance criteria
Blocked by #196.
A list/grid switch on
/competitions. List is the dense deadline-scanning view; grid is the browsing view.Files
src/components/competitions/view-toggle.tsx- new,'use client'src/components/competitions/competitions-browser.tsx- consume itsrc/components/competitions/competition-card.tsx- support both variantsNothing like this exists in the repo yet
Grep for
viewMode|view_mode|LayoutGrid|toggle.*viewacrosssrc/returns zero hits. The nearest relatives:src/components/docs/docs-index-list.tsx:8-12- its comment records that a vertical list replaced an icon-card gridsrc/components/map/results-list.tsx:29- a generictoggle?: { label, onClick }prop used for "Show all"Build it once here properly, because #205 needs the same control for the docs index and should reuse this component rather than growing a second one.
Behaviour
aria-pressedand a visible label or atitle, not an unlabelled icon pairlocalStorageunder a namespaced key, read it in an effect so the server render is deterministic and hydration stays cleantry/catch;localStoragethrows outright in some privacy modesCard variants
List: full width, description clamped to three lines, four meta pills in a row, countdown on the right.
Grid:
sm:grid-cols-2 lg:grid-cols-3, description clamped to two lines, meta pills wrapping, countdown under the title. Cards in a row must be equal height, so the save row pins to the bottom.Same component with a
variantprop, not two components that drift apart.Acceptance criteria
localStorageblocked: defaults to list, no console erroraria-pressed