@optilogic/core 1.0.0-beta.17 → 1.0.0-beta.18
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/dist/index.cjs +144 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +144 -42
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/dist/tailwind-preset.cjs +9 -1
- package/dist/tailwind-preset.cjs.map +1 -1
- package/dist/tailwind-preset.js +9 -1
- package/dist/tailwind-preset.js.map +1 -1
- package/package.json +1 -1
- package/src/components/autocomplete.tsx +2 -1
- package/src/components/button.tsx +10 -8
- package/src/components/calendar.tsx +7 -7
- package/src/components/data-grid/DataGrid.tsx +6 -1
- package/src/components/data-grid/components/CellEditor.tsx +3 -3
- package/src/components/data-grid/hooks/useDataGridState.ts +18 -3
- package/src/components/data-grid/types.ts +4 -0
- package/src/components/data-grid/utils/dataProcessing.ts +40 -11
- package/src/components/date-picker.tsx +2 -1
- package/src/components/dropdown-menu.tsx +1 -1
- package/src/components/input.tsx +1 -1
- package/src/components/popover.tsx +1 -1
- package/src/components/select.tsx +1 -1
- package/src/components/switch.tsx +5 -3
- package/src/components/textarea.tsx +1 -1
- package/src/styles.css +8 -0
- package/src/tailwind-preset.ts +10 -1
- package/src/theme/index.ts +5 -0
- package/src/theme/presets.ts +35 -2
- package/src/theme/types.ts +14 -0
- package/src/theme/utils.ts +203 -0
package/src/theme/presets.ts
CHANGED
|
@@ -40,6 +40,9 @@ export const GREEN_THEME: Theme = {
|
|
|
40
40
|
border: "#243630",
|
|
41
41
|
input: "#243630",
|
|
42
42
|
ring: "#6FCF97",
|
|
43
|
+
toggleTrack: "#354840",
|
|
44
|
+
toggleTrackForeground: "#E6F5EC",
|
|
45
|
+
inputHover: "#6FCF97",
|
|
43
46
|
chart1: "#6FCF97",
|
|
44
47
|
chart2: "#8FE3B0",
|
|
45
48
|
chart3: "#9DB8A8",
|
|
@@ -79,8 +82,11 @@ export const OPTILOGIC_LEGACY_THEME: Theme = {
|
|
|
79
82
|
chip: "#B8C5F9",
|
|
80
83
|
chipForeground: "#0C0A5A",
|
|
81
84
|
border: "#D0D0D0",
|
|
82
|
-
input: "#
|
|
85
|
+
input: "#D0D0D0",
|
|
83
86
|
ring: "#5766F2",
|
|
87
|
+
toggleTrack: "#D0D0D0",
|
|
88
|
+
toggleTrackForeground: "#FFFFFF",
|
|
89
|
+
inputHover: "#5766F2",
|
|
84
90
|
chart1: "#78D237",
|
|
85
91
|
chart2: "#F5CF47",
|
|
86
92
|
chart3: "#5766F2",
|
|
@@ -122,6 +128,9 @@ export const FUTURISTIC_THEME: Theme = {
|
|
|
122
128
|
border: "#1e293b",
|
|
123
129
|
input: "#1e293b",
|
|
124
130
|
ring: "#6366f1",
|
|
131
|
+
toggleTrack: "#334155",
|
|
132
|
+
toggleTrackForeground: "#e0e7ff",
|
|
133
|
+
inputHover: "#6366f1",
|
|
125
134
|
chart1: "#6366f1",
|
|
126
135
|
chart2: "#8b5cf6",
|
|
127
136
|
chart3: "#a855f7",
|
|
@@ -163,6 +172,9 @@ export const NATURE_THEME: Theme = {
|
|
|
163
172
|
border: "#243824",
|
|
164
173
|
input: "#243824",
|
|
165
174
|
ring: "#4caf50",
|
|
175
|
+
toggleTrack: "#3d5a3d",
|
|
176
|
+
toggleTrackForeground: "#e8f5e9",
|
|
177
|
+
inputHover: "#4caf50",
|
|
166
178
|
chart1: "#4caf50",
|
|
167
179
|
chart2: "#66bb6a",
|
|
168
180
|
chart3: "#81c784",
|
|
@@ -204,6 +216,9 @@ export const SCIFI_THEME: Theme = {
|
|
|
204
216
|
border: "#30363d",
|
|
205
217
|
input: "#21262d",
|
|
206
218
|
ring: "#00d9ff",
|
|
219
|
+
toggleTrack: "#30363d",
|
|
220
|
+
toggleTrackForeground: "#c9d1d9",
|
|
221
|
+
inputHover: "#00d9ff",
|
|
207
222
|
chart1: "#00d9ff",
|
|
208
223
|
chart2: "#ff00ff",
|
|
209
224
|
chart3: "#00ff88",
|
|
@@ -245,6 +260,9 @@ export const OCEAN_THEME: Theme = {
|
|
|
245
260
|
border: "#1e3a5f",
|
|
246
261
|
input: "#1e3a5f",
|
|
247
262
|
ring: "#2196f3",
|
|
263
|
+
toggleTrack: "#264a6e",
|
|
264
|
+
toggleTrackForeground: "#e3f2fd",
|
|
265
|
+
inputHover: "#2196f3",
|
|
248
266
|
chart1: "#2196f3",
|
|
249
267
|
chart2: "#00bcd4",
|
|
250
268
|
chart3: "#03a9f4",
|
|
@@ -286,6 +304,9 @@ export const SUNSET_THEME: Theme = {
|
|
|
286
304
|
border: "#241424",
|
|
287
305
|
input: "#241424",
|
|
288
306
|
ring: "#ff6b35",
|
|
307
|
+
toggleTrack: "#3d2a3d",
|
|
308
|
+
toggleTrackForeground: "#ffe0e6",
|
|
309
|
+
inputHover: "#ff6b35",
|
|
289
310
|
chart1: "#ff6b35",
|
|
290
311
|
chart2: "#c44569",
|
|
291
312
|
chart3: "#f7931e",
|
|
@@ -327,6 +348,9 @@ export const FOREST_THEME: Theme = {
|
|
|
327
348
|
border: "#1b5e20",
|
|
328
349
|
input: "#1a2e1a",
|
|
329
350
|
ring: "#2e7d32",
|
|
351
|
+
toggleTrack: "#2d4a2d",
|
|
352
|
+
toggleTrackForeground: "#e8f5e9",
|
|
353
|
+
inputHover: "#2e7d32",
|
|
330
354
|
chart1: "#2e7d32",
|
|
331
355
|
chart2: "#388e3c",
|
|
332
356
|
chart3: "#43a047",
|
|
@@ -368,6 +392,9 @@ export const CYBERPUNK_THEME: Theme = {
|
|
|
368
392
|
border: "#1a1a2e",
|
|
369
393
|
input: "#16213e",
|
|
370
394
|
ring: "#ff00ff",
|
|
395
|
+
toggleTrack: "#2a2a4e",
|
|
396
|
+
toggleTrackForeground: "#f0f0f0",
|
|
397
|
+
inputHover: "#ff00ff",
|
|
371
398
|
chart1: "#ff00ff",
|
|
372
399
|
chart2: "#00ffff",
|
|
373
400
|
chart3: "#00ff88",
|
|
@@ -407,8 +434,11 @@ export const MINIMALIST_LIGHT_THEME: Theme = {
|
|
|
407
434
|
chip: "#e9ecef",
|
|
408
435
|
chipForeground: "#495057",
|
|
409
436
|
border: "#dee2e6",
|
|
410
|
-
input: "#
|
|
437
|
+
input: "#dee2e6",
|
|
411
438
|
ring: "#000000",
|
|
439
|
+
toggleTrack: "#ced4da",
|
|
440
|
+
toggleTrackForeground: "#ffffff",
|
|
441
|
+
inputHover: "#6c757d",
|
|
412
442
|
chart1: "#000000",
|
|
413
443
|
chart2: "#6c757d",
|
|
414
444
|
chart3: "#adb5bd",
|
|
@@ -450,6 +480,9 @@ export const DARK_ELEGANT_THEME: Theme = {
|
|
|
450
480
|
border: "#2d2d2d",
|
|
451
481
|
input: "#1e1e1e",
|
|
452
482
|
ring: "#bb86fc",
|
|
483
|
+
toggleTrack: "#3d3d3d",
|
|
484
|
+
toggleTrackForeground: "#e0e0e0",
|
|
485
|
+
inputHover: "#bb86fc",
|
|
453
486
|
chart1: "#bb86fc",
|
|
454
487
|
chart2: "#03dac6",
|
|
455
488
|
chart3: "#4caf50",
|
package/src/theme/types.ts
CHANGED
|
@@ -54,6 +54,11 @@ export interface Theme {
|
|
|
54
54
|
input: string; // --input
|
|
55
55
|
ring: string; // --ring (focus ring)
|
|
56
56
|
|
|
57
|
+
/** Interactive control colors (optional, with fallbacks) */
|
|
58
|
+
toggleTrack?: string; // --toggle-track (switch track bg when off; fallback: muted)
|
|
59
|
+
toggleTrackForeground?: string; // --toggle-track-foreground (switch thumb when off; fallback: background)
|
|
60
|
+
inputHover?: string; // --input-hover (form control border on hover; fallback: derived)
|
|
61
|
+
|
|
57
62
|
/** Chart colors */
|
|
58
63
|
chart1: string; // --chart-1
|
|
59
64
|
chart2: string; // --chart-2
|
|
@@ -61,6 +66,9 @@ export interface Theme {
|
|
|
61
66
|
chart4: string; // --chart-4
|
|
62
67
|
chart5: string; // --chart-5
|
|
63
68
|
|
|
69
|
+
/** Disabled state */
|
|
70
|
+
disabledOpacity?: string; // --disabled-opacity (default: 0.5)
|
|
71
|
+
|
|
64
72
|
/** Border radius */
|
|
65
73
|
radius?: string; // --radius (default: 0.5rem)
|
|
66
74
|
}
|
|
@@ -105,6 +113,9 @@ export interface ThemeHSL extends Omit<Theme, keyof ThemeColorFields> {
|
|
|
105
113
|
border: string;
|
|
106
114
|
input: string;
|
|
107
115
|
ring: string;
|
|
116
|
+
toggleTrack?: string;
|
|
117
|
+
toggleTrackForeground?: string;
|
|
118
|
+
inputHover?: string;
|
|
108
119
|
chart1: string;
|
|
109
120
|
chart2: string;
|
|
110
121
|
chart3: string;
|
|
@@ -138,6 +149,9 @@ type ThemeColorFields = {
|
|
|
138
149
|
border: string;
|
|
139
150
|
input: string;
|
|
140
151
|
ring: string;
|
|
152
|
+
toggleTrack?: string;
|
|
153
|
+
toggleTrackForeground?: string;
|
|
154
|
+
inputHover?: string;
|
|
141
155
|
chart1: string;
|
|
142
156
|
chart2: string;
|
|
143
157
|
chart3: string;
|
package/src/theme/utils.ts
CHANGED
|
@@ -85,6 +85,11 @@ export function themeToHsl(theme: Theme): ThemeHSL {
|
|
|
85
85
|
border: hexToHsl(theme.border),
|
|
86
86
|
input: hexToHsl(theme.input),
|
|
87
87
|
ring: hexToHsl(theme.ring),
|
|
88
|
+
toggleTrack: theme.toggleTrack ? hexToHsl(theme.toggleTrack) : undefined,
|
|
89
|
+
toggleTrackForeground: theme.toggleTrackForeground
|
|
90
|
+
? hexToHsl(theme.toggleTrackForeground)
|
|
91
|
+
: undefined,
|
|
92
|
+
inputHover: theme.inputHover ? hexToHsl(theme.inputHover) : undefined,
|
|
88
93
|
chart1: hexToHsl(theme.chart1),
|
|
89
94
|
chart2: hexToHsl(theme.chart2),
|
|
90
95
|
chart3: hexToHsl(theme.chart3),
|
|
@@ -93,6 +98,20 @@ export function themeToHsl(theme: Theme): ThemeHSL {
|
|
|
93
98
|
};
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Derive an input hover border color from the theme's HSL values.
|
|
103
|
+
* Blends toward the foreground for a subtle but visible hover effect.
|
|
104
|
+
*/
|
|
105
|
+
function deriveInputHoverHsl(hslTheme: ThemeHSL): string {
|
|
106
|
+
const parts = hslTheme.foreground.split(/\s+/);
|
|
107
|
+
if (parts.length >= 3) {
|
|
108
|
+
const h = parts[0];
|
|
109
|
+
const s = parts[1];
|
|
110
|
+
return `${h} ${s} 50%`;
|
|
111
|
+
}
|
|
112
|
+
return hslTheme.foreground;
|
|
113
|
+
}
|
|
114
|
+
|
|
96
115
|
/**
|
|
97
116
|
* Apply a theme to the DOM
|
|
98
117
|
*
|
|
@@ -135,12 +154,31 @@ export function applyTheme(theme: Theme, targetElement?: HTMLElement): void {
|
|
|
135
154
|
element.style.setProperty("--border", hslTheme.border);
|
|
136
155
|
element.style.setProperty("--input", hslTheme.input);
|
|
137
156
|
element.style.setProperty("--ring", hslTheme.ring);
|
|
157
|
+
|
|
158
|
+
// Interactive control tokens (with fallbacks)
|
|
159
|
+
element.style.setProperty(
|
|
160
|
+
"--toggle-track",
|
|
161
|
+
hslTheme.toggleTrack ?? hslTheme.muted
|
|
162
|
+
);
|
|
163
|
+
element.style.setProperty(
|
|
164
|
+
"--toggle-track-foreground",
|
|
165
|
+
hslTheme.toggleTrackForeground ?? hslTheme.background
|
|
166
|
+
);
|
|
167
|
+
element.style.setProperty(
|
|
168
|
+
"--input-hover",
|
|
169
|
+
hslTheme.inputHover ?? deriveInputHoverHsl(hslTheme)
|
|
170
|
+
);
|
|
171
|
+
|
|
138
172
|
element.style.setProperty("--chart-1", hslTheme.chart1);
|
|
139
173
|
element.style.setProperty("--chart-2", hslTheme.chart2);
|
|
140
174
|
element.style.setProperty("--chart-3", hslTheme.chart3);
|
|
141
175
|
element.style.setProperty("--chart-4", hslTheme.chart4);
|
|
142
176
|
element.style.setProperty("--chart-5", hslTheme.chart5);
|
|
143
177
|
|
|
178
|
+
if (theme.disabledOpacity) {
|
|
179
|
+
element.style.setProperty("--disabled-opacity", theme.disabledOpacity);
|
|
180
|
+
}
|
|
181
|
+
|
|
144
182
|
if (theme.radius) {
|
|
145
183
|
element.style.setProperty("--radius", theme.radius);
|
|
146
184
|
}
|
|
@@ -254,6 +292,9 @@ export function areThemesEqual(theme1: Theme, theme2: Theme): boolean {
|
|
|
254
292
|
"border",
|
|
255
293
|
"input",
|
|
256
294
|
"ring",
|
|
295
|
+
"toggleTrack",
|
|
296
|
+
"toggleTrackForeground",
|
|
297
|
+
"inputHover",
|
|
257
298
|
"chart1",
|
|
258
299
|
"chart2",
|
|
259
300
|
"chart3",
|
|
@@ -307,3 +348,165 @@ export function importTheme(jsonString: string): {
|
|
|
307
348
|
};
|
|
308
349
|
}
|
|
309
350
|
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Contrast validation utilities
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Parse a hex color string into linear-light sRGB components (0-1).
|
|
358
|
+
*/
|
|
359
|
+
function hexToLinearRgb(hex: string): [number, number, number] {
|
|
360
|
+
hex = hex.replace(/^#/, "");
|
|
361
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
362
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
363
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
364
|
+
|
|
365
|
+
const toLinear = (c: number) =>
|
|
366
|
+
c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
367
|
+
|
|
368
|
+
return [toLinear(r), toLinear(g), toLinear(b)];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Calculate WCAG 2.x relative luminance for a hex color.
|
|
373
|
+
* @see https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
|
374
|
+
*/
|
|
375
|
+
export function getRelativeLuminance(hex: string): number {
|
|
376
|
+
const [r, g, b] = hexToLinearRgb(hex);
|
|
377
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Calculate the WCAG contrast ratio between two hex colors.
|
|
382
|
+
* Returns a value >= 1, where 1 means no contrast and 21 is maximum.
|
|
383
|
+
* @see https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
|
384
|
+
*/
|
|
385
|
+
export function getContrastRatio(hex1: string, hex2: string): number {
|
|
386
|
+
const l1 = getRelativeLuminance(hex1);
|
|
387
|
+
const l2 = getRelativeLuminance(hex2);
|
|
388
|
+
const lighter = Math.max(l1, l2);
|
|
389
|
+
const darker = Math.min(l1, l2);
|
|
390
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export interface ContrastWarning {
|
|
394
|
+
pair: [string, string];
|
|
395
|
+
pairLabels: [string, string];
|
|
396
|
+
ratio: number;
|
|
397
|
+
required: number;
|
|
398
|
+
level: "fail" | "AA-large" | "AA";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Validate contrast ratios for critical color pairs in a theme.
|
|
403
|
+
*
|
|
404
|
+
* WCAG 2.1 requirements:
|
|
405
|
+
* - **3:1** minimum for UI components and large text (AA)
|
|
406
|
+
* - **4.5:1** minimum for normal text (AA)
|
|
407
|
+
*
|
|
408
|
+
* Returns an array of warnings for pairs that fall below the recommended
|
|
409
|
+
* minimums. An empty array means all checked pairs pass.
|
|
410
|
+
*/
|
|
411
|
+
export function validateThemeContrast(theme: Theme): ContrastWarning[] {
|
|
412
|
+
const warnings: ContrastWarning[] = [];
|
|
413
|
+
|
|
414
|
+
const uiPairs: {
|
|
415
|
+
a: string;
|
|
416
|
+
b: string;
|
|
417
|
+
labelA: string;
|
|
418
|
+
labelB: string;
|
|
419
|
+
minRatio: number;
|
|
420
|
+
}[] = [
|
|
421
|
+
// Text on background
|
|
422
|
+
{
|
|
423
|
+
a: theme.foreground,
|
|
424
|
+
b: theme.background,
|
|
425
|
+
labelA: "foreground",
|
|
426
|
+
labelB: "background",
|
|
427
|
+
minRatio: 4.5,
|
|
428
|
+
},
|
|
429
|
+
// Primary button text on primary bg
|
|
430
|
+
{
|
|
431
|
+
a: theme.primaryForeground,
|
|
432
|
+
b: theme.primary,
|
|
433
|
+
labelA: "primaryForeground",
|
|
434
|
+
labelB: "primary",
|
|
435
|
+
minRatio: 4.5,
|
|
436
|
+
},
|
|
437
|
+
// Accent foreground on accent
|
|
438
|
+
{
|
|
439
|
+
a: theme.accentForeground,
|
|
440
|
+
b: theme.accent,
|
|
441
|
+
labelA: "accentForeground",
|
|
442
|
+
labelB: "accent",
|
|
443
|
+
minRatio: 4.5,
|
|
444
|
+
},
|
|
445
|
+
// Input border vs background (UI component boundary — 3:1)
|
|
446
|
+
{
|
|
447
|
+
a: theme.input,
|
|
448
|
+
b: theme.background,
|
|
449
|
+
labelA: "input",
|
|
450
|
+
labelB: "background",
|
|
451
|
+
minRatio: 3,
|
|
452
|
+
},
|
|
453
|
+
// Toggle track vs background (UI component — 3:1)
|
|
454
|
+
{
|
|
455
|
+
a: theme.toggleTrack ?? theme.muted,
|
|
456
|
+
b: theme.background,
|
|
457
|
+
labelA: "toggleTrack",
|
|
458
|
+
labelB: "background",
|
|
459
|
+
minRatio: 3,
|
|
460
|
+
},
|
|
461
|
+
// Toggle thumb vs track (UI component — 3:1)
|
|
462
|
+
{
|
|
463
|
+
a: theme.toggleTrackForeground ?? theme.background,
|
|
464
|
+
b: theme.toggleTrack ?? theme.muted,
|
|
465
|
+
labelA: "toggleTrackForeground",
|
|
466
|
+
labelB: "toggleTrack",
|
|
467
|
+
minRatio: 3,
|
|
468
|
+
},
|
|
469
|
+
// Border vs background (UI component boundary — 3:1)
|
|
470
|
+
{
|
|
471
|
+
a: theme.border,
|
|
472
|
+
b: theme.background,
|
|
473
|
+
labelA: "border",
|
|
474
|
+
labelB: "background",
|
|
475
|
+
minRatio: 3,
|
|
476
|
+
},
|
|
477
|
+
// Muted foreground on muted bg
|
|
478
|
+
{
|
|
479
|
+
a: theme.mutedForeground,
|
|
480
|
+
b: theme.muted,
|
|
481
|
+
labelA: "mutedForeground",
|
|
482
|
+
labelB: "muted",
|
|
483
|
+
minRatio: 4.5,
|
|
484
|
+
},
|
|
485
|
+
// Destructive foreground on destructive bg
|
|
486
|
+
{
|
|
487
|
+
a: theme.destructiveForeground,
|
|
488
|
+
b: theme.destructive,
|
|
489
|
+
labelA: "destructiveForeground",
|
|
490
|
+
labelB: "destructive",
|
|
491
|
+
minRatio: 4.5,
|
|
492
|
+
},
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
for (const { a, b, labelA, labelB, minRatio } of uiPairs) {
|
|
496
|
+
const ratio = getContrastRatio(a, b);
|
|
497
|
+
if (ratio < minRatio) {
|
|
498
|
+
let level: ContrastWarning["level"] = "fail";
|
|
499
|
+
if (ratio >= 3) level = "AA-large";
|
|
500
|
+
|
|
501
|
+
warnings.push({
|
|
502
|
+
pair: [a, b],
|
|
503
|
+
pairLabels: [labelA, labelB],
|
|
504
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
505
|
+
required: minRatio,
|
|
506
|
+
level,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return warnings;
|
|
512
|
+
}
|