import * as React from 'react'; import { Badge } from '@/components/badge'; import { Button } from '@/components/button'; import { BankIcon, BoxIcon, CalendarIcon, CardIcon, ChartPieIcon, ClockIcon, FilterIcon, HomeIcon, MedkitIcon, MoonIcon, SearchIcon, UserIcon, iconManifest, } from '@/icons'; import { CbarLogo } from '~/brand/logo'; import { useLang, useMessages, type Messages } from '~/i18n'; import { entries } from '~/registry'; import { PALETTES, VARIANTS } from '~/registry/axis-values'; import { Link, useParam } from '~/router'; import { CopyBlock, CopyLine } from './copy'; import { PACKAGE, SUBPATHS } from '../lib/package-name'; import { Segmented } from './segmented'; import { TOKENS } from '../lib/token-values'; /** * The intro tour's content. The mechanism — the dialog, the footer, the * keyboard — is `tour.tsx`; this file is only what the six slides say and show. * The same split `registry/` makes between the page chrome and the entries. * * **Every demo here is live.** Nothing is a screenshot, and that is a decision * with a reason rather than a preference: this site renders in two themes and * three languages, so an image would be right in one combination and wrong in * five, and it would go stale on any restyle with nothing in the build to * catch it. It is the same rule `chrome/lib/codegen.ts` follows when it serialises * the element on screen instead of storing source, and the icons page follows * when it derives every copy format from one `renderToStaticMarkup`: a fact * gets exactly one home. * * For the same reason no count and no axis list is typed out below. The * component total is the registry's, the icon total is the manifest's, the * subpath total is the generated `exports` map's, and the variant and palette * names come from `registry/axis-values.ts`, which the component pages and both * `/buttons` tabs already share. */ export interface Slide { /** Stable key, and what the dialog uses to reset scroll between slides. */ id: string; title: (m: Messages) => string; body: (m: Messages) => string; Demo: React.ComponentType; } /* ---------------------------------------------------------------- welcome */ function WelcomeDemo() { const m = useMessages(); const [lang] = useLang(); const s = m.tour.slides.welcome; return (
{/* The lockup is baked white and carries no `currentColor` — see `brand/logo.tsx` — so navy is the only ground it may be drawn on. `.cbar-tour-band` is that ground, and it is navy in both themes. */}
{s.components(entries.length)} {s.icons(iconManifest.length)} {s.subpaths(SUBPATHS)}
); } /* ------------------------------------------------------------- components */ /* Three of the seven palettes. The whole set is a `/buttons` grid; here the point is only that the two axes cross, and five treatments across three rows shows that in a box this size. */ const DEMO_PALETTES = PALETTES.slice(0, 3); function ComponentsDemo() { const m = useMessages(); return (
{DEMO_PALETTES.map((palette) => (
{/* The label is the prop value, so the demo cannot describe itself wrongly — rename a variant and this grid renames itself. */} {VARIANTS.map((variant) => ( ))}
))}

{m.tour.slides.components.caption}

); } /* ----------------------------------------------------------------- tokens */ /** CBAR's brand ramp, the one every `primary` treatment is built from. */ const BRAND_STEPS = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950']; const THEMES = ['light', 'dark'] as const; function TokensDemo() { const m = useMessages(); const s = m.tour.slides.tokens; /* The site's own theme param, not a local state: pressing this switches the page behind the dialog, which is the argument the slide is making. */ const [theme, setTheme] = useParam('theme', 'light'); return (
{BRAND_STEPS.map((step) => { const name = `--ui-color-brand-${step}`; /* `tokens.css` records CBAR's published hex as a trailing comment on every generated declaration, which is what `TOKENS` recovers — so the tooltip shows the number the design file holds rather than a conversion of the oklch the browser computes. */ const hex = TOKENS.get(name)?.hex; return (
); })}
(value === 'dark' ? s.dark : s.light)} />

{s.tryIt}

); } /* ------------------------------------------------------------------ icons */ /* Twelve of the set, picked to look like a bank's toolbar rather than at random. Imported by name so they tree-shake — importing the barrel to index into it would pull all 250 into this chunk. */ const SAMPLE_ICONS = [ HomeIcon, BankIcon, CardIcon, ChartPieIcon, CalendarIcon, ClockIcon, UserIcon, SearchIcon, FilterIcon, BoxIcon, MedkitIcon, MoonIcon, ]; const FAMILIES = ['outline', 'solid'] as const; function IconsDemo() { const m = useMessages(); const s = m.tour.slides.icons; const [family, setFamily] = React.useState<(typeof FAMILIES)[number]>('outline'); return (
{SAMPLE_ICONS.map((Icon, i) => ( ))}
(value === 'solid' ? s.solid : s.outline)} />
); } /* ------------------------------------------------------------------- site */ function SiteDemo() { const m = useMessages(); const s = m.tour.slides.site; return (
{/* A real copy control, not a picture of one: this is the exact line the site puts under every component. */} {/* Not `.cbar-kbd`: that class is the rail's, and its border and ink are tints of white meant for the navy shell — on this dialog's surface it would be very nearly invisible. Same shape, page colours. */}

/ {s.search}

); } /* ------------------------------------------------------------------ start */ /** Where the tour hands over. Slugs, not labels — `routes.tsx` owns the words. */ const NEXT_STOPS = [ { to: '/introduction', label: (m: Messages) => m.nav.introduction }, { to: '/tokens', label: (m: Messages) => m.nav.tokens }, { to: '/icons', label: (m: Messages) => m.nav.icons }, { to: '/parity', label: (m: Messages) => m.nav.parity }, ]; function StartDemo() { const m = useMessages(); const s = m.tour.slides.start; return (

{s.links}

{/* Following one of these closes the dialog — `Tour` delegates that off a single click handler rather than threading a prop through each link, the way `Rail` closes the drawer on navigation. */} {NEXT_STOPS.map((stop) => ( ))}
); } /* ------------------------------------------------------------------------ */ export const SLIDES: readonly Slide[] = [ { id: 'welcome', title: (m) => m.tour.slides.welcome.title, body: (m) => m.tour.slides.welcome.body, Demo: WelcomeDemo, }, { id: 'components', title: (m) => m.tour.slides.components.title, body: (m) => m.tour.slides.components.body, Demo: ComponentsDemo, }, { id: 'tokens', title: (m) => m.tour.slides.tokens.title, body: (m) => m.tour.slides.tokens.body, Demo: TokensDemo, }, { id: 'icons', title: (m) => m.tour.slides.icons.title, body: (m) => m.tour.slides.icons.body, Demo: IconsDemo, }, { id: 'site', title: (m) => m.tour.slides.site.title, body: (m) => m.tour.slides.site.body, Demo: SiteDemo, }, { id: 'start', title: (m) => m.tour.slides.start.title, body: (m) => m.tour.slides.start.body, Demo: StartDemo, }, ];