@opencosmos/ui 1.3.1

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 (260) hide show
  1. package/.claude/CLAUDE.md +239 -0
  2. package/README.md +161 -0
  3. package/dist/cli.mjs +151 -0
  4. package/dist/dates.d.mts +20 -0
  5. package/dist/dates.d.ts +20 -0
  6. package/dist/dates.js +240 -0
  7. package/dist/dates.js.map +1 -0
  8. package/dist/dates.mjs +203 -0
  9. package/dist/dates.mjs.map +1 -0
  10. package/dist/dnd.d.mts +126 -0
  11. package/dist/dnd.d.ts +126 -0
  12. package/dist/dnd.js +274 -0
  13. package/dist/dnd.js.map +1 -0
  14. package/dist/dnd.mjs +250 -0
  15. package/dist/dnd.mjs.map +1 -0
  16. package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
  17. package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
  18. package/dist/forms.d.mts +38 -0
  19. package/dist/forms.d.ts +38 -0
  20. package/dist/forms.js +198 -0
  21. package/dist/forms.js.map +1 -0
  22. package/dist/forms.mjs +159 -0
  23. package/dist/forms.mjs.map +1 -0
  24. package/dist/hooks-1b8WaQf1.d.mts +225 -0
  25. package/dist/hooks-CKW8vE9H.d.ts +225 -0
  26. package/dist/hooks.d.mts +3 -0
  27. package/dist/hooks.d.ts +3 -0
  28. package/dist/hooks.js +971 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/hooks.mjs +943 -0
  31. package/dist/hooks.mjs.map +1 -0
  32. package/dist/index-DscTIrZ2.d.mts +29 -0
  33. package/dist/index-DscTIrZ2.d.ts +29 -0
  34. package/dist/index.d.mts +3382 -0
  35. package/dist/index.d.ts +3382 -0
  36. package/dist/index.js +15146 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +14802 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/providers-CXPDMsl7.d.mts +30 -0
  41. package/dist/providers-Dn_Msjvz.d.ts +30 -0
  42. package/dist/providers.d.mts +3 -0
  43. package/dist/providers.d.ts +3 -0
  44. package/dist/providers.js +1885 -0
  45. package/dist/providers.js.map +1 -0
  46. package/dist/providers.mjs +1859 -0
  47. package/dist/providers.mjs.map +1 -0
  48. package/dist/tables.d.mts +10 -0
  49. package/dist/tables.d.ts +10 -0
  50. package/dist/tables.js +248 -0
  51. package/dist/tables.js.map +1 -0
  52. package/dist/tables.mjs +218 -0
  53. package/dist/tables.mjs.map +1 -0
  54. package/dist/tokens.d.mts +1065 -0
  55. package/dist/tokens.d.ts +1065 -0
  56. package/dist/tokens.js +2637 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/dist/tokens.mjs +2555 -0
  59. package/dist/tokens.mjs.map +1 -0
  60. package/dist/utils-CIIM7dAC.d.ts +986 -0
  61. package/dist/utils-Cs04sxth.d.mts +986 -0
  62. package/dist/utils.d.mts +4 -0
  63. package/dist/utils.d.ts +4 -0
  64. package/dist/utils.js +874 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/utils.mjs +806 -0
  67. package/dist/utils.mjs.map +1 -0
  68. package/dist/validation-Bj1ye-v_.d.mts +114 -0
  69. package/dist/validation-Bj1ye-v_.d.ts +114 -0
  70. package/dist/webgl.d.mts +104 -0
  71. package/dist/webgl.d.ts +104 -0
  72. package/dist/webgl.js +226 -0
  73. package/dist/webgl.js.map +1 -0
  74. package/dist/webgl.mjs +195 -0
  75. package/dist/webgl.mjs.map +1 -0
  76. package/package.json +267 -0
  77. package/src/cli.ts +206 -0
  78. package/src/component-registry.ts +183 -0
  79. package/src/components/actions/Button.test.tsx +61 -0
  80. package/src/components/actions/Button.tsx +70 -0
  81. package/src/components/actions/Link.tsx +78 -0
  82. package/src/components/actions/Magnetic.tsx +68 -0
  83. package/src/components/actions/Toggle.test.tsx +40 -0
  84. package/src/components/actions/Toggle.tsx +47 -0
  85. package/src/components/actions/ToggleGroup.tsx +70 -0
  86. package/src/components/actions/index.ts +5 -0
  87. package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
  88. package/src/components/backgrounds/OrbBackground.tsx +424 -0
  89. package/src/components/backgrounds/WarpBackground.tsx +358 -0
  90. package/src/components/backgrounds/index.ts +3 -0
  91. package/src/components/blocks/Hero.tsx +142 -0
  92. package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
  93. package/src/components/cursor/SplashCursor.tsx +1315 -0
  94. package/src/components/cursor/TargetCursor.tsx +187 -0
  95. package/src/components/cursor/index.ts +2 -0
  96. package/src/components/data-display/AspectImage.tsx +73 -0
  97. package/src/components/data-display/Avatar.test.tsx +35 -0
  98. package/src/components/data-display/Avatar.tsx +55 -0
  99. package/src/components/data-display/Badge.test.tsx +43 -0
  100. package/src/components/data-display/Badge.tsx +84 -0
  101. package/src/components/data-display/Brand.tsx +123 -0
  102. package/src/components/data-display/Calendar.tsx +70 -0
  103. package/src/components/data-display/Card.test.tsx +92 -0
  104. package/src/components/data-display/Card.tsx +115 -0
  105. package/src/components/data-display/Code.tsx +210 -0
  106. package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
  107. package/src/components/data-display/DataTable.tsx +119 -0
  108. package/src/components/data-display/DescriptionList.tsx +41 -0
  109. package/src/components/data-display/GitHubIcon.tsx +44 -0
  110. package/src/components/data-display/Heading.test.tsx +36 -0
  111. package/src/components/data-display/Heading.tsx +83 -0
  112. package/src/components/data-display/StatCard.tsx +195 -0
  113. package/src/components/data-display/Table.tsx +133 -0
  114. package/src/components/data-display/Text.test.tsx +48 -0
  115. package/src/components/data-display/Text.tsx +144 -0
  116. package/src/components/data-display/Timeline.tsx +194 -0
  117. package/src/components/data-display/TreeView.tsx +226 -0
  118. package/src/components/data-display/Typewriter.tsx +119 -0
  119. package/src/components/data-display/VariableWeightText.tsx +130 -0
  120. package/src/components/data-display/index.ts +19 -0
  121. package/src/components/feedback/Alert.test.tsx +44 -0
  122. package/src/components/feedback/Alert.tsx +65 -0
  123. package/src/components/feedback/EmptyState.tsx +113 -0
  124. package/src/components/feedback/Progress.test.tsx +60 -0
  125. package/src/components/feedback/Progress.tsx +30 -0
  126. package/src/components/feedback/ProgressBar.tsx +158 -0
  127. package/src/components/feedback/Skeleton.test.tsx +39 -0
  128. package/src/components/feedback/Skeleton.tsx +45 -0
  129. package/src/components/feedback/Sonner.tsx +28 -0
  130. package/src/components/feedback/Spinner.test.tsx +33 -0
  131. package/src/components/feedback/Spinner.tsx +99 -0
  132. package/src/components/feedback/Stepper.tsx +307 -0
  133. package/src/components/feedback/Toast/Toast.tsx +243 -0
  134. package/src/components/feedback/Toast/index.ts +2 -0
  135. package/src/components/feedback/index.ts +9 -0
  136. package/src/components/forms/Checkbox.test.tsx +40 -0
  137. package/src/components/forms/Checkbox.tsx +31 -0
  138. package/src/components/forms/ColorPicker.tsx +118 -0
  139. package/src/components/forms/Combobox.tsx +96 -0
  140. package/src/components/forms/DragDrop.tsx +440 -0
  141. package/src/components/forms/FileUpload.tsx +252 -0
  142. package/src/components/forms/FilterButton.tsx +65 -0
  143. package/src/components/forms/Form.tsx +197 -0
  144. package/src/components/forms/Input.test.tsx +46 -0
  145. package/src/components/forms/Input.tsx +43 -0
  146. package/src/components/forms/InputOTP.tsx +81 -0
  147. package/src/components/forms/Label.test.tsx +20 -0
  148. package/src/components/forms/Label.tsx +25 -0
  149. package/src/components/forms/RadioGroup.tsx +51 -0
  150. package/src/components/forms/SearchBar.tsx +215 -0
  151. package/src/components/forms/Select.test.tsx +118 -0
  152. package/src/components/forms/Select.tsx +274 -0
  153. package/src/components/forms/Slider.tsx +29 -0
  154. package/src/components/forms/Switch.test.tsx +76 -0
  155. package/src/components/forms/Switch.tsx +30 -0
  156. package/src/components/forms/TextField.tsx +152 -0
  157. package/src/components/forms/Textarea.test.tsx +41 -0
  158. package/src/components/forms/Textarea.tsx +29 -0
  159. package/src/components/forms/ThemeSwitcher.tsx +290 -0
  160. package/src/components/forms/ThemeToggle.tsx +151 -0
  161. package/src/components/forms/index.ts +19 -0
  162. package/src/components/layout/Accordion.test.tsx +66 -0
  163. package/src/components/layout/Accordion.tsx +64 -0
  164. package/src/components/layout/AspectRatio.tsx +7 -0
  165. package/src/components/layout/Carousel.tsx +277 -0
  166. package/src/components/layout/Collapsible.test.tsx +40 -0
  167. package/src/components/layout/Collapsible.tsx +31 -0
  168. package/src/components/layout/Container.test.tsx +45 -0
  169. package/src/components/layout/Container.tsx +99 -0
  170. package/src/components/layout/CustomizerPanel.tsx +400 -0
  171. package/src/components/layout/DatePicker.tsx +57 -0
  172. package/src/components/layout/Footer/Footer.tsx +175 -0
  173. package/src/components/layout/Footer/index.ts +2 -0
  174. package/src/components/layout/GlassSurface.tsx +82 -0
  175. package/src/components/layout/Grid.test.tsx +31 -0
  176. package/src/components/layout/Grid.tsx +130 -0
  177. package/src/components/layout/Header/Header.tsx +450 -0
  178. package/src/components/layout/Header/index.ts +2 -0
  179. package/src/components/layout/PageLayout.tsx +180 -0
  180. package/src/components/layout/PageTemplate.tsx +158 -0
  181. package/src/components/layout/Resizable.tsx +48 -0
  182. package/src/components/layout/ScrollArea.tsx +53 -0
  183. package/src/components/layout/Separator.test.tsx +28 -0
  184. package/src/components/layout/Separator.tsx +29 -0
  185. package/src/components/layout/Sidebar.tsx +171 -0
  186. package/src/components/layout/Stack.test.tsx +41 -0
  187. package/src/components/layout/Stack.tsx +89 -0
  188. package/src/components/layout/glass-surface.css +60 -0
  189. package/src/components/layout/index.ts +18 -0
  190. package/src/components/motion/AnimatedBeam.tsx +159 -0
  191. package/src/components/navigation/Breadcrumb.test.tsx +57 -0
  192. package/src/components/navigation/Breadcrumb.tsx +119 -0
  193. package/src/components/navigation/Breadcrumbs.tsx +221 -0
  194. package/src/components/navigation/Command.tsx +159 -0
  195. package/src/components/navigation/Menubar.tsx +115 -0
  196. package/src/components/navigation/NavLink.tsx +55 -0
  197. package/src/components/navigation/NavigationMenu.tsx +125 -0
  198. package/src/components/navigation/Pagination.tsx +121 -0
  199. package/src/components/navigation/SecondaryNav.tsx +100 -0
  200. package/src/components/navigation/Tabs.test.tsx +47 -0
  201. package/src/components/navigation/Tabs.tsx +60 -0
  202. package/src/components/navigation/TertiaryNav.tsx +90 -0
  203. package/src/components/navigation/index.ts +10 -0
  204. package/src/components/overlays/AlertDialog.test.tsx +69 -0
  205. package/src/components/overlays/AlertDialog.tsx +166 -0
  206. package/src/components/overlays/ContextMenu.tsx +243 -0
  207. package/src/components/overlays/Dialog.test.tsx +79 -0
  208. package/src/components/overlays/Dialog.tsx +158 -0
  209. package/src/components/overlays/Drawer.tsx +128 -0
  210. package/src/components/overlays/Dropdown.tsx +253 -0
  211. package/src/components/overlays/DropdownMenu.tsx +242 -0
  212. package/src/components/overlays/HoverCard.tsx +32 -0
  213. package/src/components/overlays/Modal.tsx +250 -0
  214. package/src/components/overlays/NotificationCenter.tsx +364 -0
  215. package/src/components/overlays/Popover.test.tsx +40 -0
  216. package/src/components/overlays/Popover.tsx +46 -0
  217. package/src/components/overlays/Sheet.tsx +163 -0
  218. package/src/components/overlays/Tooltip.test.tsx +33 -0
  219. package/src/components/overlays/Tooltip.tsx +32 -0
  220. package/src/components/overlays/index.ts +12 -0
  221. package/src/dates.ts +2 -0
  222. package/src/dnd.ts +1 -0
  223. package/src/forms.ts +1 -0
  224. package/src/globals.css +187 -0
  225. package/src/hooks/index.ts +6 -0
  226. package/src/hooks/useForm.ts +247 -0
  227. package/src/hooks/useMotionPreference.test.ts +102 -0
  228. package/src/hooks/useMotionPreference.ts +78 -0
  229. package/src/hooks/useTheme.ts +58 -0
  230. package/src/hooks.ts +9 -0
  231. package/src/index.ts +168 -0
  232. package/src/lib/animations.ts +356 -0
  233. package/src/lib/breadcrumbs.ts +94 -0
  234. package/src/lib/colors.ts +493 -0
  235. package/src/lib/store/customizer.ts +482 -0
  236. package/src/lib/store/index.ts +3 -0
  237. package/src/lib/store/theme.ts +55 -0
  238. package/src/lib/syntax-parser/index.ts +50 -0
  239. package/src/lib/syntax-parser/patterns.ts +64 -0
  240. package/src/lib/syntax-parser/tokenizer.ts +117 -0
  241. package/src/lib/syntax-parser/types.ts +27 -0
  242. package/src/lib/utils.ts +6 -0
  243. package/src/lib/validation.ts +204 -0
  244. package/src/lib/webgl/Color.ts +11 -0
  245. package/src/lib/webgl/Mesh.ts +41 -0
  246. package/src/lib/webgl/Program.ts +118 -0
  247. package/src/lib/webgl/Renderer.ts +51 -0
  248. package/src/lib/webgl/Triangle.ts +27 -0
  249. package/src/lib/webgl/Vec3.ts +18 -0
  250. package/src/lib/webgl/index.ts +13 -0
  251. package/src/nativewind-env.d.ts +1 -0
  252. package/src/providers/ThemeProvider.tsx +461 -0
  253. package/src/providers/index.ts +1 -0
  254. package/src/providers.ts +7 -0
  255. package/src/tables.ts +1 -0
  256. package/src/test/setup.ts +39 -0
  257. package/src/theme.css +158 -0
  258. package/src/tokens.ts +7 -0
  259. package/src/utils.ts +12 -0
  260. package/src/webgl.ts +1 -0
