import { createSignal, For, onMount, type JSX } from 'solid-js'; import type { Palette } from './theme-editor/theme-css'; // Docs-only helpers (not part of the kit's public API). They auto-discover the // kit's design tokens straight from the loaded CSS, so the reference + editor // can never drift from theme.css as tokens are added or changed. type ColorToken = { name: string; light: string; dark: string }; type RadiusToken = { name: string; value: string }; export const PURPOSE: Record = { '--color-background': 'App / page background', '--color-foreground': 'Default text', '--color-card': 'Card surface', '--color-card-foreground': 'Text on cards', '--color-popover': 'Popover / menu surface', '--color-popover-foreground': 'Text in popovers', '--color-primary': 'Primary action background', '--color-primary-foreground': 'Text on primary', '--color-secondary': 'Secondary surface', '--color-secondary-foreground': 'Text on secondary', '--color-muted': 'Muted surface (subtle fills)', '--color-muted-foreground': 'Muted / secondary text', '--color-accent': 'Accent / hover surface', '--color-accent-foreground': 'Text on accent', '--color-destructive': 'Destructive / danger', '--color-destructive-foreground': 'Text on destructive', '--color-border': 'Borders / dividers', '--color-input': 'Input field background', '--color-ring': 'Focus ring', '--color-sidebar': 'Sidebar background', '--color-code-foreground': 'Inline code text / accent', }; /** Raw walk of loaded stylesheets → light/dark custom-property maps (colors + radius). * Recurses into grouping rules (@layer, @media, @supports) because Tailwind v4 * emits the `:root` theme tokens inside an `@layer`, which a flat walk would miss. */ function collectTokens(): { light: Record; dark: Record } { const light: Record = {}; const dark: Record = {}; const visit = (rules: CSSRuleList) => { for (const rule of Array.from(rules)) { const r = rule as CSSStyleRule; if (r.selectorText && r.style) { const isRoot = /(^|,)\s*:root\b/.test(r.selectorText); const isDark = /(^|,)\s*\.dark\b/.test(r.selectorText); if (isRoot || isDark) { for (const prop of Array.from(r.style)) { if (!prop.startsWith('--color-') && !prop.startsWith('--radius')) continue; const val = r.style.getPropertyValue(prop).trim(); if (isRoot) light[prop] = val; if (isDark) dark[prop] = val; } } } // Grouping rules (@layer/@media/@supports) carry nested rules — recurse. const nested = (rule as CSSGroupingRule).cssRules; if (nested) visit(nested); } }; for (const sheet of Array.from(document.styleSheets)) { try { visit(sheet.cssRules); } catch { // cross-origin sheet } } return { light, dark }; } /** Reference data for the token table: a color token is one with a `.dark` override. */ function discover(): { colors: ColorToken[]; radii: RadiusToken[] } { const { light, dark } = collectTokens(); const colors = Object.keys(dark) .filter((n) => n.startsWith('--color-')) .sort() .map((name) => ({ name, light: light[name] || dark[name], dark: dark[name] })); const radii = Object.keys(light) .filter((n) => n.startsWith('--radius')) .sort() .map((name) => ({ name, value: light[name] })); return { colors, radii }; } /** Light/dark palettes for the theme editor: light = colors + --radius, dark = colors. * The token set is keyed off the `.dark` overrides — that's what defines a kit token * and excludes Tailwind's default palette (which has no `.dark` entry). Light values * come from `:root`, falling back to the dark value if a token only exists in dark. */ export function discoverPalettes(): { light: Palette; dark: Palette } { const { light, dark } = collectTokens(); const names = Object.keys(dark).filter((n) => n.startsWith('--color-')).sort(); const lightPalette: Palette = { '--radius': light['--radius'] ?? '0.6rem' }; const darkPalette: Palette = {}; for (const name of names) { lightPalette[name] = light[name] || dark[name]; darkPalette[name] = dark[name]; } return { light: lightPalette, dark: darkPalette, }; } /** Resolve any CSS color string (e.g. hsl(...)) to #rrggbb for a color input. */ export function toHex(css: string): string { const el = document.createElement('div'); el.style.color = css; el.style.display = 'none'; document.body.appendChild(el); const rgb = getComputedStyle(el).color; el.remove(); const m = rgb.match(/\d+(\.\d+)?/g); if (!m) return '#000000'; return '#' + m.slice(0, 3).map((x) => Math.round(+x).toString(16).padStart(2, '0')).join(''); } const cellHead: JSX.CSSProperties = { 'text-align': 'left', padding: '6px 10px', 'border-bottom': '1px solid var(--color-border)', 'font-weight': '600' }; const cell: JSX.CSSProperties = { padding: '6px 10px', 'border-bottom': '1px solid var(--color-border)', 'vertical-align': 'middle' }; function Swatch(props: { color: string }) { return ( ); } /** Auto-generated reference of every overridable token (light + dark values). */ export function TokenTable() { const [data, setData] = createSignal<{ colors: ColorToken[]; radii: RadiusToken[] }>({ colors: [], radii: [] }); onMount(() => setData(discover())); return (
{(t) => ( )} {(t) => ( )}
Token Purpose Light Dark
{t.name} {PURPOSE[t.name] || ''} {t.light} {t.dark}
{t.name} Corner radius {t.value}

This table is generated live from the loaded CSS — it always reflects the current tokens in theme.css.

); }