@unpunnyfuns/swatchbook-addon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/constants-1plfdgh7.mjs +21 -0
- package/dist/constants-1plfdgh7.mjs.map +1 -0
- package/dist/hooks/index.d.mts +37 -0
- package/dist/hooks/index.mjs +33 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +16 -0
- package/dist/index.mjs.map +1 -0
- package/dist/manager.d.mts +1 -0
- package/dist/manager.mjs +903 -0
- package/dist/manager.mjs.map +1 -0
- package/dist/options-rvGQy0uV.d.mts +17 -0
- package/dist/preset.d.mts +20 -0
- package/dist/preset.mjs +163 -0
- package/dist/preset.mjs.map +1 -0
- package/dist/preview-DcMFt0cD.mjs +240 -0
- package/dist/preview-DcMFt0cD.mjs.map +1 -0
- package/dist/preview.d.mts +13 -0
- package/dist/preview.mjs +2 -0
- package/package.json +96 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.mjs","names":["h"],"sources":["../src/panel.tsx","../src/manager.tsx"],"sourcesContent":["import React, {\n useCallback,\n useEffect,\n useMemo,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactElement,\n} from 'react';\nimport { Placeholder, ScrollArea } from 'storybook/internal/components';\nimport { addons, useGlobals } from 'storybook/manager-api';\nimport { AXES_GLOBAL_KEY, GLOBAL_KEY, INIT_EVENT } from '#/constants.ts';\n\n/** `React.createElement` alias so the manager bundle avoids `react/jsx-runtime`. */\nconst h = React.createElement;\n\ninterface VirtualToken {\n $type?: string;\n $value?: unknown;\n $description?: string;\n}\n\ninterface VirtualTheme {\n name: string;\n input: Record<string, string>;\n sources: string[];\n}\n\ninterface VirtualAxis {\n name: string;\n contexts: readonly string[];\n default: string;\n description?: string;\n source: 'resolver' | 'synthetic';\n}\n\ntype DiagnosticSeverity = 'error' | 'warn' | 'info';\n\ninterface VirtualDiagnostic {\n severity: DiagnosticSeverity;\n group: string;\n message: string;\n filename?: string;\n line?: number;\n column?: number;\n}\n\ninterface InitPayload {\n axes: VirtualAxis[];\n disabledAxes: readonly string[];\n themes: VirtualTheme[];\n defaultTheme: string | null;\n themesResolved: Record<string, Record<string, VirtualToken>>;\n diagnostics: VirtualDiagnostic[];\n cssVarPrefix: string;\n}\n\nfunction usePayload(): InitPayload | null {\n const [payload, setPayload] = useState<InitPayload | null>(null);\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n return payload;\n}\n\nfunction makeCssVarName(path: string, prefix: string): string {\n const tail = path.replaceAll('.', '-');\n return prefix ? `--${prefix}-${tail}` : `--${tail}`;\n}\n\nasync function copy(text: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n /* clipboard access denied — no fallback, the user can read the var name from the row */\n }\n}\n\n/** Format a token `$value` into a short display string. */\nfunction formatValue(value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'string' || typeof value === 'number') return String(value);\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof v['hex'] === 'string') return v['hex'] as string;\n if ('value' in v && 'unit' in v) return `${String(v['value'])}${String(v['unit'])}`;\n return JSON.stringify(value).slice(0, 80);\n }\n return String(value);\n}\n\nconst containerStyle: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n};\n\nconst headerStyle: CSSProperties = {\n padding: '8px 12px',\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n};\n\nconst axisIndicatorStyle: CSSProperties = {\n fontSize: 11,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace',\n opacity: 0.7,\n};\n\nconst treeWrapperStyle: CSSProperties = {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n padding: 8,\n};\n\nconst groupRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n userSelect: 'none',\n outline: 'none',\n};\n\nconst leafRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n border: 'none',\n background: 'transparent',\n color: 'inherit',\n width: '100%',\n textAlign: 'left',\n fontFamily: 'inherit',\n fontSize: 'inherit',\n outline: 'none',\n};\n\nconst caretStyle: CSSProperties = {\n display: 'inline-block',\n width: 12,\n textAlign: 'center',\n opacity: 0.6,\n};\n\nconst treeUlStyle: CSSProperties = { listStyle: 'none', margin: 0, padding: 0 };\n\nconst nestedUlStyle: CSSProperties = {\n listStyle: 'none',\n margin: 0,\n paddingLeft: 18,\n borderLeft: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst typePillStyle: CSSProperties = {\n display: 'inline-block',\n padding: '1px 6px',\n borderRadius: 4,\n fontSize: 10,\n letterSpacing: 0.5,\n textTransform: 'uppercase',\n background: 'rgba(128,128,128,0.15)',\n};\n\nconst valueStyle: CSSProperties = {\n marginLeft: 'auto',\n opacity: 0.7,\n fontSize: 11,\n maxWidth: '40%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\n\nconst countStyle: CSSProperties = {\n marginLeft: 'auto',\n fontSize: 11,\n opacity: 0.7,\n};\n\nconst swatchStyle: CSSProperties = {\n display: 'inline-block',\n width: 14,\n height: 14,\n borderRadius: 3,\n border: '1px solid rgba(128,128,128,0.3)',\n marginLeft: 8,\n};\n\nconst searchInputStyle: CSSProperties = {\n width: '100%',\n padding: '4px 8px',\n fontSize: 12,\n border: '1px solid rgba(128,128,128,0.3)',\n borderRadius: 4,\n background: 'transparent',\n color: 'inherit',\n};\n\ninterface LeafNode {\n kind: 'leaf';\n segment: string;\n path: string;\n token: VirtualToken;\n}\n\ninterface GroupNode {\n kind: 'group';\n segment: string;\n path: string;\n children: TreeNode[];\n}\n\ntype TreeNode = LeafNode | GroupNode;\n\nfunction buildTree(resolved: Record<string, VirtualToken>): TreeNode[] {\n const rootNode: GroupNode = { kind: 'group', segment: '', path: '', children: [] };\n for (const [path, token] of Object.entries(resolved)) {\n const segments = path.split('.');\n let node: GroupNode = rootNode;\n for (let i = 0; i < segments.length - 1; i += 1) {\n const seg = segments[i] as string;\n const prefix = segments.slice(0, i + 1).join('.');\n let child = node.children.find(\n (c): c is GroupNode => c.kind === 'group' && c.segment === seg,\n );\n if (!child) {\n child = { kind: 'group', segment: seg, path: prefix, children: [] };\n node.children.push(child);\n }\n node = child;\n }\n const leafSegment = segments[segments.length - 1] as string;\n node.children.push({ kind: 'leaf', segment: leafSegment, path, token });\n }\n sortTree(rootNode);\n return rootNode.children;\n}\n\nfunction sortTree(node: GroupNode): void {\n node.children.sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === 'group' ? -1 : 1;\n return a.segment.localeCompare(b.segment);\n });\n for (const c of node.children) {\n if (c.kind === 'group') sortTree(c);\n }\n}\n\nfunction collectInitialExpanded(nodes: TreeNode[], remainingDepth: number, out: Set<string>): void {\n if (remainingDepth <= 0) return;\n for (const node of nodes) {\n if (node.kind !== 'group') continue;\n out.add(node.path);\n collectInitialExpanded(node.children, remainingDepth - 1, out);\n }\n}\n\nfunction countLeaves(node: TreeNode): number {\n if (node.kind === 'leaf') return 1;\n let n = 0;\n for (const c of node.children) n += countLeaves(c);\n return n;\n}\n\nfunction filterTree(nodes: TreeNode[], query: string): TreeNode[] {\n if (!query) return nodes;\n const out: TreeNode[] = [];\n for (const node of nodes) {\n if (node.kind === 'leaf') {\n if (node.path.toLowerCase().includes(query)) out.push(node);\n continue;\n }\n const filteredChildren = filterTree(node.children, query);\n if (filteredChildren.length > 0) {\n out.push({ ...node, children: filteredChildren });\n }\n }\n return out;\n}\n\nfunction collectAllGroupPaths(nodes: TreeNode[], out: Set<string>): void {\n for (const node of nodes) {\n if (node.kind === 'group') {\n out.add(node.path);\n collectAllGroupPaths(node.children, out);\n }\n }\n}\n\ninterface PanelProps {\n active: boolean;\n}\n\nexport function DesignTokensPanel({ active }: PanelProps): ReactElement | null {\n const payload = usePayload();\n const [globals] = useGlobals();\n const [query, setQuery] = useState('');\n\n const axes = useMemo(() => payload?.axes ?? [], [payload]);\n const themes = useMemo(() => payload?.themes ?? [], [payload]);\n const globalAxes = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const globalTheme = globals[GLOBAL_KEY] as string | undefined;\n\n const tuple: Record<string, string> = useMemo(() => {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n if (globalAxes && typeof globalAxes === 'object') {\n for (const axis of axes) {\n const candidate = globalAxes[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n return out;\n }\n if (globalTheme) {\n const match = themes.find((t) => t.name === globalTheme);\n if (match) {\n for (const axis of axes) {\n const candidate = match.input[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, themes, globalAxes, globalTheme]);\n\n const themeName = useMemo(() => {\n const match = themes.find((t) => {\n const input = t.input;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? globalTheme ?? payload?.defaultTheme ?? '';\n }, [themes, tuple, globalTheme, payload]);\n\n const prefix = payload?.cssVarPrefix ?? '';\n const tokens = useMemo(() => payload?.themesResolved[themeName] ?? {}, [payload, themeName]);\n const tokenCount = Object.keys(tokens).length;\n\n const tree = useMemo(() => buildTree(tokens), [tokens]);\n const lowerQuery = query.toLowerCase();\n const filtered = useMemo(() => filterTree(tree, lowerQuery), [tree, lowerQuery]);\n\n const initialExpanded = useMemo(() => {\n const out = new Set<string>();\n collectInitialExpanded(tree, 1, out);\n return out;\n }, [tree]);\n\n const [expanded, setExpanded] = useState<Set<string>>(initialExpanded);\n useEffect(() => {\n setExpanded(initialExpanded);\n }, [initialExpanded]);\n\n // When searching, expand every matching group so hits are visible.\n const displayExpanded = useMemo(() => {\n if (!lowerQuery) return expanded;\n const all = new Set<string>();\n collectAllGroupPaths(filtered, all);\n return all;\n }, [expanded, filtered, lowerQuery]);\n\n const toggle = useCallback((path: string): void => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n return next;\n });\n }, []);\n\n const handleLeafClick = useCallback(\n (path: string) => {\n const varName = makeCssVarName(path, prefix);\n void copy(`var(${varName})`);\n },\n [prefix],\n );\n\n if (!active) return null;\n\n if (!payload) {\n return h(Placeholder, null, 'Waiting for swatchbook preview…');\n }\n\n const showAxisIndicator =\n axes.length > 1 || (axes.length === 1 && axes[0]?.source !== 'synthetic');\n const axisIndicatorText = axes\n .map((axis) => `${axis.name}: ${tuple[axis.name] ?? axis.default}`)\n .join(' · ');\n const disabledAxes = payload?.disabledAxes ?? [];\n const pinnedSample = themes[0]?.input ?? {};\n const disabledIndicatorText = disabledAxes\n .map((name) => `${name}: ${pinnedSample[name] ?? '?'} · pinned`)\n .join(' · ');\n\n return h(\n 'div',\n { style: containerStyle },\n h(\n 'div',\n { style: headerStyle },\n showAxisIndicator &&\n h(\n 'div',\n {\n style: axisIndicatorStyle,\n 'data-testid': 'design-tokens-panel-axis-indicator',\n },\n axisIndicatorText,\n ),\n disabledAxes.length > 0 &&\n h(\n 'div',\n {\n style: { ...axisIndicatorStyle, opacity: 0.5 },\n 'data-testid': 'design-tokens-panel-disabled-axes-indicator',\n },\n disabledIndicatorText,\n ),\n h('input', {\n style: searchInputStyle,\n type: 'search',\n placeholder: `Search ${tokenCount} tokens in ${themeName}…`,\n value: query,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => setQuery(e.target.value),\n }),\n ),\n h(DiagnosticsSection, { diagnostics: payload.diagnostics }),\n h(\n ScrollArea,\n { vertical: true },\n filtered.length === 0\n ? h(Placeholder, null, query ? 'No tokens match this filter.' : 'No tokens in this theme.')\n : h(\n 'div',\n { style: treeWrapperStyle },\n h(\n 'ul',\n { style: treeUlStyle, role: 'tree' },\n filtered.map((node) =>\n h(TreeRow, {\n key: node.path || node.segment,\n node,\n expanded: displayExpanded,\n onToggle: toggle,\n onLeafClick: handleLeafClick,\n prefix,\n }),\n ),\n ),\n ),\n ),\n );\n}\n\ninterface TreeRowProps {\n node: TreeNode;\n expanded: Set<string>;\n onToggle(path: string): void;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction TreeRow({ node, expanded, onToggle, onLeafClick, prefix }: TreeRowProps): ReactElement {\n if (node.kind === 'leaf') {\n return h(LeafRow, { node, onLeafClick, prefix });\n }\n const isOpen = expanded.has(node.path);\n const onKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggle(node.path);\n }\n };\n return h(\n 'li',\n { role: 'treeitem', 'aria-expanded': isOpen },\n h(\n 'div',\n {\n role: 'button',\n tabIndex: 0,\n style: groupRowStyle,\n onClick: () => onToggle(node.path),\n onKeyDown: onKey,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-group',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, isOpen ? '▾' : '▸'),\n h('span', null, node.segment),\n h('span', { style: countStyle }, countLeaves(node)),\n ),\n isOpen &&\n h(\n 'ul',\n { style: nestedUlStyle, role: 'group' },\n node.children.map((c) =>\n h(TreeRow, {\n key: c.path || c.segment,\n node: c,\n expanded,\n onToggle,\n onLeafClick,\n prefix,\n }),\n ),\n ),\n );\n}\n\ninterface LeafRowProps {\n node: LeafNode;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction LeafRow({ node, onLeafClick, prefix }: LeafRowProps): ReactElement {\n const type = node.token.$type ?? '';\n const value = node.token.$value;\n const displayValue = formatValue(value);\n const isColor = type === 'color' && typeof value === 'object' && value !== null;\n const colorPreview =\n isColor && typeof (value as Record<string, unknown>)['hex'] === 'string'\n ? ((value as Record<string, unknown>)['hex'] as string)\n : null;\n\n const varName = makeCssVarName(node.path, prefix);\n\n return h(\n 'li',\n { role: 'treeitem' },\n h(\n 'button',\n {\n type: 'button',\n style: leafRowStyle,\n onClick: () => onLeafClick(node.path),\n title: `Click to copy var(${varName})`,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-leaf',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, '•'),\n h('span', null, node.segment),\n type && h('span', { style: typePillStyle }, type),\n h('span', { style: valueStyle }, displayValue),\n colorPreview &&\n h('span', {\n style: { ...swatchStyle, background: colorPreview },\n 'aria-hidden': true,\n }),\n ),\n );\n}\n\nconst severityStyle: Record<DiagnosticSeverity, CSSProperties> = {\n error: { color: '#d64545' },\n warn: { color: '#b08900' },\n info: { opacity: 0.6 },\n};\n\nconst severityLabel: Record<DiagnosticSeverity, string> = {\n error: 'ERROR',\n warn: 'WARN',\n info: 'INFO',\n};\n\nconst diagnosticsSectionStyle: CSSProperties = {\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst diagnosticsSummaryStyle: CSSProperties = {\n padding: '10px 12px',\n fontSize: 12,\n cursor: 'pointer',\n userSelect: 'none',\n listStyle: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n};\n\nconst diagnosticRowStyle: CSSProperties = {\n display: 'grid',\n gridTemplateColumns: '60px 1fr',\n gap: 12,\n padding: '8px 12px',\n fontSize: 12,\n borderTop: '1px solid rgba(128,128,128,0.12)',\n};\n\ninterface DiagnosticsSectionProps {\n diagnostics: readonly VirtualDiagnostic[];\n}\n\nfunction DiagnosticsSection({ diagnostics }: DiagnosticsSectionProps): ReactElement {\n const counts = diagnostics.reduce(\n (acc, d) => {\n acc[d.severity] = (acc[d.severity] ?? 0) + 1;\n return acc;\n },\n { error: 0, warn: 0, info: 0 } as Record<DiagnosticSeverity, number>,\n );\n\n const hasErrorsOrWarnings = counts.error > 0 || counts.warn > 0;\n\n const summaryText = (() => {\n if (diagnostics.length === 0) return '✔ OK · no diagnostics';\n const parts: string[] = [];\n if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? '' : 's'}`);\n if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? '' : 's'}`);\n if (counts.info > 0) parts.push(`${counts.info} info`);\n return parts.join(' · ');\n })();\n\n const summaryColor = (() => {\n if (diagnostics.length === 0) return '#30a46c';\n if (counts.error > 0) return '#d64545';\n if (counts.warn > 0) return '#b08900';\n return 'inherit';\n })();\n\n return h(\n 'details',\n {\n style: diagnosticsSectionStyle,\n open: hasErrorsOrWarnings,\n 'data-testid': 'design-tokens-panel-diagnostics',\n },\n h(\n 'summary',\n { style: { ...diagnosticsSummaryStyle, color: summaryColor, fontWeight: 600 } },\n h('span', null, 'Diagnostics'),\n h('span', { style: { fontWeight: 400 } }, summaryText),\n ),\n diagnostics.length === 0\n ? null\n : h(\n 'div',\n null,\n diagnostics.map((d, i) =>\n h(\n 'div',\n { key: `${d.group}-${i}`, style: diagnosticRowStyle },\n h(\n 'span',\n { style: { ...severityStyle[d.severity], fontWeight: 600, fontSize: 10 } },\n severityLabel[d.severity],\n ),\n h(\n 'div',\n null,\n h('div', null, d.message),\n (d.filename || d.group) &&\n h(\n 'div',\n { style: { opacity: 0.5, fontSize: 10, marginTop: 4 } },\n [d.group, d.filename, d.line ? `:${d.line}` : ''].filter(Boolean).join(' · '),\n ),\n ),\n ),\n ),\n ),\n );\n}\n","import React, { useCallback, useEffect, useMemo, useRef, useState, type ReactElement } from 'react';\nimport { IconButton, WithTooltipPure } from 'storybook/internal/components';\nimport { addons, types, useGlobals, useStorybookApi } from 'storybook/manager-api';\nimport {\n ADDON_ID,\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n GLOBAL_KEY,\n INIT_EVENT,\n PANEL_ID,\n TOOL_ID,\n} from '#/constants.ts';\nimport { DesignTokensPanel } from '#/panel.tsx';\n\n/**\n * Use explicit `React.createElement` rather than JSX so the manager bundle\n * doesn't take a hard dependency on `react/jsx-runtime`. Storybook's manager\n * page injects its own React as a runtime global; `react/jsx-runtime` isn't\n * always part of that exposure, which breaks JSX with\n * \"Cannot read properties of undefined (reading 'recentlyCreatedOwnerStacks')\".\n * Mirrors the pattern `@storybook/addon-a11y` uses in its manager.\n */\nconst h = React.createElement;\n\ninterface AxisEntry {\n name: string;\n contexts: readonly string[];\n default: string;\n description?: string;\n source: 'resolver' | 'synthetic';\n}\n\ninterface ThemeEntry {\n name: string;\n input: Record<string, string>;\n sources: string[];\n}\n\ninterface PresetEntry {\n name: string;\n axes: Partial<Record<string, string>>;\n description?: string;\n}\n\ninterface InitPayload {\n axes: readonly AxisEntry[];\n presets: readonly PresetEntry[];\n themes: ThemeEntry[];\n defaultTheme: string | null;\n}\n\nconst EMPTY_AXES: readonly AxisEntry[] = [];\nconst EMPTY_PRESETS: readonly PresetEntry[] = [];\nconst EMPTY_THEMES: ThemeEntry[] = [];\n\n/**\n * Root toolbar glyph — a split-circle (\"yinyang\") mark: a faint filled\n * disc for the full-swatch silhouette, with a darker half-and-inset-disc\n * path reading as a pair of theme variants swapped in place.\n */\nfunction SwatchbookIcon(): ReactElement {\n return h(\n 'svg',\n { width: 14, height: 14, viewBox: '0 0 14 14', 'aria-hidden': true },\n h('circle', { cx: 7, cy: 7, r: 6, fill: 'currentColor', opacity: 0.15 }),\n h('path', {\n d: 'M7 1a6 6 0 0 0 0 12 3 3 0 0 0 0-6 3 3 0 0 1 0-6Z',\n fill: 'currentColor',\n }),\n );\n}\n\nfunction tupleMatchesInput(\n tuple: Readonly<Record<string, string>>,\n input: Readonly<Record<string, string>>,\n): boolean {\n const keys = Object.keys(input);\n if (keys.length === 0) return false;\n return keys.every((k) => input[k] === tuple[k]);\n}\n\nfunction composedNameFor(\n tuple: Readonly<Record<string, string>>,\n themes: readonly ThemeEntry[],\n fallback: string,\n): string {\n const match = themes.find((t) => tupleMatchesInput(tuple, t.input));\n return match?.name ?? fallback;\n}\n\nfunction defaultTupleFor(axes: readonly AxisEntry[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n return out;\n}\n\n/**\n * Treat the `{ name: 'theme', source: 'synthetic' }` axis — the one core\n * fabricates for single-theme projects with no resolver — as a special case\n * that uses the label \"Theme\" instead of the axis name. Authored single-axis\n * resolvers keep their real axis name (e.g. `mode`).\n */\nfunction displayLabelFor(axis: AxisEntry): string {\n if (axis.source === 'synthetic' && axis.name === 'theme') return 'Theme';\n return axis.name;\n}\n\n/**\n * Compose a preset's sanitized partial tuple with the axis defaults, so\n * applying a preset that only names some axes leaves the omitted ones at\n * their defaults (not blank). Matches the preview decorator's own fallback\n * logic so what the toolbar sends out is what the decorator honors.\n */\nfunction presetTuple(\n preset: PresetEntry,\n axes: readonly AxisEntry[],\n defaults: Readonly<Record<string, string>>,\n): Record<string, string> {\n const out: Record<string, string> = { ...defaults };\n for (const axis of axes) {\n const candidate = preset.axes[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\nfunction tuplesEqual(\n a: Readonly<Record<string, string>>,\n b: Readonly<Record<string, string>>,\n axes: readonly AxisEntry[],\n): boolean {\n for (const axis of axes) {\n if (a[axis.name] !== b[axis.name]) return false;\n }\n return true;\n}\n\nconst SECTION_LABEL_STYLE: React.CSSProperties = {\n padding: '8px 12px 4px',\n fontSize: 11,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n opacity: 0.6,\n};\n\nconst SECTION_BODY_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst AXIS_ROW_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'grid',\n gridTemplateColumns: 'max-content 1fr',\n columnGap: 12,\n rowGap: 4,\n alignItems: 'center',\n};\n\nconst AXIS_LABEL_STYLE: React.CSSProperties = {\n fontSize: 12,\n fontWeight: 600,\n opacity: 0.85,\n};\n\nconst AXIS_PILLS_STYLE: React.CSSProperties = {\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst OPTION_PILL_BASE: React.CSSProperties = {\n padding: '3px 8px',\n borderRadius: 4,\n fontSize: 12,\n lineHeight: '18px',\n // Longhand border properties (not the `border` shorthand) so\n // active → inactive only updates `borderColor`'s value instead of\n // *removing* the key from inline style. Removing it lets Storybook's\n // theme paint a stray border-color on the previously-selected pill.\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: 'transparent',\n background: 'transparent',\n cursor: 'pointer',\n color: 'inherit',\n outline: 'none',\n boxShadow: 'none',\n};\n\nconst OPTION_PILL_ACTIVE: React.CSSProperties = {\n ...OPTION_PILL_BASE,\n fontWeight: 600,\n background: 'rgba(0, 122, 255, 0.12)',\n borderColor: 'rgba(0, 122, 255, 0.45)',\n};\n\nconst PRESET_PILL_MODIFIED: React.CSSProperties = {\n display: 'inline-block',\n width: 6,\n height: 6,\n marginLeft: 6,\n borderRadius: '50%',\n background: 'currentColor',\n opacity: 0.6,\n verticalAlign: 'middle',\n};\n\nconst DIVIDER_STYLE: React.CSSProperties = {\n height: 1,\n background: 'currentColor',\n opacity: 0.1,\n margin: '2px 8px',\n};\n\ninterface OptionPillProps {\n label: string;\n active: boolean;\n title?: string;\n onClick: () => void;\n trailing?: ReactElement | null;\n}\n\nfunction OptionPill({ label, active, title, onClick, trailing }: OptionPillProps): ReactElement {\n return h(\n 'button',\n {\n type: 'button',\n title,\n onClick,\n // Skip focus on mouse click so Storybook's `:focus` border-color\n // theming doesn't stick on the previously-clicked pill. Keyboard\n // tabbing still lands focus normally — preventDefault on mousedown\n // only blocks the implicit focus-on-click behavior.\n onMouseDown: (event) => event.preventDefault(),\n style: active ? OPTION_PILL_ACTIVE : OPTION_PILL_BASE,\n },\n label,\n trailing ?? null,\n );\n}\n\ninterface PresetsSectionProps {\n presets: readonly PresetEntry[];\n axes: readonly AxisEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n lastApplied: string | null;\n onApply: (preset: PresetEntry) => void;\n}\n\nfunction PresetsSection({\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply,\n}: PresetsSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Presets'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...presets.map((preset) => {\n const tuple = presetTuple(preset, axes, defaults);\n const matches = tuplesEqual(tuple, activeTuple, axes);\n const modified = !matches && preset.name === lastApplied;\n const title = preset.description ? `${preset.name} — ${preset.description}` : preset.name;\n return h(OptionPill, {\n key: `${TOOL_ID}/preset/${preset.name}`,\n label: preset.name,\n active: matches,\n title,\n onClick: () => onApply(preset),\n trailing: modified\n ? h('span', { 'aria-hidden': true, style: PRESET_PILL_MODIFIED })\n : null,\n });\n }),\n ),\n );\n}\n\ninterface AxisSectionProps {\n axis: AxisEntry;\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction AxisSection({ axis, active, onSelect }: AxisSectionProps): ReactElement {\n const label = displayLabelFor(axis);\n return h(\n 'div',\n { style: AXIS_ROW_STYLE },\n h('div', { style: AXIS_LABEL_STYLE, title: axis.description }, label),\n h(\n 'div',\n { style: AXIS_PILLS_STYLE },\n ...axis.contexts.map((ctx) =>\n h(OptionPill, {\n key: `${TOOL_ID}/${axis.name}/${ctx}`,\n label: ctx,\n active: ctx === active,\n onClick: () => onSelect(ctx),\n }),\n ),\n ),\n );\n}\n\nconst COLOR_FORMAT_OPTIONS: readonly { id: string; label: string }[] = [\n { id: 'hex', label: 'Hex' },\n { id: 'rgb', label: 'RGB' },\n { id: 'hsl', label: 'HSL' },\n { id: 'oklch', label: 'OKLCH' },\n { id: 'raw', label: 'Raw (JSON)' },\n];\n\ninterface ColorFormatSectionProps {\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction ColorFormatSection({ active, onSelect }: ColorFormatSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Color format'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...COLOR_FORMAT_OPTIONS.map((opt) =>\n h(OptionPill, {\n key: `${TOOL_ID}/color-format/${opt.id}`,\n label: opt.label,\n active: opt.id === active,\n onClick: () => onSelect(opt.id),\n }),\n ),\n ),\n );\n}\n\ninterface PopoverBodyProps {\n axes: readonly AxisEntry[];\n presets: readonly PresetEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n activeColorFormat: string;\n lastApplied: string | null;\n onApplyPreset: (preset: PresetEntry) => void;\n onSelectAxis: (axisName: string, next: string) => void;\n onSelectColorFormat: (next: string) => void;\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\nfunction PopoverBody(props: PopoverBodyProps): ReactElement {\n const {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset,\n onSelectAxis,\n onSelectColorFormat,\n onKeyDown,\n } = props;\n const sections: ReactElement[] = [];\n if (presets.length > 0) {\n sections.push(\n h(PresetsSection, {\n key: 'presets',\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply: onApplyPreset,\n }),\n h('div', { key: 'presets-divider', style: DIVIDER_STYLE }),\n );\n }\n axes.forEach((axis, idx) => {\n sections.push(\n h(AxisSection, {\n key: `axis-${axis.name}`,\n axis,\n active: activeTuple[axis.name] ?? axis.default,\n onSelect: (next) => onSelectAxis(axis.name, next),\n }),\n );\n if (idx === axes.length - 1) {\n sections.push(h('div', { key: 'axes-divider', style: DIVIDER_STYLE }));\n }\n });\n sections.push(\n h(ColorFormatSection, {\n key: 'color-format',\n active: activeColorFormat,\n onSelect: onSelectColorFormat,\n }),\n );\n return h(\n 'div',\n {\n role: 'menu',\n tabIndex: -1,\n onKeyDown,\n style: { minWidth: 260, padding: '4px 0', outline: 'none' },\n 'data-testid': 'swatchbook-toolbar-popover',\n },\n ...sections,\n );\n}\n\nfunction AxesToolbar(): ReactElement {\n const [globals, updateGlobals] = useGlobals();\n const api = useStorybookApi();\n const [payload, setPayload] = useState<InitPayload | null>(null);\n const [open, setOpen] = useState(false);\n const bodyRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n\n const axes = payload?.axes ?? EMPTY_AXES;\n const presets = payload?.presets ?? EMPTY_PRESETS;\n const themes = payload?.themes ?? EMPTY_THEMES;\n const defaults = useMemo(() => defaultTupleFor(axes), [axes]);\n const [lastApplied, setLastApplied] = useState<string | null>(null);\n const globalTuple = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const activeColorFormat = (globals[COLOR_FORMAT_GLOBAL_KEY] as string | undefined) ?? 'hex';\n\n const activeTuple = useMemo<Record<string, string>>(() => {\n const out: Record<string, string> = { ...defaults };\n if (globalTuple) {\n for (const axis of axes) {\n const candidate = globalTuple[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, defaults, globalTuple]);\n\n const setAxis = useCallback(\n (axisName: string, next: string): void => {\n const tuple: Record<string, string> = { ...activeTuple, [axisName]: next };\n const fallback = payload?.defaultTheme ?? themes[0]?.name ?? '';\n const composed = composedNameFor(tuple, themes, fallback);\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple, [GLOBAL_KEY]: composed });\n },\n [activeTuple, themes, payload?.defaultTheme, updateGlobals],\n );\n\n const applyPreset = useCallback(\n (preset: PresetEntry): void => {\n const tuple = presetTuple(preset, axes, defaults);\n const fallback = payload?.defaultTheme ?? themes[0]?.name ?? '';\n const composed = composedNameFor(tuple, themes, fallback);\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple, [GLOBAL_KEY]: composed });\n setLastApplied(preset.name);\n },\n [axes, defaults, themes, payload?.defaultTheme, updateGlobals],\n );\n\n useEffect(() => {\n if (axes.length === 0) return;\n /**\n * alt+T cycles the primary (first) axis's contexts, keeping the rest\n * of the tuple pinned. With multi-axis projects this makes the shortcut\n * predictable — you always know which wheel you're spinning. For\n * single-axis projects the behavior is identical to the pre-N-dropdown\n * toolbar (cycle through every theme).\n */\n const primary = axes[0];\n if (!primary) return;\n api.setAddonShortcut(ADDON_ID, {\n label: `Cycle swatchbook ${displayLabelFor(primary)}`,\n defaultShortcut: ['alt', 'T'],\n actionName: 'cycleAxis',\n showInMenu: true,\n action: () => {\n const current = activeTuple[primary.name] ?? primary.default;\n const idx = primary.contexts.indexOf(current);\n const next = primary.contexts[(idx + 1) % primary.contexts.length];\n if (next !== undefined) setAxis(primary.name, next);\n },\n });\n }, [api, axes, activeTuple, setAxis]);\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n setOpen(false);\n }\n }, []);\n\n /**\n * Escape closes even when focus hasn't entered the popover yet (e.g. the\n * user opened it via click and the mouse is still over the canvas). We\n * attach a document-level listener when open.\n */\n useEffect(() => {\n if (!open) return;\n const onDocKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') setOpen(false);\n };\n document.addEventListener('keydown', onDocKey);\n return () => document.removeEventListener('keydown', onDocKey);\n }, [open]);\n\n if (axes.length === 0) {\n return h(\n IconButton,\n { key: TOOL_ID, title: 'Swatchbook theme (loading…)', disabled: true },\n h(SwatchbookIcon),\n );\n }\n\n const summary = axes.map((a) => activeTuple[a.name] ?? a.default).join(' · ');\n const title = `Swatchbook · ${summary}`;\n\n const button = h(\n IconButton,\n {\n key: TOOL_ID,\n title,\n active: open,\n onClick: () => setOpen((prev) => !prev),\n },\n h(SwatchbookIcon),\n );\n\n const tooltipBody = h(PopoverBody, {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset: applyPreset,\n onSelectAxis: setAxis,\n onSelectColorFormat: (next: string) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),\n onKeyDown: handleKeyDown,\n });\n\n return h(\n 'span',\n { ref: bodyRef, style: { display: 'inline-flex', alignItems: 'center' } },\n h(WithTooltipPure, {\n placement: 'bottom',\n trigger: 'click',\n visible: open,\n onVisibleChange: (next: boolean) => setOpen(next),\n closeOnOutsideClick: true,\n tooltip: tooltipBody,\n children: button,\n }),\n );\n}\n\naddons.register(ADDON_ID, () => {\n addons.add(TOOL_ID, {\n type: types.TOOL,\n title: 'Swatchbook theme',\n match: ({ viewMode, tabId }) => !tabId && (viewMode === 'story' || viewMode === 'docs'),\n render: () => h(AxesToolbar),\n });\n\n addons.add(PANEL_ID, {\n type: types.PANEL,\n title: 'Design Tokens',\n match: ({ viewMode }) => viewMode === 'story',\n render: ({ active }) => h(DesignTokensPanel, { active: !!active }),\n });\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Config } from "@unpunnyfuns/swatchbook-core";
|
|
2
|
+
|
|
3
|
+
//#region src/options.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Options accepted by the swatchbook preset. Either pass a full {@link Config}
|
|
6
|
+
* as `config`, or set `configPath` pointing at a module whose default export
|
|
7
|
+
* is a `Config` (supports `.ts`, `.mts`, `.js`, `.mjs` via jiti).
|
|
8
|
+
*/
|
|
9
|
+
interface AddonOptions {
|
|
10
|
+
/** Inline swatchbook config. Mutually exclusive with `configPath`. */
|
|
11
|
+
config?: Config;
|
|
12
|
+
/** Path to a config module, relative to the Storybook `configDir`. */
|
|
13
|
+
configPath?: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { AddonOptions as t };
|
|
17
|
+
//# sourceMappingURL=options-rvGQy0uV.d.mts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { t as AddonOptions } from "./options-rvGQy0uV.mjs";
|
|
2
|
+
import { InlineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
//#region src/preset.d.ts
|
|
5
|
+
interface PresetOptions extends AddonOptions {
|
|
6
|
+
/** Storybook injects this — the `.storybook` directory absolute path. */
|
|
7
|
+
configDir: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Storybook preset entry. Called by Storybook at config time; extends Vite's
|
|
11
|
+
* plugin list with our virtual-module plugin so the preview can import
|
|
12
|
+
* `virtual:swatchbook/tokens`. Also writes the typed token-path codegen so
|
|
13
|
+
* `useToken()` autocompletes against the loaded project.
|
|
14
|
+
*/
|
|
15
|
+
declare function viteFinal(viteConfig: InlineConfig, options: PresetOptions): Promise<InlineConfig>;
|
|
16
|
+
/** Storybook appends this module into the manager bundle so our toolbar tool registers. */
|
|
17
|
+
declare function managerEntries(entry?: string[]): string[];
|
|
18
|
+
//#endregion
|
|
19
|
+
export { managerEntries, viteFinal };
|
|
20
|
+
//# sourceMappingURL=preset.d.mts.map
|
package/dist/preset.mjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { l as RESOLVED_VIRTUAL_MODULE_ID } from "./constants-1plfdgh7.mjs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { loadProject, projectCss } from "@unpunnyfuns/swatchbook-core";
|
|
6
|
+
import { createJiti } from "jiti";
|
|
7
|
+
//#region src/virtual/plugin.ts
|
|
8
|
+
/**
|
|
9
|
+
* Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —
|
|
10
|
+
* a single source of truth for themes, resolved token maps, per-theme CSS,
|
|
11
|
+
* and diagnostics. Watches the token files + resolver for changes and
|
|
12
|
+
* invalidates the module so HMR reloads the preview with fresh data.
|
|
13
|
+
*/
|
|
14
|
+
function swatchbookTokensPlugin({ config, cwd }) {
|
|
15
|
+
let project;
|
|
16
|
+
let css = "";
|
|
17
|
+
async function refresh() {
|
|
18
|
+
project = await loadProject(config, cwd);
|
|
19
|
+
css = projectCss(project);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: "swatchbook:virtual-tokens",
|
|
23
|
+
enforce: "pre",
|
|
24
|
+
async buildStart() {
|
|
25
|
+
await refresh();
|
|
26
|
+
},
|
|
27
|
+
resolveId(id) {
|
|
28
|
+
if (id === "virtual:swatchbook/tokens") return RESOLVED_VIRTUAL_MODULE_ID;
|
|
29
|
+
return null;
|
|
30
|
+
},
|
|
31
|
+
load(id) {
|
|
32
|
+
if (id !== RESOLVED_VIRTUAL_MODULE_ID) return null;
|
|
33
|
+
if (!project) return "export default null;";
|
|
34
|
+
return [
|
|
35
|
+
`/* swatchbook virtual module — generated */`,
|
|
36
|
+
`export const axes = ${JSON.stringify(project.axes)};`,
|
|
37
|
+
`export const presets = ${JSON.stringify(project.presets)};`,
|
|
38
|
+
`export const disabledAxes = ${JSON.stringify(project.disabledAxes)};`,
|
|
39
|
+
`export const themes = ${JSON.stringify(project.themes)};`,
|
|
40
|
+
`export const defaultTheme = ${JSON.stringify(project.themes[0]?.name ?? null)};`,
|
|
41
|
+
`export const themesResolved = ${JSON.stringify(project.themesResolved)};`,
|
|
42
|
+
`export const diagnostics = ${JSON.stringify(project.diagnostics)};`,
|
|
43
|
+
`export const css = ${JSON.stringify(css)};`,
|
|
44
|
+
`export const cssVarPrefix = ${JSON.stringify(config.cssVarPrefix ?? "")};`
|
|
45
|
+
].join("\n");
|
|
46
|
+
},
|
|
47
|
+
configureServer(server) {
|
|
48
|
+
const watchPaths = collectWatchPaths(config, project, cwd);
|
|
49
|
+
for (const p of watchPaths) server.watcher.add(p);
|
|
50
|
+
const invalidate = async () => {
|
|
51
|
+
await refresh();
|
|
52
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
|
|
53
|
+
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
54
|
+
server.ws.send({ type: "full-reload" });
|
|
55
|
+
};
|
|
56
|
+
server.watcher.on("change", (changed) => {
|
|
57
|
+
if (watchPaths.some((p) => changed === p || changed.startsWith(p))) invalidate();
|
|
58
|
+
});
|
|
59
|
+
server.watcher.on("add", (changed) => {
|
|
60
|
+
if (watchPaths.some((p) => changed.startsWith(p))) invalidate();
|
|
61
|
+
});
|
|
62
|
+
server.watcher.on("unlink", (changed) => {
|
|
63
|
+
if (watchPaths.some((p) => changed.startsWith(p))) invalidate();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Collect the set of filesystem paths the dev server should watch for
|
|
70
|
+
* HMR. When `config.tokens` is set, use its globs (stripped to their
|
|
71
|
+
* base directories) — users opt in to broader watching this way. When
|
|
72
|
+
* absent, use the resolver file + every `$ref` target it pulled in, as
|
|
73
|
+
* tracked on `project.sourceFiles` — which stays correct as the resolver
|
|
74
|
+
* evolves without requiring a parallel `tokens` glob.
|
|
75
|
+
*/
|
|
76
|
+
function collectWatchPaths(config, project, cwd) {
|
|
77
|
+
const paths = [];
|
|
78
|
+
if (config.tokens && config.tokens.length > 0) for (const glob of config.tokens) {
|
|
79
|
+
const base = glob.replace(/\/\*.*$/, "").replace(/\/[^/]*\*.*$/, "");
|
|
80
|
+
paths.push(resolveFromCwd(base, cwd));
|
|
81
|
+
}
|
|
82
|
+
else if (project?.sourceFiles) for (const file of project.sourceFiles) paths.push(dirname(file));
|
|
83
|
+
if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));
|
|
84
|
+
return [...new Set(paths)];
|
|
85
|
+
}
|
|
86
|
+
function resolveFromCwd(p, cwd) {
|
|
87
|
+
if (isAbsolute(p)) return p;
|
|
88
|
+
return resolve(cwd, p);
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/preset.ts
|
|
92
|
+
/**
|
|
93
|
+
* Storybook preset entry. Called by Storybook at config time; extends Vite's
|
|
94
|
+
* plugin list with our virtual-module plugin so the preview can import
|
|
95
|
+
* `virtual:swatchbook/tokens`. Also writes the typed token-path codegen so
|
|
96
|
+
* `useToken()` autocompletes against the loaded project.
|
|
97
|
+
*/
|
|
98
|
+
async function viteFinal(viteConfig, options) {
|
|
99
|
+
const { config, cwd } = await resolveConfig(options);
|
|
100
|
+
await writeTokenCodegen(config, cwd, options);
|
|
101
|
+
const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];
|
|
102
|
+
plugins.push(swatchbookTokensPlugin({
|
|
103
|
+
config,
|
|
104
|
+
cwd
|
|
105
|
+
}));
|
|
106
|
+
return {
|
|
107
|
+
...viteConfig,
|
|
108
|
+
plugins
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Storybook appends this module into the manager bundle so our toolbar tool registers. */
|
|
112
|
+
function managerEntries(entry = []) {
|
|
113
|
+
const managerUrl = import.meta.resolve("@unpunnyfuns/swatchbook-addon/manager");
|
|
114
|
+
return [...entry, fileURLToPath(managerUrl)];
|
|
115
|
+
}
|
|
116
|
+
async function resolveConfig(options) {
|
|
117
|
+
const projectRoot = resolve(options.configDir, "..");
|
|
118
|
+
if (options.config) return {
|
|
119
|
+
config: options.config,
|
|
120
|
+
cwd: projectRoot
|
|
121
|
+
};
|
|
122
|
+
const path = options.configPath ?? "swatchbook.config.ts";
|
|
123
|
+
const absolute = isAbsolute(path) ? path : resolve(options.configDir, path);
|
|
124
|
+
return {
|
|
125
|
+
config: await createJiti(pathToFileURL(options.configDir).href, {
|
|
126
|
+
interopDefault: true,
|
|
127
|
+
moduleCache: false
|
|
128
|
+
}).import(absolute, { default: true }),
|
|
129
|
+
cwd: dirname(absolute)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function writeTokenCodegen(config, cwd, options) {
|
|
133
|
+
const project = await loadProject(config, cwd);
|
|
134
|
+
const outDir = resolve(resolve(options.configDir, ".."), config.outDir ?? ".swatchbook");
|
|
135
|
+
await mkdir(outDir, { recursive: true });
|
|
136
|
+
const content = renderTokenTypes(project);
|
|
137
|
+
await writeFile(resolve(outDir, "tokens.d.ts"), content);
|
|
138
|
+
}
|
|
139
|
+
function renderTokenTypes(project) {
|
|
140
|
+
const paths = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const theme of project.themes) {
|
|
142
|
+
const tokens = project.themesResolved[theme.name];
|
|
143
|
+
if (!tokens) continue;
|
|
144
|
+
for (const path of Object.keys(tokens)) paths.add(path);
|
|
145
|
+
}
|
|
146
|
+
const tokenEntries = [...paths].toSorted().map((p) => ` ${JSON.stringify(p)}: string;`);
|
|
147
|
+
const themeUnion = project.themes.map((t) => JSON.stringify(t.name)).join(" | ") || "string";
|
|
148
|
+
return [
|
|
149
|
+
"// Generated by @unpunnyfuns/swatchbook-addon. Do not edit.",
|
|
150
|
+
"declare module '@unpunnyfuns/swatchbook-addon/hooks' {",
|
|
151
|
+
" interface SwatchbookTokenMap {",
|
|
152
|
+
...tokenEntries,
|
|
153
|
+
" }",
|
|
154
|
+
"",
|
|
155
|
+
` export type SwatchbookThemeName = ${themeUnion};`,
|
|
156
|
+
"}",
|
|
157
|
+
""
|
|
158
|
+
].join("\n");
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { managerEntries, viteFinal };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=preset.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.mjs","names":["resolvePath"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject, projectCss } from '@unpunnyfuns/swatchbook-core';\nimport { dirname, isAbsolute, resolve as resolvePath } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from '#/constants.ts';\n\nexport interface SwatchbookPluginOptions {\n config: Config;\n cwd: string;\n}\n\n/**\n * Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —\n * a single source of truth for themes, resolved token maps, per-theme CSS,\n * and diagnostics. Watches the token files + resolver for changes and\n * invalidates the module so HMR reloads the preview with fresh data.\n */\nexport function swatchbookTokensPlugin({ config, cwd }: SwatchbookPluginOptions): Plugin {\n let project: Project | undefined;\n let css = '';\n\n async function refresh(): Promise<void> {\n project = await loadProject(config, cwd);\n css = projectCss(project);\n }\n\n return {\n name: 'swatchbook:virtual-tokens',\n enforce: 'pre',\n\n async buildStart() {\n await refresh();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_VIRTUAL_MODULE_ID) return null;\n if (!project) return 'export default null;';\n // Emit a typed ESM module. Values are JSON-stringified for stability.\n return [\n `/* swatchbook virtual module — generated */`,\n `export const axes = ${JSON.stringify(project.axes)};`,\n `export const presets = ${JSON.stringify(project.presets)};`,\n `export const disabledAxes = ${JSON.stringify(project.disabledAxes)};`,\n `export const themes = ${JSON.stringify(project.themes)};`,\n `export const defaultTheme = ${JSON.stringify(project.themes[0]?.name ?? null)};`,\n `export const themesResolved = ${JSON.stringify(project.themesResolved)};`,\n `export const diagnostics = ${JSON.stringify(project.diagnostics)};`,\n `export const css = ${JSON.stringify(css)};`,\n `export const cssVarPrefix = ${JSON.stringify(config.cssVarPrefix ?? '')};`,\n ].join('\\n');\n },\n\n configureServer(server) {\n const watchPaths = collectWatchPaths(config, project, cwd);\n for (const p of watchPaths) server.watcher.add(p);\n\n const invalidate = async (): Promise<void> => {\n await refresh();\n const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n };\n\n server.watcher.on('change', (changed) => {\n if (watchPaths.some((p) => changed === p || changed.startsWith(p))) {\n void invalidate();\n }\n });\n server.watcher.on('add', (changed) => {\n if (watchPaths.some((p) => changed.startsWith(p))) void invalidate();\n });\n server.watcher.on('unlink', (changed) => {\n if (watchPaths.some((p) => changed.startsWith(p))) void invalidate();\n });\n },\n };\n}\n\n/**\n * Collect the set of filesystem paths the dev server should watch for\n * HMR. When `config.tokens` is set, use its globs (stripped to their\n * base directories) — users opt in to broader watching this way. When\n * absent, use the resolver file + every `$ref` target it pulled in, as\n * tracked on `project.sourceFiles` — which stays correct as the resolver\n * evolves without requiring a parallel `tokens` glob.\n */\nfunction collectWatchPaths(config: Config, project: Project | undefined, cwd: string): string[] {\n const paths: string[] = [];\n if (config.tokens && config.tokens.length > 0) {\n for (const glob of config.tokens) {\n const base = glob.replace(/\\/\\*.*$/, '').replace(/\\/[^/]*\\*.*$/, '');\n paths.push(resolveFromCwd(base, cwd));\n }\n } else if (project?.sourceFiles) {\n for (const file of project.sourceFiles) paths.push(dirname(file));\n }\n if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));\n return [...new Set(paths)];\n}\n\nfunction resolveFromCwd(p: string, cwd: string): string {\n if (isAbsolute(p)) return p;\n return resolvePath(cwd, p);\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { createJiti } from 'jiti';\nimport type { InlineConfig } from 'vite';\nimport type { AddonOptions } from '#/options.ts';\nimport { swatchbookTokensPlugin } from '#/virtual/plugin.ts';\n\ninterface PresetOptions extends AddonOptions {\n /** Storybook injects this — the `.storybook` directory absolute path. */\n configDir: string;\n}\n\n/**\n * Storybook preset entry. Called by Storybook at config time; extends Vite's\n * plugin list with our virtual-module plugin so the preview can import\n * `virtual:swatchbook/tokens`. Also writes the typed token-path codegen so\n * `useToken()` autocompletes against the loaded project.\n */\nexport async function viteFinal(\n viteConfig: InlineConfig,\n options: PresetOptions,\n): Promise<InlineConfig> {\n const { config, cwd } = await resolveConfig(options);\n\n // Codegen runs once at Vite startup. The virtual module plugin still\n // owns the live reload path via its HMR watcher; this file just gives\n // TS autocomplete for `useToken('…')` in consumer stories.\n await writeTokenCodegen(config, cwd, options);\n\n const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];\n plugins.push(swatchbookTokensPlugin({ config, cwd }));\n\n return { ...viteConfig, plugins };\n}\n\n/** Storybook appends this module into the manager bundle so our toolbar tool registers. */\nexport function managerEntries(entry: string[] = []): string[] {\n const managerUrl = import.meta.resolve('@unpunnyfuns/swatchbook-addon/manager');\n return [...entry, fileURLToPath(managerUrl)];\n}\n\nasync function resolveConfig(options: PresetOptions): Promise<{ config: Config; cwd: string }> {\n const projectRoot = resolve(options.configDir, '..');\n\n if (options.config) {\n return { config: options.config, cwd: projectRoot };\n }\n\n const path = options.configPath ?? 'swatchbook.config.ts';\n const absolute = isAbsolute(path) ? path : resolve(options.configDir, path);\n\n const jiti = createJiti(pathToFileURL(options.configDir).href, {\n interopDefault: true,\n moduleCache: false,\n });\n const loaded = (await jiti.import(absolute, { default: true })) as Config;\n\n // If the config file isn't at projectRoot, still resolve globs from its dir.\n const cwd = dirname(absolute);\n return { config: loaded, cwd };\n}\n\nasync function writeTokenCodegen(\n config: Config,\n cwd: string,\n options: PresetOptions,\n): Promise<void> {\n const project = await loadProject(config, cwd);\n const projectRoot = resolve(options.configDir, '..');\n const outDir = resolve(projectRoot, config.outDir ?? '.swatchbook');\n await mkdir(outDir, { recursive: true });\n const content = renderTokenTypes(project);\n await writeFile(resolve(outDir, 'tokens.d.ts'), content);\n}\n\nfunction renderTokenTypes(project: Project): string {\n const paths = new Set<string>();\n for (const theme of project.themes) {\n const tokens = project.themesResolved[theme.name];\n if (!tokens) continue;\n for (const path of Object.keys(tokens)) paths.add(path);\n }\n const sorted = [...paths].toSorted();\n const tokenEntries = sorted.map((p) => ` ${JSON.stringify(p)}: string;`);\n const themeUnion = project.themes.map((t) => JSON.stringify(t.name)).join(' | ') || 'string';\n\n return [\n '// Generated by @unpunnyfuns/swatchbook-addon. Do not edit.',\n \"declare module '@unpunnyfuns/swatchbook-addon/hooks' {\",\n ' interface SwatchbookTokenMap {',\n ...tokenEntries,\n ' }',\n '',\n ` export type SwatchbookThemeName = ${themeUnion};`,\n '}',\n '',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { a as GLOBAL_KEY, c as PARAM_KEY, i as DATA_THEME_ATTR, n as AXES_GLOBAL_KEY, o as INIT_EVENT, r as COLOR_FORMAT_GLOBAL_KEY, u as STYLE_ELEMENT_ID } from "./constants-1plfdgh7.mjs";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { addons } from "storybook/preview-api";
|
|
4
|
+
import { axes, css, cssVarPrefix, defaultTheme, diagnostics, disabledAxes, presets, themes, themesResolved } from "virtual:swatchbook/tokens";
|
|
5
|
+
import { AxesContext, COLOR_FORMATS, ColorFormatContext, SwatchbookContext, ThemeContext } from "@unpunnyfuns/swatchbook-blocks";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/preview.tsx
|
|
20
|
+
var preview_exports = /* @__PURE__ */ __exportAll({
|
|
21
|
+
decorators: () => decorators,
|
|
22
|
+
globalTypes: () => globalTypes,
|
|
23
|
+
initialGlobals: () => initialGlobals
|
|
24
|
+
});
|
|
25
|
+
/** CSS var name with the active prefix applied. */
|
|
26
|
+
function v(name) {
|
|
27
|
+
return cssVarPrefix ? `--${cssVarPrefix}-${name}` : `--${name}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Inject the per-theme stylesheet plus a tiny `html, body { ... }` block so
|
|
31
|
+
* the iframe's own chrome (outside any decorator wrapper — Docs mode,
|
|
32
|
+
* autodocs, empty gutters) also picks up the active theme.
|
|
33
|
+
*/
|
|
34
|
+
function ensureStylesheet() {
|
|
35
|
+
if (typeof document === "undefined") return;
|
|
36
|
+
const text = `${css}\n${`
|
|
37
|
+
html, body {
|
|
38
|
+
background: var(${v("color-sys-surface-default")}, Canvas);
|
|
39
|
+
color: var(${v("color-sys-text-default")}, CanvasText);
|
|
40
|
+
margin: 0;
|
|
41
|
+
}
|
|
42
|
+
`}`;
|
|
43
|
+
let style = document.getElementById(STYLE_ELEMENT_ID);
|
|
44
|
+
if (!style) {
|
|
45
|
+
style = document.createElement("style");
|
|
46
|
+
style.id = STYLE_ELEMENT_ID;
|
|
47
|
+
document.head.appendChild(style);
|
|
48
|
+
}
|
|
49
|
+
if (style.textContent !== text) style.textContent = text;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Write the composed permutation ID to `data-theme` plus one
|
|
53
|
+
* `data-<axis>=<context>` per axis. The composed ID stays for CSS
|
|
54
|
+
* emission's current `[data-theme="…"]` selectors (retires in #135);
|
|
55
|
+
* per-axis attributes are what upcoming toolbar + panel work will key on.
|
|
56
|
+
*/
|
|
57
|
+
function setRootAxes(themeName, tuple) {
|
|
58
|
+
if (typeof document === "undefined") return;
|
|
59
|
+
const root = document.documentElement;
|
|
60
|
+
root.setAttribute(DATA_THEME_ATTR, themeName);
|
|
61
|
+
for (const axis of axes) {
|
|
62
|
+
const value = tuple[axis.name];
|
|
63
|
+
if (value === void 0) root.removeAttribute(`data-${axis.name}`);
|
|
64
|
+
else root.setAttribute(`data-${axis.name}`, value);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Disabled axes aren't in `virtualAxes`, but CSS may still reference their
|
|
68
|
+
* pinned value on compound selectors in extension code. Read the value
|
|
69
|
+
* from any surviving theme's `input` — every theme that survived filtering
|
|
70
|
+
* carries the same pinned context for each disabled axis.
|
|
71
|
+
*/
|
|
72
|
+
const pinnedSample = themes[0]?.input;
|
|
73
|
+
if (pinnedSample) for (const name of disabledAxes) {
|
|
74
|
+
const value = pinnedSample[name];
|
|
75
|
+
if (value !== void 0) root.setAttribute(`data-${name}`, value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Emit the full virtual-module payload to the manager over Storybook's
|
|
80
|
+
* channel so the toolbar + panel (which run in the manager bundle and
|
|
81
|
+
* can't import our virtual module) can render from it.
|
|
82
|
+
*/
|
|
83
|
+
function broadcastInit() {
|
|
84
|
+
addons.getChannel().emit(INIT_EVENT, {
|
|
85
|
+
axes,
|
|
86
|
+
disabledAxes,
|
|
87
|
+
presets,
|
|
88
|
+
themes,
|
|
89
|
+
defaultTheme,
|
|
90
|
+
themesResolved,
|
|
91
|
+
diagnostics,
|
|
92
|
+
cssVarPrefix
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Axis-default tuple, used as the baseline before overrides. */
|
|
96
|
+
function defaultTuple() {
|
|
97
|
+
const out = {};
|
|
98
|
+
for (const axis of axes) out[axis.name] = axis.default;
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** Look up a `Theme.input` by composed name. Returns `undefined` if no theme matches. */
|
|
102
|
+
function tupleForName(name) {
|
|
103
|
+
return themes.find((t) => t.name === name)?.input;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Merge a partial tuple onto the axis defaults, dropping keys for axes that
|
|
107
|
+
* don't exist and silently falling back to the default for contexts that
|
|
108
|
+
* aren't listed on the axis.
|
|
109
|
+
*/
|
|
110
|
+
function normalizeTuple(partial) {
|
|
111
|
+
const out = defaultTuple();
|
|
112
|
+
for (const axis of axes) {
|
|
113
|
+
const candidate = partial[axis.name];
|
|
114
|
+
if (candidate !== void 0 && axis.contexts.includes(candidate)) out[axis.name] = candidate;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the active tuple from all four input channels, in priority order:
|
|
120
|
+
* 1. `parameters.swatchbook.axes` — per-story tuple.
|
|
121
|
+
* 2. `parameters.swatchbook.theme` — per-story composed name (legacy).
|
|
122
|
+
* 3. `globals.swatchbookAxes` — toolbar-set tuple.
|
|
123
|
+
* 4. `globals.swatchbookTheme` — toolbar-set composed name.
|
|
124
|
+
* 5. virtual module default.
|
|
125
|
+
*/
|
|
126
|
+
function resolveTuple(globals, parameters) {
|
|
127
|
+
const param = parameters[PARAM_KEY];
|
|
128
|
+
const paramAxes = param?.["axes"];
|
|
129
|
+
if (paramAxes && typeof paramAxes === "object") return normalizeTuple(paramAxes);
|
|
130
|
+
const paramTheme = param?.["theme"];
|
|
131
|
+
if (typeof paramTheme === "string") {
|
|
132
|
+
const hit = tupleForName(paramTheme);
|
|
133
|
+
if (hit) return normalizeTuple(hit);
|
|
134
|
+
}
|
|
135
|
+
const globalAxes = globals[AXES_GLOBAL_KEY];
|
|
136
|
+
if (globalAxes && typeof globalAxes === "object") return normalizeTuple(globalAxes);
|
|
137
|
+
const globalTheme = globals[GLOBAL_KEY];
|
|
138
|
+
if (typeof globalTheme === "string") {
|
|
139
|
+
const hit = tupleForName(globalTheme);
|
|
140
|
+
if (hit) return normalizeTuple(hit);
|
|
141
|
+
}
|
|
142
|
+
return defaultTuple();
|
|
143
|
+
}
|
|
144
|
+
function resolveColorFormat(globals) {
|
|
145
|
+
const raw = globals[COLOR_FORMAT_GLOBAL_KEY];
|
|
146
|
+
if (typeof raw === "string" && COLOR_FORMATS.includes(raw)) return raw;
|
|
147
|
+
return "hex";
|
|
148
|
+
}
|
|
149
|
+
const themedDecorator = (Story, context) => {
|
|
150
|
+
const tuple = useMemo(() => resolveTuple(context.globals, context.parameters), [context.globals, context.parameters]);
|
|
151
|
+
const colorFormat = useMemo(() => resolveColorFormat(context.globals), [context.globals]);
|
|
152
|
+
const themeName = useMemo(() => {
|
|
153
|
+
return themes.find((t) => {
|
|
154
|
+
const input = t.input;
|
|
155
|
+
return Object.keys(input).every((k) => input[k] === tuple[k]);
|
|
156
|
+
})?.name ?? defaultTheme ?? themes[0]?.name ?? "Light";
|
|
157
|
+
}, [tuple]);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
ensureStylesheet();
|
|
160
|
+
broadcastInit();
|
|
161
|
+
}, []);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
setRootAxes(themeName, tuple);
|
|
164
|
+
}, [themeName, tuple]);
|
|
165
|
+
const wrapperAttrs = { [DATA_THEME_ATTR]: themeName };
|
|
166
|
+
for (const axis of axes) {
|
|
167
|
+
const value = tuple[axis.name];
|
|
168
|
+
if (value !== void 0) wrapperAttrs[`data-${axis.name}`] = value;
|
|
169
|
+
}
|
|
170
|
+
const pinnedSample = themes[0]?.input;
|
|
171
|
+
if (pinnedSample) for (const name of disabledAxes) {
|
|
172
|
+
const value = pinnedSample[name];
|
|
173
|
+
if (value !== void 0) wrapperAttrs[`data-${name}`] = value;
|
|
174
|
+
}
|
|
175
|
+
const snapshot = useMemo(() => ({
|
|
176
|
+
axes,
|
|
177
|
+
disabledAxes,
|
|
178
|
+
presets,
|
|
179
|
+
themes,
|
|
180
|
+
themesResolved,
|
|
181
|
+
activeTheme: themeName,
|
|
182
|
+
activeAxes: tuple,
|
|
183
|
+
cssVarPrefix,
|
|
184
|
+
diagnostics,
|
|
185
|
+
css
|
|
186
|
+
}), [themeName, tuple]);
|
|
187
|
+
return /* @__PURE__ */ jsx(SwatchbookContext.Provider, {
|
|
188
|
+
value: snapshot,
|
|
189
|
+
children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
|
|
190
|
+
value: themeName,
|
|
191
|
+
children: /* @__PURE__ */ jsx(AxesContext.Provider, {
|
|
192
|
+
value: tuple,
|
|
193
|
+
children: /* @__PURE__ */ jsx(ColorFormatContext.Provider, {
|
|
194
|
+
value: colorFormat,
|
|
195
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
196
|
+
...wrapperAttrs,
|
|
197
|
+
style: {
|
|
198
|
+
padding: "1rem",
|
|
199
|
+
minHeight: "100%"
|
|
200
|
+
},
|
|
201
|
+
children: /* @__PURE__ */ jsx(Story, {})
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Named exports consumed by `definePreviewAddon(previewExports)` in the
|
|
210
|
+
* addon's CSF Next factory (`src/index.ts`).
|
|
211
|
+
*/
|
|
212
|
+
const decorators = [themedDecorator];
|
|
213
|
+
const globalTypes = {
|
|
214
|
+
[GLOBAL_KEY]: {
|
|
215
|
+
name: "Theme",
|
|
216
|
+
description: "Active swatchbook theme (composed permutation ID)."
|
|
217
|
+
},
|
|
218
|
+
[AXES_GLOBAL_KEY]: {
|
|
219
|
+
name: "Axes",
|
|
220
|
+
description: "Per-axis context selection. Takes precedence over the composed theme name."
|
|
221
|
+
},
|
|
222
|
+
[COLOR_FORMAT_GLOBAL_KEY]: {
|
|
223
|
+
name: "Color format",
|
|
224
|
+
description: "Display format for color tokens in blocks. Emitted CSS is unaffected."
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
function buildInitialAxes() {
|
|
228
|
+
const out = {};
|
|
229
|
+
for (const axis of axes) out[axis.name] = axis.default;
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
const initialGlobals = {
|
|
233
|
+
[GLOBAL_KEY]: defaultTheme ?? themes[0]?.name ?? "Light",
|
|
234
|
+
[AXES_GLOBAL_KEY]: buildInitialAxes(),
|
|
235
|
+
[COLOR_FORMAT_GLOBAL_KEY]: "hex"
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
238
|
+
export { preview_exports as i, globalTypes as n, initialGlobals as r, decorators as t };
|
|
239
|
+
|
|
240
|
+
//# sourceMappingURL=preview-DcMFt0cD.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-DcMFt0cD.mjs","names":["virtualAxes","virtualDisabledAxes","virtualPresets"],"sources":["../src/preview.tsx"],"sourcesContent":["import type { Decorator, Preview } from '@storybook/react-vite';\nimport { useEffect, useMemo } from 'react';\nimport { addons } from 'storybook/preview-api';\nimport {\n axes as virtualAxes,\n css,\n cssVarPrefix,\n defaultTheme,\n diagnostics,\n disabledAxes as virtualDisabledAxes,\n presets as virtualPresets,\n themes,\n themesResolved,\n} from 'virtual:swatchbook/tokens';\nimport {\n AxesContext,\n COLOR_FORMATS,\n type ColorFormat,\n ColorFormatContext,\n type ProjectSnapshot,\n SwatchbookContext,\n ThemeContext,\n} from '@unpunnyfuns/swatchbook-blocks';\nimport {\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n DATA_THEME_ATTR,\n GLOBAL_KEY,\n INIT_EVENT,\n PARAM_KEY,\n STYLE_ELEMENT_ID,\n} from '#/constants.ts';\n\n/** CSS var name with the active prefix applied. */\nfunction v(name: string): string {\n return cssVarPrefix ? `--${cssVarPrefix}-${name}` : `--${name}`;\n}\n\n/**\n * Inject the per-theme stylesheet plus a tiny `html, body { ... }` block so\n * the iframe's own chrome (outside any decorator wrapper — Docs mode,\n * autodocs, empty gutters) also picks up the active theme.\n */\nfunction ensureStylesheet(): void {\n if (typeof document === 'undefined') return;\n const bodyRules = `\nhtml, body {\n background: var(${v('color-sys-surface-default')}, Canvas);\n color: var(${v('color-sys-text-default')}, CanvasText);\n margin: 0;\n}\n`;\n const text = `${css}\\n${bodyRules}`;\n let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement('style');\n style.id = STYLE_ELEMENT_ID;\n document.head.appendChild(style);\n }\n if (style.textContent !== text) style.textContent = text;\n}\n\n/**\n * Write the composed permutation ID to `data-theme` plus one\n * `data-<axis>=<context>` per axis. The composed ID stays for CSS\n * emission's current `[data-theme=\"…\"]` selectors (retires in #135);\n * per-axis attributes are what upcoming toolbar + panel work will key on.\n */\nfunction setRootAxes(themeName: string, tuple: Readonly<Record<string, string>>): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.setAttribute(DATA_THEME_ATTR, themeName);\n for (const axis of virtualAxes) {\n const value = tuple[axis.name];\n if (value === undefined) {\n root.removeAttribute(`data-${axis.name}`);\n } else {\n root.setAttribute(`data-${axis.name}`, value);\n }\n }\n /**\n * Disabled axes aren't in `virtualAxes`, but CSS may still reference their\n * pinned value on compound selectors in extension code. Read the value\n * from any surviving theme's `input` — every theme that survived filtering\n * carries the same pinned context for each disabled axis.\n */\n const pinnedSample = themes[0]?.input;\n if (pinnedSample) {\n for (const name of virtualDisabledAxes) {\n const value = pinnedSample[name];\n if (value !== undefined) root.setAttribute(`data-${name}`, value);\n }\n }\n}\n\n/**\n * Emit the full virtual-module payload to the manager over Storybook's\n * channel so the toolbar + panel (which run in the manager bundle and\n * can't import our virtual module) can render from it.\n */\nfunction broadcastInit(): void {\n const channel = addons.getChannel();\n channel.emit(INIT_EVENT, {\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n themes,\n defaultTheme,\n themesResolved,\n diagnostics,\n cssVarPrefix,\n });\n}\n\n/** Axis-default tuple, used as the baseline before overrides. */\nfunction defaultTuple(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\n/** Look up a `Theme.input` by composed name. Returns `undefined` if no theme matches. */\nfunction tupleForName(name: string): Record<string, string> | undefined {\n const match = themes.find((t) => t.name === name);\n return match?.input;\n}\n\n/**\n * Merge a partial tuple onto the axis defaults, dropping keys for axes that\n * don't exist and silently falling back to the default for contexts that\n * aren't listed on the axis.\n */\nfunction normalizeTuple(partial: Readonly<Record<string, string>>): Record<string, string> {\n const out = defaultTuple();\n for (const axis of virtualAxes) {\n const candidate = partial[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\n/**\n * Resolve the active tuple from all four input channels, in priority order:\n * 1. `parameters.swatchbook.axes` — per-story tuple.\n * 2. `parameters.swatchbook.theme` — per-story composed name (legacy).\n * 3. `globals.swatchbookAxes` — toolbar-set tuple.\n * 4. `globals.swatchbookTheme` — toolbar-set composed name.\n * 5. virtual module default.\n */\nfunction resolveTuple(\n globals: Record<string, unknown>,\n parameters: Record<string, Record<string, unknown>>,\n): Record<string, string> {\n const param = parameters[PARAM_KEY];\n const paramAxes = param?.['axes'];\n if (paramAxes && typeof paramAxes === 'object') {\n return normalizeTuple(paramAxes as Record<string, string>);\n }\n const paramTheme = param?.['theme'];\n if (typeof paramTheme === 'string') {\n const hit = tupleForName(paramTheme);\n if (hit) return normalizeTuple(hit);\n }\n const globalAxes = globals[AXES_GLOBAL_KEY];\n if (globalAxes && typeof globalAxes === 'object') {\n return normalizeTuple(globalAxes as Record<string, string>);\n }\n const globalTheme = globals[GLOBAL_KEY];\n if (typeof globalTheme === 'string') {\n const hit = tupleForName(globalTheme);\n if (hit) return normalizeTuple(hit);\n }\n return defaultTuple();\n}\n\nfunction resolveColorFormat(globals: Record<string, unknown>): ColorFormat {\n const raw = globals[COLOR_FORMAT_GLOBAL_KEY];\n if (typeof raw === 'string' && (COLOR_FORMATS as readonly string[]).includes(raw)) {\n return raw as ColorFormat;\n }\n return 'hex';\n}\n\nconst themedDecorator: Decorator = (Story, context) => {\n const tuple = useMemo(\n () =>\n resolveTuple(\n context.globals as Record<string, unknown>,\n context.parameters as Record<string, Record<string, unknown>>,\n ),\n [context.globals, context.parameters],\n );\n const colorFormat = useMemo(\n () => resolveColorFormat(context.globals as Record<string, unknown>),\n [context.globals],\n );\n const themeName = useMemo(() => {\n const match = themes.find((t) => {\n const input = t.input as Record<string, string>;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? defaultTheme ?? themes[0]?.name ?? 'Light';\n }, [tuple]);\n\n useEffect(() => {\n ensureStylesheet();\n broadcastInit();\n }, []);\n\n useEffect(() => {\n setRootAxes(themeName, tuple);\n }, [themeName, tuple]);\n\n const wrapperAttrs: Record<string, string> = { [DATA_THEME_ATTR]: themeName };\n for (const axis of virtualAxes) {\n const value = tuple[axis.name];\n if (value !== undefined) wrapperAttrs[`data-${axis.name}`] = value;\n }\n const pinnedSample = themes[0]?.input;\n if (pinnedSample) {\n for (const name of virtualDisabledAxes) {\n const value = pinnedSample[name];\n if (value !== undefined) wrapperAttrs[`data-${name}`] = value;\n }\n }\n\n const snapshot = useMemo<ProjectSnapshot>(\n () => ({\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n themes,\n themesResolved,\n activeTheme: themeName,\n activeAxes: tuple,\n cssVarPrefix,\n diagnostics,\n css,\n }),\n [themeName, tuple],\n );\n\n return (\n <SwatchbookContext.Provider value={snapshot}>\n <ThemeContext.Provider value={themeName}>\n <AxesContext.Provider value={tuple}>\n <ColorFormatContext.Provider value={colorFormat}>\n <div\n {...wrapperAttrs}\n style={{\n padding: '1rem',\n minHeight: '100%',\n }}\n >\n <Story />\n </div>\n </ColorFormatContext.Provider>\n </AxesContext.Provider>\n </ThemeContext.Provider>\n </SwatchbookContext.Provider>\n );\n};\n\n/**\n * Named exports consumed by `definePreviewAddon(previewExports)` in the\n * addon's CSF Next factory (`src/index.ts`).\n */\nexport const decorators: NonNullable<Preview['decorators']> = [themedDecorator];\n\nexport const globalTypes: NonNullable<Preview['globalTypes']> = {\n [GLOBAL_KEY]: {\n name: 'Theme',\n description: 'Active swatchbook theme (composed permutation ID).',\n },\n [AXES_GLOBAL_KEY]: {\n name: 'Axes',\n description: 'Per-axis context selection. Takes precedence over the composed theme name.',\n },\n [COLOR_FORMAT_GLOBAL_KEY]: {\n name: 'Color format',\n description: 'Display format for color tokens in blocks. Emitted CSS is unaffected.',\n },\n};\n\nfunction buildInitialAxes(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\nexport const initialGlobals: NonNullable<Preview['initialGlobals']> = {\n [GLOBAL_KEY]: defaultTheme ?? themes[0]?.name ?? 'Light',\n [AXES_GLOBAL_KEY]: buildInitialAxes(),\n [COLOR_FORMAT_GLOBAL_KEY]: 'hex',\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Preview } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
//#region src/preview.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Named exports consumed by `definePreviewAddon(previewExports)` in the
|
|
6
|
+
* addon's CSF Next factory (`src/index.ts`).
|
|
7
|
+
*/
|
|
8
|
+
declare const decorators: NonNullable<Preview['decorators']>;
|
|
9
|
+
declare const globalTypes: NonNullable<Preview['globalTypes']>;
|
|
10
|
+
declare const initialGlobals: NonNullable<Preview['initialGlobals']>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { decorators, globalTypes, initialGlobals };
|
|
13
|
+
//# sourceMappingURL=preview.d.mts.map
|