@@ -10,6 +10,9 @@ import {
1010} from "@/lib/task-filter" ;
1111import { TagChip } from "@/components/tag-chip" ;
1212import { TaskCard } from "@/components/task-card" ;
13+ import { TaskRow } from "@/components/task-row" ;
14+ import clsx from "@/components/utils/clsx" ;
15+ import { IconViewGrid , IconViewList } from "@/components/icons" ;
1316import { formatMaturityLabel } from "@/components/maturity-badge" ;
1417
1518function FacetSection ( {
@@ -53,15 +56,58 @@ function FacetSection({
5356 ) ;
5457}
5558
59+ function ViewToggle ( {
60+ view,
61+ setView
62+ } : {
63+ view : "list" | "cards" ;
64+ setView : ( v : "list" | "cards" ) => void ;
65+ } ) {
66+ return (
67+ < div
68+ className = "inline-flex rounded-xl border border-slate-200 bg-white p-1 shadow-sm"
69+ role = "group"
70+ aria-label = "Gallery view"
71+ >
72+ < button
73+ type = "button"
74+ aria-pressed = { view === "list" }
75+ className = { clsx (
76+ "tb-focus-ring inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-semibold transition-colors" ,
77+ view === "list"
78+ ? "bg-brand-700 text-white"
79+ : "text-slate-800 hover:bg-brand-50 hover:text-brand-900"
80+ ) }
81+ onClick = { ( ) => setView ( "list" ) }
82+ >
83+ < IconViewList className = "size-4" />
84+ List
85+ </ button >
86+ < button
87+ type = "button"
88+ aria-pressed = { view === "cards" }
89+ className = { clsx (
90+ "tb-focus-ring inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-semibold transition-colors" ,
91+ view === "cards"
92+ ? "bg-brand-700 text-white"
93+ : "text-slate-800 hover:bg-brand-50 hover:text-brand-900"
94+ ) }
95+ onClick = { ( ) => setView ( "cards" ) }
96+ >
97+ < IconViewGrid className = "size-4" />
98+ Cards
99+ </ button >
100+ </ div >
101+ ) ;
102+ }
103+
56104export function GalleryClient ( { tasks } : { tasks : TaskIndexItem [ ] } ) {
57105 const [ query , setQuery ] = useState < string > ( "" ) ;
58106 const [ selected , setSelected ] = useState < SelectedFacets > ( ( ) => emptySelectedFacets ( ) ) ;
107+ const [ view , setView ] = useState < "list" | "cards" > ( "list" ) ;
59108
60109 const allMaturities = useMemo ( ( ) => facetValues ( tasks , "maturity" ) , [ tasks ] ) ;
61110 const allParadigms = useMemo ( ( ) => facetValues ( tasks , "paradigm" ) , [ tasks ] ) ;
62- const allResponses = useMemo ( ( ) => facetValues ( tasks , "response" ) , [ tasks ] ) ;
63- const allModalities = useMemo ( ( ) => facetValues ( tasks , "modality" ) , [ tasks ] ) ;
64- const allLanguages = useMemo ( ( ) => facetValues ( tasks , "language" ) , [ tasks ] ) ;
65111
66112 const filtered = useMemo ( ( ) => filterTasks ( tasks , query , selected ) , [ tasks , query , selected ] ) ;
67113
@@ -105,23 +151,27 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
105151 id = "search"
106152 value = { query }
107153 onChange = { ( e ) => setQuery ( e . target . value ) }
108- placeholder = "e.g. stroop, sst, EEG "
154+ placeholder = "e.g. stroop, sst"
109155 className = "tb-focus-ring mt-2 w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400"
110156 />
111- < div className = "mt-3 flex items-center justify-between gap-3" >
157+ < div className = "mt-3 flex flex-wrap items-center justify-between gap-3" >
112158 < div className = "text-sm text-slate-600" >
113159 Showing < span className = "font-semibold text-slate-900" > { filtered . length } </ span > of{ " " }
114160 < span className = "font-semibold text-slate-900" > { tasks . length } </ span >
115161 </ div >
116- { anyFilters ? (
117- < button
118- type = "button"
119- className = "tb-focus-ring rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-800 shadow-sm hover:border-brand-200 hover:bg-brand-50"
120- onClick = { clearAll }
121- >
122- Clear
123- </ button >
124- ) : null }
162+
163+ < div className = "flex flex-wrap items-center gap-2" >
164+ { anyFilters ? (
165+ < button
166+ type = "button"
167+ className = "tb-focus-ring rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-800 shadow-sm hover:border-brand-200 hover:bg-brand-50"
168+ onClick = { clearAll }
169+ >
170+ Clear
171+ </ button >
172+ ) : null }
173+ < ViewToggle view = { view } setView = { setView } />
174+ </ div >
125175 </ div >
126176 </ section >
127177
@@ -139,27 +189,6 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
139189 selected = { selected }
140190 onToggle = { toggleFacet }
141191 />
142- < FacetSection
143- title = "Response Type"
144- facet = "response"
145- values = { allResponses }
146- selected = { selected }
147- onToggle = { toggleFacet }
148- />
149- < FacetSection
150- title = "Modality"
151- facet = "modality"
152- values = { allModalities }
153- selected = { selected }
154- onToggle = { toggleFacet }
155- />
156- < FacetSection
157- title = "Language"
158- facet = "language"
159- values = { allLanguages }
160- selected = { selected }
161- onToggle = { toggleFacet }
162- />
163192 </ div >
164193 </ aside >
165194
@@ -182,6 +211,16 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
182211 </ button >
183212 </ div >
184213 </ div >
214+ ) : view === "list" ? (
215+ < div className = "space-y-3" >
216+ { filtered . map ( ( t ) => (
217+ < TaskRow
218+ key = { t . repo }
219+ task = { t }
220+ onTagClick = { ( facet : TaskTagFacet , value ) => toggleFacet ( facet , value ) }
221+ />
222+ ) ) }
223+ </ div >
185224 ) : (
186225 < div className = "grid grid-cols-1 gap-4 sm:grid-cols-2" >
187226 { filtered . map ( ( t ) => (
0 commit comments