Skip to content

Inverted statistics panel

A dark band of four derived figures, used as punctuation between light sections.

Marketingstarterstatsinverteddarkcounts

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

import { Container } from '@/components/ui/layout'
import { catalogueCounts } from '@/lib/catalogue'

/**
 * Inverted statistics panel
 *
 * A dark band used as punctuation between two light sections. Every figure is
 * read from the live catalogue, so the panel cannot claim a number the library
 * does not contain.
 */
export default function InvertedStatsPanel() {
  const figures = [
    {
      value: catalogueCounts.component,
      label: 'Primitives',
      detail: 'Actions, forms, feedback, overlay, data, layout',
    },
    {
      value: catalogueCounts.navigation,
      label: 'Navigation systems',
      detail: 'Navbars, headers, footers, shells',
    },
    {
      value: catalogueCounts.section,
      label: 'Sections',
      detail: 'Marketing, commerce, application, states',
    },
    {
      value: catalogueCounts.starter,
      label: 'Starter products',
      detail: 'Multi-route, with working navigation',
    },
  ]

  return (
    <section className="bg-surface-inverse py-section text-ink-inverse">
      <Container>
        <h2 className="display-type max-w-2xl text-2xl font-semibold sm:text-3xl">
          The catalogue, counted from the catalogue.
        </h2>
        <p className="mt-3 max-w-lg text-sm opacity-75">
          These figures are derived at build time. Adding an item changes them; nothing is typed in.
        </p>

        <dl className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
          {figures.map((figure) => (
            <div key={figure.label} className="border-t border-white/20 pt-5">
              <dd className="display-type text-4xl font-semibold tabular-nums">{figure.value}</dd>
              <dt className="mt-2 text-sm font-medium">{figure.label}</dt>
              <p className="mt-1.5 text-xs opacity-70">{figure.detail}</p>
            </div>
          ))}
        </dl>
      </Container>
    </section>
  )
}

components/blocks/sections/stats/inverted-panel.tsx

import { Container } from '@/components/ui/layout'
import { catalogueCounts } from '@/lib/catalogue'

/**
 * Inverted statistics panel
 *
 * A dark band used as punctuation between two light sections. Every figure is
 * read from the live catalogue, so the panel cannot claim a number the library
 * does not contain.
 */
export default function InvertedStatsPanel() {
  const figures = [
    {
      value: catalogueCounts.component,
      label: 'Primitives',
      detail: 'Actions, forms, feedback, overlay, data, layout',
    },
    {
      value: catalogueCounts.navigation,
      label: 'Navigation systems',
      detail: 'Navbars, headers, footers, shells',
    },
    {
      value: catalogueCounts.section,
      label: 'Sections',
      detail: 'Marketing, commerce, application, states',
    },
    {
      value: catalogueCounts.starter,
      label: 'Starter products',
      detail: 'Multi-route, with working navigation',
    },
  ]

  return (
    <section className="bg-surface-inverse py-section text-ink-inverse">
      <Container>
        <h2 className="display-type max-w-2xl text-2xl font-semibold sm:text-3xl">
          The catalogue, counted from the catalogue.
        </h2>
        <p className="mt-3 max-w-lg text-sm opacity-75">
          These figures are derived at build time. Adding an item changes them; nothing is typed in.
        </p>

        <dl className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
          {figures.map((figure) => (
            <div key={figure.label} className="border-t border-white/20 pt-5">
              <dd className="display-type text-4xl font-semibold tabular-nums">{figure.value}</dd>
              <dt className="mt-2 text-sm font-medium">{figure.label}</dt>
              <p className="mt-1.5 text-xs opacity-70">{figure.detail}</p>
            </div>
          ))}
        </dl>
      </Container>
    </section>
  )
}

lib/catalogue/index.ts

import { componentItems } from './components'
import { navigationItems } from './navigation'
import { formItems } from './forms'
import { sectionItems } from './sections'
import { patternItems } from './patterns'
import { starterItems } from './starters'
import { families, familyBySegment, familyByName } from './families'
import type { CatalogueFamily, CatalogueItem, FamilyDefinition } from './types'

export * from './types'
export { families, familyBySegment, familyByName } from './families'

/**
 * The catalogue graph.
 *
 * One flat array plus a handful of indexes. Every browse, filter, search and
 * related-content surface in the application reads from these — which is why
 * adding an item never requires touching a page component.
 */
export const catalogue: CatalogueItem[] = [
  ...componentItems,
  ...navigationItems,
  ...formItems,
  ...sectionItems,
  ...patternItems,
  ...starterItems,
]

export const catalogueById = new Map(catalogue.map((item) => [item.id, item]))

export const catalogueByFamily = new Map<CatalogueFamily, CatalogueItem[]>(
  families.map((family) => [
    family.family,
    catalogue.filter((item) => item.family === family.family),
  ]),
)

export function itemsForFamily(family: CatalogueFamily): CatalogueItem[] {
  return catalogueByFamily.get(family) ?? []
}

export function itemsForSegment(segment: string): CatalogueItem[] {
  const family = familyBySegment.get(segment)
  return family ? itemsForFamily(family.family) : []
}

export function findItem(family: CatalogueFamily, slug: string): CatalogueItem | undefined {
  return catalogueById.get(`${family}/${slug}`)
}

export function resolveRelated(item: CatalogueItem): CatalogueItem[] {
  return item.relatedItems
    .map((id) => catalogueById.get(id))
    .filter((candidate): candidate is CatalogueItem => Boolean(candidate))
}

/** Route segment for an item, e.g. `/components/button`. */
export function hrefForItem(item: CatalogueItem): string {
  const family = familyByName.get(item.family)
  return `/${family?.segment ?? 'components'}/${item.slug}`
}

export function categoriesForFamily(family: CatalogueFamily): FamilyDefinition['categories'] {
  return familyByName.get(family)?.categories ?? []
}

/** Live counts. Never hard-code a marketing number — derive it from here. */
export const catalogueCounts = {
  component: itemsForFamily('component').length,
  navigation: itemsForFamily('navigation').length,
  form: itemsForFamily('form').length,
  section: itemsForFamily('section').length,
  pattern: itemsForFamily('pattern').length,
  starter: itemsForFamily('starter').length,
  total: catalogue.length,
} as const

export function countForCategory(family: CatalogueFamily, category: string): number {
  return itemsForFamily(family).filter((item) => item.category === category).length
}

export const featuredItems = catalogue.filter((item) => item.featured)

/** Every tag in use, with its frequency, most used first. */
export const tagIndex: Array<{ tag: string; count: number }> = (() => {
  const counts = new Map<string, number>()
  for (const item of catalogue) {
    for (const tag of item.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1)
  }
  return Array.from(counts.entries())
    .map(([tag, count]) => ({ tag, count }))
    .sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag))
})()

Demo source — adapt to your project. Foundry is not published as a package.

Usage

Every figure is read from the live catalogue, so the panel cannot claim a number the library does not contain.

  • Use one inverted band per page. Two makes both feel arbitrary.
  • State the provenance of derived numbers, so the reader knows they are counted rather than claimed.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Four columns at lg
  • Rule above each figure
  • Counts derived at build time

Accessibility

Contrast
Foreground uses the ink-inverse token, so contrast holds in every palette.
Definition list
Figures and labels are programmatically paired.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.