import { Badge } from '@/components/badge'; import { Button } from '@/components/button'; import { Spinner } from '@/components/spinner'; import { Switch } from '@/components/switch'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/tooltip'; import { InfoIcon, MoonIcon, SunIcon } from '@/icons'; import { useBridge, useFigmaMode } from '~/figma/use-figma'; import { LANG_LABELS, LANGS, type Messages, isLang, useLang, useMessages } from '~/i18n'; import { useParam } from '~/router'; import { Segmented } from './segmented'; import { useA11y, useA11yMode } from '../hooks/use-a11y'; /** * Global toggles. They live in the URL hash rather than in React state so a * particular view — dark theme, a given page — is a link you can send someone. * * All three are kit components, which is the rule for the site's chrome. The * playground's controls are the deliberate exception, and `playground.tsx` says * why. */ /* Only the tint is fixed here — both the short word in the pill and the sentence in the tooltip come from the catalogue, keyed by the same name. */ const STATUS: Record< string, { palette: 'green' | 'yellow' | 'black'; key: keyof Messages['toolbar']['bridge'] } > = { live: { palette: 'green', key: 'live' }, 'no-plugin': { palette: 'yellow', key: 'noPlugin' }, off: { palette: 'black', key: 'off' }, idle: { palette: 'black', key: 'idle' }, }; /** * The bridge indicator, and the switch that turns it off. * * It was one button doing both jobs; a Switch says which of the two states you * are in before you read anything, and the pill beside it reports what the * bridge actually answered — those are different facts, and conflating them is * why "off" and "running but nothing polling it" used to look alike. * * Rendered in development only, like `A11yToggle` below and for the same * reason: `useBridge` refuses to poll in a built site, so the switch there * would be a control that reports nothing and changes nothing. */ function FigmaToggle() { const [mode, setMode] = useFigmaMode(); const bridge = useBridge(); const m = useMessages(); const status = bridge.enabled ? bridge.status : 'idle'; const tone = STATUS[status] ?? STATUS.off; /* Switched on but never probed is the one state with nothing to report yet, so it gets the spinner rather than a word that would be wrong a moment later. Switched *off* reports as `idle` too, which is why `enabled` has to be in the test — that state is settled, not pending. */ const probing = bridge.enabled && bridge.status === 'idle'; return ( {m.toolbar.bridge[tone.key]} {bridge.file ? ( <>
{m.toolbar.file}: {bridge.file} ) : null}
); } /** * The axe switch — the same shape as the Figma one, and present only in * development. * * `import.meta.env.DEV` is a literal at build time, so this whole component and * everything it reaches (including the dynamic `import('axe-core')`) is dropped * from a production `showcase:build` rather than merely hidden. `?a11y=on` * would do nothing in a built site, which is why there is no control for it * there either. */ function A11yToggle() { const [mode, setMode] = useA11yMode(); const a11y = useA11y(); const m = useMessages(); const total = a11y.violations.reduce((sum, violation) => sum + violation.nodes.length, 0); return ( {m.toolbar.a11yHint} ); } /** * Three segments rather than a `