@goliapkg/gds 2.0.1 → 2.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.
@@ -11,7 +11,9 @@ var f = {
11
11
  elevation: "raised",
12
12
  glass: "full",
13
13
  motion: "full",
14
- colorOverrides: null
14
+ colorOverrides: null,
15
+ colorOverridesLight: null,
16
+ colorOverridesDark: null
15
17
  }, p = "gds-theme";
16
18
  function m(e) {
17
19
  try {
@@ -72,7 +74,9 @@ var _ = o(h() ?? f), v = o((e) => {
72
74
  });
73
75
  function y(i, o) {
74
76
  let s = { ...e(o === "dark" ? t(i.primaryColor) : n(i.primaryColor), o) };
75
- if (Object.assign(s, r()), Object.assign(s, a(i.shape, i.density, i.elevation, i.glass, i.motion, o)), i.colorOverrides !== null) for (let [e, t] of Object.entries(i.colorOverrides)) t !== void 0 && (s[e] = t);
77
+ Object.assign(s, r()), Object.assign(s, a(i.shape, i.density, i.elevation, i.glass, i.motion, o));
78
+ let c = [i.colorOverrides, o === "dark" ? i.colorOverridesDark : i.colorOverridesLight];
79
+ for (let e of c) if (e != null) for (let [t, n] of Object.entries(e)) n !== void 0 && (s[t] = n);
76
80
  return s;
77
81
  }
78
82
  function b(e, t, n) {
@@ -107,18 +111,81 @@ var x = {
107
111
  elevation: "subtle",
108
112
  glass: "off",
109
113
  motion: "full"
114
+ },
115
+ "zinc-neutral": {
116
+ primaryColor: "#3b7ddd",
117
+ shape: "default",
118
+ density: "default",
119
+ elevation: "subtle",
120
+ glass: "subtle",
121
+ motion: "full",
122
+ colorOverridesLight: {
123
+ "--gds-bg": "#fafafa",
124
+ "--gds-bg-secondary": "#f4f4f5",
125
+ "--gds-bg-tertiary": "#e4e4e7",
126
+ "--gds-surface": "#ffffff",
127
+ "--gds-surface-raised": "#ffffff",
128
+ "--gds-fg": "#09090b",
129
+ "--gds-fg-secondary": "#3f3f46",
130
+ "--gds-fg-muted": "#71717a",
131
+ "--gds-border": "#e4e4e7",
132
+ "--gds-border-strong": "#d4d4d8",
133
+ "--gds-overlay": "rgba(0,0,0,0.5)",
134
+ "--gds-accent": "#3b7ddd",
135
+ "--gds-accent-hover": "#2b6bc5",
136
+ "--gds-accent-fg": "#ffffff",
137
+ "--gds-success": "#0ca678",
138
+ "--gds-warning": "#e67700",
139
+ "--gds-danger": "#e03131",
140
+ "--gds-info": "#3b7ddd"
141
+ },
142
+ colorOverridesDark: {
143
+ "--gds-bg": "#09090b",
144
+ "--gds-bg-secondary": "#0a0a0a",
145
+ "--gds-bg-tertiary": "#18181b",
146
+ "--gds-surface": "#18181b",
147
+ "--gds-surface-raised": "#27272a",
148
+ "--gds-fg": "#fafafa",
149
+ "--gds-fg-secondary": "#a1a1aa",
150
+ "--gds-fg-muted": "#71717a",
151
+ "--gds-border": "#27272a",
152
+ "--gds-border-strong": "#3f3f46",
153
+ "--gds-overlay": "rgba(0,0,0,0.7)",
154
+ "--gds-accent": "#3b82f6",
155
+ "--gds-accent-hover": "#60a5fa",
156
+ "--gds-accent-fg": "#ffffff",
157
+ "--gds-success": "#22c55e",
158
+ "--gds-warning": "#f59e0b",
159
+ "--gds-danger": "#ef4444",
160
+ "--gds-info": "#3b82f6"
161
+ }
110
162
  }
111
- }, S = { colorPresets: {} };
112
- function C(e) {
113
- S = e;
163
+ }, S = "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&family=Noto+Sans+JP:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;600;700&display=swap", C = ["https://fonts.googleapis.com", "https://fonts.gstatic.com"], w = "gds-cjk-fonts";
164
+ function T() {
165
+ u(() => {
166
+ if (typeof document > "u" || document.getElementById(w) !== null) return;
167
+ let e = document.head;
168
+ for (let t of C) {
169
+ let n = document.createElement("link");
170
+ n.rel = "preconnect", n.href = t, t.includes("gstatic") && (n.crossOrigin = "anonymous"), n.setAttribute("data-gds", "font-preconnect"), e.appendChild(n);
171
+ }
172
+ let t = document.createElement("link");
173
+ t.id = w, t.rel = "stylesheet", t.href = S, t.setAttribute("data-gds", "cjk-fonts"), e.appendChild(t);
174
+ }, []);
175
+ }
176
+ //#endregion
177
+ //#region src/l1-systems/use-theme.ts
178
+ var E = { colorPresets: {} };
179
+ function D(e) {
180
+ E = e;
114
181
  }
115
- function w() {
182
+ function O() {
116
183
  return c(_);
117
184
  }
118
- function T() {
185
+ function k() {
119
186
  return c(v);
120
187
  }
121
- function E() {
188
+ function A() {
122
189
  let [, e] = s(_);
123
190
  return l((t) => {
124
191
  e((e) => ({
@@ -127,29 +194,45 @@ function E() {
127
194
  }));
128
195
  }, [e]);
129
196
  }
130
- function D() {
197
+ function j() {
131
198
  let [, e] = s(_);
132
199
  return l((t) => {
133
- let n = S.colorPresets[t]?.primaryColor ?? f.primaryColor;
200
+ let n = x[t];
201
+ if (n !== void 0) {
202
+ e((e) => ({
203
+ ...e,
204
+ presetId: t,
205
+ colorOverrides: null,
206
+ colorOverridesLight: null,
207
+ colorOverridesDark: null,
208
+ ...n
209
+ }));
210
+ return;
211
+ }
212
+ let r = E.colorPresets[t]?.primaryColor ?? f.primaryColor;
134
213
  e((e) => ({
135
214
  ...e,
136
215
  presetId: t,
137
- primaryColor: n,
138
- colorOverrides: null
216
+ primaryColor: r,
217
+ colorOverrides: null,
218
+ colorOverridesLight: null,
219
+ colorOverridesDark: null
139
220
  }));
140
221
  }, [e]);
141
222
  }
142
- function O() {
223
+ function M() {
143
224
  let [, e] = s(_);
144
225
  return l((t) => {
145
226
  e((e) => ({
146
227
  ...e,
147
228
  primaryColor: t,
148
- colorOverrides: null
229
+ colorOverrides: null,
230
+ colorOverridesLight: null,
231
+ colorOverridesDark: null
149
232
  }));
150
233
  }, [e]);
151
234
  }
152
- function k() {
235
+ function N() {
153
236
  let [, e] = s(_);
154
237
  return l((t) => {
155
238
  e((e) => ({
@@ -158,7 +241,7 @@ function k() {
158
241
  }));
159
242
  }, [e]);
160
243
  }
161
- function A() {
244
+ function P() {
162
245
  let [, e] = s(_);
163
246
  return l((t) => {
164
247
  e((e) => ({
@@ -167,7 +250,7 @@ function A() {
167
250
  }));
168
251
  }, [e]);
169
252
  }
170
- function j() {
253
+ function F() {
171
254
  let [, e] = s(_);
172
255
  return l((t) => {
173
256
  e((e) => ({
@@ -176,7 +259,7 @@ function j() {
176
259
  }));
177
260
  }, [e]);
178
261
  }
179
- function M() {
262
+ function I() {
180
263
  let [, e] = s(_);
181
264
  return l((t) => {
182
265
  e((e) => ({
@@ -185,7 +268,7 @@ function M() {
185
268
  }));
186
269
  }, [e]);
187
270
  }
188
- function N() {
271
+ function L() {
189
272
  let [, e] = s(_);
190
273
  return l((t) => {
191
274
  e((e) => ({
@@ -194,22 +277,24 @@ function N() {
194
277
  }));
195
278
  }, [e]);
196
279
  }
197
- function P() {
280
+ function R() {
198
281
  let [, e] = s(_);
199
282
  return l((t) => {
200
283
  e((e) => ({
201
284
  ...e,
202
- colorOverrides: t
285
+ colorOverrides: t,
286
+ colorOverridesLight: null,
287
+ colorOverridesDark: null
203
288
  }));
204
289
  }, [e]);
205
290
  }
206
- function F() {
291
+ function z() {
207
292
  let [, e] = s(_);
208
293
  return l(() => {
209
294
  e(f);
210
295
  }, [e]);
211
296
  }
212
- function I() {
297
+ function B() {
213
298
  let e = c(_), t = c(v), n = d([]);
214
299
  u(() => {
215
300
  n.current = b(y(e, t), t, n.current), m(e);
@@ -219,6 +304,6 @@ function I() {
219
304
  }, []);
220
305
  }
221
306
  //#endregion
222
- export { y as _, A as a, x as b, E as c, O as d, k as f, h as g, f as h, P as i, N as l, I as m, F as n, j as o, w as p, T as r, M as s, C as t, D as u, v, _ as y };
307
+ export { h as _, P as a, _ as b, A as c, M as d, N as f, f as g, T as h, R as i, L as l, B as m, z as n, F as o, O as p, k as r, I as s, D as t, j as u, y as v, x, v as y };
223
308
 
224
- //# sourceMappingURL=use-theme-D_THp_K2.js.map
309
+ //# sourceMappingURL=use-theme-DJmWAXqb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-theme-DJmWAXqb.js","names":[],"sources":["../src/l1-systems/theme.ts","../src/l1-systems/use-fonts.ts","../src/l1-systems/use-theme.ts"],"sourcesContent":["// L1 — Theme System\n// manages color preset + 5 dimensional axes + dark/light mode\n// applies CSS variable overrides to document root\n// persists to localStorage, syncs across tabs\n\nimport { atom } from 'jotai'\n\nimport {\n deriveDarkPalette,\n deriveLightPalette,\n paletteToVars,\n} from '../l0-tokens/color-derive'\nimport { fontToCssVars } from '../l0-tokens/font-system'\nimport { DEFAULT_PRIMARY } from '../l0-tokens/generate-defaults'\nimport type {\n ThemeDensity,\n ThemeElevation,\n ThemeGlass,\n ThemeMotion,\n ThemeShape,\n} from '../l0-tokens/scales'\nimport { resolveAxesToCssVars } from '../l0-tokens/scales'\n\n// color overrides — per-token overrides for advanced users\n// consumers can override ANY --gds-* color variable to fully customize the palette\nexport type ThemeColorOverrides = {\n // accent family\n '--gds-accent': string\n '--gds-accent-fg': string\n '--gds-accent-hover': string\n\n // base surfaces\n '--gds-bg': string\n '--gds-bg-secondary': string\n '--gds-bg-tertiary': string\n '--gds-surface': string\n '--gds-surface-raised': string\n\n // foreground\n '--gds-fg': string\n '--gds-fg-secondary': string\n '--gds-fg-muted': string\n\n // borders\n '--gds-border': string\n '--gds-border-strong': string\n\n // overlay\n '--gds-overlay': string\n\n // semantic\n '--gds-danger': string\n '--gds-info': string\n '--gds-success': string\n '--gds-warning': string\n}\n\nexport type ThemeMode = 'dark' | 'light' | 'system'\n\n// full theme state — what the user has configured\nexport type ThemeState = {\n mode: ThemeMode\n primaryColor: string // single source — everything derived from this\n presetId: string // for UI display only (\"default\", \"teal\", \"amber\"...)\n // dimensional axes — each constrained to L0 scale options\n shape: ThemeShape\n density: ThemeDensity\n elevation: ThemeElevation\n glass: ThemeGlass\n motion: ThemeMotion\n // optional per-token color overrides (advanced — overrides derivation)\n colorOverrides: Partial<ThemeColorOverrides> | null\n // mode-aware overrides — applied on top of colorOverrides for the matching mode\n colorOverridesLight: Partial<ThemeColorOverrides> | null\n colorOverridesDark: Partial<ThemeColorOverrides> | null\n}\n\n// default theme — beautiful out of the box\nexport const DEFAULT_THEME: ThemeState = {\n mode: 'system',\n primaryColor: DEFAULT_PRIMARY,\n presetId: 'default',\n shape: 'default',\n density: 'default',\n elevation: 'raised',\n glass: 'full',\n motion: 'full',\n colorOverrides: null,\n colorOverridesLight: null,\n colorOverridesDark: null,\n}\n\n// persistence — must be defined before themeAtom so atom init can load from localStorage\nconst STORAGE_KEY = 'gds-theme'\n\nexport function persistTheme(state: ThemeState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n } catch {\n // storage full or unavailable\n }\n}\n\nexport function loadPersistedTheme(): ThemeState | null {\n try {\n if (typeof window === 'undefined') return null\n const raw = localStorage.getItem(STORAGE_KEY)\n if (raw === null) return null\n const parsed = JSON.parse(raw) as Partial<ThemeState>\n // validate and merge with defaults to handle schema evolution\n return {\n ...DEFAULT_THEME,\n ...parsed,\n // validate primaryColor is a hex string\n primaryColor:\n typeof parsed.primaryColor === 'string' &&\n /^#[0-9a-fA-F]{6}$/.test(parsed.primaryColor)\n ? parsed.primaryColor\n : DEFAULT_THEME.primaryColor,\n // ensure constrained values are valid\n shape: validateOption(\n parsed.shape,\n ['sharp', 'default', 'rounded'],\n DEFAULT_THEME.shape\n ),\n density: validateOption(\n parsed.density,\n ['compact', 'default', 'comfortable'],\n DEFAULT_THEME.density\n ),\n elevation: validateOption(\n parsed.elevation,\n ['flat', 'subtle', 'raised'],\n DEFAULT_THEME.elevation\n ),\n glass: validateOption(\n parsed.glass,\n ['off', 'subtle', 'full'],\n DEFAULT_THEME.glass\n ),\n motion: validateOption(\n parsed.motion,\n ['off', 'reduced', 'full'],\n DEFAULT_THEME.motion\n ),\n mode: validateOption(\n parsed.mode,\n ['light', 'dark', 'system'],\n DEFAULT_THEME.mode\n ),\n }\n } catch {\n return null\n }\n}\n\nfunction validateOption<T extends string>(\n value: unknown,\n options: T[],\n fallback: T\n): T {\n if (typeof value === 'string' && options.includes(value as T)) {\n return value as T\n }\n return fallback\n}\n\n// jotai atoms — reactive theme state\n// initialize from localStorage to avoid race condition with useThemeEffect\nexport const themeAtom = atom<ThemeState>(loadPersistedTheme() ?? DEFAULT_THEME)\n\nexport const resolvedModeAtom = atom<'dark' | 'light'>((get) => {\n const { mode } = get(themeAtom)\n if (mode !== 'system') return mode\n if (typeof window === 'undefined') return 'dark'\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n})\n\n// resolve theme state → flat CSS variable overrides\n// all colors derived from primaryColor via L0 functions — no manual color presets\nexport function resolveThemeCssVars(\n state: ThemeState,\n resolvedMode: 'dark' | 'light'\n): Record<string, string> {\n // 1. derive colors from primaryColor\n const palette =\n resolvedMode === 'dark'\n ? deriveDarkPalette(state.primaryColor)\n : deriveLightPalette(state.primaryColor)\n const vars: Record<string, string> = {\n ...paletteToVars(palette, resolvedMode),\n }\n\n // 2. font stacks + weights\n Object.assign(vars, fontToCssVars())\n\n // 3. dimensional axes — computed by L0 system functions\n Object.assign(\n vars,\n resolveAxesToCssVars(\n state.shape,\n state.density,\n state.elevation,\n state.glass,\n state.motion,\n resolvedMode\n )\n )\n\n // 4. per-token color overrides (highest priority — advanced users)\n // apply order: colorOverrides (both modes) → colorOverrides{Light|Dark} (mode-specific)\n const overrideLayers = [\n state.colorOverrides,\n resolvedMode === 'dark'\n ? state.colorOverridesDark\n : state.colorOverridesLight,\n ]\n for (const layer of overrideLayers) {\n if (layer !== null && layer !== undefined) {\n for (const [key, val] of Object.entries(layer)) {\n if (val !== undefined) {\n vars[key] = val\n }\n }\n }\n }\n\n return vars\n}\n\n// apply resolved vars to document\nexport function applyThemeToDocument(\n vars: Record<string, string>,\n resolvedMode: 'dark' | 'light',\n previousKeys?: string[]\n): string[] {\n const root = document.documentElement\n\n // clear previous overrides\n if (previousKeys !== undefined) {\n for (const key of previousKeys) {\n root.style.removeProperty(key)\n }\n }\n\n // set mode attribute\n const mode = root.getAttribute('data-theme-mode')\n if (mode !== resolvedMode) {\n root.setAttribute('data-theme-mode', resolvedMode)\n }\n\n // apply new overrides\n const keys = Object.keys(vars)\n for (const [key, val] of Object.entries(vars)) {\n root.style.setProperty(key, val)\n }\n\n return keys\n}\n\n// named theme presets — optimized axis combinations for specific application types\n// override fields are optional — simple presets only need axes + primaryColor\nexport type ThemePreset = Omit<\n ThemeState,\n | 'mode'\n | 'presetId'\n | 'colorOverrides'\n | 'colorOverridesLight'\n | 'colorOverridesDark'\n> & {\n colorOverrides?: Partial<ThemeColorOverrides> | null\n colorOverridesLight?: Partial<ThemeColorOverrides> | null\n colorOverridesDark?: Partial<ThemeColorOverrides> | null\n}\n\nexport const themePresets = {\n // default: balanced for general-purpose dashboards\n default: {\n primaryColor: DEFAULT_PRIMARY,\n shape: 'default' as const,\n density: 'default' as const,\n elevation: 'raised' as const,\n glass: 'full' as const,\n motion: 'full' as const,\n },\n // email: optimized for email/productivity apps (mailrs-proven)\n // comfortable density for readable 14px base, subtle elevation for clean modern look\n email: {\n primaryColor: '#3b7ddd', // slightly desaturated blue, validated in production\n shape: 'default' as const,\n density: 'comfortable' as const,\n elevation: 'subtle' as const,\n glass: 'subtle' as const,\n motion: 'full' as const,\n },\n // dashboard: data-dense monitoring/analytics\n dashboard: {\n primaryColor: DEFAULT_PRIMARY,\n shape: 'default' as const,\n density: 'compact' as const,\n elevation: 'subtle' as const,\n glass: 'off' as const,\n motion: 'full' as const,\n },\n // zinc-neutral: pure neutral surfaces for productivity apps (email, editors, docs)\n // zero hue tint — uses Tailwind zinc scale for content-neutral backgrounds\n // mailrs-proven palette, optimized for long-session comfort\n 'zinc-neutral': {\n primaryColor: '#3b7ddd',\n shape: 'default' as const,\n density: 'default' as const,\n elevation: 'subtle' as const,\n glass: 'subtle' as const,\n motion: 'full' as const,\n colorOverridesLight: {\n '--gds-bg': '#fafafa', // zinc-50\n '--gds-bg-secondary': '#f4f4f5', // zinc-100\n '--gds-bg-tertiary': '#e4e4e7', // zinc-200\n '--gds-surface': '#ffffff',\n '--gds-surface-raised': '#ffffff',\n '--gds-fg': '#09090b', // zinc-950\n '--gds-fg-secondary': '#3f3f46', // zinc-700\n '--gds-fg-muted': '#71717a', // zinc-500\n '--gds-border': '#e4e4e7', // zinc-200\n '--gds-border-strong': '#d4d4d8', // zinc-300\n '--gds-overlay': 'rgba(0,0,0,0.5)',\n '--gds-accent': '#3b7ddd',\n '--gds-accent-hover': '#2b6bc5',\n '--gds-accent-fg': '#ffffff',\n '--gds-success': '#0ca678', // mantine green\n '--gds-warning': '#e67700', // mantine orange\n '--gds-danger': '#e03131', // mantine red\n '--gds-info': '#3b7ddd',\n },\n colorOverridesDark: {\n '--gds-bg': '#09090b', // zinc-950\n '--gds-bg-secondary': '#0a0a0a', // near-black\n '--gds-bg-tertiary': '#18181b', // zinc-900\n '--gds-surface': '#18181b', // zinc-900\n '--gds-surface-raised': '#27272a', // zinc-800\n '--gds-fg': '#fafafa', // zinc-50\n '--gds-fg-secondary': '#a1a1aa', // zinc-400\n '--gds-fg-muted': '#71717a', // zinc-500\n '--gds-border': '#27272a', // zinc-800\n '--gds-border-strong': '#3f3f46', // zinc-700\n '--gds-overlay': 'rgba(0,0,0,0.7)',\n '--gds-accent': '#3b82f6', // blue-500\n '--gds-accent-hover': '#60a5fa', // blue-400\n '--gds-accent-fg': '#ffffff',\n '--gds-success': '#22c55e', // green-500\n '--gds-warning': '#f59e0b', // amber-500\n '--gds-danger': '#ef4444', // red-500\n '--gds-info': '#3b82f6',\n },\n },\n} as const satisfies Record<string, ThemePreset>\n\nexport type ThemePresetId = keyof typeof themePresets\n","// use-fonts — auto-inject CJK font links into document head\n// call once in app root to enable full CJK support (SC, JP, KR)\n// latin fonts are loaded via fonts.css (self-hosted woff2)\n\nimport { useEffect } from 'react'\n\nconst GOOGLE_FONTS_URL =\n 'https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&family=Noto+Sans+JP:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;600;700&display=swap'\n\nconst PRECONNECT_URLS = [\n 'https://fonts.googleapis.com',\n 'https://fonts.gstatic.com',\n]\n\nconst MARKER_ID = 'gds-cjk-fonts'\n\n/** Auto-inject CJK font <link> tags. Call once at app root alongside useThemeEffect(). */\nexport function useFonts(): void {\n useEffect(() => {\n if (typeof document === 'undefined') return\n // skip if already injected\n if (document.getElementById(MARKER_ID) !== null) return\n\n const head = document.head\n\n // preconnect links\n for (const url of PRECONNECT_URLS) {\n const link = document.createElement('link')\n link.rel = 'preconnect'\n link.href = url\n if (url.includes('gstatic')) {\n link.crossOrigin = 'anonymous'\n }\n link.setAttribute('data-gds', 'font-preconnect')\n head.appendChild(link)\n }\n\n // stylesheet link\n const stylesheet = document.createElement('link')\n stylesheet.id = MARKER_ID\n stylesheet.rel = 'stylesheet'\n stylesheet.href = GOOGLE_FONTS_URL\n stylesheet.setAttribute('data-gds', 'cjk-fonts')\n head.appendChild(stylesheet)\n }, [])\n}\n","// L1 — theme hooks\n// components use these to read/write theme state\n// all changes go through constrained API — no raw CSS manipulation\n\nimport { useAtom, useAtomValue } from 'jotai'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport type {\n ThemeDensity,\n ThemeElevation,\n ThemeGlass,\n ThemeMotion,\n ThemeShape,\n} from '../l0-tokens/scales'\nimport type { ThemeColorOverrides, ThemeMode, ThemeState } from './theme'\nimport {\n applyThemeToDocument,\n DEFAULT_THEME,\n persistTheme,\n resolvedModeAtom,\n resolveThemeCssVars,\n themeAtom,\n themePresets,\n} from './theme'\n\n// color presets — app registers named presets, each is just a primaryColor\ntype ThemeConfig = {\n colorPresets: Record<string, { primaryColor: string }>\n}\n\nlet themeConfig: ThemeConfig = { colorPresets: {} }\n\n// called once at app init — register named color presets\nexport function configureTheme(config: ThemeConfig): void {\n themeConfig = config\n}\n\n// read current theme state (reactive)\nexport function useTheme(): ThemeState {\n return useAtomValue(themeAtom)\n}\n\n// read resolved dark/light mode (reactive)\nexport function useResolvedMode(): 'dark' | 'light' {\n return useAtomValue(resolvedModeAtom)\n}\n\n// theme mutation hooks — each returns a setter for one axis\nexport function useSetThemeMode(): (mode: ThemeMode) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (mode: ThemeMode) => {\n setTheme((prev) => ({ ...prev, mode }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemePreset(): (presetId: string) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (presetId: string) => {\n // check built-in presets first, then registered color presets\n const builtIn = themePresets[presetId as keyof typeof themePresets]\n if (builtIn !== undefined) {\n setTheme((prev) => ({\n ...prev,\n presetId,\n // reset all overrides first, then apply preset (which may set mode-aware ones)\n colorOverrides: null,\n colorOverridesLight: null,\n colorOverridesDark: null,\n ...builtIn,\n }))\n return\n }\n const preset = themeConfig.colorPresets[presetId]\n const primaryColor = preset?.primaryColor ?? DEFAULT_THEME.primaryColor\n setTheme((prev) => ({\n ...prev,\n presetId,\n primaryColor,\n colorOverrides: null,\n colorOverridesLight: null,\n colorOverridesDark: null,\n }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemePrimaryColor(): (color: string) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (primaryColor: string) => {\n setTheme((prev) => ({\n ...prev,\n primaryColor,\n colorOverrides: null,\n colorOverridesLight: null,\n colorOverridesDark: null,\n }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeShape(): (shape: ThemeShape) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (shape: ThemeShape) => {\n setTheme((prev) => ({ ...prev, shape }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeDensity(): (density: ThemeDensity) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (density: ThemeDensity) => {\n setTheme((prev) => ({ ...prev, density }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeElevation(): (elevation: ThemeElevation) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (elevation: ThemeElevation) => {\n setTheme((prev) => ({ ...prev, elevation }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeGlass(): (glass: ThemeGlass) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (glass: ThemeGlass) => {\n setTheme((prev) => ({ ...prev, glass }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeMotion(): (motion: ThemeMotion) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (motion: ThemeMotion) => {\n setTheme((prev) => ({ ...prev, motion }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeColors(): (\n overrides: Partial<ThemeColorOverrides> | null\n) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (colorOverrides: Partial<ThemeColorOverrides> | null) => {\n setTheme((prev) => ({\n ...prev,\n colorOverrides,\n // clear mode-aware overrides — user's explicit override should not be\n // shadowed by leftover mode-specific values from a preset\n colorOverridesLight: null,\n colorOverridesDark: null,\n }))\n },\n [setTheme]\n )\n}\n\nexport function useResetTheme(): () => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(() => {\n setTheme(DEFAULT_THEME)\n }, [setTheme])\n}\n\n// side-effect hook: apply theme to DOM + persist + listen to system changes\n// call this ONCE in the app root\nexport function useThemeEffect(): void {\n const theme = useAtomValue(themeAtom)\n const resolvedMode = useAtomValue(resolvedModeAtom)\n const prevKeysRef = useRef<string[]>([])\n\n // apply to DOM whenever theme changes\n useEffect(() => {\n const vars = resolveThemeCssVars(theme, resolvedMode)\n prevKeysRef.current = applyThemeToDocument(\n vars,\n resolvedMode,\n prevKeysRef.current\n )\n persistTheme(theme)\n }, [theme, resolvedMode])\n\n // listen for system theme preference changes\n useEffect(() => {\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = () => {\n // trigger re-render via resolvedModeAtom re-evaluation\n // jotai derived atoms auto-recompute, but we need to force it\n // by touching the base atom (no-op write)\n }\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [])\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goliapkg/gds",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "GOLIA Design System — enterprise-grade UI component library with contextual depth, glass materials, AI-native structure, and 10 design principles",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -108,6 +108,7 @@
108
108
  "peerDependencies": {
109
109
  "react": ">=19.0.0",
110
110
  "react-dom": ">=19.0.0",
111
+ "jotai": "^2.19.0",
111
112
  "@tiptap/react": "^3.0.0",
112
113
  "@tiptap/starter-kit": "^3.0.0",
113
114
  "@tiptap/extension-code-block-lowlight": "^3.0.0",
@@ -162,7 +163,6 @@
162
163
  "dependencies": {
163
164
  "class-variance-authority": "^0.7.1",
164
165
  "clsx": "^2.1.1",
165
- "jotai": "^2.19.0",
166
166
  "lucide-react": "^1.7.0",
167
167
  "recharts": "^3.8.1",
168
168
  "shiki": "^4.0.2",
@@ -196,6 +196,7 @@
196
196
  "eslint-plugin-react-hooks": "^7.0.1",
197
197
  "eslint-plugin-simple-import-sort": "^12.1.1",
198
198
  "happy-dom": "^20.8.9",
199
+ "jotai": "^2.19.0",
199
200
  "lowlight": "^3.3.0",
200
201
  "prettier": "^3.8.1",
201
202
  "prettier-plugin-tailwindcss": "^0.7.2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"l0-tokens-DZkyVlac.js","names":[],"sources":["../src/l0-tokens/deps.ts","../src/l0-tokens/breakpoint-system.ts"],"sourcesContent":["// L-dep — dependency manifest\n// documents every external dependency, its role, and layer constraints\n\nexport type DepInfo = {\n name: string\n version: string\n role: string\n layer: 'L-dep'\n type: 'peer' | 'runtime' | 'dev'\n usedBy: string[]\n}\n\nexport const GDS_DEPS: DepInfo[] = [\n // peer — provided by consumer app\n {\n name: 'react',\n version: '>=19.0.0',\n role: 'UI runtime — component rendering, hooks, reconciler',\n layer: 'L-dep',\n type: 'peer',\n usedBy: ['L1-systems', 'L2-primitives', 'L3+'],\n },\n {\n name: 'react-dom',\n version: '>=19.0.0',\n role: 'DOM binding for React — portal, createRoot',\n layer: 'L-dep',\n type: 'peer',\n usedBy: ['L-dep utils (portal wrapper)'],\n },\n\n // runtime — bundled with gds\n {\n name: 'tailwindcss',\n version: '^4.2.1',\n role: 'CSS engine — utility-first classes, @theme mapping, @utility registration',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L0-tokens'],\n },\n {\n name: 'clsx',\n version: '^2.1.1',\n role: 'conditional className joining — lightweight boolean class logic',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L2+'],\n },\n {\n name: 'tailwind-merge',\n version: '^3.5.0',\n role: 'deduplicates conflicting Tailwind classes — powers cx() utility',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L2+'],\n },\n {\n name: 'class-variance-authority',\n version: '^0.7.1',\n role: 'type-safe variant API — cva() for component variant definitions',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L3-atoms', 'L4-molecules'],\n },\n {\n name: 'jotai',\n version: '^2.18.1',\n role: 'atomic state management — theme, i18n, responsive atoms',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L1-systems'],\n },\n {\n name: 'lucide-react',\n version: '^0.577.0',\n role: 'icon library — 1500+ SVG icons as React components',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L3-atoms', 'L4-molecules', 'L5-organisms'],\n },\n {\n name: 'recharts',\n version: '^3.8.0',\n role: 'chart foundation — ResponsiveContainer, ComposedChart, RadialBarChart',\n layer: 'L-dep',\n type: 'runtime',\n usedBy: ['L6-charts'],\n },\n]\n\n// internal utilities — part of L-dep, available to all layers\n// these wrap external deps so components never import clsx/twMerge/cva directly\nexport const GDS_INTERNAL_UTILS = [\n {\n name: 'cx',\n module: 'utils/cx',\n role: 'class merging (clsx + tailwind-merge)',\n usedBy: 'L2+',\n },\n {\n name: 'focusCls',\n module: 'utils/a11y',\n role: 'focus ring preset class string',\n usedBy: 'L2+',\n },\n {\n name: 'srOnly',\n module: 'utils/a11y',\n role: 'screen-reader-only class string',\n usedBy: 'L2+',\n },\n {\n name: 'mergeRefs',\n module: 'utils/dom',\n role: 'combine multiple refs into one',\n usedBy: 'L2+',\n },\n {\n name: 'isActivationKey',\n module: 'utils/dom',\n role: 'detect Enter/Space on keydown',\n usedBy: 'L3+',\n },\n {\n name: 'clamp',\n module: 'utils/dom',\n role: 'clamp number to min/max',\n usedBy: 'L2+',\n },\n {\n name: 'uid',\n module: 'utils/dom',\n role: 'unique id generator for a11y',\n usedBy: 'L2+',\n },\n {\n name: 'VariantProps',\n module: 'utils/types',\n role: 'extract variant type from cva()',\n usedBy: 'L3+',\n },\n {\n name: 'MergeProps',\n module: 'utils/types',\n role: 'merge native + component props',\n usedBy: 'L2+',\n },\n {\n name: 'AsProps',\n module: 'utils/types',\n role: 'polymorphic as prop',\n usedBy: 'L2',\n },\n // hooks\n {\n name: 'useScrollLock',\n module: 'utils/hooks',\n role: 'prevent body scroll when overlay open',\n usedBy: 'L5+',\n },\n {\n name: 'useEscapeKey',\n module: 'utils/hooks',\n role: 'close overlay on Escape',\n usedBy: 'L5+',\n },\n {\n name: 'useClickOutside',\n module: 'utils/hooks',\n role: 'detect click outside element',\n usedBy: 'L5+',\n },\n {\n name: 'useMediaQuery',\n module: 'utils/hooks',\n role: 'subscribe to CSS media query',\n usedBy: 'L2+',\n },\n {\n name: 'useIsMobile',\n module: 'utils/hooks',\n role: 'boolean mobile breakpoint',\n usedBy: 'L2+',\n },\n {\n name: 'useIsDesktop',\n module: 'utils/hooks',\n role: 'boolean desktop breakpoint',\n usedBy: 'L2+',\n },\n {\n name: 'useFocusTrap',\n module: 'utils/hooks',\n role: 'trap tab focus in container',\n usedBy: 'L5+',\n },\n {\n name: 'renderPortal',\n module: 'utils/portal',\n role: 'portal rendering via react-dom createPortal',\n usedBy: 'L2+',\n },\n] as const\n\n// engineering infrastructure — enforced at every layer, not optional\nexport const GDS_INFRA = {\n typecheck: {\n tool: 'typescript',\n config: 'tsconfig.json',\n command: 'bun run typecheck',\n rule: 'zero errors on every commit. strict mode, no any in lib code',\n },\n lint: {\n tool: 'eslint',\n config: 'eslint.config.js',\n command: 'bun run lint',\n rule: 'zero warnings. import sorting, hooks rules, no console',\n },\n test: {\n tool: 'vitest',\n config: 'vitest.config.ts',\n command: 'bun run test',\n rule: 'TDD mandatory. tests BEFORE code. 80% coverage threshold enforced',\n },\n format: {\n tool: 'eslint --fix + prettier (via admin)',\n rule: 'no semicolons, single quotes JS, double quotes JSX, 2-space indent',\n },\n} as const\n\n// dependency constraint: which layers can import which external deps\n// internal utils (cx, focusCls, etc.) are always allowed — they are L-dep\nexport const LAYER_DEP_CONSTRAINTS: Record<string, string[]> = {\n 'L0-tokens': ['tailwindcss'],\n 'L1-systems': ['react', 'jotai'],\n 'L2-primitives': [\n 'react',\n 'clsx',\n 'tailwind-merge',\n 'class-variance-authority',\n ],\n 'L3-atoms': [\n 'react',\n 'clsx',\n 'tailwind-merge',\n 'class-variance-authority',\n 'lucide-react',\n ],\n 'L4-molecules': [\n 'react',\n 'clsx',\n 'tailwind-merge',\n 'class-variance-authority',\n 'lucide-react',\n ],\n 'L5-organisms': [\n 'react',\n 'clsx',\n 'tailwind-merge',\n 'class-variance-authority',\n 'lucide-react',\n ],\n 'L6-charts': ['react', 'clsx', 'tailwind-merge', 'recharts', 'lucide-react'],\n 'L7-patterns': ['react', 'clsx', 'tailwind-merge'],\n}\n","// L0 — breakpoint system\n// responsive breakpoints with semantic names and overlap detection\n\n// breakpoint scale — mobile-first, min-width values\n// each step is roughly 1.5-2× the mobile base (320px)\nexport const breakpoints = {\n sm: 640, // large phone / small tablet\n md: 768, // tablet portrait\n lg: 1024, // tablet landscape / small laptop\n xl: 1280, // laptop\n '2xl': 1536, // desktop\n} as const\n\nexport type BreakpointKey = keyof typeof breakpoints\n\n// device category detection (from width)\nexport type DeviceCategory = 'mobile' | 'tablet' | 'desktop'\n\nexport function deviceCategory(width: number): DeviceCategory {\n if (width < breakpoints.md) return 'mobile'\n if (width < breakpoints.lg) return 'tablet'\n return 'desktop'\n}\n\n// media query helpers — generate valid CSS media query strings\nexport function minWidth(bp: BreakpointKey): string {\n return `(min-width: ${breakpoints[bp]}px)`\n}\n\nexport function maxWidth(bp: BreakpointKey): string {\n return `(max-width: ${breakpoints[bp] - 1}px)`\n}\n\nexport function between(min: BreakpointKey, max: BreakpointKey): string {\n return `(min-width: ${breakpoints[min]}px) and (max-width: ${breakpoints[max] - 1}px)`\n}\n\n// overlap detection — validate that custom breakpoints don't conflict\nexport function detectOverlap(\n custom: Record<string, number>\n): { a: string; b: string; overlap: number }[] {\n const sorted = Object.entries(custom).sort(([, a], [, b]) => a - b)\n const overlaps: { a: string; b: string; overlap: number }[] = []\n for (let i = 0; i < sorted.length - 1; i++) {\n const [nameA, valA] = sorted[i]\n const [nameB, valB] = sorted[i + 1]\n if (valA === valB) {\n overlaps.push({ a: nameA, b: nameB, overlap: valA })\n }\n }\n return overlaps\n}\n\nexport function breakpointToCssVars(): Record<string, string> {\n const vars: Record<string, string> = {}\n for (const [key, val] of Object.entries(breakpoints)) {\n vars[`--gds-breakpoint-${key}`] = `${val}px`\n }\n return vars\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/l1-systems/use-fonts.ts"],"sourcesContent":["// use-fonts — auto-inject CJK font links into document head\n// call once in app root to enable full CJK support (SC, JP, KR)\n// latin fonts are loaded via fonts.css (self-hosted woff2)\n\nimport { useEffect } from 'react'\n\nconst GOOGLE_FONTS_URL =\n 'https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&family=Noto+Sans+JP:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;600;700&display=swap'\n\nconst PRECONNECT_URLS = [\n 'https://fonts.googleapis.com',\n 'https://fonts.gstatic.com',\n]\n\nconst MARKER_ID = 'gds-cjk-fonts'\n\n/** Auto-inject CJK font <link> tags. Call once at app root alongside useThemeEffect(). */\nexport function useFonts(): void {\n useEffect(() => {\n if (typeof document === 'undefined') return\n // skip if already injected\n if (document.getElementById(MARKER_ID) !== null) return\n\n const head = document.head\n\n // preconnect links\n for (const url of PRECONNECT_URLS) {\n const link = document.createElement('link')\n link.rel = 'preconnect'\n link.href = url\n if (url.includes('gstatic')) {\n link.crossOrigin = 'anonymous'\n }\n link.setAttribute('data-gds', 'font-preconnect')\n head.appendChild(link)\n }\n\n // stylesheet link\n const stylesheet = document.createElement('link')\n stylesheet.id = MARKER_ID\n stylesheet.rel = 'stylesheet'\n stylesheet.href = GOOGLE_FONTS_URL\n stylesheet.setAttribute('data-gds', 'cjk-fonts')\n head.appendChild(stylesheet)\n }, [])\n}\n"],"mappings":";;;AAMA,IAAM,IACJ,yLAEI,IAAkB,CACtB,gCACA,4BACD,EAEK,IAAY;AAGlB,SAAgB,IAAiB;AAC/B,SAAgB;AAGd,MAFI,OAAO,WAAa,OAEpB,SAAS,eAAe,EAAU,KAAK,KAAM;EAEjD,IAAM,IAAO,SAAS;AAGtB,OAAK,IAAM,KAAO,GAAiB;GACjC,IAAM,IAAO,SAAS,cAAc,OAAO;AAO3C,GANA,EAAK,MAAM,cACX,EAAK,OAAO,GACR,EAAI,SAAS,UAAU,KACzB,EAAK,cAAc,cAErB,EAAK,aAAa,YAAY,kBAAkB,EAChD,EAAK,YAAY,EAAK;;EAIxB,IAAM,IAAa,SAAS,cAAc,OAAO;AAKjD,EAJA,EAAW,KAAK,GAChB,EAAW,MAAM,cACjB,EAAW,OAAO,GAClB,EAAW,aAAa,YAAY,YAAY,EAChD,EAAK,YAAY,EAAW;IAC3B,EAAE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-theme-D_THp_K2.js","names":[],"sources":["../src/l1-systems/theme.ts","../src/l1-systems/use-theme.ts"],"sourcesContent":["// L1 — Theme System\n// manages color preset + 5 dimensional axes + dark/light mode\n// applies CSS variable overrides to document root\n// persists to localStorage, syncs across tabs\n\nimport { atom } from 'jotai'\n\nimport {\n deriveDarkPalette,\n deriveLightPalette,\n paletteToVars,\n} from '../l0-tokens/color-derive'\nimport { fontToCssVars } from '../l0-tokens/font-system'\nimport { DEFAULT_PRIMARY } from '../l0-tokens/generate-defaults'\nimport type {\n ThemeDensity,\n ThemeElevation,\n ThemeGlass,\n ThemeMotion,\n ThemeShape,\n} from '../l0-tokens/scales'\nimport { resolveAxesToCssVars } from '../l0-tokens/scales'\n\n// color overrides — per-token overrides for advanced users\nexport type ThemeColorOverrides = {\n '--gds-accent': string\n '--gds-accent-fg': string\n '--gds-accent-hover': string\n '--gds-danger': string\n '--gds-success': string\n '--gds-warning': string\n}\n\nexport type ThemeMode = 'dark' | 'light' | 'system'\n\n// full theme state — what the user has configured\nexport type ThemeState = {\n mode: ThemeMode\n primaryColor: string // single source — everything derived from this\n presetId: string // for UI display only (\"default\", \"teal\", \"amber\"...)\n // dimensional axes — each constrained to L0 scale options\n shape: ThemeShape\n density: ThemeDensity\n elevation: ThemeElevation\n glass: ThemeGlass\n motion: ThemeMotion\n // optional per-token color overrides (advanced — overrides derivation)\n colorOverrides: Partial<ThemeColorOverrides> | null\n}\n\n// default theme — beautiful out of the box\nexport const DEFAULT_THEME: ThemeState = {\n mode: 'system',\n primaryColor: DEFAULT_PRIMARY,\n presetId: 'default',\n shape: 'default',\n density: 'default',\n elevation: 'raised',\n glass: 'full',\n motion: 'full',\n colorOverrides: null,\n}\n\n// persistence — must be defined before themeAtom so atom init can load from localStorage\nconst STORAGE_KEY = 'gds-theme'\n\nexport function persistTheme(state: ThemeState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n } catch {\n // storage full or unavailable\n }\n}\n\nexport function loadPersistedTheme(): ThemeState | null {\n try {\n if (typeof window === 'undefined') return null\n const raw = localStorage.getItem(STORAGE_KEY)\n if (raw === null) return null\n const parsed = JSON.parse(raw) as Partial<ThemeState>\n // validate and merge with defaults to handle schema evolution\n return {\n ...DEFAULT_THEME,\n ...parsed,\n // validate primaryColor is a hex string\n primaryColor:\n typeof parsed.primaryColor === 'string' &&\n /^#[0-9a-fA-F]{6}$/.test(parsed.primaryColor)\n ? parsed.primaryColor\n : DEFAULT_THEME.primaryColor,\n // ensure constrained values are valid\n shape: validateOption(\n parsed.shape,\n ['sharp', 'default', 'rounded'],\n DEFAULT_THEME.shape\n ),\n density: validateOption(\n parsed.density,\n ['compact', 'default', 'comfortable'],\n DEFAULT_THEME.density\n ),\n elevation: validateOption(\n parsed.elevation,\n ['flat', 'subtle', 'raised'],\n DEFAULT_THEME.elevation\n ),\n glass: validateOption(\n parsed.glass,\n ['off', 'subtle', 'full'],\n DEFAULT_THEME.glass\n ),\n motion: validateOption(\n parsed.motion,\n ['off', 'reduced', 'full'],\n DEFAULT_THEME.motion\n ),\n mode: validateOption(\n parsed.mode,\n ['light', 'dark', 'system'],\n DEFAULT_THEME.mode\n ),\n }\n } catch {\n return null\n }\n}\n\nfunction validateOption<T extends string>(\n value: unknown,\n options: T[],\n fallback: T\n): T {\n if (typeof value === 'string' && options.includes(value as T)) {\n return value as T\n }\n return fallback\n}\n\n// jotai atoms — reactive theme state\n// initialize from localStorage to avoid race condition with useThemeEffect\nexport const themeAtom = atom<ThemeState>(loadPersistedTheme() ?? DEFAULT_THEME)\n\nexport const resolvedModeAtom = atom<'dark' | 'light'>((get) => {\n const { mode } = get(themeAtom)\n if (mode !== 'system') return mode\n if (typeof window === 'undefined') return 'dark'\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n})\n\n// resolve theme state → flat CSS variable overrides\n// all colors derived from primaryColor via L0 functions — no manual color presets\nexport function resolveThemeCssVars(\n state: ThemeState,\n resolvedMode: 'dark' | 'light'\n): Record<string, string> {\n // 1. derive colors from primaryColor\n const palette =\n resolvedMode === 'dark'\n ? deriveDarkPalette(state.primaryColor)\n : deriveLightPalette(state.primaryColor)\n const vars: Record<string, string> = {\n ...paletteToVars(palette, resolvedMode),\n }\n\n // 2. font stacks + weights\n Object.assign(vars, fontToCssVars())\n\n // 3. dimensional axes — computed by L0 system functions\n Object.assign(\n vars,\n resolveAxesToCssVars(\n state.shape,\n state.density,\n state.elevation,\n state.glass,\n state.motion,\n resolvedMode\n )\n )\n\n // 4. per-token color overrides (highest priority — advanced users)\n if (state.colorOverrides !== null) {\n for (const [key, val] of Object.entries(state.colorOverrides)) {\n if (val !== undefined) {\n vars[key] = val\n }\n }\n }\n\n return vars\n}\n\n// apply resolved vars to document\nexport function applyThemeToDocument(\n vars: Record<string, string>,\n resolvedMode: 'dark' | 'light',\n previousKeys?: string[]\n): string[] {\n const root = document.documentElement\n\n // clear previous overrides\n if (previousKeys !== undefined) {\n for (const key of previousKeys) {\n root.style.removeProperty(key)\n }\n }\n\n // set mode attribute\n const mode = root.getAttribute('data-theme-mode')\n if (mode !== resolvedMode) {\n root.setAttribute('data-theme-mode', resolvedMode)\n }\n\n // apply new overrides\n const keys = Object.keys(vars)\n for (const [key, val] of Object.entries(vars)) {\n root.style.setProperty(key, val)\n }\n\n return keys\n}\n\n// named theme presets — optimized axis combinations for specific application types\nexport type ThemePreset = Omit<\n ThemeState,\n 'mode' | 'presetId' | 'colorOverrides'\n>\n\nexport const themePresets = {\n // default: balanced for general-purpose dashboards\n default: {\n primaryColor: DEFAULT_PRIMARY,\n shape: 'default' as const,\n density: 'default' as const,\n elevation: 'raised' as const,\n glass: 'full' as const,\n motion: 'full' as const,\n },\n // email: optimized for email/productivity apps (mailrs-proven)\n // comfortable density for readable 14px base, subtle elevation for clean modern look\n email: {\n primaryColor: '#3b7ddd', // slightly desaturated blue, validated in production\n shape: 'default' as const,\n density: 'comfortable' as const,\n elevation: 'subtle' as const,\n glass: 'subtle' as const,\n motion: 'full' as const,\n },\n // dashboard: data-dense monitoring/analytics\n dashboard: {\n primaryColor: DEFAULT_PRIMARY,\n shape: 'default' as const,\n density: 'compact' as const,\n elevation: 'subtle' as const,\n glass: 'off' as const,\n motion: 'full' as const,\n },\n} as const satisfies Record<string, ThemePreset>\n\nexport type ThemePresetId = keyof typeof themePresets\n","// L1 — theme hooks\n// components use these to read/write theme state\n// all changes go through constrained API — no raw CSS manipulation\n\nimport { useAtom, useAtomValue } from 'jotai'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport type {\n ThemeDensity,\n ThemeElevation,\n ThemeGlass,\n ThemeMotion,\n ThemeShape,\n} from '../l0-tokens/scales'\nimport type { ThemeColorOverrides, ThemeMode, ThemeState } from './theme'\nimport {\n applyThemeToDocument,\n DEFAULT_THEME,\n persistTheme,\n resolvedModeAtom,\n resolveThemeCssVars,\n themeAtom,\n} from './theme'\n\n// color presets — app registers named presets, each is just a primaryColor\ntype ThemeConfig = {\n colorPresets: Record<string, { primaryColor: string }>\n}\n\nlet themeConfig: ThemeConfig = { colorPresets: {} }\n\n// called once at app init — register named color presets\nexport function configureTheme(config: ThemeConfig): void {\n themeConfig = config\n}\n\n// read current theme state (reactive)\nexport function useTheme(): ThemeState {\n return useAtomValue(themeAtom)\n}\n\n// read resolved dark/light mode (reactive)\nexport function useResolvedMode(): 'dark' | 'light' {\n return useAtomValue(resolvedModeAtom)\n}\n\n// theme mutation hooks — each returns a setter for one axis\nexport function useSetThemeMode(): (mode: ThemeMode) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (mode: ThemeMode) => {\n setTheme((prev) => ({ ...prev, mode }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemePreset(): (presetId: string) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (presetId: string) => {\n const preset = themeConfig.colorPresets[presetId]\n const primaryColor = preset?.primaryColor ?? DEFAULT_THEME.primaryColor\n setTheme((prev) => ({\n ...prev,\n presetId,\n primaryColor,\n colorOverrides: null,\n }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemePrimaryColor(): (color: string) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (primaryColor: string) => {\n setTheme((prev) => ({ ...prev, primaryColor, colorOverrides: null }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeShape(): (shape: ThemeShape) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (shape: ThemeShape) => {\n setTheme((prev) => ({ ...prev, shape }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeDensity(): (density: ThemeDensity) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (density: ThemeDensity) => {\n setTheme((prev) => ({ ...prev, density }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeElevation(): (elevation: ThemeElevation) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (elevation: ThemeElevation) => {\n setTheme((prev) => ({ ...prev, elevation }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeGlass(): (glass: ThemeGlass) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (glass: ThemeGlass) => {\n setTheme((prev) => ({ ...prev, glass }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeMotion(): (motion: ThemeMotion) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (motion: ThemeMotion) => {\n setTheme((prev) => ({ ...prev, motion }))\n },\n [setTheme]\n )\n}\n\nexport function useSetThemeColors(): (\n overrides: Partial<ThemeColorOverrides> | null\n) => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(\n (colorOverrides: Partial<ThemeColorOverrides> | null) => {\n setTheme((prev) => ({ ...prev, colorOverrides }))\n },\n [setTheme]\n )\n}\n\nexport function useResetTheme(): () => void {\n const [, setTheme] = useAtom(themeAtom)\n return useCallback(() => {\n setTheme(DEFAULT_THEME)\n }, [setTheme])\n}\n\n// side-effect hook: apply theme to DOM + persist + listen to system changes\n// call this ONCE in the app root\nexport function useThemeEffect(): void {\n const theme = useAtomValue(themeAtom)\n const resolvedMode = useAtomValue(resolvedModeAtom)\n const prevKeysRef = useRef<string[]>([])\n\n // apply to DOM whenever theme changes\n useEffect(() => {\n const vars = resolveThemeCssVars(theme, resolvedMode)\n prevKeysRef.current = applyThemeToDocument(\n vars,\n resolvedMode,\n prevKeysRef.current\n )\n persistTheme(theme)\n }, [theme, resolvedMode])\n\n // listen for system theme preference changes\n useEffect(() => {\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = () => {\n // trigger re-render via resolvedModeAtom re-evaluation\n // jotai derived atoms auto-recompute, but we need to force it\n // by touching the base atom (no-op write)\n }\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [])\n}\n"],"mappings":"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"}