Skip to content

Commit 6bf8941

Browse files
committed
Split landing page from task explorer
1 parent 0f9eeaa commit 6bf8941

19 files changed

Lines changed: 831 additions & 521 deletions

File tree

src/app/_components/gallery-client.tsx

Lines changed: 128 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ import {
99
type SelectedFacets
1010
} from "@/lib/task-filter";
1111
import { TagChip } from "@/components/tag-chip";
12-
import { TaskCard } from "@/components/task-card";
1312
import { TaskRow } from "@/components/task-row";
1413
import { TaskDrawer } from "@/components/task-drawer";
15-
import clsx from "@/components/utils/clsx";
16-
import { IconViewGrid, IconViewList } from "@/components/icons";
1714
import { formatMaturityLabel } from "@/components/maturity-badge";
15+
import { formatIsoDateTime } from "@/lib/format";
1816

1917
function FacetSection({
2018
title,
@@ -66,55 +64,15 @@ function FacetSection({
6664
);
6765
}
6866

69-
function ViewToggle({
70-
view,
71-
setView
67+
export function GalleryClient({
68+
tasks,
69+
generatedAt
7270
}: {
73-
view: "list" | "cards";
74-
setView: (view: "list" | "cards") => void;
71+
tasks: TaskIndexItem[];
72+
generatedAt: string;
7573
}) {
76-
return (
77-
<div
78-
className="inline-flex rounded-xl border border-slate-200 bg-white p-1 shadow-sm"
79-
role="group"
80-
aria-label="Gallery view"
81-
>
82-
<button
83-
type="button"
84-
aria-pressed={view === "list"}
85-
className={clsx(
86-
"tb-focus-ring inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-semibold transition-colors",
87-
view === "list"
88-
? "bg-brand-700 text-white"
89-
: "text-slate-800 hover:bg-brand-50 hover:text-brand-900"
90-
)}
91-
onClick={() => setView("list")}
92-
>
93-
<IconViewList className="size-4" />
94-
List
95-
</button>
96-
<button
97-
type="button"
98-
aria-pressed={view === "cards"}
99-
className={clsx(
100-
"tb-focus-ring inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-semibold transition-colors",
101-
view === "cards"
102-
? "bg-brand-700 text-white"
103-
: "text-slate-800 hover:bg-brand-50 hover:text-brand-900"
104-
)}
105-
onClick={() => setView("cards")}
106-
>
107-
<IconViewGrid className="size-4" />
108-
Cards
109-
</button>
110-
</div>
111-
);
112-
}
113-
114-
export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
11574
const [query, setQuery] = useState<string>("");
11675
const [selected, setSelected] = useState<SelectedFacets>(() => emptySelectedFacets());
117-
const [view, setView] = useState<"list" | "cards">("list");
11876
const [activeRepo, setActiveRepo] = useState<string | null>(null);
11977
const [openFacets, setOpenFacets] = useState<{ maturity: boolean; paradigm: boolean }>({
12078
maturity: true,
@@ -124,6 +82,14 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
12482
const deferredQuery = useDeferredValue(query);
12583
const allMaturities = useMemo(() => facetValues(tasks, "maturity"), [tasks]);
12684
const allParadigms = useMemo(() => facetValues(tasks, "paradigm"), [tasks]);
85+
const previewCount = useMemo(() => tasks.filter((task) => task.web_variant).length, [tasks]);
86+
const paradigmCount = useMemo(
87+
() =>
88+
new Set(
89+
tasks.flatMap((task) => task.tags?.paradigm ?? []).map((value) => value.toLowerCase())
90+
).size,
91+
[tasks]
92+
);
12793
const filtered = useMemo(
12894
() => filterTasks(tasks, deferredQuery, selected),
12995
[deferredQuery, selected, tasks]
@@ -162,55 +128,102 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
162128
}
163129

164130
return (
165-
<section id="explorer" className="space-y-5">
131+
<section className="space-y-6">
166132
<div className="rounded-[32px] border border-slate-200 bg-white/90 p-5 shadow-sm">
167-
<div className="flex flex-col gap-5">
168-
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
169-
<div className="min-w-0 flex-1">
133+
<div className="flex flex-col gap-6">
134+
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
135+
<div>
170136
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">
171-
Task explorer
137+
Tasks
172138
</div>
173-
<div className="mt-2 font-heading text-2xl font-semibold tracking-tight text-slate-900">
174-
Browse canonical local tasks and attached browser previews.
139+
<div className="mt-2 font-heading text-3xl font-semibold tracking-tight text-slate-900">
140+
Browse canonical local tasks and aligned previews.
175141
</div>
176142
<div className="mt-2 max-w-3xl text-sm leading-6 text-slate-700">
177-
Search by task name, repo, paradigm, or ID. Switch between a dense list and a flow-oriented card browser, then expand any task to load its README snapshot on demand.
143+
This page keeps the denser explorer layout: filter from the left, scan the list on
144+
the right, and open README-backed details only when needed.
178145
</div>
179146
</div>
180147

181-
<div className="flex flex-wrap items-center gap-2">
182-
{anyFilters ? (
183-
<button
184-
type="button"
185-
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"
186-
onClick={clearAll}
187-
>
188-
Clear filters
189-
</button>
190-
) : null}
191-
<ViewToggle view={view} setView={setView} />
148+
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 px-4 py-3 text-sm text-slate-700 shadow-sm">
149+
Index updated{" "}
150+
<span className="font-semibold text-slate-900">
151+
{formatIsoDateTime(generatedAt)}
152+
</span>
192153
</div>
193154
</div>
194155

195-
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
196-
<label className="block" htmlFor="task-explorer-search">
197-
<span className="text-xs font-semibold uppercase tracking-wide text-slate-600">Search</span>
198-
<input
199-
id="task-explorer-search"
200-
value={query}
201-
onChange={(event) => setQuery(event.target.value)}
202-
placeholder="e.g. stroop, T000012, H000006, EEG"
203-
className="tb-focus-ring mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-400"
204-
/>
205-
</label>
206-
207-
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 px-4 py-3 text-sm text-slate-700 shadow-sm">
208-
Showing <span className="font-semibold text-slate-900">{filtered.length}</span> of{" "}
209-
<span className="font-semibold text-slate-900">{tasks.length}</span> tasks
156+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
157+
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 p-4 shadow-sm">
158+
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
159+
Total tasks
160+
</div>
161+
<div className="mt-2 font-heading text-2xl font-semibold text-slate-900">
162+
{tasks.length}
163+
</div>
164+
</div>
165+
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 p-4 shadow-sm">
166+
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
167+
Matching now
168+
</div>
169+
<div className="mt-2 font-heading text-2xl font-semibold text-slate-900">
170+
{filtered.length}
171+
</div>
172+
</div>
173+
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 p-4 shadow-sm">
174+
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
175+
Previews
176+
</div>
177+
<div className="mt-2 font-heading text-2xl font-semibold text-slate-900">
178+
{previewCount}
179+
</div>
180+
</div>
181+
<div className="rounded-2xl border border-slate-200 bg-slate-50/85 p-4 shadow-sm">
182+
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
183+
Paradigms
184+
</div>
185+
<div className="mt-2 font-heading text-2xl font-semibold text-slate-900">
186+
{paradigmCount}
187+
</div>
210188
</div>
211189
</div>
190+
</div>
191+
</div>
192+
193+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
194+
<aside className="lg:col-span-4 xl:col-span-3">
195+
<div className="sticky top-24 space-y-4">
196+
<section className="rounded-2xl border border-slate-200 bg-white/90 p-4 shadow-sm">
197+
<label className="block" htmlFor="task-explorer-search">
198+
<span className="text-xs font-semibold uppercase tracking-wide text-slate-600">
199+
Search
200+
</span>
201+
<input
202+
id="task-explorer-search"
203+
value={query}
204+
onChange={(event) => setQuery(event.target.value)}
205+
placeholder="e.g. stroop, T000012, H000006, EEG"
206+
className="tb-focus-ring mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-400"
207+
/>
208+
</label>
209+
210+
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
211+
<div className="text-sm text-slate-700">
212+
Showing <span className="font-semibold text-slate-900">{filtered.length}</span>{" "}
213+
of <span className="font-semibold text-slate-900">{tasks.length}</span>
214+
</div>
215+
{anyFilters ? (
216+
<button
217+
type="button"
218+
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"
219+
onClick={clearAll}
220+
>
221+
Clear
222+
</button>
223+
) : null}
224+
</div>
225+
</section>
212226

213-
<div className="grid gap-3 lg:grid-cols-2">
214227
<FacetSection
215228
title="Maturity"
216229
facet="maturity"
@@ -234,50 +247,39 @@ export function GalleryClient({ tasks }: { tasks: TaskIndexItem[] }) {
234247
}
235248
/>
236249
</div>
237-
</div>
238-
</div>
250+
</aside>
239251

240-
{filtered.length === 0 ? (
241-
<div className="rounded-[32px] border border-slate-200 bg-white/90 p-10 text-center shadow-sm">
242-
<div className="font-heading text-lg font-semibold tracking-tight text-slate-900">
243-
No matches
244-
</div>
245-
<div className="mt-2 text-sm text-slate-700">
246-
Try clearing filters or searching by paradigm name, repo handle, or task ID.
247-
</div>
248-
<div className="mt-5">
249-
<button
250-
type="button"
251-
className="tb-focus-ring rounded-lg bg-cta-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-cta-600"
252-
onClick={clearAll}
253-
>
254-
Reset explorer
255-
</button>
256-
</div>
257-
</div>
258-
) : view === "list" ? (
259-
<div className="space-y-3">
260-
{filtered.map((task) => (
261-
<TaskRow
262-
key={task.repo}
263-
task={task}
264-
onTagClick={(facet: TaskTagFacet, value) => toggleFacet(facet, value)}
265-
onOpen={(nextTask) => setActiveRepo(nextTask.repo)}
266-
/>
267-
))}
268-
</div>
269-
) : (
270-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
271-
{filtered.map((task) => (
272-
<TaskCard
273-
key={task.repo}
274-
task={task}
275-
onTagClick={(facet: TaskTagFacet, value) => toggleFacet(facet, value)}
276-
onOpen={(nextTask) => setActiveRepo(nextTask.repo)}
277-
/>
278-
))}
279-
</div>
280-
)}
252+
<section className="space-y-3 lg:col-span-8 xl:col-span-9">
253+
{filtered.length === 0 ? (
254+
<div className="rounded-[32px] border border-slate-200 bg-white/90 p-10 text-center shadow-sm">
255+
<div className="font-heading text-lg font-semibold tracking-tight text-slate-900">
256+
No matches
257+
</div>
258+
<div className="mt-2 text-sm text-slate-700">
259+
Try clearing filters or searching by paradigm name, repo handle, or task ID.
260+
</div>
261+
<div className="mt-5">
262+
<button
263+
type="button"
264+
className="tb-focus-ring rounded-lg bg-cta-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-cta-600"
265+
onClick={clearAll}
266+
>
267+
Reset explorer
268+
</button>
269+
</div>
270+
</div>
271+
) : (
272+
filtered.map((task) => (
273+
<TaskRow
274+
key={task.repo}
275+
task={task}
276+
onTagClick={(facet: TaskTagFacet, value) => toggleFacet(facet, value)}
277+
onOpen={(nextTask) => setActiveRepo(nextTask.repo)}
278+
/>
279+
))
280+
)}
281+
</section>
282+
</div>
281283

282284
<TaskDrawer task={activeTask} onClose={() => setActiveRepo(null)} />
283285
</section>

0 commit comments

Comments
 (0)