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. */}
);
}
/* ------------------------------------------------------------- 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) => (
))}