@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,461 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Theme Provider
5
+ * Applies theme tokens as CSS variables and manages transitions
6
+ */
7
+
8
+ import { useEffect, useState } from 'react';
9
+ import { useThemeStore } from '../lib/store/theme';
10
+ import { useCustomizer, type ColorPalette } from '../lib/store/customizer';
11
+ import { studioTokens, terraTokens, voltTokens, speedboatTokens, syntaxColors, codeColors } from '@thesage/tokens';
12
+ import type { ThemeName, ColorMode } from '@thesage/tokens';
13
+
14
+ // ── Type-safe token access ──────────────────────────────────────────────────
15
+
16
+ interface ThemeTokenColors {
17
+ background: string;
18
+ backgroundSecondary: string;
19
+ backgroundTertiary: string;
20
+ foreground: string;
21
+ foregroundSecondary: string;
22
+ foregroundTertiary: string;
23
+ primary: string;
24
+ primaryForeground: string;
25
+ secondary: string;
26
+ secondaryForeground: string;
27
+ accent: string;
28
+ accentForeground: string;
29
+ border: string;
30
+ borderSubtle: string;
31
+ hover: string;
32
+ active: string;
33
+ linkHover: string;
34
+ linkHoverForeground: string;
35
+ success: string;
36
+ successForeground: string;
37
+ warning: string;
38
+ warningForeground: string;
39
+ error: string;
40
+ errorForeground: string;
41
+ info: string;
42
+ infoForeground: string;
43
+ glass: string;
44
+ glassBorder: string;
45
+ card?: string;
46
+ cardForeground?: string;
47
+ popover?: string;
48
+ popoverForeground?: string;
49
+ muted?: string;
50
+ mutedForeground?: string;
51
+ destructive?: string;
52
+ destructiveForeground?: string;
53
+ input?: string;
54
+ ring?: string;
55
+ surface?: string;
56
+ link?: string;
57
+ primaryHover?: string;
58
+ accentHover?: string;
59
+ }
60
+
61
+ interface ThemeTokenEffects {
62
+ blur: { sm: string; md: string; lg: string; xl: string };
63
+ shadow: { sm: string; md: string; lg: string; xl: string; '2xl': string };
64
+ }
65
+
66
+ interface InteractionTokens {
67
+ hover: { overlayColor: { light: string; dark: string }; opacity: number };
68
+ active: { scale: number };
69
+ focus: { ringWidth: string; ringOffset: string };
70
+ disabled: { opacity: number };
71
+ }
72
+
73
+ interface ThemeMotion {
74
+ getDuration: (intensity: number) => string;
75
+ ease: { default: string; in: string; out: string; spring: string };
76
+ }
77
+
78
+ interface ThemeModeTokens {
79
+ colors: ThemeTokenColors;
80
+ effects: ThemeTokenEffects;
81
+ }
82
+
83
+ // ── Theme token map ─────────────────────────────────────────────────────────
84
+
85
+ const themeTokens = {
86
+ studio: studioTokens,
87
+ terra: terraTokens,
88
+ volt: voltTokens,
89
+ speedboat: speedboatTokens,
90
+ } satisfies Record<ThemeName, any>;
91
+
92
+ // Font family map (CSS variables defined in layout)
93
+ const fontFamilies: Record<ThemeName, Record<string, string>> = {
94
+ studio: {
95
+ heading: 'var(--font-studio-heading)',
96
+ body: 'var(--font-studio-body)',
97
+ mono: 'var(--font-mono)',
98
+ },
99
+ terra: {
100
+ sans: 'var(--font-terra-body)',
101
+ serif: 'var(--font-terra-heading)',
102
+ mono: 'var(--font-mono)',
103
+ },
104
+ volt: {
105
+ sans: 'var(--font-volt-heading)',
106
+ mono: 'var(--font-mono)',
107
+ },
108
+ speedboat: {
109
+ heading: 'var(--font-montserrat)',
110
+ body: 'var(--font-roboto)',
111
+ mono: 'var(--font-mono)',
112
+ },
113
+ };
114
+
115
+ // ── Helpers ─────────────────────────────────────────────────────────────────
116
+
117
+ /** Extract raw pixel value from blur() CSS function */
118
+ function extractBlurValue(blurFunction: string): string {
119
+ const match = blurFunction.match(/blur\(([^)]+)\)/);
120
+ return match ? match[1] : '8px';
121
+ }
122
+
123
+ // ── Token → CSS variable mapping ────────────────────────────────────────────
124
+
125
+ /**
126
+ * Convert theme tokens to CSS variables
127
+ */
128
+ function getThemeVars(theme: ThemeName, mode: ColorMode, motionIntensity: number): Record<string, string> {
129
+ const tokens = themeTokens[theme];
130
+ const modeTokens = tokens[mode] as ThemeModeTokens;
131
+ const colors = modeTokens?.colors;
132
+ const effects = modeTokens?.effects;
133
+ const fonts = fontFamilies[theme];
134
+ const motion = tokens.motion as ThemeMotion | undefined;
135
+ const interactions = (tokens as any).interactions as InteractionTokens | undefined;
136
+
137
+ // Compute motion duration from theme + user preference
138
+ const duration = motion?.getDuration?.(motionIntensity) || '300ms';
139
+ const durationMs = parseInt(duration) || 300;
140
+
141
+ return {
142
+ // Colors - Base
143
+ '--color-background': colors?.background || '#ffffff',
144
+ '--color-background-secondary': colors?.backgroundSecondary || colors?.background || '#fafafa',
145
+ '--color-background-tertiary': colors?.backgroundTertiary || colors?.backgroundSecondary || colors?.background || '#f5f5f5',
146
+ '--color-foreground': colors?.foreground || '#0a0a0a',
147
+ '--color-primary': colors?.primary || '#0a0a0a',
148
+ '--color-primary-foreground': colors?.primaryForeground || '#ffffff',
149
+ '--color-secondary': colors?.secondary || '#f5f5f5',
150
+ '--color-secondary-foreground': colors?.secondaryForeground || '#0a0a0a',
151
+ '--color-accent': colors?.accent || colors?.primary || '#0070f3',
152
+ '--color-accent-foreground': colors?.accentForeground || '#ffffff',
153
+ '--color-success': colors?.success || '#00a86b',
154
+ '--color-success-foreground': colors?.successForeground || '#ffffff',
155
+ '--color-warning': colors?.warning || '#f59e0b',
156
+ '--color-warning-foreground': colors?.warningForeground || '#ffffff',
157
+ '--color-error': colors?.error || '#ef4444',
158
+ '--color-error-foreground': colors?.errorForeground || '#ffffff',
159
+ '--color-info': colors?.info || colors?.accent || '#0070f3',
160
+ '--color-info-foreground': colors?.infoForeground || '#ffffff',
161
+ '--color-glass': colors?.glass || 'rgba(255, 255, 255, 0.7)',
162
+ '--color-glass-border': colors?.glassBorder || 'rgba(0, 0, 0, 0.1)',
163
+
164
+ // Semantic color aliases (matching README examples)
165
+ '--color-text-primary': colors?.foreground || '#0a0a0a',
166
+ '--color-text-secondary': colors?.foregroundSecondary || '#525252',
167
+ '--color-text-muted': colors?.foregroundTertiary || '#a3a3a3',
168
+ '--color-surface': colors?.backgroundSecondary || colors?.background || '#fafafa',
169
+ '--color-border': colors?.border || colors?.glassBorder || 'rgba(0, 0, 0, 0.1)',
170
+ '--color-focus': colors?.accent || colors?.primary || '#0070f3',
171
+
172
+ // Links and focus rings (can be overridden by derived tokens)
173
+ '--color-link': colors?.link || colors?.primary || '#0a0a0a',
174
+ '--color-ring': colors?.ring || colors?.primary || '#0a0a0a',
175
+
176
+ // Interactive states
177
+ '--color-hover': colors?.hover || colors?.backgroundSecondary || '#fafafa',
178
+ '--color-active': colors?.active || colors?.backgroundTertiary || '#f0f0f0',
179
+ '--color-link-hover': colors?.linkHover || colors?.primary || '#0a0a0a',
180
+ '--color-link-hover-foreground': colors?.linkHoverForeground || colors?.background || '#ffffff',
181
+
182
+ // Component-specific (previously only set in globals.css defaults)
183
+ '--color-card': colors?.card || colors?.background || '#ffffff',
184
+ '--color-card-foreground': colors?.cardForeground || colors?.foreground || '#0a0a0a',
185
+ '--color-popover': colors?.popover || colors?.background || '#ffffff',
186
+ '--color-popover-foreground': colors?.popoverForeground || colors?.foreground || '#0a0a0a',
187
+ '--color-muted': colors?.muted || colors?.backgroundSecondary || '#f5f5f5',
188
+ '--color-muted-foreground': colors?.mutedForeground || colors?.foregroundTertiary || '#737373',
189
+ '--color-destructive': colors?.destructive || colors?.error || '#ef4444',
190
+ '--color-destructive-foreground': colors?.destructiveForeground || '#ffffff',
191
+ '--color-input': colors?.input || colors?.border || '#d4d4d4',
192
+
193
+ // Effects - Blur (full function for style attributes)
194
+ '--effect-blur-sm': effects?.blur?.sm || 'blur(4px)',
195
+ '--effect-blur-md': effects?.blur?.md || 'blur(8px)',
196
+ '--effect-blur-lg': effects?.blur?.lg || 'blur(16px)',
197
+ '--effect-blur-xl': effects?.blur?.xl || effects?.blur?.lg || 'blur(24px)',
198
+
199
+ // Effects - Blur (raw values for Tailwind blur-*/backdrop-blur-* utilities)
200
+ '--blur-sm': extractBlurValue(effects?.blur?.sm || 'blur(4px)'),
201
+ '--blur-md': extractBlurValue(effects?.blur?.md || 'blur(8px)'),
202
+ '--blur-lg': extractBlurValue(effects?.blur?.lg || 'blur(16px)'),
203
+ '--blur-xl': extractBlurValue(effects?.blur?.xl || 'blur(24px)'),
204
+
205
+ // Effects - Shadow (complete set)
206
+ '--effect-shadow-sm': effects?.shadow?.sm || '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
207
+ '--effect-shadow-md': effects?.shadow?.md || '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
208
+ '--effect-shadow-lg': effects?.shadow?.lg || '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
209
+ '--effect-shadow-xl': effects?.shadow?.xl || '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
210
+ '--effect-shadow-2xl': effects?.shadow?.['2xl'] || '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
211
+
212
+ // Interaction tokens (theme-aware)
213
+ '--color-interaction-overlay': interactions?.hover?.overlayColor?.[mode] || (mode === 'dark' ? '#ffffff' : '#000000'),
214
+ '--opacity-interaction-hover': String(interactions?.hover?.opacity ?? 0.08),
215
+ '--scale-interaction-active': String(interactions?.active?.scale ?? 0.98),
216
+ '--color-interaction-focus-ring': colors?.ring || colors?.primary || '#0a0a0a',
217
+ '--width-interaction-focus-ring': interactions?.focus?.ringWidth || '2px',
218
+ '--width-interaction-focus-offset': interactions?.focus?.ringOffset || '2px',
219
+ '--opacity-interaction-disabled': String(interactions?.disabled?.opacity ?? 0.5),
220
+
221
+ // Typography - Font Families
222
+ '--font-heading': fonts?.heading || (theme === 'terra' && fonts?.serif ? fonts.serif : fonts?.sans) || 'var(--font-studio-heading)',
223
+ '--font-body': fonts?.body || fonts?.sans || 'var(--font-studio-body)',
224
+ '--font-mono': fonts?.mono || 'var(--font-studio-mono)',
225
+
226
+ // Motion - Easing (complete set)
227
+ '--ease-default': motion?.ease?.default || 'cubic-bezier(0.4, 0, 0.2, 1)',
228
+ '--ease-in': motion?.ease?.in || 'cubic-bezier(0.4, 0, 1, 1)',
229
+ '--ease-out': motion?.ease?.out || 'cubic-bezier(0, 0, 0.2, 1)',
230
+ '--ease-spring': motion?.ease?.spring || 'cubic-bezier(0.16, 1, 0.3, 1)',
231
+
232
+ // Motion - Duration (computed from theme + user motion preference)
233
+ '--duration-default': duration,
234
+ '--duration-fast': `${Math.max(0, Math.round(durationMs * 0.5))}ms`,
235
+ '--duration-slow': `${Math.min(1000, Math.round(durationMs * 1.5))}ms`,
236
+
237
+ // Syntax Highlighting - Based on VS Code Dark+ theme
238
+ '--syntax-comment': mode === 'light' ? syntaxColors.light.comment : syntaxColors.dark.comment,
239
+ '--syntax-keyword': mode === 'light' ? syntaxColors.light.keyword : syntaxColors.dark.keyword,
240
+ '--syntax-function': mode === 'light' ? syntaxColors.light.function : syntaxColors.dark.function,
241
+ '--syntax-string': mode === 'light' ? syntaxColors.light.string : syntaxColors.dark.string,
242
+ '--syntax-number': mode === 'light' ? syntaxColors.light.number : syntaxColors.dark.number,
243
+ '--syntax-boolean': mode === 'light' ? syntaxColors.light.boolean : syntaxColors.dark.boolean,
244
+ '--syntax-operator': mode === 'light' ? syntaxColors.light.operator : syntaxColors.dark.operator,
245
+ '--syntax-property': mode === 'light' ? syntaxColors.light.property : syntaxColors.dark.property,
246
+ '--syntax-className': mode === 'light' ? syntaxColors.light.className : syntaxColors.dark.className,
247
+ '--syntax-tag': mode === 'light' ? syntaxColors.light.tag : syntaxColors.dark.tag,
248
+ '--syntax-attribute': mode === 'light' ? syntaxColors.light.attribute : syntaxColors.dark.attribute,
249
+ '--syntax-variable': mode === 'light' ? syntaxColors.light.variable : syntaxColors.dark.variable,
250
+ '--syntax-punctuation': mode === 'light' ? syntaxColors.light.punctuation : syntaxColors.dark.punctuation,
251
+ '--syntax-plain': mode === 'light' ? syntaxColors.light.plain : syntaxColors.dark.plain,
252
+
253
+ // Code Block Backgrounds and Borders - Accessible contrast (WCAG AA 4.5:1)
254
+ '--code-block-bg': mode === 'light' ? codeColors.light.blockBackground : codeColors.dark.blockBackground,
255
+ '--code-inline-bg': mode === 'light' ? codeColors.light.inlineBackground : codeColors.dark.inlineBackground,
256
+ '--code-border': mode === 'light' ? codeColors.light.border : codeColors.dark.border,
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Merge custom color palette with base theme tokens
262
+ * This is where "change once, ripple everywhere" happens!
263
+ */
264
+ function mergeCustomColorTokens(
265
+ baseTokens: Record<string, string>,
266
+ customPalette: ColorPalette
267
+ ): Record<string, string> {
268
+ return {
269
+ ...baseTokens,
270
+
271
+ // Override primary color
272
+ '--color-primary': customPalette.primary,
273
+ '--color-primary-foreground': customPalette.primaryForeground,
274
+
275
+ // Apply color scale (for utilities like bg-primary-200)
276
+ '--color-primary-50': customPalette.scale[50],
277
+ '--color-primary-100': customPalette.scale[100],
278
+ '--color-primary-200': customPalette.scale[200],
279
+ '--color-primary-300': customPalette.scale[300],
280
+ '--color-primary-400': customPalette.scale[400],
281
+ '--color-primary-500': customPalette.scale[500],
282
+ '--color-primary-600': customPalette.scale[600],
283
+ '--color-primary-700': customPalette.scale[700],
284
+ '--color-primary-800': customPalette.scale[800],
285
+ '--color-primary-900': customPalette.scale[900],
286
+
287
+ // Override secondary color if provided (advanced mode)
288
+ ...(customPalette.secondary && {
289
+ '--color-secondary': customPalette.secondary,
290
+ '--color-secondary-foreground': customPalette.secondaryForeground || baseTokens['--color-secondary-foreground'],
291
+ }),
292
+
293
+ // Override accent color if provided (advanced mode)
294
+ ...(customPalette.accent && {
295
+ '--color-accent': customPalette.accent,
296
+ '--color-accent-foreground': customPalette.accentForeground || baseTokens['--color-accent-foreground'],
297
+ }),
298
+
299
+ // Apply ALL derived tokens from dependency graph
300
+ ...customPalette.derivedTokens,
301
+ };
302
+ }
303
+
304
+ /**
305
+ * Validate theme tokens in development mode
306
+ */
307
+ function validateThemeTokens(theme: ThemeName, mode: ColorMode): void {
308
+ // @ts-expect-error - process.env is injected by bundler
309
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return;
310
+
311
+ const root = document.documentElement;
312
+ const style = getComputedStyle(root);
313
+
314
+ const requiredTokens = [
315
+ '--color-background',
316
+ '--color-foreground',
317
+ '--color-primary',
318
+ '--color-primary-foreground',
319
+ '--color-border',
320
+ '--color-ring',
321
+ '--font-heading',
322
+ '--font-body',
323
+ '--font-mono',
324
+ ];
325
+
326
+ const missingTokens: string[] = [];
327
+ const invalidTokens: string[] = [];
328
+
329
+ requiredTokens.forEach((token) => {
330
+ const value = style.getPropertyValue(token).trim();
331
+
332
+ if (!value) {
333
+ missingTokens.push(token);
334
+ } else if (token.startsWith('--color-') && !value.match(/^(#|rgb|hsl|var\()/)) {
335
+ invalidTokens.push(`${token} = "${value}"`);
336
+ } else if (token.startsWith('--font-') && value === '') {
337
+ invalidTokens.push(`${token} = empty`);
338
+ }
339
+ });
340
+
341
+ if (missingTokens.length > 0) {
342
+ console.warn(
343
+ `[ThemeProvider] Missing CSS variables for theme "${theme}" (${mode} mode):`,
344
+ missingTokens
345
+ );
346
+ }
347
+
348
+ if (invalidTokens.length > 0) {
349
+ console.warn(
350
+ `[ThemeProvider] Invalid CSS variable values for theme "${theme}" (${mode} mode):`,
351
+ invalidTokens
352
+ );
353
+ }
354
+
355
+ if (missingTokens.length === 0 && invalidTokens.length === 0) {
356
+ console.log(`[ThemeProvider] ✓ Theme validation passed for "${theme}" (${mode} mode)`);
357
+ }
358
+ }
359
+
360
+ // ── Component ───────────────────────────────────────────────────────────────
361
+
362
+ export interface ThemeProviderProps {
363
+ children: React.ReactNode;
364
+ /**
365
+ * Default theme to use on first load (before localStorage).
366
+ * Does NOT override a previously persisted theme.
367
+ */
368
+ defaultTheme?: ThemeName;
369
+ /**
370
+ * Default color mode to use on first load (before localStorage).
371
+ * Does NOT override a previously persisted mode.
372
+ */
373
+ defaultMode?: ColorMode;
374
+ }
375
+
376
+ export function ThemeProvider({ children, defaultTheme, defaultMode }: ThemeProviderProps) {
377
+ const { theme, mode, setTheme, setMode } = useThemeStore();
378
+ const customPalette = useCustomizer((state) => state.customColors?.[theme]?.[mode]);
379
+ const motionIntensity = useCustomizer((state) => state.motion);
380
+
381
+ const [isTransitioning, setIsTransitioning] = useState(false);
382
+ const [mounted, setMounted] = useState(false);
383
+
384
+ // Apply defaults on first mount if no persisted preference exists
385
+ useEffect(() => {
386
+ if (!defaultTheme && !defaultMode) return;
387
+ const persisted = typeof window !== 'undefined' && localStorage.getItem('ecosystem-theme');
388
+ if (persisted) return;
389
+ if (defaultTheme) setTheme(defaultTheme);
390
+ if (defaultMode) setMode(defaultMode);
391
+ // eslint-disable-next-line react-hooks/exhaustive-deps
392
+ }, []);
393
+
394
+ // Prevent hydration mismatch
395
+ useEffect(() => {
396
+ setMounted(true);
397
+ }, []);
398
+
399
+ // Apply theme variables with transition
400
+ useEffect(() => {
401
+ if (!mounted) return;
402
+
403
+ setIsTransitioning(true);
404
+
405
+ const root = document.documentElement;
406
+
407
+ // 1. Get base theme tokens (including motion-aware durations)
408
+ const baseTokens = getThemeVars(theme, mode, motionIntensity);
409
+
410
+ // 2. Debug logging (development only)
411
+ // @ts-expect-error - process.env is injected by bundler
412
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
413
+ console.log('[ThemeProvider] Update:', {
414
+ theme,
415
+ mode,
416
+ motionIntensity,
417
+ hasCustomPalette: !!customPalette,
418
+ customPrimary: customPalette?.primary,
419
+ timestamp: new Date().toISOString()
420
+ });
421
+ }
422
+
423
+ // 3. Merge tokens (custom overrides base)
424
+ const finalTokens = customPalette
425
+ ? mergeCustomColorTokens(baseTokens, customPalette)
426
+ : baseTokens;
427
+
428
+ // Apply transition class
429
+ root.classList.add('theme-transitioning');
430
+
431
+ // Apply CSS variables IMMEDIATELY
432
+ Object.entries(finalTokens).forEach(([key, value]) => {
433
+ root.style.setProperty(key, value);
434
+ });
435
+
436
+ // Set data attributes for theme and mode
437
+ root.setAttribute('data-theme', theme);
438
+ root.setAttribute('data-mode', mode);
439
+ root.setAttribute('data-custom-colors', customPalette ? 'active' : 'default');
440
+
441
+ // Toggle 'dark' class for Tailwind dark: modifier support
442
+ if (mode === 'dark') {
443
+ root.classList.add('dark');
444
+ } else {
445
+ root.classList.remove('dark');
446
+ }
447
+
448
+ // Validate theme tokens in development mode
449
+ validateThemeTokens(theme, mode);
450
+
451
+ // End transition after animation completes
452
+ const timeout = setTimeout(() => {
453
+ root.classList.remove('theme-transitioning');
454
+ setIsTransitioning(false);
455
+ }, 400);
456
+
457
+ return () => clearTimeout(timeout);
458
+ }, [theme, mode, mounted, customPalette, motionIntensity]);
459
+
460
+ return <>{children}</>;
461
+ }
@@ -0,0 +1 @@
1
+ export { ThemeProvider } from './ThemeProvider';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Providers subpath export
3
+ * Allows: import { ... } from '@thesage/ui/providers'
4
+ */
5
+
6
+ // Re-export all providers
7
+ export * from './providers/ThemeProvider';
package/src/tables.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components/data-display/DataTable';
@@ -0,0 +1,39 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+
3
+ // Mock window.matchMedia (used by useMotionPreference)
4
+ Object.defineProperty(window, 'matchMedia', {
5
+ writable: true,
6
+ value: vi.fn().mockImplementation((query: string) => ({
7
+ matches: false,
8
+ media: query,
9
+ onchange: null,
10
+ addListener: vi.fn(),
11
+ removeListener: vi.fn(),
12
+ addEventListener: vi.fn(),
13
+ removeEventListener: vi.fn(),
14
+ dispatchEvent: vi.fn(),
15
+ })),
16
+ });
17
+
18
+ // Mock ResizeObserver (used by various Radix UI components and @floating-ui)
19
+ global.ResizeObserver = class ResizeObserver {
20
+ observe = vi.fn();
21
+ unobserve = vi.fn();
22
+ disconnect = vi.fn();
23
+ };
24
+
25
+ // Mock pointer capture methods (used by Radix UI Select and other primitives)
26
+ if (!Element.prototype.hasPointerCapture) {
27
+ Element.prototype.hasPointerCapture = vi.fn().mockReturnValue(false);
28
+ }
29
+ if (!Element.prototype.setPointerCapture) {
30
+ Element.prototype.setPointerCapture = vi.fn();
31
+ }
32
+ if (!Element.prototype.releasePointerCapture) {
33
+ Element.prototype.releasePointerCapture = vi.fn();
34
+ }
35
+
36
+ // Mock scrollIntoView (used by Radix UI Select for scroll behavior)
37
+ if (!Element.prototype.scrollIntoView) {
38
+ Element.prototype.scrollIntoView = vi.fn();
39
+ }
package/src/theme.css ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Tailwind v4 Theme Extension
3
+ *
4
+ * Registers design token CSS variables as Tailwind utilities.
5
+ * Uses @theme (not @theme inline) to EXTEND Tailwind defaults,
6
+ * preserving classes like bg-black, text-white, etc.
7
+ *
8
+ * Import this in your app's globals.css:
9
+ * @import "@thesage/ui/theme.css";
10
+ *
11
+ * See: packages/ui/src/globals.css for CSS variable defaults
12
+ * See: packages/ui/src/providers/ThemeProvider.tsx for runtime injection
13
+ */
14
+
15
+ @theme {
16
+ /* ── Colors ─────────────────────────────────────────────────────── */
17
+ --color-background: var(--color-background);
18
+ --color-background-secondary: var(--color-background-secondary);
19
+ --color-background-tertiary: var(--color-background-tertiary);
20
+
21
+ --color-foreground: var(--color-foreground);
22
+ --color-foreground-secondary: var(--color-text-secondary);
23
+ --color-foreground-tertiary: var(--color-text-muted);
24
+
25
+ --color-primary: var(--color-primary);
26
+ --color-primary-foreground: var(--color-primary-foreground);
27
+
28
+ --color-secondary: var(--color-secondary);
29
+ --color-secondary-foreground: var(--color-secondary-foreground);
30
+
31
+ --color-accent: var(--color-accent);
32
+ --color-accent-foreground: var(--color-accent-foreground);
33
+
34
+ --color-destructive: var(--color-destructive);
35
+ --color-destructive-foreground: var(--color-destructive-foreground);
36
+
37
+ --color-muted: var(--color-muted);
38
+ --color-muted-foreground: var(--color-muted-foreground);
39
+
40
+ --color-popover: var(--color-popover);
41
+ --color-popover-foreground: var(--color-popover-foreground);
42
+
43
+ --color-card: var(--color-card);
44
+ --color-card-foreground: var(--color-card-foreground);
45
+
46
+ --color-success: var(--color-success);
47
+ --color-success-foreground: var(--color-success-foreground);
48
+
49
+ --color-warning: var(--color-warning);
50
+ --color-warning-foreground: var(--color-warning-foreground);
51
+
52
+ --color-error: var(--color-error);
53
+ --color-error-foreground: var(--color-error-foreground);
54
+
55
+ --color-info: var(--color-info);
56
+ --color-info-foreground: var(--color-info-foreground);
57
+
58
+ --color-surface: var(--color-surface);
59
+ --color-border: var(--color-border);
60
+ --color-input: var(--color-input);
61
+ --color-ring: var(--color-ring);
62
+
63
+ --color-glass: var(--color-glass);
64
+ --color-glass-border: var(--color-glass-border);
65
+
66
+ /* ── Primary Scale (populated by Customizer) ───────────────────── */
67
+ --color-primary-50: var(--color-primary-50);
68
+ --color-primary-100: var(--color-primary-100);
69
+ --color-primary-200: var(--color-primary-200);
70
+ --color-primary-300: var(--color-primary-300);
71
+ --color-primary-400: var(--color-primary-400);
72
+ --color-primary-500: var(--color-primary-500);
73
+ --color-primary-600: var(--color-primary-600);
74
+ --color-primary-700: var(--color-primary-700);
75
+ --color-primary-800: var(--color-primary-800);
76
+ --color-primary-900: var(--color-primary-900);
77
+
78
+ /* ── Shadows (theme-aware) ─────────────────────────────────────── */
79
+ --shadow-sm: var(--effect-shadow-sm);
80
+ --shadow-md: var(--effect-shadow-md);
81
+ --shadow-lg: var(--effect-shadow-lg);
82
+ --shadow-xl: var(--effect-shadow-xl);
83
+ --shadow-2xl: var(--effect-shadow-2xl);
84
+
85
+ /* ── Blur (raw values — enables blur-sm, backdrop-blur-sm, etc.) ─ */
86
+ --blur-sm: var(--blur-sm);
87
+ --blur-md: var(--blur-md);
88
+ --blur-lg: var(--blur-lg);
89
+ --blur-xl: var(--blur-xl);
90
+
91
+ /* ── Typography ─────────────────────────────────────────────────── */
92
+ --font-heading: var(--font-heading);
93
+ --font-body: var(--font-body);
94
+ --font-mono: var(--font-mono);
95
+
96
+ /* ── Border Radius ──────────────────────────────────────────────── */
97
+ --radius-lg: var(--radius);
98
+ --radius-md: calc(var(--radius) - 2px);
99
+ --radius-sm: calc(var(--radius) - 4px);
100
+ }
101
+
102
+ /* ── Keyframes ──────────────────────────────────────────────────────── */
103
+
104
+ @keyframes accordion-down {
105
+ from { height: 0; }
106
+ to { height: var(--radix-accordion-content-height); }
107
+ }
108
+
109
+ @keyframes accordion-up {
110
+ from { height: var(--radix-accordion-content-height); }
111
+ to { height: 0; }
112
+ }
113
+
114
+ @keyframes collapsible-down {
115
+ from { height: 0; }
116
+ to { height: var(--radix-collapsible-content-height); }
117
+ }
118
+
119
+ @keyframes collapsible-up {
120
+ from { height: var(--radix-collapsible-content-height); }
121
+ to { height: 0; }
122
+ }
123
+
124
+ /* ── Animation Utilities (motion-preference-aware durations) ────────── */
125
+
126
+ @utility animate-accordion-down {
127
+ animation: accordion-down var(--duration-default, 0.2s) var(--ease-default, ease-out);
128
+ }
129
+
130
+ @utility animate-accordion-up {
131
+ animation: accordion-up var(--duration-default, 0.2s) var(--ease-default, ease-out);
132
+ }
133
+
134
+ @utility animate-collapsible-down {
135
+ animation: collapsible-down var(--duration-default, 0.2s) var(--ease-default, ease-out);
136
+ }
137
+
138
+ @utility animate-collapsible-up {
139
+ animation: collapsible-up var(--duration-default, 0.2s) var(--ease-default, ease-out);
140
+ }
141
+
142
+ /* ── Font Utilities ─────────────────────────────────────────────────── */
143
+
144
+ @utility font-heading {
145
+ font-family: var(--font-heading);
146
+ }
147
+
148
+ @utility font-body {
149
+ font-family: var(--font-body);
150
+ }
151
+
152
+ @utility font-mono {
153
+ font-family: var(--font-mono);
154
+ }
155
+
156
+ @utility text-balance {
157
+ text-wrap: balance;
158
+ }
package/src/tokens.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tokens subpath export
3
+ * Allows: import { ... } from '@thesage/ui/tokens'
4
+ */
5
+
6
+ // Re-export all tokens from @thesage/tokens
7
+ export * from '@thesage/tokens';