@shohojdhara/atomix 0.6.2 → 0.6.4
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/README.md +510 -106
- package/dist/atomix.css +26 -22
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +5 -5
- package/dist/atomix.min.css.map +1 -1
- package/dist/atomix.umd.js +1 -1
- package/dist/atomix.umd.js.map +1 -1
- package/dist/atomix.umd.min.js +1 -1
- package/dist/charts.d.ts +2 -2
- package/dist/charts.js +251 -131
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +5 -39
- package/dist/core.js +254 -137
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +2 -1
- package/dist/forms.js +342 -177
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +254 -135
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +141 -159
- package/dist/index.esm.js +348 -195
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +348 -195
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +14 -6
- package/dist/theme.js +2 -9
- package/dist/theme.js.map +1 -1
- package/package.json +26 -26
- package/src/components/AtomixGlass/AtomixGlass.tsx +1 -1
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +8 -1
- package/src/components/AtomixGlass/glass-utils.ts +29 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +32 -1
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/Button.tsx +6 -5
- package/src/components/Card/Card.tsx +2 -2
- package/src/components/Dropdown/Dropdown.tsx +1 -0
- package/src/components/EdgePanel/EdgePanel.tsx +1 -3
- package/src/components/Form/Select.test.tsx +75 -0
- package/src/components/Form/Select.tsx +348 -252
- package/src/components/Form/SelectOption.tsx +16 -10
- package/src/components/index.ts +1 -1
- package/src/layouts/CssGrid/index.ts +1 -0
- package/src/lib/composables/useAtomixGlass.ts +238 -138
- package/src/lib/composables/useAtomixGlassStyles.ts +201 -149
- package/src/lib/constants/components.ts +50 -35
- package/src/lib/theme/config/configLoader.ts +1 -1
- package/src/lib/theme/test/testTheme.ts +2 -2
- package/src/lib/theme/utils/themeUtils.ts +98 -110
- package/src/lib/types/components.ts +21 -63
- package/src/styles/01-settings/_settings.atomix-glass.scss +5 -5
- package/src/styles/01-settings/_settings.spacing.scss +6 -1
- package/src/styles/03-generic/_generic.reset.scss +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +20 -29
- package/src/styles/06-components/_components.data-table.scss +5 -4
- package/src/styles/06-components/_components.dynamic-background.scss +9 -8
- package/src/styles/06-components/_components.footer.scss +8 -7
- package/src/styles/06-components/_components.hero.scss +2 -2
- package/src/styles/06-components/_components.messages.scss +16 -16
- package/src/styles/06-components/_components.select.scss +15 -2
- package/src/styles/06-components/_components.upload.scss +3 -3
- package/CHANGELOG.md +0 -165
|
@@ -219,7 +219,7 @@ export async function loadThemeFromConfig(options?: { configPath?: string; requi
|
|
|
219
219
|
const req = typeof require !== 'undefined' ? require : undefined;
|
|
220
220
|
if (!req) throw new Error('require is not available');
|
|
221
221
|
const { loadAtomixConfig: loader } = req('../../config/loader');
|
|
222
|
-
loadAtomixConfig = loader;
|
|
222
|
+
const loadAtomixConfig: any = loader;
|
|
223
223
|
config = loadAtomixConfig({ configPath: options?.configPath, required: options?.required !== false });
|
|
224
224
|
} catch (error) {
|
|
225
225
|
// If loadAtomixConfig is not available (e.g., in browser bundle), provide helpful error
|
|
@@ -9,7 +9,7 @@ import type { DesignTokens } from '../tokens/tokens';
|
|
|
9
9
|
import type { Theme, ThemeOptions } from '../types';
|
|
10
10
|
import { createTheme } from '../core/createTheme';
|
|
11
11
|
import { createThemeObject } from '../core/createThemeObject';
|
|
12
|
-
import { themeToDesignTokens
|
|
12
|
+
import { themeToDesignTokens } from '../adapters/themeAdapter';
|
|
13
13
|
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// Test Theme 1: Using DesignTokens (Recommended - Flat Structure)
|
|
@@ -311,7 +311,7 @@ export function testThemeAdapter(): {
|
|
|
311
311
|
} {
|
|
312
312
|
const theme = createTestThemeObject();
|
|
313
313
|
const tokens = themeToDesignTokens(theme);
|
|
314
|
-
const fullTokens =
|
|
314
|
+
const fullTokens = themeToDesignTokens(theme);
|
|
315
315
|
|
|
316
316
|
return {
|
|
317
317
|
theme,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme Utilities
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Helper functions for common theme operations including:
|
|
5
5
|
* - Theme switching (dark/light mode)
|
|
6
6
|
* - Theme persistence (localStorage)
|
|
@@ -13,6 +13,8 @@ import type { DesignTokens } from '../tokens';
|
|
|
13
13
|
import type { SpacingFunction, SpacingOptions } from '../types';
|
|
14
14
|
import { injectCSS, removeCSS } from '../utils/injectCSS';
|
|
15
15
|
import { deepMerge } from '../core/composeTheme';
|
|
16
|
+
import { getSystemTheme } from './domUtils';
|
|
17
|
+
export { getSystemTheme };
|
|
16
18
|
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Type Definitions
|
|
@@ -44,28 +46,25 @@ export interface ThemePersistenceOptions {
|
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Switch between light and dark themes
|
|
47
|
-
*
|
|
49
|
+
*
|
|
48
50
|
* Automatically toggles a class on the root element and persists the choice.
|
|
49
|
-
*
|
|
51
|
+
*
|
|
50
52
|
* @param mode - Theme mode ('light', 'dark', or 'system')
|
|
51
53
|
* @param options - Configuration options
|
|
52
|
-
*
|
|
54
|
+
*
|
|
53
55
|
* @example
|
|
54
56
|
* ```typescript
|
|
55
57
|
* import { switchTheme } from '@shohojdhara/atomix/theme/utils';
|
|
56
|
-
*
|
|
58
|
+
*
|
|
57
59
|
* // Switch to dark mode
|
|
58
60
|
* switchTheme('dark');
|
|
59
|
-
*
|
|
61
|
+
*
|
|
60
62
|
* // Toggle between light/dark
|
|
61
63
|
* const current = getCurrentTheme();
|
|
62
64
|
* switchTheme(current === 'dark' ? 'light' : 'dark');
|
|
63
65
|
* ```
|
|
64
66
|
*/
|
|
65
|
-
export function switchTheme(
|
|
66
|
-
mode: ThemeMode,
|
|
67
|
-
options: ThemeSwitcherOptions = {}
|
|
68
|
-
): void {
|
|
67
|
+
export function switchTheme(mode: ThemeMode, options: ThemeSwitcherOptions = {}): void {
|
|
69
68
|
const {
|
|
70
69
|
selector = ':root',
|
|
71
70
|
storageKey = 'atomix-theme',
|
|
@@ -84,7 +83,7 @@ export function switchTheme(
|
|
|
84
83
|
if (enableTransition) {
|
|
85
84
|
const htmlRoot = root as HTMLElement;
|
|
86
85
|
htmlRoot.style.transition = `all ${transitionDuration}ms ease-in-out`;
|
|
87
|
-
|
|
86
|
+
|
|
88
87
|
// Remove transition after it completes
|
|
89
88
|
setTimeout(() => {
|
|
90
89
|
htmlRoot.style.transition = '';
|
|
@@ -102,17 +101,19 @@ export function switchTheme(
|
|
|
102
101
|
persistTheme(resolvedMode, { storageKey });
|
|
103
102
|
|
|
104
103
|
// Dispatch custom event for listeners
|
|
105
|
-
window.dispatchEvent(
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
window.dispatchEvent(
|
|
105
|
+
new CustomEvent('atomix-theme-change', {
|
|
106
|
+
detail: { mode: resolvedMode },
|
|
107
|
+
})
|
|
108
|
+
);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
/**
|
|
111
112
|
* Toggle between light and dark themes
|
|
112
|
-
*
|
|
113
|
+
*
|
|
113
114
|
* @param options - Configuration options
|
|
114
115
|
* @returns The new theme mode
|
|
115
|
-
*
|
|
116
|
+
*
|
|
116
117
|
* @example
|
|
117
118
|
* ```typescript
|
|
118
119
|
* const newMode = toggleTheme();
|
|
@@ -128,56 +129,44 @@ export function toggleTheme(options: ThemeSwitcherOptions = {}): ThemeMode {
|
|
|
128
129
|
|
|
129
130
|
/**
|
|
130
131
|
* Get current theme mode
|
|
131
|
-
*
|
|
132
|
+
*
|
|
132
133
|
* @param storageKey - Storage key (default: 'atomix-theme')
|
|
133
134
|
* @returns Current theme mode or 'light' if not set
|
|
134
135
|
*/
|
|
135
136
|
export function getCurrentTheme(storageKey: string = 'atomix-theme'): ThemeMode {
|
|
136
137
|
if (typeof window === 'undefined') return 'light';
|
|
137
|
-
|
|
138
|
+
|
|
138
139
|
const stored = localStorage.getItem(storageKey);
|
|
139
140
|
return (stored as ThemeMode) || 'light';
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
/**
|
|
143
|
-
* Get system theme preference
|
|
144
|
-
*
|
|
145
|
-
* @returns 'dark' if system prefers dark mode, 'light' otherwise
|
|
146
|
-
*/
|
|
147
|
-
export function getSystemTheme(): ThemeMode {
|
|
148
|
-
if (typeof window === 'undefined') return 'light';
|
|
149
|
-
|
|
150
|
-
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
151
|
-
return prefersDark ? 'dark' : 'light';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
143
|
/**
|
|
155
144
|
* Initialize theme based on saved preference or system preference
|
|
156
|
-
*
|
|
145
|
+
*
|
|
157
146
|
* Call this once at app startup.
|
|
158
|
-
*
|
|
147
|
+
*
|
|
159
148
|
* @param options - Configuration options
|
|
160
149
|
* @returns The initialized theme mode
|
|
161
|
-
*
|
|
150
|
+
*
|
|
162
151
|
* @example
|
|
163
152
|
* ```typescript
|
|
164
153
|
* // In your app entry point
|
|
165
154
|
* import { initializeTheme } from '@shohojdhara/atomix/theme/utils';
|
|
166
|
-
*
|
|
155
|
+
*
|
|
167
156
|
* const theme = initializeTheme();
|
|
168
157
|
* console.log('Theme initialized:', theme);
|
|
169
158
|
* ```
|
|
170
159
|
*/
|
|
171
160
|
export function initializeTheme(options: ThemeSwitcherOptions = {}): ThemeMode {
|
|
172
161
|
const saved = getCurrentTheme(options.storageKey);
|
|
173
|
-
|
|
162
|
+
|
|
174
163
|
// If no saved preference, use system preference
|
|
175
164
|
if (!saved || saved === 'system') {
|
|
176
165
|
const system = getSystemTheme();
|
|
177
166
|
switchTheme(system, options);
|
|
178
167
|
return system;
|
|
179
168
|
}
|
|
180
|
-
|
|
169
|
+
|
|
181
170
|
// Use saved preference
|
|
182
171
|
switchTheme(saved, options);
|
|
183
172
|
return saved;
|
|
@@ -185,36 +174,36 @@ export function initializeTheme(options: ThemeSwitcherOptions = {}): ThemeMode {
|
|
|
185
174
|
|
|
186
175
|
/**
|
|
187
176
|
* Listen for system theme changes
|
|
188
|
-
*
|
|
177
|
+
*
|
|
189
178
|
* @param callback - Function to call when system theme changes
|
|
190
179
|
* @returns Cleanup function to stop listening
|
|
191
|
-
*
|
|
180
|
+
*
|
|
192
181
|
* @example
|
|
193
182
|
* ```typescript
|
|
194
183
|
* const cleanup = listenToSystemTheme((mode) => {
|
|
195
184
|
* console.log('System theme changed to:', mode);
|
|
196
185
|
* switchTheme(mode);
|
|
197
186
|
* });
|
|
198
|
-
*
|
|
187
|
+
*
|
|
199
188
|
* // Later, when component unmounts
|
|
200
189
|
* cleanup();
|
|
201
190
|
* ```
|
|
202
191
|
*/
|
|
203
192
|
export function listenToSystemTheme(callback: (mode: ThemeMode) => void): () => void {
|
|
204
193
|
if (typeof window === 'undefined') return () => {};
|
|
205
|
-
|
|
194
|
+
|
|
206
195
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
207
|
-
|
|
196
|
+
|
|
208
197
|
const handler = (e: MediaQueryListEvent) => {
|
|
209
198
|
callback(e.matches ? 'dark' : 'light');
|
|
210
199
|
};
|
|
211
|
-
|
|
200
|
+
|
|
212
201
|
// Modern browsers
|
|
213
202
|
if (mediaQuery.addEventListener) {
|
|
214
203
|
mediaQuery.addEventListener('change', handler);
|
|
215
204
|
return () => mediaQuery.removeEventListener('change', handler);
|
|
216
205
|
}
|
|
217
|
-
|
|
206
|
+
|
|
218
207
|
// Fallback for older browsers
|
|
219
208
|
mediaQuery.addListener(handler);
|
|
220
209
|
return () => mediaQuery.removeListener(handler);
|
|
@@ -226,38 +215,29 @@ export function listenToSystemTheme(callback: (mode: ThemeMode) => void): () =>
|
|
|
226
215
|
|
|
227
216
|
/**
|
|
228
217
|
* Save theme preference to storage
|
|
229
|
-
*
|
|
218
|
+
*
|
|
230
219
|
* @param mode - Theme mode to save
|
|
231
220
|
* @param options - Persistence options
|
|
232
221
|
*/
|
|
233
|
-
export function persistTheme(
|
|
234
|
-
mode: ThemeMode,
|
|
235
|
-
options: ThemePersistenceOptions = {}
|
|
236
|
-
): void {
|
|
222
|
+
export function persistTheme(mode: ThemeMode, options: ThemePersistenceOptions = {}): void {
|
|
237
223
|
if (typeof window === 'undefined') return;
|
|
238
|
-
|
|
239
|
-
const {
|
|
240
|
-
|
|
241
|
-
storageType = 'localStorage',
|
|
242
|
-
} = options;
|
|
243
|
-
|
|
224
|
+
|
|
225
|
+
const { storageKey = 'atomix-theme', storageType = 'localStorage' } = options;
|
|
226
|
+
|
|
244
227
|
const storage = storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
245
228
|
storage.setItem(storageKey, mode);
|
|
246
229
|
}
|
|
247
230
|
|
|
248
231
|
/**
|
|
249
232
|
* Clear saved theme preference
|
|
250
|
-
*
|
|
233
|
+
*
|
|
251
234
|
* @param options - Persistence options
|
|
252
235
|
*/
|
|
253
236
|
export function clearThemePreference(options: ThemePersistenceOptions = {}): void {
|
|
254
237
|
if (typeof window === 'undefined') return;
|
|
255
|
-
|
|
256
|
-
const {
|
|
257
|
-
|
|
258
|
-
storageType = 'localStorage',
|
|
259
|
-
} = options;
|
|
260
|
-
|
|
238
|
+
|
|
239
|
+
const { storageKey = 'atomix-theme', storageType = 'localStorage' } = options;
|
|
240
|
+
|
|
261
241
|
const storage = storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
262
242
|
storage.removeItem(storageKey);
|
|
263
243
|
}
|
|
@@ -268,12 +248,12 @@ export function clearThemePreference(options: ThemePersistenceOptions = {}): voi
|
|
|
268
248
|
|
|
269
249
|
/**
|
|
270
250
|
* Merge multiple token sets
|
|
271
|
-
*
|
|
251
|
+
*
|
|
272
252
|
* Deep merges token objects, with later tokens overriding earlier ones.
|
|
273
|
-
*
|
|
253
|
+
*
|
|
274
254
|
* @param tokens - Token objects to merge
|
|
275
255
|
* @returns Merged tokens
|
|
276
|
-
*
|
|
256
|
+
*
|
|
277
257
|
* @example
|
|
278
258
|
* ```typescript
|
|
279
259
|
* const merged = mergeTokens(
|
|
@@ -290,13 +270,13 @@ export function mergeTokens(...tokens: Array<Partial<DesignTokens>>): Partial<De
|
|
|
290
270
|
|
|
291
271
|
/**
|
|
292
272
|
* Override specific tokens
|
|
293
|
-
*
|
|
273
|
+
*
|
|
294
274
|
* Creates a new token object with specific overrides.
|
|
295
|
-
*
|
|
275
|
+
*
|
|
296
276
|
* @param base - Base tokens
|
|
297
277
|
* @param overrides - Tokens to override
|
|
298
278
|
* @returns New tokens with overrides applied
|
|
299
|
-
*
|
|
279
|
+
*
|
|
300
280
|
* @example
|
|
301
281
|
* ```typescript
|
|
302
282
|
* const customized = overrideTokens(defaultTokens, {
|
|
@@ -315,13 +295,13 @@ export function overrideTokens(
|
|
|
315
295
|
|
|
316
296
|
/**
|
|
317
297
|
* Pick specific token categories
|
|
318
|
-
*
|
|
298
|
+
*
|
|
319
299
|
* Extracts only the specified categories from tokens.
|
|
320
|
-
*
|
|
300
|
+
*
|
|
321
301
|
* @param tokens - Source tokens
|
|
322
302
|
* @param categories - Categories to pick
|
|
323
303
|
* @returns Tokens with only selected categories
|
|
324
|
-
*
|
|
304
|
+
*
|
|
325
305
|
* @example
|
|
326
306
|
* ```typescript
|
|
327
307
|
* const colorTokens = pickTokens(allTokens, ['colors']);
|
|
@@ -332,25 +312,25 @@ export function pickTokens(
|
|
|
332
312
|
categories: Array<keyof DesignTokens>
|
|
333
313
|
): Partial<DesignTokens> {
|
|
334
314
|
const result: Partial<DesignTokens> = {};
|
|
335
|
-
|
|
315
|
+
|
|
336
316
|
categories.forEach(category => {
|
|
337
317
|
if (tokens[category]) {
|
|
338
318
|
result[category] = tokens[category];
|
|
339
319
|
}
|
|
340
320
|
});
|
|
341
|
-
|
|
321
|
+
|
|
342
322
|
return result;
|
|
343
323
|
}
|
|
344
324
|
|
|
345
325
|
/**
|
|
346
326
|
* Omit specific token categories
|
|
347
|
-
*
|
|
327
|
+
*
|
|
348
328
|
* Removes specified categories from tokens.
|
|
349
|
-
*
|
|
329
|
+
*
|
|
350
330
|
* @param tokens - Source tokens
|
|
351
331
|
* @param categories - Categories to omit
|
|
352
332
|
* @returns Tokens without omitted categories
|
|
353
|
-
*
|
|
333
|
+
*
|
|
354
334
|
* @example
|
|
355
335
|
* ```typescript
|
|
356
336
|
* const withoutColors = omitTokens(allTokens, ['colors']);
|
|
@@ -361,11 +341,11 @@ export function omitTokens(
|
|
|
361
341
|
categories: Array<keyof DesignTokens>
|
|
362
342
|
): Partial<DesignTokens> {
|
|
363
343
|
const result = { ...tokens };
|
|
364
|
-
|
|
344
|
+
|
|
365
345
|
categories.forEach(category => {
|
|
366
346
|
delete result[category];
|
|
367
347
|
});
|
|
368
|
-
|
|
348
|
+
|
|
369
349
|
return result;
|
|
370
350
|
}
|
|
371
351
|
|
|
@@ -375,22 +355,25 @@ export function omitTokens(
|
|
|
375
355
|
|
|
376
356
|
/**
|
|
377
357
|
* Convert hex color to RGB
|
|
378
|
-
*
|
|
358
|
+
*
|
|
379
359
|
* @param hex - Hex color (with or without #)
|
|
380
360
|
* @returns RGB object { r, g, b }
|
|
381
361
|
*/
|
|
382
362
|
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
|
383
363
|
// Remove # if present
|
|
384
364
|
hex = hex.replace(/^#/, '');
|
|
385
|
-
|
|
365
|
+
|
|
386
366
|
// Handle shorthand hex
|
|
387
367
|
if (hex.length === 3) {
|
|
388
|
-
hex = hex
|
|
368
|
+
hex = hex
|
|
369
|
+
.split('')
|
|
370
|
+
.map(c => c + c)
|
|
371
|
+
.join('');
|
|
389
372
|
}
|
|
390
|
-
|
|
373
|
+
|
|
391
374
|
// Validate
|
|
392
375
|
if (hex.length !== 6) return null;
|
|
393
|
-
|
|
376
|
+
|
|
394
377
|
const num = parseInt(hex, 16);
|
|
395
378
|
return {
|
|
396
379
|
r: (num >> 16) & 255,
|
|
@@ -401,42 +384,47 @@ export function hexToRgb(hex: string): { r: number; g: number; b: number } | nul
|
|
|
401
384
|
|
|
402
385
|
/**
|
|
403
386
|
* Convert RGB to hex
|
|
404
|
-
*
|
|
387
|
+
*
|
|
405
388
|
* @param r - Red (0-255)
|
|
406
389
|
* @param g - Green (0-255)
|
|
407
390
|
* @param b - Blue (0-255)
|
|
408
391
|
* @returns Hex color with #
|
|
409
392
|
*/
|
|
410
393
|
export function rgbToHex(r: number, g: number, b: number): string {
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
394
|
+
return (
|
|
395
|
+
'#' +
|
|
396
|
+
[r, g, b]
|
|
397
|
+
.map(x => {
|
|
398
|
+
const hex = x.toString(16);
|
|
399
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
400
|
+
})
|
|
401
|
+
.join('')
|
|
402
|
+
);
|
|
415
403
|
}
|
|
416
404
|
|
|
417
405
|
/**
|
|
418
406
|
* Calculate luminance of a color
|
|
419
|
-
*
|
|
407
|
+
*
|
|
420
408
|
* Used for determining contrast ratios.
|
|
421
|
-
*
|
|
409
|
+
*
|
|
422
410
|
* @param hex - Hex color
|
|
423
411
|
* @returns Luminance value (0-1)
|
|
424
412
|
*/
|
|
425
413
|
export function getLuminance(hex: string): number {
|
|
426
414
|
const rgb = hexToRgb(hex);
|
|
427
415
|
if (!rgb) return 0;
|
|
428
|
-
|
|
416
|
+
|
|
429
417
|
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(v => {
|
|
430
418
|
v /= 255;
|
|
431
419
|
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
432
420
|
});
|
|
433
|
-
|
|
421
|
+
|
|
434
422
|
return 0.2126 * (r ?? 0) + 0.7152 * (g ?? 0) + 0.0722 * (b ?? 0);
|
|
435
423
|
}
|
|
436
424
|
|
|
437
425
|
/**
|
|
438
426
|
* Calculate contrast ratio between two colors
|
|
439
|
-
*
|
|
427
|
+
*
|
|
440
428
|
* @param hex1 - First hex color
|
|
441
429
|
* @param hex2 - Second hex color
|
|
442
430
|
* @returns Contrast ratio (1-21)
|
|
@@ -444,16 +432,16 @@ export function getLuminance(hex: string): number {
|
|
|
444
432
|
export function getContrastRatio(hex1: string, hex2: string): number {
|
|
445
433
|
const lum1 = getLuminance(hex1);
|
|
446
434
|
const lum2 = getLuminance(hex2);
|
|
447
|
-
|
|
435
|
+
|
|
448
436
|
const brightest = Math.max(lum1, lum2);
|
|
449
437
|
const darkest = Math.min(lum1, lum2);
|
|
450
|
-
|
|
438
|
+
|
|
451
439
|
return (brightest + 0.05) / (darkest + 0.05);
|
|
452
440
|
}
|
|
453
441
|
|
|
454
442
|
/**
|
|
455
443
|
* Check if text color passes WCAG AA standard
|
|
456
|
-
*
|
|
444
|
+
*
|
|
457
445
|
* @param textColor - Text color hex
|
|
458
446
|
* @param backgroundColor - Background color hex
|
|
459
447
|
* @param size - Font size ('small' or 'large')
|
|
@@ -465,16 +453,16 @@ export function isAccessible(
|
|
|
465
453
|
size: 'small' | 'large' = 'small'
|
|
466
454
|
): boolean {
|
|
467
455
|
const ratio = getContrastRatio(textColor, backgroundColor);
|
|
468
|
-
|
|
456
|
+
|
|
469
457
|
// WCAG AA requires 4.5:1 for normal text, 3:1 for large text
|
|
470
458
|
const minimumRatio = size === 'large' ? 3 : 4.5;
|
|
471
|
-
|
|
459
|
+
|
|
472
460
|
return ratio >= minimumRatio;
|
|
473
461
|
}
|
|
474
462
|
|
|
475
463
|
/**
|
|
476
464
|
* Get appropriate text color (black or white) for a background
|
|
477
|
-
*
|
|
465
|
+
*
|
|
478
466
|
* @param backgroundColor - Background hex color
|
|
479
467
|
* @param threshold - Contrast threshold (default: 3)
|
|
480
468
|
* @returns '#000000' or '#FFFFFF'
|
|
@@ -489,7 +477,7 @@ export function getContrastText(backgroundColor: string, threshold: number = 3):
|
|
|
489
477
|
|
|
490
478
|
/**
|
|
491
479
|
* Lighten a color
|
|
492
|
-
*
|
|
480
|
+
*
|
|
493
481
|
* @param hex - Base hex color
|
|
494
482
|
* @param amount - Amount to lighten (0-1)
|
|
495
483
|
* @returns Lightened hex color
|
|
@@ -497,20 +485,20 @@ export function getContrastText(backgroundColor: string, threshold: number = 3):
|
|
|
497
485
|
export function lighten(hex: string, amount: number = 0): string {
|
|
498
486
|
const rgb = hexToRgb(hex);
|
|
499
487
|
if (!rgb) return hex;
|
|
500
|
-
|
|
488
|
+
|
|
501
489
|
// Use amount directly as factor (0-1)
|
|
502
490
|
const factor = Math.max(0, Math.min(1, amount));
|
|
503
|
-
|
|
491
|
+
|
|
504
492
|
const r = Math.round(rgb.r + (255 - rgb.r) * factor);
|
|
505
493
|
const g = Math.round(rgb.g + (255 - rgb.g) * factor);
|
|
506
494
|
const b = Math.round(rgb.b + (255 - rgb.b) * factor);
|
|
507
|
-
|
|
495
|
+
|
|
508
496
|
return rgbToHex(Math.min(255, r), Math.min(255, g), Math.min(255, b));
|
|
509
497
|
}
|
|
510
498
|
|
|
511
499
|
/**
|
|
512
500
|
* Darken a color
|
|
513
|
-
*
|
|
501
|
+
*
|
|
514
502
|
* @param hex - Base hex color
|
|
515
503
|
* @param amount - Amount to darken (0-1)
|
|
516
504
|
* @returns Darkened hex color
|
|
@@ -518,20 +506,20 @@ export function lighten(hex: string, amount: number = 0): string {
|
|
|
518
506
|
export function darken(hex: string, amount: number = 0): string {
|
|
519
507
|
const rgb = hexToRgb(hex);
|
|
520
508
|
if (!rgb) return hex;
|
|
521
|
-
|
|
509
|
+
|
|
522
510
|
// Use amount directly as factor (0-1)
|
|
523
511
|
const factor = Math.max(0, Math.min(1, amount));
|
|
524
|
-
|
|
512
|
+
|
|
525
513
|
const r = Math.round(rgb.r * (1 - factor));
|
|
526
514
|
const g = Math.round(rgb.g * (1 - factor));
|
|
527
515
|
const b = Math.round(rgb.b * (1 - factor));
|
|
528
|
-
|
|
516
|
+
|
|
529
517
|
return rgbToHex(Math.max(0, r), Math.max(0, g), Math.max(0, b));
|
|
530
518
|
}
|
|
531
519
|
|
|
532
520
|
/**
|
|
533
521
|
* Add alpha to a color
|
|
534
|
-
*
|
|
522
|
+
*
|
|
535
523
|
* @param hex - Hex color
|
|
536
524
|
* @param opacity - Opacity value (0-1)
|
|
537
525
|
* @returns RGBA color string
|
|
@@ -539,14 +527,14 @@ export function darken(hex: string, amount: number = 0): string {
|
|
|
539
527
|
export function alpha(hex: string, opacity: number): string {
|
|
540
528
|
const rgb = hexToRgb(hex);
|
|
541
529
|
if (!rgb) return hex;
|
|
542
|
-
|
|
530
|
+
|
|
543
531
|
const validOpacity = Math.max(0, Math.min(1, opacity));
|
|
544
532
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${validOpacity})`;
|
|
545
533
|
}
|
|
546
534
|
|
|
547
535
|
/**
|
|
548
536
|
* Emphasize a color (lighten if dark, darken if light)
|
|
549
|
-
*
|
|
537
|
+
*
|
|
550
538
|
* @param hex - Hex color
|
|
551
539
|
* @param amount - Amount to emphasize (0-1)
|
|
552
540
|
* @returns Emphasized hex color
|
|
@@ -558,7 +546,7 @@ export function emphasize(hex: string, amount: number = 0.15): string {
|
|
|
558
546
|
|
|
559
547
|
/**
|
|
560
548
|
* Create a spacing utility
|
|
561
|
-
*
|
|
549
|
+
*
|
|
562
550
|
* @param spacingInput - Spacing configuration
|
|
563
551
|
* @returns Spacing function
|
|
564
552
|
*/
|