From cf6132dbe8818b8e8bfad43cfee16b9f1723a6c5 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Wed, 24 Jun 2026 16:18:54 +0200 Subject: [PATCH] fix(nav): bring back category pills on /benchmarks/category/ + drop sub-nav strip The header sub-nav strip added in the CMC redesign duplicated the pill row that already exists in BenchmarkGrid, with a less legible text + underline style. Reverting: - Drop the SiteSubNav strip from the header. - Always render the pill row inside BenchmarkGrid, even when the page passes a lockedCategory (category hub routes). - New allCategories prop so the hub routes pass the full taxonomy even though their benchmarks prop is pre-filtered to one category. - On locked pages pill clicks navigate naturally (no preventDefault) so users move between /benchmarks/category/ URLs. --- src/app/benchmarks/category/[cat]/page.tsx | 6 +- src/components/benchmark-grid.tsx | 94 ++++++++++++---------- src/components/site-header.tsx | 3 - src/components/site-sub-nav.tsx | 82 ------------------- 4 files changed, 57 insertions(+), 128 deletions(-) delete mode 100644 src/components/site-sub-nav.tsx diff --git a/src/app/benchmarks/category/[cat]/page.tsx b/src/app/benchmarks/category/[cat]/page.tsx index ac7f977f..8f22acbc 100644 --- a/src/app/benchmarks/category/[cat]/page.tsx +++ b/src/app/benchmarks/category/[cat]/page.tsx @@ -102,7 +102,11 @@ export default async function BenchmarkCategoryPage({ {entry.description}

- + b.category)))} + /> ); } diff --git a/src/components/benchmark-grid.tsx b/src/components/benchmark-grid.tsx index e9bee30b..cf055535 100644 --- a/src/components/benchmark-grid.tsx +++ b/src/components/benchmark-grid.tsx @@ -28,12 +28,17 @@ import { categorySlugFromLabel } from "@/lib/categories"; export function BenchmarkGrid({ benchmarks, lockedCategory = null, + allCategories, }: { benchmarks: Benchmark[]; - /** When set, force the grid to this category and hide the filter pills. - * Used by the per-category hub routes so the rendered DOM matches the - * URL and the in-page filter UI doesn't conflict with the route. */ + /** When set, force the grid to this category. The pill row still + * renders so users can jump to other category hubs via the same UI + * they had on the /benchmarks root. */ lockedCategory?: string | null; + /** Full category list to show in the pills, used by the category hub + * routes where the `benchmarks` prop is pre-filtered to a single + * category. If unset, the grid derives pills from `benchmarks`. */ + allCategories?: string[]; }) { const [query, setQuery] = useState(""); const [activeCategory, setActiveCategory] = useState( @@ -43,6 +48,7 @@ export function BenchmarkGrid({ const q = query.trim().toLowerCase(); const categories = useMemo(() => { + if (allCategories && allCategories.length > 0) return allCategories; const seen = new Set(); const list: string[] = []; for (const b of benchmarks) { @@ -52,7 +58,7 @@ export function BenchmarkGrid({ } } return list; - }, [benchmarks]); + }, [benchmarks, allCategories]); const filtered = useMemo(() => { return benchmarks.filter((b) => { @@ -71,48 +77,52 @@ export function BenchmarkGrid({ }); }, [benchmarks, q, activeCategory]); - const showFilterPills = !lockedCategory; - + // Pills always render. When the grid is locked to a category route, + // pill clicks navigate (no preventDefault) so the user moves between + // /benchmarks/category/ URLs. On the unlocked /benchmarks page + // they filter in place for a snappier UX without a navigation + // roundtrip. return (
{/* Filter row */}
- {showFilterPills && ( -
    -
  • - { - e.preventDefault(); - setActiveCategory(null); - }} - > - All - -
  • - {categories.map((c) => { - const slug = categorySlugFromLabel(c); - const href = slug ? `/benchmarks/category/${slug}` : "/benchmarks"; - return ( -
  • - { - e.preventDefault(); - setActiveCategory(activeCategory === c ? null : c); - }} - > - {c} - -
  • - ); - })} -
- )} +
    +
  • + { + if (lockedCategory) return; + e.preventDefault(); + setActiveCategory(null); + }} + > + All + +
  • + {categories.map((c) => { + const slug = categorySlugFromLabel(c); + const href = slug ? `/benchmarks/category/${slug}` : "/benchmarks"; + const isActive = lockedCategory === c || (!lockedCategory && activeCategory === c); + return ( +
  • + { + if (lockedCategory) return; + e.preventDefault(); + setActiveCategory(activeCategory === c ? null : c); + }} + > + {c} + +
  • + ); + })} +
{/* View toggle */} diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 8d414265..0c62dc1f 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -6,7 +6,6 @@ import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import { SearchTrigger } from "@/components/search/search-trigger"; import { SiteLogoSwitcher } from "@/components/site-logo-switcher"; -import { SiteSubNav } from "@/components/site-sub-nav"; import { ThemeToggle } from "@/components/theme-toggle"; function GithubIcon({ size = 15 }: { size?: number }) { @@ -190,8 +189,6 @@ export function SiteHeader() { )} - -
); } diff --git a/src/components/site-sub-nav.tsx b/src/components/site-sub-nav.tsx deleted file mode 100644 index 87baee1f..00000000 --- a/src/components/site-sub-nav.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { CATEGORIES } from "@/lib/categories"; - -type SubItem = { href: string; label: string; match: (p: string) => boolean }; - -// Per-section contextual sub-nav. Modelled on the CMC chart-hub pattern -// (horizontally scrollable category tabs below the main nav). The -// bench section is the only one with a deep enough taxonomy to need -// this today; other sections render nothing rather than an empty -// 40px-tall strip that would just push content down. -function getItems(pathname: string): SubItem[] | null { - if (pathname === "/benchmarks" || pathname.startsWith("/benchmarks/")) { - const items: SubItem[] = [ - { - href: "/benchmarks", - label: "All", - match: (p) => - p === "/benchmarks" || - (p.startsWith("/benchmarks/") && !p.startsWith("/benchmarks/category/")), - }, - ]; - for (const c of CATEGORIES) { - const href = `/benchmarks/category/${c.slug}`; - items.push({ - href, - label: c.label, - match: (p) => p === href, - }); - } - return items; - } - return null; -} - -export function SiteSubNav({ pathname }: { pathname: string }) { - const items = getItems(pathname); - if (!items) return null; - - return ( -
- -
- ); -}