@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.
- package/.claude/CLAUDE.md +239 -0
- package/README.md +161 -0
- package/dist/cli.mjs +151 -0
- package/dist/dates.d.mts +20 -0
- package/dist/dates.d.ts +20 -0
- package/dist/dates.js +240 -0
- package/dist/dates.js.map +1 -0
- package/dist/dates.mjs +203 -0
- package/dist/dates.mjs.map +1 -0
- package/dist/dnd.d.mts +126 -0
- package/dist/dnd.d.ts +126 -0
- package/dist/dnd.js +274 -0
- package/dist/dnd.js.map +1 -0
- package/dist/dnd.mjs +250 -0
- package/dist/dnd.mjs.map +1 -0
- package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
- package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
- package/dist/forms.d.mts +38 -0
- package/dist/forms.d.ts +38 -0
- package/dist/forms.js +198 -0
- package/dist/forms.js.map +1 -0
- package/dist/forms.mjs +159 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/hooks-1b8WaQf1.d.mts +225 -0
- package/dist/hooks-CKW8vE9H.d.ts +225 -0
- package/dist/hooks.d.mts +3 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +971 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.mjs +943 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index-DscTIrZ2.d.mts +29 -0
- package/dist/index-DscTIrZ2.d.ts +29 -0
- package/dist/index.d.mts +3382 -0
- package/dist/index.d.ts +3382 -0
- package/dist/index.js +15146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14802 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers-CXPDMsl7.d.mts +30 -0
- package/dist/providers-Dn_Msjvz.d.ts +30 -0
- package/dist/providers.d.mts +3 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +1885 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +1859 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/tables.d.mts +10 -0
- package/dist/tables.d.ts +10 -0
- package/dist/tables.js +248 -0
- package/dist/tables.js.map +1 -0
- package/dist/tables.mjs +218 -0
- package/dist/tables.mjs.map +1 -0
- package/dist/tokens.d.mts +1065 -0
- package/dist/tokens.d.ts +1065 -0
- package/dist/tokens.js +2637 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tokens.mjs +2555 -0
- package/dist/tokens.mjs.map +1 -0
- package/dist/utils-CIIM7dAC.d.ts +986 -0
- package/dist/utils-Cs04sxth.d.mts +986 -0
- package/dist/utils.d.mts +4 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +874 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +806 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/validation-Bj1ye-v_.d.mts +114 -0
- package/dist/validation-Bj1ye-v_.d.ts +114 -0
- package/dist/webgl.d.mts +104 -0
- package/dist/webgl.d.ts +104 -0
- package/dist/webgl.js +226 -0
- package/dist/webgl.js.map +1 -0
- package/dist/webgl.mjs +195 -0
- package/dist/webgl.mjs.map +1 -0
- package/package.json +267 -0
- package/src/cli.ts +206 -0
- package/src/component-registry.ts +183 -0
- package/src/components/actions/Button.test.tsx +61 -0
- package/src/components/actions/Button.tsx +70 -0
- package/src/components/actions/Link.tsx +78 -0
- package/src/components/actions/Magnetic.tsx +68 -0
- package/src/components/actions/Toggle.test.tsx +40 -0
- package/src/components/actions/Toggle.tsx +47 -0
- package/src/components/actions/ToggleGroup.tsx +70 -0
- package/src/components/actions/index.ts +5 -0
- package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
- package/src/components/backgrounds/OrbBackground.tsx +424 -0
- package/src/components/backgrounds/WarpBackground.tsx +358 -0
- package/src/components/backgrounds/index.ts +3 -0
- package/src/components/blocks/Hero.tsx +142 -0
- package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
- package/src/components/cursor/SplashCursor.tsx +1315 -0
- package/src/components/cursor/TargetCursor.tsx +187 -0
- package/src/components/cursor/index.ts +2 -0
- package/src/components/data-display/AspectImage.tsx +73 -0
- package/src/components/data-display/Avatar.test.tsx +35 -0
- package/src/components/data-display/Avatar.tsx +55 -0
- package/src/components/data-display/Badge.test.tsx +43 -0
- package/src/components/data-display/Badge.tsx +84 -0
- package/src/components/data-display/Brand.tsx +123 -0
- package/src/components/data-display/Calendar.tsx +70 -0
- package/src/components/data-display/Card.test.tsx +92 -0
- package/src/components/data-display/Card.tsx +115 -0
- package/src/components/data-display/Code.tsx +210 -0
- package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
- package/src/components/data-display/DataTable.tsx +119 -0
- package/src/components/data-display/DescriptionList.tsx +41 -0
- package/src/components/data-display/GitHubIcon.tsx +44 -0
- package/src/components/data-display/Heading.test.tsx +36 -0
- package/src/components/data-display/Heading.tsx +83 -0
- package/src/components/data-display/StatCard.tsx +195 -0
- package/src/components/data-display/Table.tsx +133 -0
- package/src/components/data-display/Text.test.tsx +48 -0
- package/src/components/data-display/Text.tsx +144 -0
- package/src/components/data-display/Timeline.tsx +194 -0
- package/src/components/data-display/TreeView.tsx +226 -0
- package/src/components/data-display/Typewriter.tsx +119 -0
- package/src/components/data-display/VariableWeightText.tsx +130 -0
- package/src/components/data-display/index.ts +19 -0
- package/src/components/feedback/Alert.test.tsx +44 -0
- package/src/components/feedback/Alert.tsx +65 -0
- package/src/components/feedback/EmptyState.tsx +113 -0
- package/src/components/feedback/Progress.test.tsx +60 -0
- package/src/components/feedback/Progress.tsx +30 -0
- package/src/components/feedback/ProgressBar.tsx +158 -0
- package/src/components/feedback/Skeleton.test.tsx +39 -0
- package/src/components/feedback/Skeleton.tsx +45 -0
- package/src/components/feedback/Sonner.tsx +28 -0
- package/src/components/feedback/Spinner.test.tsx +33 -0
- package/src/components/feedback/Spinner.tsx +99 -0
- package/src/components/feedback/Stepper.tsx +307 -0
- package/src/components/feedback/Toast/Toast.tsx +243 -0
- package/src/components/feedback/Toast/index.ts +2 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/forms/Checkbox.test.tsx +40 -0
- package/src/components/forms/Checkbox.tsx +31 -0
- package/src/components/forms/ColorPicker.tsx +118 -0
- package/src/components/forms/Combobox.tsx +96 -0
- package/src/components/forms/DragDrop.tsx +440 -0
- package/src/components/forms/FileUpload.tsx +252 -0
- package/src/components/forms/FilterButton.tsx +65 -0
- package/src/components/forms/Form.tsx +197 -0
- package/src/components/forms/Input.test.tsx +46 -0
- package/src/components/forms/Input.tsx +43 -0
- package/src/components/forms/InputOTP.tsx +81 -0
- package/src/components/forms/Label.test.tsx +20 -0
- package/src/components/forms/Label.tsx +25 -0
- package/src/components/forms/RadioGroup.tsx +51 -0
- package/src/components/forms/SearchBar.tsx +215 -0
- package/src/components/forms/Select.test.tsx +118 -0
- package/src/components/forms/Select.tsx +274 -0
- package/src/components/forms/Slider.tsx +29 -0
- package/src/components/forms/Switch.test.tsx +76 -0
- package/src/components/forms/Switch.tsx +30 -0
- package/src/components/forms/TextField.tsx +152 -0
- package/src/components/forms/Textarea.test.tsx +41 -0
- package/src/components/forms/Textarea.tsx +29 -0
- package/src/components/forms/ThemeSwitcher.tsx +290 -0
- package/src/components/forms/ThemeToggle.tsx +151 -0
- package/src/components/forms/index.ts +19 -0
- package/src/components/layout/Accordion.test.tsx +66 -0
- package/src/components/layout/Accordion.tsx +64 -0
- package/src/components/layout/AspectRatio.tsx +7 -0
- package/src/components/layout/Carousel.tsx +277 -0
- package/src/components/layout/Collapsible.test.tsx +40 -0
- package/src/components/layout/Collapsible.tsx +31 -0
- package/src/components/layout/Container.test.tsx +45 -0
- package/src/components/layout/Container.tsx +99 -0
- package/src/components/layout/CustomizerPanel.tsx +400 -0
- package/src/components/layout/DatePicker.tsx +57 -0
- package/src/components/layout/Footer/Footer.tsx +175 -0
- package/src/components/layout/Footer/index.ts +2 -0
- package/src/components/layout/GlassSurface.tsx +82 -0
- package/src/components/layout/Grid.test.tsx +31 -0
- package/src/components/layout/Grid.tsx +130 -0
- package/src/components/layout/Header/Header.tsx +450 -0
- package/src/components/layout/Header/index.ts +2 -0
- package/src/components/layout/PageLayout.tsx +180 -0
- package/src/components/layout/PageTemplate.tsx +158 -0
- package/src/components/layout/Resizable.tsx +48 -0
- package/src/components/layout/ScrollArea.tsx +53 -0
- package/src/components/layout/Separator.test.tsx +28 -0
- package/src/components/layout/Separator.tsx +29 -0
- package/src/components/layout/Sidebar.tsx +171 -0
- package/src/components/layout/Stack.test.tsx +41 -0
- package/src/components/layout/Stack.tsx +89 -0
- package/src/components/layout/glass-surface.css +60 -0
- package/src/components/layout/index.ts +18 -0
- package/src/components/motion/AnimatedBeam.tsx +159 -0
- package/src/components/navigation/Breadcrumb.test.tsx +57 -0
- package/src/components/navigation/Breadcrumb.tsx +119 -0
- package/src/components/navigation/Breadcrumbs.tsx +221 -0
- package/src/components/navigation/Command.tsx +159 -0
- package/src/components/navigation/Menubar.tsx +115 -0
- package/src/components/navigation/NavLink.tsx +55 -0
- package/src/components/navigation/NavigationMenu.tsx +125 -0
- package/src/components/navigation/Pagination.tsx +121 -0
- package/src/components/navigation/SecondaryNav.tsx +100 -0
- package/src/components/navigation/Tabs.test.tsx +47 -0
- package/src/components/navigation/Tabs.tsx +60 -0
- package/src/components/navigation/TertiaryNav.tsx +90 -0
- package/src/components/navigation/index.ts +10 -0
- package/src/components/overlays/AlertDialog.test.tsx +69 -0
- package/src/components/overlays/AlertDialog.tsx +166 -0
- package/src/components/overlays/ContextMenu.tsx +243 -0
- package/src/components/overlays/Dialog.test.tsx +79 -0
- package/src/components/overlays/Dialog.tsx +158 -0
- package/src/components/overlays/Drawer.tsx +128 -0
- package/src/components/overlays/Dropdown.tsx +253 -0
- package/src/components/overlays/DropdownMenu.tsx +242 -0
- package/src/components/overlays/HoverCard.tsx +32 -0
- package/src/components/overlays/Modal.tsx +250 -0
- package/src/components/overlays/NotificationCenter.tsx +364 -0
- package/src/components/overlays/Popover.test.tsx +40 -0
- package/src/components/overlays/Popover.tsx +46 -0
- package/src/components/overlays/Sheet.tsx +163 -0
- package/src/components/overlays/Tooltip.test.tsx +33 -0
- package/src/components/overlays/Tooltip.tsx +32 -0
- package/src/components/overlays/index.ts +12 -0
- package/src/dates.ts +2 -0
- package/src/dnd.ts +1 -0
- package/src/forms.ts +1 -0
- package/src/globals.css +187 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useForm.ts +247 -0
- package/src/hooks/useMotionPreference.test.ts +102 -0
- package/src/hooks/useMotionPreference.ts +78 -0
- package/src/hooks/useTheme.ts +58 -0
- package/src/hooks.ts +9 -0
- package/src/index.ts +168 -0
- package/src/lib/animations.ts +356 -0
- package/src/lib/breadcrumbs.ts +94 -0
- package/src/lib/colors.ts +493 -0
- package/src/lib/store/customizer.ts +482 -0
- package/src/lib/store/index.ts +3 -0
- package/src/lib/store/theme.ts +55 -0
- package/src/lib/syntax-parser/index.ts +50 -0
- package/src/lib/syntax-parser/patterns.ts +64 -0
- package/src/lib/syntax-parser/tokenizer.ts +117 -0
- package/src/lib/syntax-parser/types.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +204 -0
- package/src/lib/webgl/Color.ts +11 -0
- package/src/lib/webgl/Mesh.ts +41 -0
- package/src/lib/webgl/Program.ts +118 -0
- package/src/lib/webgl/Renderer.ts +51 -0
- package/src/lib/webgl/Triangle.ts +27 -0
- package/src/lib/webgl/Vec3.ts +18 -0
- package/src/lib/webgl/index.ts +13 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/providers/ThemeProvider.tsx +461 -0
- package/src/providers/index.ts +1 -0
- package/src/providers.ts +7 -0
- package/src/tables.ts +1 -0
- package/src/test/setup.ts +39 -0
- package/src/theme.css +158 -0
- package/src/tokens.ts +7 -0
- package/src/utils.ts +12 -0
- 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;
|