@fayz-ai/ui 0.9.0 → 0.10.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.
Files changed (156) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics/AnalyticsCard.d.ts +10 -0
  3. package/dist/analytics/AnalyticsCard.d.ts.map +1 -0
  4. package/dist/analytics/AnalyticsControls.d.ts +18 -0
  5. package/dist/analytics/AnalyticsControls.d.ts.map +1 -0
  6. package/dist/analytics/AnalyticsGrid.d.ts +16 -0
  7. package/dist/analytics/AnalyticsGrid.d.ts.map +1 -0
  8. package/dist/analytics/context.d.ts +58 -0
  9. package/dist/analytics/context.d.ts.map +1 -0
  10. package/dist/analytics/derive.d.ts +81 -0
  11. package/dist/analytics/derive.d.ts.map +1 -0
  12. package/dist/analytics/index.d.ts +13 -0
  13. package/dist/analytics/index.d.ts.map +1 -0
  14. package/dist/analytics/range.d.ts +22 -0
  15. package/dist/analytics/range.d.ts.map +1 -0
  16. package/dist/{chunk-PVIB724L.js → chunk-22EVFMS3.js} +5 -3
  17. package/dist/chunk-22EVFMS3.js.map +1 -0
  18. package/dist/{chunk-G6OKZ7WL.js → chunk-6V5MGYXH.js} +13 -9
  19. package/dist/chunk-6V5MGYXH.js.map +1 -0
  20. package/dist/{chunk-KV3MHB6N.js → chunk-H63TOSQ7.js} +196 -53
  21. package/dist/chunk-H63TOSQ7.js.map +1 -0
  22. package/dist/{chunk-OUPJDGKE.js → chunk-I6CPKAOD.js} +134 -37
  23. package/dist/chunk-I6CPKAOD.js.map +1 -0
  24. package/dist/{chunk-YRVE4FOD.js → chunk-OKZ6WQB3.js} +3 -3
  25. package/dist/chunk-OKZ6WQB3.js.map +1 -0
  26. package/dist/{chunk-S5KNM4D2.js → chunk-SEIY2F5F.js} +235 -107
  27. package/dist/chunk-SEIY2F5F.js.map +1 -0
  28. package/dist/{chunk-75EFDLST.js → chunk-VAKWUUVC.js} +157 -6
  29. package/dist/chunk-VAKWUUVC.js.map +1 -0
  30. package/dist/dashboard/OnboardingWidget.d.ts +2 -14
  31. package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
  32. package/dist/dashboard/SidebarOnboarding.d.ts +28 -0
  33. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -0
  34. package/dist/dashboard/index.d.ts +2 -0
  35. package/dist/dashboard/index.d.ts.map +1 -1
  36. package/dist/dashboard/index.js +4 -4
  37. package/dist/dashboard/types.d.ts +18 -0
  38. package/dist/dashboard/types.d.ts.map +1 -1
  39. package/dist/dashboard/useOnboardingProgress.d.ts +28 -0
  40. package/dist/dashboard/useOnboardingProgress.d.ts.map +1 -0
  41. package/dist/icons.js +1 -1
  42. package/dist/index.d.ts +5 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +960 -14
  45. package/dist/index.js.map +1 -1
  46. package/dist/layout/AppShell.d.ts +14 -1
  47. package/dist/layout/AppShell.d.ts.map +1 -1
  48. package/dist/layout/ModulePage.d.ts +11 -2
  49. package/dist/layout/ModulePage.d.ts.map +1 -1
  50. package/dist/layout/Sidebar.d.ts +8 -1
  51. package/dist/layout/Sidebar.d.ts.map +1 -1
  52. package/dist/layout/index.js +4 -4
  53. package/dist/lib/cn.d.ts.map +1 -0
  54. package/dist/preset.d.ts +3 -0
  55. package/dist/preset.d.ts.map +1 -0
  56. package/dist/primitives/data-table.d.ts +19 -4
  57. package/dist/primitives/data-table.d.ts.map +1 -1
  58. package/dist/primitives/date-picker.d.ts +4 -2
  59. package/dist/primitives/date-picker.d.ts.map +1 -1
  60. package/dist/primitives/filter-chip.d.ts +26 -0
  61. package/dist/primitives/filter-chip.d.ts.map +1 -0
  62. package/dist/primitives/header-tabs.d.ts +32 -0
  63. package/dist/primitives/header-tabs.d.ts.map +1 -0
  64. package/dist/primitives/hint.d.ts +25 -0
  65. package/dist/primitives/hint.d.ts.map +1 -0
  66. package/dist/primitives/index.d.ts +7 -0
  67. package/dist/primitives/index.d.ts.map +1 -1
  68. package/dist/primitives/index.js +4 -4
  69. package/dist/primitives/list-view.d.ts +14 -1
  70. package/dist/primitives/list-view.d.ts.map +1 -1
  71. package/dist/primitives/page-header.d.ts +23 -0
  72. package/dist/primitives/page-header.d.ts.map +1 -0
  73. package/dist/primitives/stat-tiles.d.ts +30 -0
  74. package/dist/primitives/stat-tiles.d.ts.map +1 -0
  75. package/dist/primitives/switch.d.ts +11 -0
  76. package/dist/primitives/switch.d.ts.map +1 -0
  77. package/dist/stores/index.d.ts +1 -0
  78. package/dist/stores/index.d.ts.map +1 -1
  79. package/dist/stores/page-header.store.d.ts +69 -0
  80. package/dist/stores/page-header.store.d.ts.map +1 -0
  81. package/package.json +4 -13
  82. package/dist/chunk-75EFDLST.js.map +0 -1
  83. package/dist/chunk-G6OKZ7WL.js.map +0 -1
  84. package/dist/chunk-KV3MHB6N.js.map +0 -1
  85. package/dist/chunk-OUPJDGKE.js.map +0 -1
  86. package/dist/chunk-PVIB724L.js.map +0 -1
  87. package/dist/chunk-S5KNM4D2.js.map +0 -1
  88. package/dist/chunk-YRVE4FOD.js.map +0 -1
  89. package/dist/utils/cn.d.ts.map +0 -1
  90. package/src/dashboard/ChartWidget.tsx +0 -50
  91. package/src/dashboard/ChartWidgetInner.tsx +0 -83
  92. package/src/dashboard/DashboardCanvas.tsx +0 -210
  93. package/src/dashboard/DashboardCustomizeMenu.tsx +0 -53
  94. package/src/dashboard/DashboardGrid.tsx +0 -35
  95. package/src/dashboard/DashboardRange.tsx +0 -62
  96. package/src/dashboard/GoldCard.tsx +0 -38
  97. package/src/dashboard/KpiCard.tsx +0 -238
  98. package/src/dashboard/OnboardingWidget.tsx +0 -140
  99. package/src/dashboard/TableWidget.tsx +0 -51
  100. package/src/dashboard/define.ts +0 -53
  101. package/src/dashboard/icon.ts +0 -13
  102. package/src/dashboard/index.ts +0 -38
  103. package/src/dashboard/layout.ts +0 -50
  104. package/src/dashboard/preferences.ts +0 -71
  105. package/src/dashboard/types.ts +0 -41
  106. package/src/icons.ts +0 -66
  107. package/src/index.ts +0 -238
  108. package/src/layout/AppShell.tsx +0 -987
  109. package/src/layout/ContentSplit.tsx +0 -153
  110. package/src/layout/ModulePage.tsx +0 -466
  111. package/src/layout/PageTransition.tsx +0 -57
  112. package/src/layout/SaveBar.tsx +0 -247
  113. package/src/layout/Sidebar.tsx +0 -535
  114. package/src/layout/Topbar.tsx +0 -462
  115. package/src/layout/index.ts +0 -39
  116. package/src/layout/useCrossModuleBack.ts +0 -30
  117. package/src/primitives/Avatar.tsx +0 -48
  118. package/src/primitives/SearchCombobox.tsx +0 -216
  119. package/src/primitives/badge.tsx +0 -68
  120. package/src/primitives/breadcrumb.tsx +0 -43
  121. package/src/primitives/button.tsx +0 -60
  122. package/src/primitives/card.tsx +0 -79
  123. package/src/primitives/checkbox.tsx +0 -53
  124. package/src/primitives/confirm-dialog.tsx +0 -125
  125. package/src/primitives/currency-input.tsx +0 -121
  126. package/src/primitives/data-table.tsx +0 -160
  127. package/src/primitives/date-picker.tsx +0 -225
  128. package/src/primitives/dropdown.tsx +0 -196
  129. package/src/primitives/index.ts +0 -87
  130. package/src/primitives/input.tsx +0 -50
  131. package/src/primitives/list-view.tsx +0 -252
  132. package/src/primitives/markdown-editor.tsx +0 -75
  133. package/src/primitives/markdown.tsx +0 -169
  134. package/src/primitives/modal.tsx +0 -156
  135. package/src/primitives/phone-link.tsx +0 -138
  136. package/src/primitives/popover.tsx +0 -30
  137. package/src/primitives/search-select.tsx +0 -314
  138. package/src/primitives/segmented-control.tsx +0 -66
  139. package/src/primitives/select.tsx +0 -157
  140. package/src/primitives/separator.tsx +0 -29
  141. package/src/primitives/sheet.tsx +0 -181
  142. package/src/primitives/skeleton.tsx +0 -46
  143. package/src/primitives/tabs.tsx +0 -53
  144. package/src/primitives/time-picker.tsx +0 -148
  145. package/src/primitives/toast.tsx +0 -46
  146. package/src/primitives/tooltip.tsx +0 -28
  147. package/src/stores/index.ts +0 -10
  148. package/src/stores/layout.store.ts +0 -31
  149. package/src/stores/theme.store.ts +0 -457
  150. package/src/styles.css +0 -461
  151. package/src/tailwind.ts +0 -43
  152. package/src/theme/index.ts +0 -94
  153. package/src/theme/preset.ts +0 -209
  154. package/src/theme/presets.ts +0 -106
  155. package/src/utils/cn.ts +0 -6
  156. /package/dist/{utils → lib}/cn.d.ts +0 -0