@@ -0,0 +1,482 @@
1
+ import { create } from 'zustand';
2
+ import { persist } from 'zustand/middleware';
3
+ import { computeDerivedTokens, type FontTheme } from '@thesage/tokens';
4
+ import {
5
+ generateColorScale,
6
+ getOptimalForeground,
7
+ } from '../colors';
8
+
9
+ export type CustomizationMode = 'simple' | 'advanced';
10
+
11
+ export interface ColorPalette {
12
+ /* Metadata */
13
+ name?: string; // Name of the source palette
14
+ description?: string; // Description of the source palette
15
+
16
+ /* Colors */
17
+ primary: string; // Base hex
18
+ primaryForeground: string; // Calculated contrast
19
+ secondary?: string; // Optional secondary color
20
+ secondaryForeground?: string; // Calculated contrast
21
+ accent?: string; // Optional accent color
22
+ accentForeground?: string; // Calculated contrast
23
+ scale: Record<number, string>; // 50-900 tints/shades
24
+ derivedTokens: Record<string, string>; // Computed dependent tokens
25
+ }
26
+
27
+ export interface SavedPalette {
28
+ id: string; // Unique ID
29
+ name: string; // User-defined name
30
+ description: string; // Palette description
31
+ primary: string; // Primary color hex
32
+ accent: string; // Accent color hex
33
+ secondary?: string; // Optional secondary color
34
+ category: 'custom'; // Always 'custom' for user palettes
35
+ wcagAA: boolean; // Calculated accessibility
36
+ wcagAAA: boolean; // Calculated accessibility
37
+ createdAt: number; // Timestamp
38
+ mood: string[]; // Mood tags
39
+ bestFor?: string[]; // Best use cases
40
+ harmony?: string;
41
+ rationale?: string;
42
+ }
43
+
44
+ export interface SavedFontTheme extends FontTheme {
45
+ id: string; // Unique ID (overrides FontTheme.id)
46
+ createdAt: number; // Timestamp
47
+ category: 'custom'; // Always 'custom' for user font themes
48
+ }
49
+
50
+ export interface ColorCustomization {
51
+ mode: CustomizationMode;
52
+ palette: ColorPalette | null;
53
+ }
54
+
55
+ export type ThemeName = 'studio' | 'terra' | 'volt' | 'speedboat';
56
+ export type ColorMode = 'light' | 'dark';
57
+
58
+ interface CustomizerState {
59
+ // Motion settings
60
+ motion: number; // 0-10
61
+ prefersReducedMotion: boolean;
62
+
63
+ // Color customization
64
+ customizationMode: CustomizationMode;
65
+ customColors: {
66
+ [theme in ThemeName]?: {
67
+ [mode in ColorMode]?: ColorPalette;
68
+ };
69
+ };
70
+
71
+ // Saved custom palettes
72
+ savedPalettes: SavedPalette[];
73
+
74
+ // Font theme customization
75
+ customFontThemes: {
76
+ [theme in ThemeName]?: {
77
+ [mode in ColorMode]?: FontTheme;
78
+ };
79
+ };
80
+
81
+ // Saved custom font themes
82
+ savedFontThemes: SavedFontTheme[];
83
+
84
+ // Motion actions
85
+ setMotion: (level: number) => void;
86
+ setPrefersReducedMotion: (value: boolean) => void;
87
+
88
+ // Color customization actions
89
+ setCustomizationMode: (mode: CustomizationMode) => void;
90
+
91
+ setCustomPrimaryColor: (
92
+ theme: ThemeName,
93
+ mode: ColorMode,
94
+ hexColor: string
95
+ ) => void;
96
+
97
+ setCustomSecondaryColor: (
98
+ theme: ThemeName,
99
+ mode: ColorMode,
100
+ hexColor: string
101
+ ) => void;
102
+
103
+ setCustomAccentColor: (
104
+ theme: ThemeName,
105
+ mode: ColorMode,
106
+ hexColor: string
107
+ ) => void;
108
+
109
+ applyColorPalette: (
110
+ theme: ThemeName,
111
+ mode: ColorMode,
112
+ colors: {
113
+ primary: string;
114
+ secondary?: string;
115
+ accent?: string;
116
+ name?: string;
117
+ description?: string;
118
+ }
119
+ ) => void;
120
+
121
+ resetCustomColors: (theme: ThemeName, mode?: ColorMode) => void;
122
+
123
+ getActiveColorPalette: (theme: ThemeName, mode: ColorMode) => ColorPalette | null;
124
+
125
+ // Saved palette actions
126
+ savePalette: (palette: Omit<SavedPalette, 'id' | 'createdAt' | 'category'>) => void;
127
+ updatePalette: (id: string, updates: Partial<SavedPalette>) => void;
128
+ renamePalette: (id: string, newName: string) => void;
129
+ deletePalette: (id: string) => void;
130
+ reorderPalettes: (palettes: SavedPalette[]) => void;
131
+ getSavedPalettes: () => SavedPalette[];
132
+
133
+ // Font theme actions
134
+ applyFontTheme: (
135
+ theme: ThemeName,
136
+ mode: ColorMode,
137
+ fontTheme: FontTheme
138
+ ) => void;
139
+
140
+ resetCustomFonts: (theme: ThemeName, mode?: ColorMode) => void;
141
+
142
+ getActiveFontTheme: (theme: ThemeName, mode: ColorMode) => FontTheme | null;
143
+
144
+ // Saved font theme actions
145
+ saveFontTheme: (fontTheme: Omit<SavedFontTheme, 'id' | 'createdAt' | 'category'>) => void;
146
+ updateFontTheme: (id: string, updates: Partial<SavedFontTheme>) => void;
147
+ renameFontTheme: (id: string, newName: string) => void;
148
+ deleteFontTheme: (id: string) => void;
149
+ reorderFontThemes: (fontThemes: SavedFontTheme[]) => void;
150
+ getSavedFontThemes: () => SavedFontTheme[];
151
+ }
152
+
153
+ export const useCustomizer = create<CustomizerState>()(
154
+ persist(
155
+ (set, get) => ({
156
+ motion: 5,
157
+ prefersReducedMotion: false,
158
+ customizationMode: 'simple',
159
+ customColors: {},
160
+ savedPalettes: [],
161
+ customFontThemes: {},
162
+ savedFontThemes: [],
163
+
164
+ setMotion: (level) => set({ motion: level }),
165
+ setPrefersReducedMotion: (value) => set({ prefersReducedMotion: value }),
166
+ setCustomizationMode: (mode) => set({ customizationMode: mode }),
167
+
168
+ setCustomPrimaryColor: (theme, mode, hexColor) => {
169
+ const state = get();
170
+ const currentPalette = state.customColors[theme]?.[mode];
171
+
172
+ // Generate complete color palette
173
+ const scale = generateColorScale(hexColor);
174
+ const primaryForeground = getOptimalForeground(hexColor);
175
+
176
+ // Compute all derived tokens based on dependency graph
177
+ const derivedTokens = computeDerivedTokens('--color-primary', hexColor, mode);
178
+
179
+ // In simple mode, generate secondary/accent from primary
180
+ const isSimple = state.customizationMode === 'simple';
181
+
182
+ const palette: ColorPalette = {
183
+ primary: hexColor,
184
+ primaryForeground,
185
+ secondary: isSimple ? undefined : currentPalette?.secondary,
186
+ secondaryForeground: isSimple ? undefined : currentPalette?.secondaryForeground,
187
+ accent: isSimple ? undefined : currentPalette?.accent,
188
+ accentForeground: isSimple ? undefined : currentPalette?.accentForeground,
189
+ scale,
190
+ derivedTokens,
191
+ };
192
+
193
+ set((state) => ({
194
+ customColors: {
195
+ ...state.customColors,
196
+ [theme]: {
197
+ ...state.customColors[theme],
198
+ [mode]: palette,
199
+ },
200
+ },
201
+ }));
202
+ },
203
+
204
+ setCustomSecondaryColor: (theme, mode, hexColor) => {
205
+ const state = get();
206
+ const currentPalette = state.customColors[theme]?.[mode];
207
+
208
+ if (!currentPalette) return;
209
+
210
+ const secondaryForeground = getOptimalForeground(hexColor);
211
+ const derivedTokens = computeDerivedTokens('--color-secondary', hexColor, mode);
212
+
213
+ set((state) => ({
214
+ customColors: {
215
+ ...state.customColors,
216
+ [theme]: {
217
+ ...state.customColors[theme],
218
+ [mode]: {
219
+ ...currentPalette,
220
+ secondary: hexColor,
221
+ secondaryForeground,
222
+ derivedTokens: {
223
+ ...currentPalette.derivedTokens,
224
+ ...derivedTokens,
225
+ },
226
+ },
227
+ },
228
+ },
229
+ }));
230
+ },
231
+
232
+ setCustomAccentColor: (theme, mode, hexColor) => {
233
+ const state = get();
234
+ const currentPalette = state.customColors[theme]?.[mode];
235
+
236
+ if (!currentPalette) return;
237
+
238
+ const accentForeground = getOptimalForeground(hexColor);
239
+ const derivedTokens = computeDerivedTokens('--color-accent', hexColor, mode);
240
+
241
+ set((state) => ({
242
+ customColors: {
243
+ ...state.customColors,
244
+ [theme]: {
245
+ ...state.customColors[theme],
246
+ [mode]: {
247
+ ...currentPalette,
248
+ accent: hexColor,
249
+ accentForeground,
250
+ derivedTokens: {
251
+ ...currentPalette.derivedTokens,
252
+ ...derivedTokens,
253
+ },
254
+ },
255
+ },
256
+ },
257
+ }));
258
+ },
259
+
260
+ applyColorPalette: (theme, mode, colors: {
261
+ primary: string;
262
+ secondary?: string;
263
+ accent?: string;
264
+ name?: string;
265
+ description?: string;
266
+ }) => {
267
+ // Generate complete color palette with all three colors in a single atomic update
268
+ const scale = generateColorScale(colors.primary);
269
+ const primaryForeground = getOptimalForeground(colors.primary);
270
+
271
+ // Compute all derived tokens
272
+ let derivedTokens = computeDerivedTokens('--color-primary', colors.primary, mode);
273
+
274
+ // Add secondary color if provided
275
+ const secondary = colors.secondary;
276
+ const secondaryForeground = secondary ? getOptimalForeground(secondary) : undefined;
277
+ if (secondary) {
278
+ const secondaryDerived = computeDerivedTokens('--color-secondary', secondary, mode);
279
+ derivedTokens = { ...derivedTokens, ...secondaryDerived };
280
+ }
281
+
282
+ // Add accent color if provided
283
+ const accent = colors.accent;
284
+ const accentForeground = accent ? getOptimalForeground(accent) : undefined;
285
+ if (accent) {
286
+ const accentDerived = computeDerivedTokens('--color-accent', accent, mode);
287
+ derivedTokens = { ...derivedTokens, ...accentDerived };
288
+ }
289
+
290
+ const palette: ColorPalette = {
291
+ name: colors.name,
292
+ description: colors.description,
293
+ primary: colors.primary,
294
+ primaryForeground,
295
+ secondary,
296
+ secondaryForeground,
297
+ accent,
298
+ accentForeground,
299
+ scale,
300
+ derivedTokens,
301
+ };
302
+
303
+ set((state) => ({
304
+ customColors: {
305
+ ...state.customColors,
306
+ [theme]: {
307
+ ...state.customColors[theme],
308
+ [mode]: palette,
309
+ },
310
+ },
311
+ }));
312
+ },
313
+
314
+ resetCustomColors: (theme, mode) => {
315
+ if (mode) {
316
+ // Reset specific mode
317
+ set((state) => ({
318
+ customColors: {
319
+ ...state.customColors,
320
+ [theme]: {
321
+ ...state.customColors[theme],
322
+ [mode]: undefined,
323
+ },
324
+ },
325
+ }));
326
+ } else {
327
+ // Reset entire theme
328
+ set((state) => {
329
+ const { [theme]: _, ...rest } = state.customColors;
330
+ return { customColors: rest };
331
+ });
332
+ }
333
+ },
334
+
335
+ getActiveColorPalette: (theme, mode) => {
336
+ return get().customColors[theme]?.[mode] || null;
337
+ },
338
+
339
+ // Saved palette management
340
+ savePalette: (paletteData) => {
341
+ const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
342
+ const newPalette: SavedPalette = {
343
+ ...paletteData,
344
+ id,
345
+ category: 'custom',
346
+ createdAt: Date.now(),
347
+ };
348
+
349
+ set((state) => ({
350
+ savedPalettes: [...state.savedPalettes, newPalette],
351
+ }));
352
+ },
353
+
354
+ updatePalette: (id, updates) => {
355
+ set((state) => ({
356
+ savedPalettes: state.savedPalettes.map((p) =>
357
+ p.id === id ? { ...p, ...updates } : p
358
+ ),
359
+ }));
360
+ },
361
+
362
+ renamePalette: (id, newName) => {
363
+ set((state) => ({
364
+ savedPalettes: state.savedPalettes.map((p) =>
365
+ p.id === id ? { ...p, name: newName } : p
366
+ ),
367
+ }));
368
+ },
369
+
370
+ deletePalette: (id) => {
371
+ set((state) => ({
372
+ savedPalettes: state.savedPalettes.filter((p) => p.id !== id),
373
+ }));
374
+ },
375
+
376
+ reorderPalettes: (palettes) => {
377
+ set({ savedPalettes: palettes });
378
+ },
379
+
380
+ getSavedPalettes: () => {
381
+ return get().savedPalettes;
382
+ },
383
+
384
+ // Font theme management
385
+ applyFontTheme: (theme, mode, fontTheme) => {
386
+ set((state) => ({
387
+ customFontThemes: {
388
+ ...state.customFontThemes,
389
+ [theme]: {
390
+ ...state.customFontThemes[theme],
391
+ [mode]: fontTheme,
392
+ },
393
+ },
394
+ }));
395
+ },
396
+
397
+ resetCustomFonts: (theme, mode) => {
398
+ if (mode) {
399
+ // Reset specific mode
400
+ set((state) => ({
401
+ customFontThemes: {
402
+ ...state.customFontThemes,
403
+ [theme]: {
404
+ ...state.customFontThemes[theme],
405
+ [mode]: undefined,
406
+ },
407
+ },
408
+ }));
409
+ } else {
410
+ // Reset entire theme
411
+ set((state) => {
412
+ const { [theme]: _, ...rest } = state.customFontThemes;
413
+ return { customFontThemes: rest };
414
+ });
415
+ }
416
+ },
417
+
418
+ getActiveFontTheme: (theme, mode) => {
419
+ return get().customFontThemes[theme]?.[mode] || null;
420
+ },
421
+
422
+ // Saved font theme management
423
+ saveFontTheme: (fontThemeData) => {
424
+ const id = `font-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
425
+ const newFontTheme: SavedFontTheme = {
426
+ ...fontThemeData,
427
+ id,
428
+ category: 'custom',
429
+ createdAt: Date.now(),
430
+ isCustom: true,
431
+ };
432
+
433
+ set((state) => ({
434
+ savedFontThemes: [...state.savedFontThemes, newFontTheme],
435
+ }));
436
+ },
437
+
438
+ updateFontTheme: (id, updates) => {
439
+ set((state) => ({
440
+ savedFontThemes: state.savedFontThemes.map((ft) =>
441
+ ft.id === id ? { ...ft, ...updates } : ft
442
+ ),
443
+ }));
444
+ },
445
+
446
+ renameFontTheme: (id, newName) => {
447
+ set((state) => ({
448
+ savedFontThemes: state.savedFontThemes.map((ft) =>
449
+ ft.id === id ? { ...ft, name: newName } : ft
450
+ ),
451
+ }));
452
+ },
453
+
454
+ deleteFontTheme: (id) => {
455
+ set((state) => ({
456
+ savedFontThemes: state.savedFontThemes.filter((ft) => ft.id !== id),
457
+ }));
458
+ },
459
+
460
+ reorderFontThemes: (fontThemes) => {
461
+ set({ savedFontThemes: fontThemes });
462
+ },
463
+
464
+ getSavedFontThemes: () => {
465
+ return get().savedFontThemes;
466
+ },
467
+ }),
468
+ {
469
+ name: 'ecosystem-customizer',
470
+ version: 4,
471
+ partialize: (state) => ({
472
+ motion: state.motion,
473
+ prefersReducedMotion: state.prefersReducedMotion,
474
+ customizationMode: state.customizationMode,
475
+ customColors: state.customColors,
476
+ savedPalettes: state.savedPalettes,
477
+ customFontThemes: state.customFontThemes,
478
+ savedFontThemes: state.savedFontThemes,
479
+ }),
480
+ }
481
+ )
482
+ );
@@ -0,0 +1,3 @@
1
+ export { useThemeStore } from './theme';
2
+ export { useCustomizer } from './customizer';
3
+ export type { ThemeName, ColorMode } from './theme';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Theme Store
3
+ * Manages theme and color mode state with localStorage persistence
4
+ */
5
+
6
+ import { create } from 'zustand';
7
+ import { persist } from 'zustand/middleware';
8
+ import type { ThemeName, ColorMode } from '@thesage/tokens';
9
+
10
+ // Re-export types for convenience
11
+ export type { ThemeName, ColorMode };
12
+
13
+ interface ThemeState {
14
+ // Current theme and mode
15
+ theme: ThemeName;
16
+ mode: ColorMode;
17
+
18
+ // Actions
19
+ setTheme: (theme: ThemeName) => void;
20
+ setMode: (mode: ColorMode) => void;
21
+ toggleMode: () => void;
22
+
23
+ // Computed
24
+ themeConfig: { name: ThemeName; mode: ColorMode };
25
+ }
26
+
27
+ export const useThemeStore = create<ThemeState>()(
28
+ persist(
29
+ (set, get) => ({
30
+ // Defaults
31
+ theme: 'volt',
32
+ mode: 'dark',
33
+
34
+ // Actions
35
+ setTheme: (theme) => set({ theme }),
36
+ setMode: (mode) => set({ mode }),
37
+ toggleMode: () =>
38
+ set((state) => ({ mode: state.mode === 'light' ? 'dark' : 'light' })),
39
+
40
+ // Computed
41
+ get themeConfig() {
42
+ const state = get();
43
+ return { name: state.theme, mode: state.mode };
44
+ },
45
+ }),
46
+ {
47
+ name: 'ecosystem-theme',
48
+ // Only persist theme and mode
49
+ partialize: (state) => ({
50
+ theme: state.theme,
51
+ mode: state.mode,
52
+ }),
53
+ }
54
+ )
55
+ );
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Syntax Parser
3
+ *
4
+ * Lightweight syntax parser for automatic code tokenization.
5
+ * Converts plain code strings into syntax-highlighted token arrays.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { parseCode } from '@thesage/ui';
10
+ *
11
+ * const tokens = parseCode('const greeting = "Hello World";');
12
+ * // Use with CollapsibleCodeBlock:
13
+ * <CollapsibleCodeBlock
14
+ * id="example"
15
+ * code={tokens}
16
+ * />
17
+ * ```
18
+ */
19
+
20
+ export { tokenize, detectLanguage } from './tokenizer';
21
+ export type { SyntaxToken, SyntaxType, Language } from './types';
22
+
23
+ import { tokenize, detectLanguage } from './tokenizer';
24
+ import type { SyntaxToken, Language } from './types';
25
+
26
+ /**
27
+ * Parses source code into syntax tokens for highlighting
28
+ *
29
+ * @param code - The source code to parse
30
+ * @param language - Optional language hint (auto-detected if not provided)
31
+ * @returns Array of syntax tokens ready for CollapsibleCodeBlock
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const code = `
36
+ * import { useState } from 'react';
37
+ *
38
+ * export function Counter() {
39
+ * const [count, setCount] = useState(0);
40
+ * return <button onClick={() => setCount(count + 1)}>{count}</button>;
41
+ * }
42
+ * `;
43
+ *
44
+ * const tokens = parseCode(code);
45
+ * ```
46
+ */
47
+ export function parseCode(code: string, language?: Language): SyntaxToken[] {
48
+ const lang = language || detectLanguage(code);
49
+ return tokenize(code, lang);
50
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Syntax Parser Patterns
3
+ * Regex patterns for tokenizing TypeScript/JavaScript/JSX code
4
+ */
5
+
6
+ /**
7
+ * Token pattern configuration
8
+ * Order matters - patterns are tested in sequence
9
+ */
10
+ export const TOKEN_PATTERNS = [
11
+ // Single-line comments
12
+ { type: 'comment' as const, pattern: /\/\/.*$/gm },
13
+
14
+ // Multi-line comments
15
+ { type: 'comment' as const, pattern: /\/\*[\s\S]*?\*\//g },
16
+
17
+ // Template literals and strings
18
+ { type: 'string' as const, pattern: /`(?:\\.|[^`\\])*`/g },
19
+ { type: 'string' as const, pattern: /"(?:\\.|[^"\\])*"/g },
20
+ { type: 'string' as const, pattern: /'(?:\\.|[^'\\])*'/g },
21
+
22
+ // JSX/TSX tags
23
+ { type: 'tag' as const, pattern: /<\/?[A-Z][a-zA-Z0-9]*(?=[\s>])/g },
24
+ { type: 'tag' as const, pattern: /<\/?[a-z][a-zA-Z0-9-]*(?=[\s>])/g },
25
+
26
+ // Keywords
27
+ {
28
+ type: 'keyword' as const,
29
+ pattern: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|throw|try|catch|finally|new|typeof|instanceof|void|delete|async|await|yield|export|import|from|default|class|extends|implements|interface|type|enum|namespace|declare|public|private|protected|static|readonly|abstract|as|is|in|of|null|undefined)\b/g
30
+ },
31
+
32
+ // Booleans
33
+ { type: 'boolean' as const, pattern: /\b(true|false)\b/g },
34
+
35
+ // Numbers
36
+ { type: 'number' as const, pattern: /\b\d+\.?\d*(?:e[+-]?\d+)?(?:n)?\b/gi },
37
+ { type: 'number' as const, pattern: /\b0x[0-9a-f]+\b/gi },
38
+
39
+ // Function calls
40
+ { type: 'function' as const, pattern: /\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*\()/g },
41
+
42
+ // Class names (PascalCase identifiers)
43
+ { type: 'className' as const, pattern: /\b[A-Z][a-zA-Z0-9]*\b/g },
44
+
45
+ // JSX attributes
46
+ { type: 'attribute' as const, pattern: /\b[a-z][a-zA-Z0-9]*(?=\s*=)/g },
47
+
48
+ // Object properties (after dot)
49
+ { type: 'property' as const, pattern: /(?<=\.)[a-zA-Z_$][a-zA-Z0-9_$]*/g },
50
+
51
+ // Operators
52
+ {
53
+ type: 'operator' as const,
54
+ pattern: /[+\-*/%=!<>&|^~?:]+|&&|\|\||\.\.\.|\?\?|===|!==|==|!=|<=|>=|<<|>>|>>>/g
55
+ },
56
+
57
+ // Punctuation
58
+ { type: 'punctuation' as const, pattern: /[{}[\](),.;]/g },
59
+ ] as const;
60
+
61
+ /**
62
+ * Characters that should be treated as whitespace
63
+ */
64
+ export const WHITESPACE = /\s+/g;