@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,493 @@
1
+ /**
2
+ * Semantic Color Utilities
3
+ *
4
+ * Helper functions for working with the design system's color tokens
5
+ * and CSS variables.
6
+ */
7
+
8
+ /**
9
+ * Color token categories
10
+ */
11
+ export const colorTokens = {
12
+ // Background colors
13
+ background: 'var(--color-background)',
14
+ backgroundSecondary: 'var(--color-background-secondary)',
15
+ backgroundTertiary: 'var(--color-background-tertiary)',
16
+ surface: 'var(--color-surface)',
17
+
18
+ // Foreground/Text colors
19
+ foreground: 'var(--color-foreground)',
20
+ foregroundSecondary: 'var(--color-foreground-secondary)',
21
+ foregroundTertiary: 'var(--color-foreground-tertiary)',
22
+ textPrimary: 'var(--color-text-primary)',
23
+ textSecondary: 'var(--color-text-secondary)',
24
+ textMuted: 'var(--color-text-muted)',
25
+
26
+ // Brand colors
27
+ primary: 'var(--color-primary)',
28
+ primaryForeground: 'var(--color-primary-foreground)',
29
+ secondary: 'var(--color-secondary)',
30
+ secondaryForeground: 'var(--color-secondary-foreground)',
31
+ accent: 'var(--color-accent)',
32
+ accentForeground: 'var(--color-accent-foreground)',
33
+
34
+ // Semantic colors
35
+ success: 'var(--color-success)',
36
+ successForeground: 'var(--color-success-foreground)',
37
+ warning: 'var(--color-warning)',
38
+ warningForeground: 'var(--color-warning-foreground)',
39
+ error: 'var(--color-error)',
40
+ errorForeground: 'var(--color-error-foreground)',
41
+ info: 'var(--color-info)',
42
+ infoForeground: 'var(--color-info-foreground)',
43
+
44
+ // Borders
45
+ border: 'var(--color-border)',
46
+ borderSubtle: 'var(--color-border-subtle)',
47
+
48
+ // Interactive states
49
+ hover: 'var(--color-hover)',
50
+ active: 'var(--color-active)',
51
+ focus: 'var(--color-focus)',
52
+
53
+ // Links
54
+ link: 'var(--color-link)',
55
+ linkHover: 'var(--color-link-hover)',
56
+ linkHoverForeground: 'var(--color-link-hover-foreground)',
57
+ } as const;
58
+
59
+ /**
60
+ * Get CSS variable value from computed styles
61
+ *
62
+ * @param variableName - CSS variable name (with or without --)
63
+ * @param element - Element to get computed style from (defaults to document.documentElement)
64
+ * @returns The computed value of the CSS variable
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const primaryColor = getCSSVariable('--color-primary');
69
+ * // Returns: '#0066ff' (or whatever the current theme's primary color is)
70
+ * ```
71
+ */
72
+ export function getCSSVariable(
73
+ variableName: string,
74
+ element: HTMLElement = document.documentElement
75
+ ): string {
76
+ const name = variableName.startsWith('--') ? variableName : `--${variableName}`;
77
+ return getComputedStyle(element).getPropertyValue(name).trim();
78
+ }
79
+
80
+ /**
81
+ * Set CSS variable value
82
+ *
83
+ * @param variableName - CSS variable name (with or without --)
84
+ * @param value - Value to set
85
+ * @param element - Element to set the variable on (defaults to document.documentElement)
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * setCSSVariable('--color-primary', '#ff0000');
90
+ * ```
91
+ */
92
+ export function setCSSVariable(
93
+ variableName: string,
94
+ value: string,
95
+ element: HTMLElement = document.documentElement
96
+ ): void {
97
+ const name = variableName.startsWith('--') ? variableName : `--${variableName}`;
98
+ element.style.setProperty(name, value);
99
+ }
100
+
101
+ /**
102
+ * Get contrasting foreground color for a background color
103
+ *
104
+ * @param backgroundToken - Background color token name (without 'var()')
105
+ * @returns The appropriate foreground color token
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const foreground = getForegroundColor('--color-primary');
110
+ * // Returns: 'var(--color-primary-foreground)'
111
+ * ```
112
+ */
113
+ export function getForegroundColor(backgroundToken: string): string {
114
+ // Remove var() wrapper if present
115
+ const token = backgroundToken.replace(/var\(|\)/g, '');
116
+
117
+ // Map background tokens to their foreground pairs
118
+ const foregroundMap: Record<string, string> = {
119
+ '--color-primary': 'var(--color-primary-foreground)',
120
+ '--color-secondary': 'var(--color-secondary-foreground)',
121
+ '--color-accent': 'var(--color-accent-foreground)',
122
+ '--color-success': 'var(--color-success-foreground)',
123
+ '--color-warning': 'var(--color-warning-foreground)',
124
+ '--color-error': 'var(--color-error-foreground)',
125
+ '--color-info': 'var(--color-info-foreground)',
126
+ '--color-background': 'var(--color-foreground)',
127
+ '--color-surface': 'var(--color-text-primary)',
128
+ };
129
+
130
+ return foregroundMap[token] || 'var(--color-text-primary)';
131
+ }
132
+
133
+ /**
134
+ * Semantic color groups for different use cases
135
+ */
136
+ export const semanticColors = {
137
+ /**
138
+ * Status colors for indicating states
139
+ */
140
+ status: {
141
+ success: {
142
+ bg: colorTokens.success,
143
+ fg: colorTokens.successForeground,
144
+ },
145
+ warning: {
146
+ bg: colorTokens.warning,
147
+ fg: colorTokens.warningForeground,
148
+ },
149
+ error: {
150
+ bg: colorTokens.error,
151
+ fg: colorTokens.errorForeground,
152
+ },
153
+ info: {
154
+ bg: colorTokens.info,
155
+ fg: colorTokens.infoForeground,
156
+ },
157
+ },
158
+
159
+ /**
160
+ * Brand colors for primary UI elements
161
+ */
162
+ brand: {
163
+ primary: {
164
+ bg: colorTokens.primary,
165
+ fg: colorTokens.primaryForeground,
166
+ },
167
+ secondary: {
168
+ bg: colorTokens.secondary,
169
+ fg: colorTokens.secondaryForeground,
170
+ },
171
+ accent: {
172
+ bg: colorTokens.accent,
173
+ fg: colorTokens.accentForeground,
174
+ },
175
+ },
176
+
177
+ /**
178
+ * Interactive state colors
179
+ */
180
+ interactive: {
181
+ default: {
182
+ bg: colorTokens.background,
183
+ fg: colorTokens.foreground,
184
+ },
185
+ hover: {
186
+ bg: colorTokens.hover,
187
+ fg: colorTokens.foreground,
188
+ },
189
+ active: {
190
+ bg: colorTokens.active,
191
+ fg: colorTokens.foreground,
192
+ },
193
+ focus: {
194
+ border: colorTokens.focus,
195
+ },
196
+ },
197
+ } as const;
198
+
199
+ /**
200
+ * Helper to create color pairs (background + foreground)
201
+ *
202
+ * @param type - Semantic color type
203
+ * @returns Object with bg and fg (and optionally border)
204
+ *
205
+ * @example
206
+ * ```tsx
207
+ * const errorColors = getSemanticColorPair('error');
208
+ * <div style={{
209
+ * backgroundColor: errorColors.bg,
210
+ * color: errorColors.fg
211
+ * }}>
212
+ * Error message
213
+ * </div>
214
+ * ```
215
+ */
216
+ export function getSemanticColorPair(
217
+ type: 'success' | 'warning' | 'error' | 'info' | 'primary' | 'secondary' | 'accent'
218
+ ): { bg: string; fg: string } {
219
+ if (type === 'primary' || type === 'secondary' || type === 'accent') {
220
+ return semanticColors.brand[type];
221
+ }
222
+ return semanticColors.status[type];
223
+ }
224
+
225
+ /**
226
+ * Convert hex color to RGB values
227
+ *
228
+ * @param hex - Hex color string (with or without #)
229
+ * @returns Object with r, g, b values (0-255)
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * const rgb = hexToRgb('#0066ff');
234
+ * // Returns: { r: 0, g: 102, b: 255 }
235
+ * ```
236
+ */
237
+ export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
238
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
239
+ return result
240
+ ? {
241
+ r: parseInt(result[1], 16),
242
+ g: parseInt(result[2], 16),
243
+ b: parseInt(result[3], 16),
244
+ }
245
+ : null;
246
+ }
247
+
248
+ /**
249
+ * Calculate relative luminance of a color (WCAG formula)
250
+ *
251
+ * @param r - Red value (0-255)
252
+ * @param g - Green value (0-255)
253
+ * @param b - Blue value (0-255)
254
+ * @returns Relative luminance (0-1)
255
+ */
256
+ export function getLuminance(r: number, g: number, b: number): number {
257
+ const [rs, gs, bs] = [r, g, b].map((c) => {
258
+ const srgb = c / 255;
259
+ return srgb <= 0.03928 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
260
+ });
261
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
262
+ }
263
+
264
+ /**
265
+ * Calculate contrast ratio between two colors (WCAG formula)
266
+ *
267
+ * @param hex1 - First color (hex)
268
+ * @param hex2 - Second color (hex)
269
+ * @returns Contrast ratio (1-21)
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * const ratio = getContrastRatio('#ffffff', '#000000');
274
+ * // Returns: 21 (maximum contrast)
275
+ * ```
276
+ */
277
+ export function getContrastRatio(hex1: string, hex2: string): number {
278
+ const rgb1 = hexToRgb(hex1);
279
+ const rgb2 = hexToRgb(hex2);
280
+
281
+ if (!rgb1 || !rgb2) return 0;
282
+
283
+ const lum1 = getLuminance(rgb1.r, rgb1.g, rgb1.b);
284
+ const lum2 = getLuminance(rgb2.r, rgb2.g, rgb2.b);
285
+
286
+ const lighter = Math.max(lum1, lum2);
287
+ const darker = Math.min(lum1, lum2);
288
+
289
+ return (lighter + 0.05) / (darker + 0.05);
290
+ }
291
+
292
+ /**
293
+ * Check if color pair meets WCAG AA contrast requirements
294
+ *
295
+ * @param foreground - Foreground color (hex)
296
+ * @param background - Background color (hex)
297
+ * @param level - WCAG level ('AA' or 'AAA')
298
+ * @param size - Text size ('normal' or 'large')
299
+ * @returns true if contrast ratio meets requirements
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * const isAccessible = meetsContrastRequirements('#000000', '#ffffff', 'AA', 'normal');
304
+ * // Returns: true
305
+ * ```
306
+ */
307
+ export function meetsContrastRequirements(
308
+ foreground: string,
309
+ background: string,
310
+ level: 'AA' | 'AAA' = 'AA',
311
+ size: 'normal' | 'large' = 'normal'
312
+ ): boolean {
313
+ const ratio = getContrastRatio(foreground, background);
314
+
315
+ const requirements = {
316
+ AA: { normal: 4.5, large: 3 },
317
+ AAA: { normal: 7, large: 4.5 },
318
+ };
319
+
320
+ return ratio >= requirements[level][size];
321
+ }
322
+
323
+ /**
324
+ * Convert hex to HSL for manipulation
325
+ */
326
+ export function hexToHSL(hex: string): { h: number; s: number; l: number } {
327
+ const rgb = hexToRgb(hex);
328
+ if (!rgb) return { h: 0, s: 0, l: 0 };
329
+
330
+ const r = rgb.r / 255;
331
+ const g = rgb.g / 255;
332
+ const b = rgb.b / 255;
333
+
334
+ const max = Math.max(r, g, b);
335
+ const min = Math.min(r, g, b);
336
+ let h = 0, s = 0; const l = (max + min) / 2;
337
+
338
+ if (max !== min) {
339
+ const d = max - min;
340
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
341
+
342
+ switch (max) {
343
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
344
+ case g: h = ((b - r) / d + 2) / 6; break;
345
+ case b: h = ((r - g) / d + 4) / 6; break;
346
+ }
347
+ }
348
+
349
+ return {
350
+ h: Math.round(h * 360),
351
+ s: Math.round(s * 100),
352
+ l: Math.round(l * 100),
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Convert HSL to hex
358
+ */
359
+ export function hslToHex(h: number, s: number, l: number): string {
360
+ h = h / 360;
361
+ s = s / 100;
362
+ l = l / 100;
363
+
364
+ let r, g, b;
365
+
366
+ if (s === 0) {
367
+ r = g = b = l;
368
+ } else {
369
+ const hue2rgb = (p: number, q: number, t: number) => {
370
+ if (t < 0) t += 1;
371
+ if (t > 1) t -= 1;
372
+ if (t < 1/6) return p + (q - p) * 6 * t;
373
+ if (t < 1/2) return q;
374
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
375
+ return p;
376
+ };
377
+
378
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
379
+ const p = 2 * l - q;
380
+
381
+ r = hue2rgb(p, q, h + 1/3);
382
+ g = hue2rgb(p, q, h);
383
+ b = hue2rgb(p, q, h - 1/3);
384
+ }
385
+
386
+ const toHex = (x: number) => {
387
+ const hex = Math.round(x * 255).toString(16);
388
+ return hex.length === 1 ? '0' + hex : hex;
389
+ };
390
+
391
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
392
+ }
393
+
394
+ /**
395
+ * Adjust lightness of a hex color
396
+ * @param hex - Input color
397
+ * @param percent - Amount to adjust (-100 to 100)
398
+ */
399
+ export function adjustLightness(hex: string, percent: number): string {
400
+ const hsl = hexToHSL(hex);
401
+ const newL = Math.max(0, Math.min(100, hsl.l + percent));
402
+ return hslToHex(hsl.h, hsl.s, newL);
403
+ }
404
+
405
+ /**
406
+ * Adjust saturation of a hex color
407
+ * @param hex - Input color
408
+ * @param percent - Amount to adjust (-100 to 100)
409
+ */
410
+ export function adjustSaturation(hex: string, percent: number): string {
411
+ const hsl = hexToHSL(hex);
412
+ const newS = Math.max(0, Math.min(100, hsl.s + percent));
413
+ return hslToHex(hsl.h, newS, hsl.l);
414
+ }
415
+
416
+ /**
417
+ * Rotate hue of a hex color
418
+ * @param hex - Input color
419
+ * @param degrees - Degrees to rotate (0-360)
420
+ */
421
+ export function rotateHue(hex: string, degrees: number): string {
422
+ const hsl = hexToHSL(hex);
423
+ const newH = (hsl.h + degrees) % 360;
424
+ return hslToHex(newH, hsl.s, hsl.l);
425
+ }
426
+
427
+ /**
428
+ * Add opacity to a hex color (returns rgba CSS value)
429
+ * @param hex - Input color
430
+ * @param opacity - Opacity (0-1)
431
+ */
432
+ export function adjustOpacity(hex: string, opacity: number): string {
433
+ const rgb = hexToRgb(hex);
434
+ if (!rgb) return hex;
435
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
436
+ }
437
+
438
+ /**
439
+ * Get optimal foreground color (white or black) for a background
440
+ * Uses WCAG contrast formula
441
+ */
442
+ export function getOptimalForeground(
443
+ bgHex: string,
444
+ whiteHex: string = '#ffffff',
445
+ blackHex: string = '#000000'
446
+ ): string {
447
+ const whiteRatio = getContrastRatio(bgHex, whiteHex);
448
+ const blackRatio = getContrastRatio(bgHex, blackHex);
449
+
450
+ return whiteRatio > blackRatio ? whiteHex : blackHex;
451
+ }
452
+
453
+ /**
454
+ * Generate a complete tint/shade scale (like Tailwind)
455
+ * Returns 50, 100, 200, ..., 900 variants
456
+ */
457
+ export function generateColorScale(baseHex: string): Record<number, string> {
458
+ const hsl = hexToHSL(baseHex);
459
+
460
+ return {
461
+ 50: hslToHex(hsl.h, Math.max(hsl.s - 10, 20), 95),
462
+ 100: hslToHex(hsl.h, Math.max(hsl.s - 5, 30), 90),
463
+ 200: hslToHex(hsl.h, hsl.s, 80),
464
+ 300: hslToHex(hsl.h, hsl.s, 70),
465
+ 400: hslToHex(hsl.h, hsl.s, 60),
466
+ 500: baseHex, // Base color
467
+ 600: hslToHex(hsl.h, Math.min(hsl.s + 5, 100), 45),
468
+ 700: hslToHex(hsl.h, Math.min(hsl.s + 10, 100), 35),
469
+ 800: hslToHex(hsl.h, Math.min(hsl.s + 15, 100), 25),
470
+ 900: hslToHex(hsl.h, Math.min(hsl.s + 20, 100), 15),
471
+ };
472
+ }
473
+
474
+ /**
475
+ * Color utilities for common operations
476
+ */
477
+ export const colorUtils = {
478
+ getCSSVariable,
479
+ setCSSVariable,
480
+ getForegroundColor,
481
+ getSemanticColorPair,
482
+ hexToRgb,
483
+ hexToHSL,
484
+ hslToHex,
485
+ adjustLightness,
486
+ adjustSaturation,
487
+ rotateHue,
488
+ adjustOpacity,
489
+ getOptimalForeground,
490
+ generateColorScale,
491
+ getContrastRatio,
492
+ meetsContrastRequirements,
493
+ } as const;