@@ -1,457 +0,0 @@
1
- import { create, type StoreApi, type UseBoundStore } from 'zustand'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Theme token types
5
- // ---------------------------------------------------------------------------
6
-
7
- export interface SemanticColors {
8
- primary: string
9
- primaryForeground: string
10
- secondary: string
11
- secondaryForeground: string
12
- accent: string
13
- accentForeground: string
14
- background: string
15
- foreground: string
16
- card: string
17
- cardForeground: string
18
- muted: string
19
- mutedForeground: string
20
- border: string
21
- input: string
22
- ring: string
23
- popover: string
24
- popoverForeground: string
25
- destructive: string
26
- destructiveForeground: string
27
- success: string
28
- successForeground: string
29
- warning: string
30
- warningForeground: string
31
- // Layout surface colors
32
- sidebar: string
33
- sidebarForeground: string
34
- sidebarBorder: string
35
- sidebarAccent: string
36
- sidebarAccentForeground: string
37
- sidebarMuted: string
38
- content: string
39
- }
40
-
41
- export interface UIPerceptionTokens {
42
- buttonRadius: string
43
- cardRadius: string
44
- inputRadius: string
45
- modalRadius: string
46
- fontFamily: string
47
- fontFamilyMono: string
48
- shadowSm: string
49
- shadowMd: string
50
- shadowLg: string
51
- shadowButton?: string
52
- shadowButtonPrimary?: string
53
- shadowButtonInset?: string
54
- surfaceBackdropFilter?: string
55
- modalBackground?: string
56
- modalBorder?: string
57
- modalOverlayBackground?: string
58
- modalOverlayBackdropFilter?: string
59
- modalShadow?: string
60
- glassEdgeGradient?: string
61
- glassPrimaryEdgeGradient?: string
62
- glassInnerHighlight?: string
63
- fieldBackground?: string
64
- fieldBorder?: string
65
- fieldShadow?: string
66
- buttonBackground?: string
67
- buttonBackgroundHover?: string
68
- buttonBorder?: string
69
- buttonPrimaryBackground?: string
70
- buttonPrimaryBackgroundHover?: string
71
- buttonPrimaryBorder?: string
72
- buttonBackdropFilter?: string
73
- }
74
-
75
- export interface ThemeTokens {
76
- name: string
77
- colors: SemanticColors
78
- perception: UIPerceptionTokens
79
- }
80
-
81
- export interface CreateThemeOptions {
82
- name?: string
83
- colors?: Partial<SemanticColors>
84
- /** Dark mode color overrides — applied instead of `colors` when in dark mode */
85
- darkColors?: Partial<SemanticColors>
86
- perception?: Partial<UIPerceptionTokens>
87
- /** Shorthand: single HSL value that auto-derives primary, ring, and accent */
88
- brand?: string
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // Built-in themes
93
- // ---------------------------------------------------------------------------
94
-
95
- export const lightTheme: ThemeTokens = {
96
- name: 'light',
97
- colors: {
98
- primary: '0 0% 18.8%',
99
- primaryForeground: '0 0% 100%',
100
- secondary: '0 0% 96.9%',
101
- secondaryForeground: '0 0% 18.8%',
102
- accent: '0 0% 94.5%',
103
- accentForeground: '0 0% 18.8%',
104
- background: '0 0% 94.5%',
105
- foreground: '0 0% 18.8%',
106
- card: '0 0% 100%',
107
- cardForeground: '0 0% 18.8%',
108
- muted: '0 0% 96.9%',
109
- mutedForeground: '0 0% 38%',
110
- border: '0 0% 89%',
111
- input: '0 0% 54.1%',
112
- ring: '214 100% 41.4%',
113
- popover: '0 0% 100%',
114
- popoverForeground: '0 0% 18.8%',
115
- destructive: '354 92% 41%',
116
- destructiveForeground: '0 0% 100%',
117
- success: '167 95% 25.1%',
118
- successForeground: '0 0% 100%',
119
- warning: '43 100% 50%',
120
- warningForeground: '0 0% 18.8%',
121
- sidebar: '0 0% 92.2%',
122
- sidebarForeground: '0 0% 18.8%',
123
- sidebarBorder: '0 0% 86.3%',
124
- sidebarAccent: '0 0% 100%',
125
- sidebarAccentForeground: '0 0% 18.8%',
126
- sidebarMuted: '0 0% 38%',
127
- content: '0 0% 94.5%',
128
- },
129
- perception: {
130
- buttonRadius: '0.5rem',
131
- cardRadius: '0.5rem',
132
- inputRadius: '0.5rem',
133
- modalRadius: '1rem',
134
- fontFamily: 'Inter, ui-sans-serif, system-ui, -apple-system, sans-serif',
135
- fontFamilyMono: 'JetBrains Mono, ui-monospace, SFMono-Regular, monospace',
136
- shadowSm: '0 1px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(26, 26, 26, 0.06), 0 1px 2px -1px rgba(26, 26, 26, 0.06)',
137
- shadowMd: '0 1px 1px -1px rgba(26, 26, 26, 0.07), 0 4px 8px -2px rgba(26, 26, 26, 0.10)',
138
- shadowLg: '0 4px 12px -2px rgba(26, 26, 26, 0.15), 0 1px 3px rgba(26, 26, 26, 0.08)',
139
- shadowButton:
140
- 'inset 0 -1px 0 rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.50), 0 1px 0 rgba(0,0,0,0.05)',
141
- shadowButtonPrimary:
142
- 'inset 0 -1px 0 rgba(0,0,0,0.20), inset 0 1px 0 rgba(255,255,255,0.20), 0 1px 1.5px rgba(0,0,0,0.20)',
143
- shadowButtonInset:
144
- 'inset 0 2px 1px -1px rgba(0,0,0,0.20), inset 0 0 0 1px rgba(0,0,0,0.05)',
145
- surfaceBackdropFilter: 'none',
146
- buttonBackground: 'hsl(var(--card))',
147
- buttonBackgroundHover: 'hsl(var(--muted))',
148
- buttonBorder: 'hsl(var(--border))',
149
- buttonPrimaryBackground: 'hsl(var(--primary))',
150
- buttonPrimaryBackgroundHover: 'hsl(var(--primary) / 0.9)',
151
- buttonPrimaryBorder: 'hsl(var(--primary))',
152
- buttonBackdropFilter: 'none',
153
- },
154
- }
155
-
156
- export const darkTheme: ThemeTokens = {
157
- name: 'dark',
158
- colors: {
159
- primary: '0 0% 100%',
160
- primaryForeground: '0 0% 10.2%',
161
- secondary: '0 0% 18.8%',
162
- secondaryForeground: '0 0% 89%',
163
- accent: '0 0% 22.7%',
164
- accentForeground: '0 0% 100%',
165
- background: '0 0% 10.2%',
166
- foreground: '0 0% 89%',
167
- card: '0 0% 14.9%',
168
- cardForeground: '0 0% 89%',
169
- muted: '0 0% 18.8%',
170
- mutedForeground: '0 0% 64.7%',
171
- border: '0 0% 22.7%',
172
- input: '0 0% 32.2%',
173
- ring: '217 100% 68.4%',
174
- popover: '0 0% 18.8%',
175
- popoverForeground: '0 0% 89%',
176
- destructive: '7 100% 44.9%',
177
- destructiveForeground: '0 0% 100%',
178
- success: '156 71% 42.5%',
179
- successForeground: '0 0% 0%',
180
- warning: '41 100% 65.1%',
181
- warningForeground: '0 0% 10.2%',
182
- sidebar: '0 0% 10.2%',
183
- sidebarForeground: '0 0% 89%',
184
- sidebarBorder: '0 0% 16.5%',
185
- sidebarAccent: '0 0% 18.8%',
186
- sidebarAccentForeground: '0 0% 100%',
187
- sidebarMuted: '0 0% 64.7%',
188
- content: '0 0% 10.2%',
189
- },
190
- perception: {
191
- buttonRadius: '0.5rem',
192
- cardRadius: '0.5rem',
193
- inputRadius: '0.5rem',
194
- modalRadius: '1rem',
195
- fontFamily: 'Inter, ui-sans-serif, system-ui, -apple-system, sans-serif',
196
- fontFamilyMono: 'JetBrains Mono, ui-monospace, SFMono-Regular, monospace',
197
- shadowSm: '0 1px 0 rgba(0, 0, 0, 0.4), 0 1px 3px 0 rgba(0, 0, 0, 0.35), 0 1px 2px -1px rgba(0, 0, 0, 0.35)',
198
- shadowMd: '0 1px 1px -1px rgba(0, 0, 0, 0.5), 0 4px 8px -2px rgba(0, 0, 0, 0.6)',
199
- shadowLg: '0 4px 12px -2px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.4)',
200
- shadowButton:
201
- 'inset 0 -1px 0 rgba(0,0,0,0.40), inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 0 rgba(0,0,0,0.30)',
202
- shadowButtonPrimary:
203
- 'inset 0 -1px 0 rgba(0,0,0,0.20), inset 0 1px 0 rgba(255,255,255,0.40), 0 1px 1.5px rgba(0,0,0,0.40)',
204
- shadowButtonInset:
205
- 'inset 0 2px 1px -1px rgba(0,0,0,0.50), inset 0 0 0 1px rgba(0,0,0,0.20)',
206
- surfaceBackdropFilter: 'none',
207
- buttonBackground: 'hsl(var(--card))',
208
- buttonBackgroundHover: 'hsl(var(--muted))',
209
- buttonBorder: 'hsl(var(--border))',
210
- buttonPrimaryBackground: 'hsl(var(--primary))',
211
- buttonPrimaryBackgroundHover: 'hsl(var(--primary) / 0.9)',
212
- buttonPrimaryBorder: 'hsl(var(--primary))',
213
- buttonBackdropFilter: 'none',
214
- },
215
- }
216
-
217
- // ---------------------------------------------------------------------------
218
- // CSS variable maps
219
- // ---------------------------------------------------------------------------
220
-
221
- const colorVarMap: Record<string, string> = {
222
- primary: '--primary',
223
- primaryForeground: '--primary-foreground',
224
- secondary: '--secondary',
225
- secondaryForeground: '--secondary-foreground',
226
- accent: '--accent',
227
- accentForeground: '--accent-foreground',
228
- background: '--background',
229
- foreground: '--foreground',
230
- card: '--card',
231
- cardForeground: '--card-foreground',
232
- muted: '--muted',
233
- mutedForeground: '--muted-foreground',
234
- border: '--border',
235
- input: '--input',
236
- ring: '--ring',
237
- popover: '--popover',
238
- popoverForeground: '--popover-foreground',
239
- destructive: '--destructive',
240
- destructiveForeground: '--destructive-foreground',
241
- success: '--success',
242
- successForeground: '--success-foreground',
243
- warning: '--warning',
244
- warningForeground: '--warning-foreground',
245
- sidebar: '--sidebar',
246
- sidebarForeground: '--sidebar-foreground',
247
- sidebarBorder: '--sidebar-border',
248
- sidebarAccent: '--sidebar-accent',
249
- sidebarAccentForeground: '--sidebar-accent-foreground',
250
- sidebarMuted: '--sidebar-muted',
251
- content: '--content',
252
- }
253
-
254
- const perceptionVarMap: Record<string, string> = {
255
- buttonRadius: '--button-radius',
256
- cardRadius: '--card-radius',
257
- inputRadius: '--input-radius',
258
- modalRadius: '--modal-radius',
259
- fontFamily: '--font-family',
260
- fontFamilyMono: '--font-family-mono',
261
- shadowSm: '--shadow-sm',
262
- shadowMd: '--shadow-md',
263
- shadowLg: '--shadow-lg',
264
- shadowButton: '--shadow-button',
265
- shadowButtonPrimary: '--shadow-button-primary',
266
- shadowButtonInset: '--shadow-button-inset',
267
- surfaceBackdropFilter: '--surface-backdrop-filter',
268
- modalBackground: '--modal-bg',
269
- modalBorder: '--modal-border',
270
- modalOverlayBackground: '--modal-overlay-bg',
271
- modalOverlayBackdropFilter: '--modal-overlay-backdrop-filter',
272
- modalShadow: '--modal-shadow',
273
- glassEdgeGradient: '--glass-edge-gradient',
274
- glassPrimaryEdgeGradient: '--glass-primary-edge-gradient',
275
- glassInnerHighlight: '--glass-inner-highlight',
276
- fieldBackground: '--field-bg',
277
- fieldBorder: '--field-border',
278
- fieldShadow: '--field-shadow',
279
- buttonBackground: '--button-bg',
280
- buttonBackgroundHover: '--button-hover-bg',
281
- buttonBorder: '--button-border',
282
- buttonPrimaryBackground: '--button-primary-bg',
283
- buttonPrimaryBackgroundHover: '--button-primary-hover-bg',
284
- buttonPrimaryBorder: '--button-primary-border',
285
- buttonBackdropFilter: '--button-backdrop-filter',
286
- }
287
-
288
- function applyTheme(theme: ThemeTokens, element?: HTMLElement): void {
289
- const target = element ?? document.documentElement
290
-
291
- for (const [key, cssVar] of Object.entries(colorVarMap)) {
292
- const value = theme.colors[key as keyof typeof theme.colors]
293
- if (value) {
294
- target.style.setProperty(cssVar, value)
295
- }
296
- }
297
-
298
- for (const [key, cssVar] of Object.entries(perceptionVarMap)) {
299
- const value = theme.perception[key as keyof typeof theme.perception]
300
- if (value) {
301
- target.style.setProperty(cssVar, value)
302
- }
303
- }
304
- }
305
-
306
- // ---------------------------------------------------------------------------
307
- // Store
308
- // ---------------------------------------------------------------------------
309
-
310
- type ThemeMode = 'light' | 'dark' | 'system'
311
-
312
- const STORAGE_KEY = 'fayz-ui:theme-mode'
313
-
314
- interface ThemeState {
315
- mode: ThemeMode
316
- resolvedMode: 'light' | 'dark'
317
- /** Only the user's partial overrides — NOT a full theme */
318
- overrides: CreateThemeOptions | null
319
- setMode: (mode: ThemeMode) => void
320
- setOverrides: (overrides: CreateThemeOptions | null) => void
321
- initialize: () => void
322
- // Keep old API name working
323
- setCustomTheme: (theme: ThemeTokens | null) => void
324
- }
325
-
326
- function getSystemPreference(): 'light' | 'dark' {
327
- if (typeof window === 'undefined') return 'light'
328
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
329
- }
330
-
331
- function getSavedMode(): ThemeMode {
332
- if (typeof window === 'undefined') return 'light'
333
- const saved = localStorage.getItem(STORAGE_KEY)
334
- if (saved === 'light' || saved === 'dark' || saved === 'system') return saved
335
- return 'light'
336
- }
337
-
338
- function resolveMode(mode: ThemeMode): 'light' | 'dark' {
339
- return mode === 'system' ? getSystemPreference() : mode
340
- }
341
-
342
- /** Merge base theme (light or dark) with user's partial overrides */
343
- function buildTheme(base: ThemeTokens, overrides: CreateThemeOptions | null): ThemeTokens {
344
- if (!overrides) return base
345
-
346
- const isDark = base.name === 'dark'
347
- const colors = { ...base.colors }
348
-
349
- // Brand shorthand: derive primary, ring, accent from single HSL
350
- if (overrides.brand) {
351
- colors.primary = overrides.brand
352
- colors.primaryForeground = '0 0% 100%'
353
- colors.ring = overrides.brand
354
- const parts = overrides.brand.split(' ')
355
- if (parts.length >= 3) {
356
- const hue = (parseFloat(parts[0]) - 50 + 360) % 360
357
- colors.accent = `${hue} ${parts[1]} ${parts[2]}`
358
- colors.accentForeground = '0 0% 100%'
359
- }
360
- }
361
-
362
- // Apply explicit color overrides.
363
- const colorOverrides = isDark && overrides.darkColors
364
- ? overrides.darkColors
365
- : overrides.colors
366
-
367
- if (colorOverrides) {
368
- for (const [key, value] of Object.entries(colorOverrides)) {
369
- if (value !== undefined) {
370
- (colors as any)[key] = value
371
- }
372
- }
373
- }
374
-
375
- return {
376
- name: overrides.name ?? base.name,
377
- colors,
378
- perception: {
379
- ...base.perception,
380
- ...(overrides.perception ?? {}),
381
- },
382
- }
383
- }
384
-
385
- const STORE_KEY = '__fayz_ui_theme_store__'
386
-
387
- function createThemeStore(): UseBoundStore<StoreApi<ThemeState>> {
388
- // Window-level singleton to survive linked-package dual-module loading
389
- if (typeof window !== 'undefined' && (window as any)[STORE_KEY]) {
390
- return (window as any)[STORE_KEY]
391
- }
392
-
393
- const store = create<ThemeState>((set, get) => ({
394
- mode: getSavedMode(),
395
- resolvedMode: 'light',
396
- overrides: null,
397
-
398
- setMode: (mode) => {
399
- const resolved = resolveMode(mode)
400
- set({ mode, resolvedMode: resolved })
401
-
402
- if (typeof window !== 'undefined') {
403
- localStorage.setItem(STORAGE_KEY, mode)
404
- }
405
-
406
- const { overrides } = get()
407
- const baseTheme = resolved === 'dark' ? darkTheme : lightTheme
408
- const theme = buildTheme(baseTheme, overrides)
409
-
410
- if (typeof document !== 'undefined') {
411
- document.documentElement.classList.add('theme-transitioning')
412
- applyTheme(theme)
413
- document.documentElement.classList.toggle('dark', resolved === 'dark')
414
- setTimeout(() => {
415
- document.documentElement.classList.remove('theme-transitioning')
416
- }, 450)
417
- } else {
418
- applyTheme(theme)
419
- }
420
- },
421
-
422
- setOverrides: (overrides) => {
423
- set({ overrides })
424
- const { resolvedMode } = get()
425
- const baseTheme = resolvedMode === 'dark' ? darkTheme : lightTheme
426
- const theme = buildTheme(baseTheme, overrides)
427
- applyTheme(theme)
428
- },
429
-
430
- // Legacy compat
431
- setCustomTheme: (_theme) => {
432
- // Ignore — callers should use setOverrides now
433
- },
434
-
435
- initialize: () => {
436
- const { mode, setMode } = get()
437
- setMode(mode)
438
-
439
- if (typeof window !== 'undefined') {
440
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
441
- const current = get()
442
- if (current.mode === 'system') {
443
- current.setMode('system')
444
- }
445
- })
446
- }
447
- },
448
- }))
449
-
450
- if (typeof window !== 'undefined') {
451
- (window as any)[STORE_KEY] = store
452
- }
453
-
454
- return store
455
- }
456
-
457
- export const useThemeStore = createThemeStore()