@adea-ai/ui 0.72.5 → 0.74.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/dist/components/theme/theme-provider.d.ts +13 -0
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +9 -1
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/ui/entity-icon/entity-icon.d.ts +1 -1
- package/dist/components/ui/sheet/sheet.d.ts +1 -1
- package/dist/lib/utils.d.ts +13 -16
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +21 -27
- package/dist/lib/utils.js.map +1 -1
- package/dist/r/lib.json +1 -2
- package/dist/r/registry.json +1 -2
- package/dist/r/src/components/theme/theme-provider.tsx +23 -1
- package/dist/r/src/lib/utils.ts +21 -27
- package/package.json +2 -3
- package/registry.json +1 -2
- package/src/components/theme/theme-provider.tsx +23 -1
- package/src/lib/utils.ts +21 -27
|
@@ -86,6 +86,19 @@ export type ThemeProviderProps = {
|
|
|
86
86
|
storageKey?: string;
|
|
87
87
|
/** Start from this instead of the stored preference. */
|
|
88
88
|
initial?: Partial<ThemeSelection>;
|
|
89
|
+
/**
|
|
90
|
+
* The selection, owned by the caller.
|
|
91
|
+
*
|
|
92
|
+
* For a consumer that keeps the state itself — a settings store, or a workshop
|
|
93
|
+
* whose selection lives in Storybook globals — rather than handing it to this
|
|
94
|
+
* provider's signal. Each change lands here and the document follows; nothing is
|
|
95
|
+
* written to the store, because the caller is the one who persists. A picker
|
|
96
|
+
* below the provider still works, and its change is the caller's to read back.
|
|
97
|
+
*
|
|
98
|
+
* Absent, the provider is the whole owner: it reads `storageKey` on mount,
|
|
99
|
+
* seeds from `initial`, and persists every `setSelection`.
|
|
100
|
+
*/
|
|
101
|
+
selection?: Partial<ThemeSelection>;
|
|
89
102
|
};
|
|
90
103
|
export declare function ThemeProvider(props: ThemeProviderProps): JSX.Element;
|
|
91
104
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../../src/components/theme/theme-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAWnC,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2EAA2E;IAC3E,UAAU,EAAE,eAAe,GAAG,QAAQ,CAAA;IACtC,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,cAOnC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,SAAS,CAAA;AAEpD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,cAAc,GAAG,YAAY,CAEvE;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gCAAgC;IAChC,SAAS,EAAE,MAAM,cAAc,CAAA;IAC/B,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,eAAe,CAAA;IACzC,0BAA0B;IAC1B,OAAO,EAAE,MAAM,YAAY,CAAA;IAC3B,qCAAqC;IACrC,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IACtD,gEAAgE;IAChE,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;CAChC,CAAA;AAID,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C;AAsCD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../../src/components/theme/theme-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAWnC,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2EAA2E;IAC3E,UAAU,EAAE,eAAe,GAAG,QAAQ,CAAA;IACtC,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,cAOnC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,SAAS,CAAA;AAEpD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,cAAc,GAAG,YAAY,CAEvE;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gCAAgC;IAChC,SAAS,EAAE,MAAM,cAAc,CAAA;IAC/B,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,eAAe,CAAA;IACzC,0BAA0B;IAC1B,OAAO,EAAE,MAAM,YAAY,CAAA;IAC3B,qCAAqC;IACrC,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IACtD,gEAAgE;IAChE,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;CAChC,CAAA;AAID,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C;AAsCD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IACjC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CACpC,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,eAsGtD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,UAAU,SAAoB,GAAG,MAAM,CAElE"}
|
|
@@ -49,13 +49,21 @@ function ThemeProvider(props) {
|
|
|
49
49
|
const [local] = splitProps(props, [
|
|
50
50
|
"children",
|
|
51
51
|
"storageKey",
|
|
52
|
-
"initial"
|
|
52
|
+
"initial",
|
|
53
|
+
"selection"
|
|
53
54
|
]);
|
|
54
55
|
const storageKey = () => local.storageKey ?? "adea-appearance";
|
|
55
56
|
const [selection, setSelectionState] = createSignal({
|
|
56
57
|
...readSelection(storageKey()),
|
|
57
58
|
...local.initial
|
|
58
59
|
});
|
|
60
|
+
createEffect(() => {
|
|
61
|
+
const incoming = local.selection;
|
|
62
|
+
if (incoming) setSelectionState((current) => ({
|
|
63
|
+
...current,
|
|
64
|
+
...incoming
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
59
67
|
const [systemAppearance, setSystemAppearance] = createSignal("light");
|
|
60
68
|
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
61
69
|
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-provider.js","names":["JSX","createContext","createEffect","createMemo","createSignal","onCleanup","splitProps","useContext","accentPresets","fontOptions","accentVariables","builtinThemes","defaultDarkThemeId","defaultLightThemeId","themeById","themeCssVariables","ThemeAppearance","ThemeVariant","ThemeSelection","appearance","lightThemeId","darkThemeId","accent","font","density","ThemeDensity","defaultThemeSelection","resolvedDensity","selection","ThemeContextValue","resolvedAppearance","variant","setSelection","patch","Partial","themes","ThemeContext","useTheme","value","Error","readSelection","storageKey","localStorage","raw","getItem","parsed","JSON","parse","some","preset","id","option","ThemeProviderProps","children","Element","initial","ThemeProvider","props","local","setSelectionState","systemAppearance","setSystemAppearance","window","matchMedia","query","matches","onChange","event","MediaQueryListEvent","addEventListener","removeEventListener","preference","document","root","documentElement","active","properties","Object","assign","name","entries","style","setProperty","classList","toggle","dataset","colorScheme","removeAttribute","next","setItem","stringify","_$createComponent","Provider","inlineScriptLiteral","replace","character","charCodeAt","toString","padStart","themeScript","_$createComponent"],"sources":["../../../src/components/theme/theme-provider.tsx"],"sourcesContent":["import type { JSX } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createMemo,\n createSignal,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { accentPresets, fontOptions } from '#lib/tokens'\nimport {\n accentVariables,\n builtinThemes,\n defaultDarkThemeId,\n defaultLightThemeId,\n themeById,\n themeCssVariables,\n type ThemeAppearance,\n type ThemeVariant,\n} from '#lib/themes'\n\n/**\n * ThemeProvider.\n *\n * Owns the appearance preference and applies it to the document. One provider near\n * the root; everything below reads it.\n *\n * ## What a preference is\n *\n * Four independent choices, and keeping them independent is the point:\n *\n * - **appearance** — light, dark, or follow the system.\n * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and\n * Solarized Light by day without re-choosing when the system flips.\n * - **an accent** — adea's six presets, which re-colour the interactive roles on\n * top of whichever theme is active.\n * - **a font** — the interface face.\n *\n * Four axes rather than one list of presets, because they compose: a user who wants\n * Nord with a violet accent and the system font should not need someone to author\n * that combination.\n *\n * ## Why the variants are applied as inline properties\n *\n * The catalogue is data, and a theme is applied by writing its roles onto\n * `<html>` as custom properties. That is what makes an imported palette work\n * without a hand-written `[data-theme]` block per theme, and it is why a settings\n * UI can show a preview by reading the same object rather than by loading a\n * stylesheet. `theme.css` keeps the default theme's values so the first paint is\n * correct before any script runs.\n */\nexport type ThemeSelection = {\n /** `system` follows `prefers-color-scheme` and updates when it changes. */\n appearance: ThemeAppearance | 'system'\n /** The variant to use when the resolved appearance is light. */\n lightThemeId: string\n /** The variant to use when the resolved appearance is dark. */\n darkThemeId: string\n /** An `accentPresets` id, or `theme` for the variant's own primary. */\n accent: string\n /** A `fontOptions` id, or `space-grotesk` for the default. */\n font: string\n /**\n * `comfortable` is the system's default geometry; `compact` moves the control\n * ladder down one rung.\n *\n * Optional, and defaulted on read, so a preference stored before density existed\n * keeps working and `ThemeSelection` stays assignable without it.\n */\n density?: ThemeDensity\n}\n\nexport const defaultThemeSelection: ThemeSelection = {\n appearance: 'system',\n lightThemeId: defaultLightThemeId,\n darkThemeId: defaultDarkThemeId,\n accent: 'theme',\n font: 'space-grotesk',\n density: 'comfortable',\n}\n\n/**\n * The two density rungs.\n *\n * `comfortable` is the geometry in `theme.css`, so it is the absence of an\n * attribute rather than a value: a document that ships no `data-density` at all\n * renders correctly, which is what keeps this additive.\n */\nexport type ThemeDensity = 'comfortable' | 'compact'\n\n/**\n * A stored selection's density, defaulted.\n *\n * The stored value is read through here rather than trusted, because a preference\n * written before density existed has no `density` key and a hand-edited store may\n * hold anything.\n */\nexport function resolvedDensity(selection: ThemeSelection): ThemeDensity {\n return selection.density === 'compact' ? 'compact' : 'comfortable'\n}\n\nexport type ThemeContextValue = {\n /** The preference as stored. */\n selection: () => ThemeSelection\n /** What the preference resolves to right now, after `system`. */\n resolvedAppearance: () => ThemeAppearance\n /** The variant in use. */\n variant: () => ThemeVariant\n /** Update part of the preference. */\n setSelection: (patch: Partial<ThemeSelection>) => void\n /** The catalogue, so a picker does not import it separately. */\n themes: readonly ThemeVariant[]\n}\n\nconst ThemeContext = createContext<ThemeContextValue>()\n\nexport function useTheme(): ThemeContextValue {\n const value = useContext(ThemeContext)\n if (!value) throw new Error('useTheme must be called inside a <ThemeProvider>.')\n return value\n}\n\n/** The stored preference, validated against the catalogue. */\nfunction readSelection(storageKey: string): ThemeSelection {\n if (typeof localStorage === 'undefined') return defaultThemeSelection\n try {\n const raw = localStorage.getItem(storageKey)\n if (!raw) return defaultThemeSelection\n const parsed = JSON.parse(raw) as Partial<ThemeSelection>\n return {\n appearance:\n parsed.appearance === 'light' ||\n parsed.appearance === 'dark' ||\n parsed.appearance === 'system'\n ? parsed.appearance\n : defaultThemeSelection.appearance,\n // An unknown id would leave the document with no theme at all, so a stale\n // preference from an older catalogue falls back rather than applying nothing.\n lightThemeId:\n themeById(parsed.lightThemeId ?? '')?.appearance === 'light'\n ? (parsed.lightThemeId as string)\n : defaultLightThemeId,\n darkThemeId:\n themeById(parsed.darkThemeId ?? '')?.appearance === 'dark'\n ? (parsed.darkThemeId as string)\n : defaultDarkThemeId,\n accent: accentPresets.some((preset) => preset.id === parsed.accent)\n ? (parsed.accent as string)\n : defaultThemeSelection.accent,\n font: fontOptions.some((option) => option.id === parsed.font)\n ? (parsed.font as string)\n : defaultThemeSelection.font,\n }\n } catch {\n return defaultThemeSelection\n }\n}\n\nexport type ThemeProviderProps = {\n children?: JSX.Element\n /** Where the preference is persisted. Distinct per application. */\n storageKey?: string\n /** Start from this instead of the stored preference. */\n initial?: Partial<ThemeSelection>\n}\n\nexport function ThemeProvider(props: ThemeProviderProps) {\n const [local] = splitProps(props, ['children', 'storageKey', 'initial'])\n const storageKey = () => local.storageKey ?? 'adea-appearance'\n\n const [selection, setSelectionState] = createSignal<ThemeSelection>({\n ...readSelection(storageKey()),\n ...local.initial,\n })\n\n const [systemAppearance, setSystemAppearance] = createSignal<ThemeAppearance>('light')\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n const query = window.matchMedia('(prefers-color-scheme: dark)')\n setSystemAppearance(query.matches ? 'dark' : 'light')\n const onChange = (event: MediaQueryListEvent) =>\n setSystemAppearance(event.matches ? 'dark' : 'light')\n query.addEventListener('change', onChange)\n onCleanup(() => query.removeEventListener('change', onChange))\n }\n\n const resolvedAppearance = createMemo<ThemeAppearance>(() => {\n const preference = selection().appearance\n if (preference === 'system') return systemAppearance()\n return preference\n })\n\n const variant = createMemo<ThemeVariant>(() => {\n const id = resolvedAppearance() === 'dark' ? selection().darkThemeId : selection().lightThemeId\n return themeById(id) ?? builtinThemes[0]!\n })\n\n createEffect(() => {\n if (typeof document === 'undefined') return\n const root = document.documentElement\n const active = variant()\n\n // The variant's roles, as properties. `--primary` and `--ring` are written here\n // too, and an accent selection is layered over them below.\n //\n // The accent is applied *here* rather than left to the `[data-accent]` blocks in\n // `theme.css`, because this loop writes inline custom properties and an inline\n // declaration outranks every stylesheet selector however specific it is. The\n // blocks still exist and are still correct — they are what styles the window\n // before this runs — but they cannot win from here, so leaving the accent to\n // them meant the attribute was set, the rules were present, and nothing\n // changed on screen.\n const properties = { ...themeCssVariables(active) }\n const accent = accentVariables(selection().accent, resolvedAppearance())\n if (accent) Object.assign(properties, accent)\n\n for (const [name, value] of Object.entries(properties)) {\n root.style.setProperty(name, value)\n }\n\n root.classList.toggle('dark', resolvedAppearance() === 'dark')\n root.dataset['theme'] = active.id\n root.dataset['appearance'] = resolvedAppearance()\n root.style.colorScheme = resolvedAppearance()\n\n if (selection().accent === 'theme') root.removeAttribute('data-accent')\n else root.dataset['accent'] = selection().accent\n\n if (selection().font === 'space-grotesk') root.removeAttribute('data-font')\n else root.dataset['font'] = selection().font\n\n // Density is a pure token shift, so unlike the colour axes there is nothing to\n // compute: the attribute selects a block in `theme.css`. It is applied here for\n // the same reason as the others — this is the one place that knows what the\n // current selection is, and it is the only writer of these attributes.\n if (resolvedDensity(selection()) === 'comfortable') root.removeAttribute('data-density')\n else root.dataset['density'] = resolvedDensity(selection())\n })\n\n const value: ThemeContextValue = {\n selection,\n resolvedAppearance,\n variant,\n themes: builtinThemes,\n setSelection(patch) {\n const next = { ...selection(), ...patch }\n setSelectionState(next)\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(storageKey(), JSON.stringify(next))\n } catch {\n // A full or unavailable store must not break the switch: the preference\n // simply does not survive the reload.\n }\n }\n },\n }\n\n return <ThemeContext.Provider value={value}>{local.children}</ThemeContext.Provider>\n}\n\n/**\n * A string as a JavaScript literal that is also safe inside an inline `<script>`.\n *\n * `JSON.stringify` produces a correct JavaScript literal, and it is **not** safe\n * here: it leaves `<` and `>` alone, so a value containing `</script>` ends the tag\n * early and everything after it is parsed as HTML. That is the classic inline-script\n * injection, and it is a real one even though the value is a developer-supplied prop\n * rather than user input — a prop can come from configuration, and a key that happens\n * to contain a closing tag should not be able to execute.\n *\n * The escape is also correct JavaScript: `\\u003c` is `<` to the parser, so the value\n * is byte-identical at runtime and inert to the HTML tokenizer. `\\u2028` and `\\u2029`\n * are escaped for the neighbouring reason — they are legal inside a JSON string and\n * were, before ES2019, line terminators to a JavaScript parser.\n *\n * CodeQL flags the unescaped form as `js/bad-code-sanitization`, which is how this\n * was found; `tests/theme-script.test.ts` pins it so it cannot come back.\n */\nexport function inlineScriptLiteral(value: string): string {\n return JSON.stringify(value).replace(\n /[<>\\u2028\\u2029]/g,\n (character) => `\\\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`\n )\n}\n\n/**\n * The no-flash script.\n *\n * Rendered inline in `<head>` before any stylesheet that matters. Without it a dark\n * preference paints the light theme first and then swaps, which is the flash every\n * themed application has to solve and the one thing a provider cannot do from\n * inside the body.\n *\n * Deliberately tiny and dependency-free: it reads the stored preference, resolves\n * `system`, and sets the class. It does not apply the variant's roles — the default\n * theme in `theme.css` covers the first paint, and the provider writes the rest on\n * mount.\n *\n * The script reads two values out of `localStorage` and writes them to `dataset`,\n * which is a *property* assignment rather than code: a stored accent or font id can\n * set an attribute and cannot execute. The one value that reaches the source as text\n * is `storageKey`, and it goes through {@link inlineScriptLiteral} for the reason\n * documented there.\n */\nexport function themeScript(storageKey = 'adea-appearance'): string {\n return `(function(){try{var s=localStorage.getItem(${inlineScriptLiteral(storageKey)});var p=s?JSON.parse(s):{};var a=p.appearance||'system';var d=a==='dark'||(a==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);var r=document.documentElement;if(d)r.classList.add('dark');r.style.colorScheme=d?'dark':'light';if(p.accent&&p.accent!=='theme')r.dataset.accent=p.accent;if(p.font&&p.font!=='space-grotesk')r.dataset.font=p.font;if(p.density==='compact')r.dataset.density='compact';}catch(e){}})();`\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme-provider.js","names":["JSX","createContext","createEffect","createMemo","createSignal","onCleanup","splitProps","useContext","accentPresets","fontOptions","accentVariables","builtinThemes","defaultDarkThemeId","defaultLightThemeId","themeById","themeCssVariables","ThemeAppearance","ThemeVariant","ThemeSelection","appearance","lightThemeId","darkThemeId","accent","font","density","ThemeDensity","defaultThemeSelection","resolvedDensity","selection","ThemeContextValue","resolvedAppearance","variant","setSelection","patch","Partial","themes","ThemeContext","useTheme","value","Error","readSelection","storageKey","localStorage","raw","getItem","parsed","JSON","parse","some","preset","id","option","ThemeProviderProps","children","Element","initial","ThemeProvider","props","local","setSelectionState","incoming","current","systemAppearance","setSystemAppearance","window","matchMedia","query","matches","onChange","event","MediaQueryListEvent","addEventListener","removeEventListener","preference","document","root","documentElement","active","properties","Object","assign","name","entries","style","setProperty","classList","toggle","dataset","colorScheme","removeAttribute","next","setItem","stringify","_$createComponent","Provider","inlineScriptLiteral","replace","character","charCodeAt","toString","padStart","themeScript","_$createComponent"],"sources":["../../../src/components/theme/theme-provider.tsx"],"sourcesContent":["import type { JSX } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createMemo,\n createSignal,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { accentPresets, fontOptions } from '#lib/tokens'\nimport {\n accentVariables,\n builtinThemes,\n defaultDarkThemeId,\n defaultLightThemeId,\n themeById,\n themeCssVariables,\n type ThemeAppearance,\n type ThemeVariant,\n} from '#lib/themes'\n\n/**\n * ThemeProvider.\n *\n * Owns the appearance preference and applies it to the document. One provider near\n * the root; everything below reads it.\n *\n * ## What a preference is\n *\n * Four independent choices, and keeping them independent is the point:\n *\n * - **appearance** — light, dark, or follow the system.\n * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and\n * Solarized Light by day without re-choosing when the system flips.\n * - **an accent** — adea's six presets, which re-colour the interactive roles on\n * top of whichever theme is active.\n * - **a font** — the interface face.\n *\n * Four axes rather than one list of presets, because they compose: a user who wants\n * Nord with a violet accent and the system font should not need someone to author\n * that combination.\n *\n * ## Why the variants are applied as inline properties\n *\n * The catalogue is data, and a theme is applied by writing its roles onto\n * `<html>` as custom properties. That is what makes an imported palette work\n * without a hand-written `[data-theme]` block per theme, and it is why a settings\n * UI can show a preview by reading the same object rather than by loading a\n * stylesheet. `theme.css` keeps the default theme's values so the first paint is\n * correct before any script runs.\n */\nexport type ThemeSelection = {\n /** `system` follows `prefers-color-scheme` and updates when it changes. */\n appearance: ThemeAppearance | 'system'\n /** The variant to use when the resolved appearance is light. */\n lightThemeId: string\n /** The variant to use when the resolved appearance is dark. */\n darkThemeId: string\n /** An `accentPresets` id, or `theme` for the variant's own primary. */\n accent: string\n /** A `fontOptions` id, or `space-grotesk` for the default. */\n font: string\n /**\n * `comfortable` is the system's default geometry; `compact` moves the control\n * ladder down one rung.\n *\n * Optional, and defaulted on read, so a preference stored before density existed\n * keeps working and `ThemeSelection` stays assignable without it.\n */\n density?: ThemeDensity\n}\n\nexport const defaultThemeSelection: ThemeSelection = {\n appearance: 'system',\n lightThemeId: defaultLightThemeId,\n darkThemeId: defaultDarkThemeId,\n accent: 'theme',\n font: 'space-grotesk',\n density: 'comfortable',\n}\n\n/**\n * The two density rungs.\n *\n * `comfortable` is the geometry in `theme.css`, so it is the absence of an\n * attribute rather than a value: a document that ships no `data-density` at all\n * renders correctly, which is what keeps this additive.\n */\nexport type ThemeDensity = 'comfortable' | 'compact'\n\n/**\n * A stored selection's density, defaulted.\n *\n * The stored value is read through here rather than trusted, because a preference\n * written before density existed has no `density` key and a hand-edited store may\n * hold anything.\n */\nexport function resolvedDensity(selection: ThemeSelection): ThemeDensity {\n return selection.density === 'compact' ? 'compact' : 'comfortable'\n}\n\nexport type ThemeContextValue = {\n /** The preference as stored. */\n selection: () => ThemeSelection\n /** What the preference resolves to right now, after `system`. */\n resolvedAppearance: () => ThemeAppearance\n /** The variant in use. */\n variant: () => ThemeVariant\n /** Update part of the preference. */\n setSelection: (patch: Partial<ThemeSelection>) => void\n /** The catalogue, so a picker does not import it separately. */\n themes: readonly ThemeVariant[]\n}\n\nconst ThemeContext = createContext<ThemeContextValue>()\n\nexport function useTheme(): ThemeContextValue {\n const value = useContext(ThemeContext)\n if (!value) throw new Error('useTheme must be called inside a <ThemeProvider>.')\n return value\n}\n\n/** The stored preference, validated against the catalogue. */\nfunction readSelection(storageKey: string): ThemeSelection {\n if (typeof localStorage === 'undefined') return defaultThemeSelection\n try {\n const raw = localStorage.getItem(storageKey)\n if (!raw) return defaultThemeSelection\n const parsed = JSON.parse(raw) as Partial<ThemeSelection>\n return {\n appearance:\n parsed.appearance === 'light' ||\n parsed.appearance === 'dark' ||\n parsed.appearance === 'system'\n ? parsed.appearance\n : defaultThemeSelection.appearance,\n // An unknown id would leave the document with no theme at all, so a stale\n // preference from an older catalogue falls back rather than applying nothing.\n lightThemeId:\n themeById(parsed.lightThemeId ?? '')?.appearance === 'light'\n ? (parsed.lightThemeId as string)\n : defaultLightThemeId,\n darkThemeId:\n themeById(parsed.darkThemeId ?? '')?.appearance === 'dark'\n ? (parsed.darkThemeId as string)\n : defaultDarkThemeId,\n accent: accentPresets.some((preset) => preset.id === parsed.accent)\n ? (parsed.accent as string)\n : defaultThemeSelection.accent,\n font: fontOptions.some((option) => option.id === parsed.font)\n ? (parsed.font as string)\n : defaultThemeSelection.font,\n }\n } catch {\n return defaultThemeSelection\n }\n}\n\nexport type ThemeProviderProps = {\n children?: JSX.Element\n /** Where the preference is persisted. Distinct per application. */\n storageKey?: string\n /** Start from this instead of the stored preference. */\n initial?: Partial<ThemeSelection>\n /**\n * The selection, owned by the caller.\n *\n * For a consumer that keeps the state itself — a settings store, or a workshop\n * whose selection lives in Storybook globals — rather than handing it to this\n * provider's signal. Each change lands here and the document follows; nothing is\n * written to the store, because the caller is the one who persists. A picker\n * below the provider still works, and its change is the caller's to read back.\n *\n * Absent, the provider is the whole owner: it reads `storageKey` on mount,\n * seeds from `initial`, and persists every `setSelection`.\n */\n selection?: Partial<ThemeSelection>\n}\n\nexport function ThemeProvider(props: ThemeProviderProps) {\n const [local] = splitProps(props, ['children', 'storageKey', 'initial', 'selection'])\n const storageKey = () => local.storageKey ?? 'adea-appearance'\n\n const [selection, setSelectionState] = createSignal<ThemeSelection>({\n ...readSelection(storageKey()),\n ...local.initial,\n })\n\n // The controlled half of the contract. A patch from the owner replaces the\n // matching axes; the stored preference is untouched, since the owner is the one\n // that persists. Reading `local.selection` — a prop getter, not a destructured\n // value — is what keeps this subscribed for the provider's lifetime.\n createEffect(() => {\n const incoming = local.selection\n if (incoming) setSelectionState((current) => ({ ...current, ...incoming }))\n })\n\n const [systemAppearance, setSystemAppearance] = createSignal<ThemeAppearance>('light')\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n const query = window.matchMedia('(prefers-color-scheme: dark)')\n setSystemAppearance(query.matches ? 'dark' : 'light')\n const onChange = (event: MediaQueryListEvent) =>\n setSystemAppearance(event.matches ? 'dark' : 'light')\n query.addEventListener('change', onChange)\n onCleanup(() => query.removeEventListener('change', onChange))\n }\n\n const resolvedAppearance = createMemo<ThemeAppearance>(() => {\n const preference = selection().appearance\n if (preference === 'system') return systemAppearance()\n return preference\n })\n\n const variant = createMemo<ThemeVariant>(() => {\n const id = resolvedAppearance() === 'dark' ? selection().darkThemeId : selection().lightThemeId\n return themeById(id) ?? builtinThemes[0]!\n })\n\n createEffect(() => {\n if (typeof document === 'undefined') return\n const root = document.documentElement\n const active = variant()\n\n // The variant's roles, as properties. `--primary` and `--ring` are written here\n // too, and an accent selection is layered over them below.\n //\n // The accent is applied *here* rather than left to the `[data-accent]` blocks in\n // `theme.css`, because this loop writes inline custom properties and an inline\n // declaration outranks every stylesheet selector however specific it is. The\n // blocks still exist and are still correct — they are what styles the window\n // before this runs — but they cannot win from here, so leaving the accent to\n // them meant the attribute was set, the rules were present, and nothing\n // changed on screen.\n const properties = { ...themeCssVariables(active) }\n const accent = accentVariables(selection().accent, resolvedAppearance())\n if (accent) Object.assign(properties, accent)\n\n for (const [name, value] of Object.entries(properties)) {\n root.style.setProperty(name, value)\n }\n\n root.classList.toggle('dark', resolvedAppearance() === 'dark')\n root.dataset['theme'] = active.id\n root.dataset['appearance'] = resolvedAppearance()\n root.style.colorScheme = resolvedAppearance()\n\n if (selection().accent === 'theme') root.removeAttribute('data-accent')\n else root.dataset['accent'] = selection().accent\n\n if (selection().font === 'space-grotesk') root.removeAttribute('data-font')\n else root.dataset['font'] = selection().font\n\n // Density is a pure token shift, so unlike the colour axes there is nothing to\n // compute: the attribute selects a block in `theme.css`. It is applied here for\n // the same reason as the others — this is the one place that knows what the\n // current selection is, and it is the only writer of these attributes.\n if (resolvedDensity(selection()) === 'comfortable') root.removeAttribute('data-density')\n else root.dataset['density'] = resolvedDensity(selection())\n })\n\n const value: ThemeContextValue = {\n selection,\n resolvedAppearance,\n variant,\n themes: builtinThemes,\n setSelection(patch) {\n const next = { ...selection(), ...patch }\n setSelectionState(next)\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(storageKey(), JSON.stringify(next))\n } catch {\n // A full or unavailable store must not break the switch: the preference\n // simply does not survive the reload.\n }\n }\n },\n }\n\n return <ThemeContext.Provider value={value}>{local.children}</ThemeContext.Provider>\n}\n\n/**\n * A string as a JavaScript literal that is also safe inside an inline `<script>`.\n *\n * `JSON.stringify` produces a correct JavaScript literal, and it is **not** safe\n * here: it leaves `<` and `>` alone, so a value containing `</script>` ends the tag\n * early and everything after it is parsed as HTML. That is the classic inline-script\n * injection, and it is a real one even though the value is a developer-supplied prop\n * rather than user input — a prop can come from configuration, and a key that happens\n * to contain a closing tag should not be able to execute.\n *\n * The escape is also correct JavaScript: `\\u003c` is `<` to the parser, so the value\n * is byte-identical at runtime and inert to the HTML tokenizer. `\\u2028` and `\\u2029`\n * are escaped for the neighbouring reason — they are legal inside a JSON string and\n * were, before ES2019, line terminators to a JavaScript parser.\n *\n * CodeQL flags the unescaped form as `js/bad-code-sanitization`, which is how this\n * was found; `tests/theme-script.test.ts` pins it so it cannot come back.\n */\nexport function inlineScriptLiteral(value: string): string {\n return JSON.stringify(value).replace(\n /[<>\\u2028\\u2029]/g,\n (character) => `\\\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`\n )\n}\n\n/**\n * The no-flash script.\n *\n * Rendered inline in `<head>` before any stylesheet that matters. Without it a dark\n * preference paints the light theme first and then swaps, which is the flash every\n * themed application has to solve and the one thing a provider cannot do from\n * inside the body.\n *\n * Deliberately tiny and dependency-free: it reads the stored preference, resolves\n * `system`, and sets the class. It does not apply the variant's roles — the default\n * theme in `theme.css` covers the first paint, and the provider writes the rest on\n * mount.\n *\n * The script reads two values out of `localStorage` and writes them to `dataset`,\n * which is a *property* assignment rather than code: a stored accent or font id can\n * set an attribute and cannot execute. The one value that reaches the source as text\n * is `storageKey`, and it goes through {@link inlineScriptLiteral} for the reason\n * documented there.\n */\nexport function themeScript(storageKey = 'adea-appearance'): string {\n return `(function(){try{var s=localStorage.getItem(${inlineScriptLiteral(storageKey)});var p=s?JSON.parse(s):{};var a=p.appearance||'system';var d=a==='dark'||(a==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);var r=document.documentElement;if(d)r.classList.add('dark');r.style.colorScheme=d?'dark':'light';if(p.accent&&p.accent!=='theme')r.dataset.accent=p.accent;if(p.font&&p.font!=='space-grotesk')r.dataset.font=p.font;if(p.density==='compact')r.dataset.density='compact';}catch(e){}})();`\n}\n"],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ import { type VariantProps } from '#lib/variants';
|
|
|
26
26
|
* hold a face.
|
|
27
27
|
*/
|
|
28
28
|
export declare const entityIconVariants: (props?: ({
|
|
29
|
-
shape?: "
|
|
29
|
+
shape?: "circle" | "square" | "rounded" | null | undefined;
|
|
30
30
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
31
31
|
tone?: "success" | "warning" | "info" | "neutral" | "primary" | "bare" | "danger" | null | undefined;
|
|
32
32
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -14,7 +14,7 @@ import { type VariantProps } from '#lib/variants';
|
|
|
14
14
|
* follows from the side, so a caller never pairs them by hand.
|
|
15
15
|
*/
|
|
16
16
|
declare const sheetVariants: (props?: ({
|
|
17
|
-
side?: "
|
|
17
|
+
side?: "end" | "start" | "bottom" | "top" | null | undefined;
|
|
18
18
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
19
19
|
export type SheetContentProps = ComponentProps<typeof KobalteDialogContent> & VariantProps<typeof sheetVariants> & {
|
|
20
20
|
closeButton?: JSX.Element | false;
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
3
|
-
* `twMerge` needs to be told about the utilities this design system adds.
|
|
4
|
-
*
|
|
5
|
-
* Every token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …)
|
|
6
|
-
* is an unknown name to a default `tailwind-merge` instance. Left
|
|
7
|
-
* unregistered, `cn('h-control-md', 'h-control-sm')` keeps both classes and
|
|
8
|
-
* the winner is decided by stylesheet order rather than by the caller — a bug
|
|
9
|
-
* that only shows up as a control that ignores its `size` prop, long after the
|
|
10
|
-
* line that caused it was written.
|
|
11
|
-
*
|
|
12
|
-
* Registering the families here is what makes "the last class wins" true for
|
|
13
|
-
* the whole system, including for consumers overriding a component's default.
|
|
14
|
-
*/
|
|
15
|
-
export declare const twMergeConfig: (...classLists: import("tailwind-merge").ClassNameValue[]) => string;
|
|
1
|
+
import type { CnFunction } from 'cn';
|
|
16
2
|
/**
|
|
17
3
|
* Merge conditional class values, letting the last conflicting utility win.
|
|
18
4
|
*
|
|
@@ -24,6 +10,17 @@ export declare const twMergeConfig: (...classLists: import("tailwind-merge").Cla
|
|
|
24
10
|
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
25
11
|
* will be reported, because a class the linter cannot read is a class it
|
|
26
12
|
* cannot check against the theme.
|
|
13
|
+
*
|
|
14
|
+
* `cn` also has to be told about the utilities this design system adds. Every
|
|
15
|
+
* token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …) is an
|
|
16
|
+
* unknown name to a default merge instance. Left unregistered,
|
|
17
|
+
* `cn('h-control-md', 'h-control-sm')` keeps both classes and the winner is
|
|
18
|
+
* decided by stylesheet order rather than by the caller — a bug that only
|
|
19
|
+
* shows up as a control that ignores its `size` prop, long after the line
|
|
20
|
+
* that caused it was written.
|
|
21
|
+
*
|
|
22
|
+
* Registering the families below is what makes "the last class wins" true for
|
|
23
|
+
* the whole system, including for consumers overriding a component's default.
|
|
27
24
|
*/
|
|
28
|
-
export declare
|
|
25
|
+
export declare const cn: CnFunction;
|
|
29
26
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAEpC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,EAAE,EAAE,UA2Cf,CAAA"}
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
1
|
+
import { createCn } from "cn/config";
|
|
3
2
|
//#region src/lib/utils.ts
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
* Merge conditional class values, letting the last conflicting utility win.
|
|
5
|
+
*
|
|
6
|
+
* Accepts an object whose keys are classes and whose values enable them, which
|
|
7
|
+
* is the form the design-system linter can read statically:
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
* is an unknown name to a default `tailwind-merge` instance. Left
|
|
9
|
-
* unregistered, `cn('h-control-md', 'h-control-sm')` keeps both classes and
|
|
10
|
-
* the winner is decided by stylesheet order rather than by the caller — a bug
|
|
11
|
-
* that only shows up as a control that ignores its `size` prop, long after the
|
|
12
|
-
* line that caused it was written.
|
|
9
|
+
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
13
10
|
*
|
|
14
|
-
*
|
|
11
|
+
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
12
|
+
* will be reported, because a class the linter cannot read is a class it
|
|
13
|
+
* cannot check against the theme.
|
|
14
|
+
*
|
|
15
|
+
* `cn` also has to be told about the utilities this design system adds. Every
|
|
16
|
+
* token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …) is an
|
|
17
|
+
* unknown name to a default merge instance. Left unregistered,
|
|
18
|
+
* `cn('h-control-md', 'h-control-sm')` keeps both classes and the winner is
|
|
19
|
+
* decided by stylesheet order rather than by the caller — a bug that only
|
|
20
|
+
* shows up as a control that ignores its `size` prop, long after the line
|
|
21
|
+
* that caused it was written.
|
|
22
|
+
*
|
|
23
|
+
* Registering the families below is what makes "the last class wins" true for
|
|
15
24
|
* the whole system, including for consumers overriding a component's default.
|
|
16
25
|
*/
|
|
17
|
-
var
|
|
26
|
+
var cn = createCn({ extend: { classGroups: {
|
|
18
27
|
"ds-height": [{ h: [
|
|
19
28
|
"control-2xs",
|
|
20
29
|
"control-xs",
|
|
@@ -45,22 +54,7 @@ var twMergeConfig = extendTailwindMerge({ extend: { classGroups: {
|
|
|
45
54
|
"rail-item"
|
|
46
55
|
] }]
|
|
47
56
|
} } });
|
|
48
|
-
/**
|
|
49
|
-
* Merge conditional class values, letting the last conflicting utility win.
|
|
50
|
-
*
|
|
51
|
-
* Accepts an object whose keys are classes and whose values enable them, which
|
|
52
|
-
* is the form the design-system linter can read statically:
|
|
53
|
-
*
|
|
54
|
-
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
55
|
-
*
|
|
56
|
-
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
57
|
-
* will be reported, because a class the linter cannot read is a class it
|
|
58
|
-
* cannot check against the theme.
|
|
59
|
-
*/
|
|
60
|
-
function cn(...inputs) {
|
|
61
|
-
return twMergeConfig(clsx(inputs));
|
|
62
|
-
}
|
|
63
57
|
//#endregion
|
|
64
|
-
export { cn
|
|
58
|
+
export { cn };
|
|
65
59
|
|
|
66
60
|
//# sourceMappingURL=utils.js.map
|
package/dist/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../src/lib/utils.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../src/lib/utils.ts"],"sourcesContent":["import { createCn } from 'cn/config'\nimport type { CnFunction } from 'cn'\n\n/**\n * Merge conditional class values, letting the last conflicting utility win.\n *\n * Accepts an object whose keys are classes and whose values enable them, which\n * is the form the design-system linter can read statically:\n *\n * cn('base', { 'is-open': open(), 'is-disabled': disabled() })\n *\n * A template literal (`cn(`base--${value}`)`) is invisible to the linter and\n * will be reported, because a class the linter cannot read is a class it\n * cannot check against the theme.\n *\n * `cn` also has to be told about the utilities this design system adds. Every\n * token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …) is an\n * unknown name to a default merge instance. Left unregistered,\n * `cn('h-control-md', 'h-control-sm')` keeps both classes and the winner is\n * decided by stylesheet order rather than by the caller — a bug that only\n * shows up as a control that ignores its `size` prop, long after the line\n * that caused it was written.\n *\n * Registering the families below is what makes \"the last class wins\" true for\n * the whole system, including for consumers overriding a component's default.\n */\nexport const cn: CnFunction = createCn({\n extend: {\n classGroups: {\n 'ds-height': [\n {\n h: [\n 'control-2xs',\n 'control-xs',\n 'control-sm',\n 'control-md',\n 'control-lg',\n 'control-xl',\n 'row-sm',\n 'row-md',\n 'row-lg',\n 'rail-item',\n 'topbar',\n 'statusbar',\n ],\n },\n ],\n 'ds-width': [\n {\n w: ['rail', 'rail-expanded', 'sidebar', 'sidebar-compact'],\n },\n ],\n // `size-*` is a real Tailwind group, but the token-derived values are\n // not, so they need the same treatment as height and width.\n 'ds-size': [\n {\n size: [\n 'control-2xs',\n 'control-xs',\n 'control-sm',\n 'control-md',\n 'control-lg',\n 'control-xl',\n 'rail-item',\n ],\n },\n ],\n },\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAa,KAAiB,SAAS,EACrC,QAAQ,EACN,aAAa;CACX,aAAa,CACX,EACE,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EACF,CACF;CACA,YAAY,CACV,EACE,GAAG;EAAC;EAAQ;EAAiB;EAAW;CAAiB,EAC3D,CACF;CAGA,WAAW,CACT,EACE,MAAM;EACJ;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EACF,CACF;AACF,EACF,EACF,CAAC"}
|
package/dist/r/lib.json
CHANGED
package/dist/r/registry.json
CHANGED
|
@@ -163,10 +163,23 @@ export type ThemeProviderProps = {
|
|
|
163
163
|
storageKey?: string
|
|
164
164
|
/** Start from this instead of the stored preference. */
|
|
165
165
|
initial?: Partial<ThemeSelection>
|
|
166
|
+
/**
|
|
167
|
+
* The selection, owned by the caller.
|
|
168
|
+
*
|
|
169
|
+
* For a consumer that keeps the state itself — a settings store, or a workshop
|
|
170
|
+
* whose selection lives in Storybook globals — rather than handing it to this
|
|
171
|
+
* provider's signal. Each change lands here and the document follows; nothing is
|
|
172
|
+
* written to the store, because the caller is the one who persists. A picker
|
|
173
|
+
* below the provider still works, and its change is the caller's to read back.
|
|
174
|
+
*
|
|
175
|
+
* Absent, the provider is the whole owner: it reads `storageKey` on mount,
|
|
176
|
+
* seeds from `initial`, and persists every `setSelection`.
|
|
177
|
+
*/
|
|
178
|
+
selection?: Partial<ThemeSelection>
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
export function ThemeProvider(props: ThemeProviderProps) {
|
|
169
|
-
const [local] = splitProps(props, ['children', 'storageKey', 'initial'])
|
|
182
|
+
const [local] = splitProps(props, ['children', 'storageKey', 'initial', 'selection'])
|
|
170
183
|
const storageKey = () => local.storageKey ?? 'adea-appearance'
|
|
171
184
|
|
|
172
185
|
const [selection, setSelectionState] = createSignal<ThemeSelection>({
|
|
@@ -174,6 +187,15 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
174
187
|
...local.initial,
|
|
175
188
|
})
|
|
176
189
|
|
|
190
|
+
// The controlled half of the contract. A patch from the owner replaces the
|
|
191
|
+
// matching axes; the stored preference is untouched, since the owner is the one
|
|
192
|
+
// that persists. Reading `local.selection` — a prop getter, not a destructured
|
|
193
|
+
// value — is what keeps this subscribed for the provider's lifetime.
|
|
194
|
+
createEffect(() => {
|
|
195
|
+
const incoming = local.selection
|
|
196
|
+
if (incoming) setSelectionState((current) => ({ ...current, ...incoming }))
|
|
197
|
+
})
|
|
198
|
+
|
|
177
199
|
const [systemAppearance, setSystemAppearance] = createSignal<ThemeAppearance>('light')
|
|
178
200
|
|
|
179
201
|
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
package/dist/r/src/lib/utils.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createCn } from 'cn/config'
|
|
2
|
+
import type { CnFunction } from 'cn'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Merge conditional class values, letting the last conflicting utility win.
|
|
6
|
+
*
|
|
7
|
+
* Accepts an object whose keys are classes and whose values enable them, which
|
|
8
|
+
* is the form the design-system linter can read statically:
|
|
9
|
+
*
|
|
10
|
+
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the winner is decided by stylesheet order rather than by the caller — a bug
|
|
11
|
-
* that only shows up as a control that ignores its `size` prop, long after the
|
|
12
|
-
* line that caused it was written.
|
|
12
|
+
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
13
|
+
* will be reported, because a class the linter cannot read is a class it
|
|
14
|
+
* cannot check against the theme.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
16
|
+
* `cn` also has to be told about the utilities this design system adds. Every
|
|
17
|
+
* token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …) is an
|
|
18
|
+
* unknown name to a default merge instance. Left unregistered,
|
|
19
|
+
* `cn('h-control-md', 'h-control-sm')` keeps both classes and the winner is
|
|
20
|
+
* decided by stylesheet order rather than by the caller — a bug that only
|
|
21
|
+
* shows up as a control that ignores its `size` prop, long after the line
|
|
22
|
+
* that caused it was written.
|
|
23
|
+
*
|
|
24
|
+
* Registering the families below is what makes "the last class wins" true for
|
|
15
25
|
* the whole system, including for consumers overriding a component's default.
|
|
16
26
|
*/
|
|
17
|
-
export const
|
|
27
|
+
export const cn: CnFunction = createCn({
|
|
18
28
|
extend: {
|
|
19
29
|
classGroups: {
|
|
20
30
|
'ds-height': [
|
|
@@ -58,19 +68,3 @@ export const twMergeConfig = extendTailwindMerge<'ds-height' | 'ds-width' | 'ds-
|
|
|
58
68
|
},
|
|
59
69
|
},
|
|
60
70
|
})
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Merge conditional class values, letting the last conflicting utility win.
|
|
64
|
-
*
|
|
65
|
-
* Accepts an object whose keys are classes and whose values enable them, which
|
|
66
|
-
* is the form the design-system linter can read statically:
|
|
67
|
-
*
|
|
68
|
-
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
69
|
-
*
|
|
70
|
-
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
71
|
-
* will be reported, because a class the linter cannot read is a class it
|
|
72
|
-
* cannot check against the theme.
|
|
73
|
-
*/
|
|
74
|
-
export function cn(...inputs: ClassValue[]): string {
|
|
75
|
-
return twMergeConfig(clsx(inputs))
|
|
76
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adea",
|
|
@@ -117,11 +117,10 @@
|
|
|
117
117
|
"@fontsource-variable/space-grotesk": "^5.3.0",
|
|
118
118
|
"@kobalte/core": "^0.13.14",
|
|
119
119
|
"class-variance-authority": "^0.7.1",
|
|
120
|
-
"clsx": "^2.1.1",
|
|
121
120
|
"cmdk-solid": "^1.2.0",
|
|
121
|
+
"cn": "^0.4.0",
|
|
122
122
|
"lucide-solid": "^1.48.0",
|
|
123
123
|
"solid-motionone": "^1.0.4",
|
|
124
|
-
"tailwind-merge": "^3.7.0",
|
|
125
124
|
"tw-animate-css": "^1.4.0"
|
|
126
125
|
},
|
|
127
126
|
"devDependencies": {
|
package/registry.json
CHANGED
|
@@ -163,10 +163,23 @@ export type ThemeProviderProps = {
|
|
|
163
163
|
storageKey?: string
|
|
164
164
|
/** Start from this instead of the stored preference. */
|
|
165
165
|
initial?: Partial<ThemeSelection>
|
|
166
|
+
/**
|
|
167
|
+
* The selection, owned by the caller.
|
|
168
|
+
*
|
|
169
|
+
* For a consumer that keeps the state itself — a settings store, or a workshop
|
|
170
|
+
* whose selection lives in Storybook globals — rather than handing it to this
|
|
171
|
+
* provider's signal. Each change lands here and the document follows; nothing is
|
|
172
|
+
* written to the store, because the caller is the one who persists. A picker
|
|
173
|
+
* below the provider still works, and its change is the caller's to read back.
|
|
174
|
+
*
|
|
175
|
+
* Absent, the provider is the whole owner: it reads `storageKey` on mount,
|
|
176
|
+
* seeds from `initial`, and persists every `setSelection`.
|
|
177
|
+
*/
|
|
178
|
+
selection?: Partial<ThemeSelection>
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
export function ThemeProvider(props: ThemeProviderProps) {
|
|
169
|
-
const [local] = splitProps(props, ['children', 'storageKey', 'initial'])
|
|
182
|
+
const [local] = splitProps(props, ['children', 'storageKey', 'initial', 'selection'])
|
|
170
183
|
const storageKey = () => local.storageKey ?? 'adea-appearance'
|
|
171
184
|
|
|
172
185
|
const [selection, setSelectionState] = createSignal<ThemeSelection>({
|
|
@@ -174,6 +187,15 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
174
187
|
...local.initial,
|
|
175
188
|
})
|
|
176
189
|
|
|
190
|
+
// The controlled half of the contract. A patch from the owner replaces the
|
|
191
|
+
// matching axes; the stored preference is untouched, since the owner is the one
|
|
192
|
+
// that persists. Reading `local.selection` — a prop getter, not a destructured
|
|
193
|
+
// value — is what keeps this subscribed for the provider's lifetime.
|
|
194
|
+
createEffect(() => {
|
|
195
|
+
const incoming = local.selection
|
|
196
|
+
if (incoming) setSelectionState((current) => ({ ...current, ...incoming }))
|
|
197
|
+
})
|
|
198
|
+
|
|
177
199
|
const [systemAppearance, setSystemAppearance] = createSignal<ThemeAppearance>('light')
|
|
178
200
|
|
|
179
201
|
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createCn } from 'cn/config'
|
|
2
|
+
import type { CnFunction } from 'cn'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Merge conditional class values, letting the last conflicting utility win.
|
|
6
|
+
*
|
|
7
|
+
* Accepts an object whose keys are classes and whose values enable them, which
|
|
8
|
+
* is the form the design-system linter can read statically:
|
|
9
|
+
*
|
|
10
|
+
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the winner is decided by stylesheet order rather than by the caller — a bug
|
|
11
|
-
* that only shows up as a control that ignores its `size` prop, long after the
|
|
12
|
-
* line that caused it was written.
|
|
12
|
+
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
13
|
+
* will be reported, because a class the linter cannot read is a class it
|
|
14
|
+
* cannot check against the theme.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
16
|
+
* `cn` also has to be told about the utilities this design system adds. Every
|
|
17
|
+
* token-derived utility (`h-control-md`, `w-rail`, `p-control-lg`, …) is an
|
|
18
|
+
* unknown name to a default merge instance. Left unregistered,
|
|
19
|
+
* `cn('h-control-md', 'h-control-sm')` keeps both classes and the winner is
|
|
20
|
+
* decided by stylesheet order rather than by the caller — a bug that only
|
|
21
|
+
* shows up as a control that ignores its `size` prop, long after the line
|
|
22
|
+
* that caused it was written.
|
|
23
|
+
*
|
|
24
|
+
* Registering the families below is what makes "the last class wins" true for
|
|
15
25
|
* the whole system, including for consumers overriding a component's default.
|
|
16
26
|
*/
|
|
17
|
-
export const
|
|
27
|
+
export const cn: CnFunction = createCn({
|
|
18
28
|
extend: {
|
|
19
29
|
classGroups: {
|
|
20
30
|
'ds-height': [
|
|
@@ -58,19 +68,3 @@ export const twMergeConfig = extendTailwindMerge<'ds-height' | 'ds-width' | 'ds-
|
|
|
58
68
|
},
|
|
59
69
|
},
|
|
60
70
|
})
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Merge conditional class values, letting the last conflicting utility win.
|
|
64
|
-
*
|
|
65
|
-
* Accepts an object whose keys are classes and whose values enable them, which
|
|
66
|
-
* is the form the design-system linter can read statically:
|
|
67
|
-
*
|
|
68
|
-
* cn('base', { 'is-open': open(), 'is-disabled': disabled() })
|
|
69
|
-
*
|
|
70
|
-
* A template literal (`cn(`base--${value}`)`) is invisible to the linter and
|
|
71
|
-
* will be reported, because a class the linter cannot read is a class it
|
|
72
|
-
* cannot check against the theme.
|
|
73
|
-
*/
|
|
74
|
-
export function cn(...inputs: ClassValue[]): string {
|
|
75
|
-
return twMergeConfig(clsx(inputs))
|
|
76
|
-
}
|