@optilogic/core 1.0.0-beta.0
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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.cjs +6003 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2310 -0
- package/dist/index.d.ts +2310 -0
- package/dist/index.js +5828 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +96 -0
- package/dist/tailwind-preset.cjs +106 -0
- package/dist/tailwind-preset.cjs.map +1 -0
- package/dist/tailwind-preset.d.cts +23 -0
- package/dist/tailwind-preset.d.ts +23 -0
- package/dist/tailwind-preset.js +101 -0
- package/dist/tailwind-preset.js.map +1 -0
- package/package.json +154 -0
- package/src/components/accordion.tsx +187 -0
- package/src/components/alert-dialog.tsx +143 -0
- package/src/components/autocomplete.tsx +271 -0
- package/src/components/badge.tsx +62 -0
- package/src/components/button.tsx +85 -0
- package/src/components/calendar.tsx +235 -0
- package/src/components/card.tsx +94 -0
- package/src/components/checkbox.tsx +77 -0
- package/src/components/chip.tsx +77 -0
- package/src/components/confirmation-modal.tsx +195 -0
- package/src/components/context-menu.tsx +406 -0
- package/src/components/copy-button.tsx +84 -0
- package/src/components/data-grid/DataGrid.tsx +1027 -0
- package/src/components/data-grid/components/CellEditor.tsx +346 -0
- package/src/components/data-grid/components/FilterPopover.tsx +459 -0
- package/src/components/data-grid/components/HeaderCell.tsx +207 -0
- package/src/components/data-grid/components/index.ts +14 -0
- package/src/components/data-grid/hooks/index.ts +28 -0
- package/src/components/data-grid/hooks/useColumnResize.ts +378 -0
- package/src/components/data-grid/hooks/useDataGridState.ts +346 -0
- package/src/components/data-grid/hooks/useKeyboardNavigation.ts +361 -0
- package/src/components/data-grid/index.ts +71 -0
- package/src/components/data-grid/types.ts +478 -0
- package/src/components/data-grid/utils/dataProcessing.ts +277 -0
- package/src/components/data-grid/utils/index.ts +12 -0
- package/src/components/date-picker.tsx +366 -0
- package/src/components/dropdown-menu.tsx +230 -0
- package/src/components/icon-button.tsx +157 -0
- package/src/components/input.tsx +40 -0
- package/src/components/label.tsx +37 -0
- package/src/components/loading-spinner.tsx +113 -0
- package/src/components/modal.tsx +207 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/progress.tsx +41 -0
- package/src/components/resizable-panel.tsx +434 -0
- package/src/components/resize-handle.tsx +187 -0
- package/src/components/select.tsx +160 -0
- package/src/components/separator.tsx +50 -0
- package/src/components/skeleton.tsx +37 -0
- package/src/components/switch.tsx +59 -0
- package/src/components/table.tsx +136 -0
- package/src/components/tabs.tsx +102 -0
- package/src/components/textarea.tsx +36 -0
- package/src/components/theme-picker.tsx +245 -0
- package/src/components/toaster.tsx +84 -0
- package/src/components/tooltip.tsx +199 -0
- package/src/index.ts +318 -0
- package/src/styles.css +96 -0
- package/src/tailwind-preset.ts +129 -0
- package/src/theme/index.ts +41 -0
- package/src/theme/presets.ts +502 -0
- package/src/theme/types.ts +164 -0
- package/src/theme/utils.ts +309 -0
- package/src/utils/cn.ts +14 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Utilities
|
|
3
|
+
*
|
|
4
|
+
* Functions for theme conversion and application.
|
|
5
|
+
* Ported from platform-leapfrog for consistent theming.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Theme, ThemeHSL } from "./types";
|
|
9
|
+
import { getDefaultTheme } from "./presets";
|
|
10
|
+
|
|
11
|
+
// Re-export for convenience
|
|
12
|
+
export type { Theme };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert hex color to HSL format for CSS variables
|
|
16
|
+
*/
|
|
17
|
+
export function hexToHsl(hex: string): string {
|
|
18
|
+
// Remove # if present
|
|
19
|
+
hex = hex.replace(/^#/, "");
|
|
20
|
+
|
|
21
|
+
// Parse hex values
|
|
22
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
23
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
24
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
25
|
+
|
|
26
|
+
const max = Math.max(r, g, b);
|
|
27
|
+
const min = Math.min(r, g, b);
|
|
28
|
+
let h = 0;
|
|
29
|
+
let s = 0;
|
|
30
|
+
const l = (max + min) / 2;
|
|
31
|
+
|
|
32
|
+
if (max !== min) {
|
|
33
|
+
const d = max - min;
|
|
34
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
35
|
+
|
|
36
|
+
switch (max) {
|
|
37
|
+
case r:
|
|
38
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
39
|
+
break;
|
|
40
|
+
case g:
|
|
41
|
+
h = ((b - r) / d + 2) / 6;
|
|
42
|
+
break;
|
|
43
|
+
case b:
|
|
44
|
+
h = ((r - g) / d + 4) / 6;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Convert to degrees and percentages
|
|
50
|
+
const hDeg = Math.round(h * 360);
|
|
51
|
+
const sPercent = Math.round(s * 100);
|
|
52
|
+
const lPercent = Math.round(l * 100);
|
|
53
|
+
|
|
54
|
+
return `${hDeg} ${sPercent}% ${lPercent}%`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Convert hex theme to HSL theme
|
|
59
|
+
*/
|
|
60
|
+
export function themeToHsl(theme: Theme): ThemeHSL {
|
|
61
|
+
return {
|
|
62
|
+
...theme,
|
|
63
|
+
background: hexToHsl(theme.background),
|
|
64
|
+
foreground: hexToHsl(theme.foreground),
|
|
65
|
+
card: hexToHsl(theme.card),
|
|
66
|
+
cardForeground: hexToHsl(theme.cardForeground),
|
|
67
|
+
popover: hexToHsl(theme.popover),
|
|
68
|
+
popoverForeground: hexToHsl(theme.popoverForeground),
|
|
69
|
+
primary: hexToHsl(theme.primary),
|
|
70
|
+
primaryForeground: hexToHsl(theme.primaryForeground),
|
|
71
|
+
accent: hexToHsl(theme.accent),
|
|
72
|
+
accentForeground: hexToHsl(theme.accentForeground),
|
|
73
|
+
secondary: hexToHsl(theme.secondary),
|
|
74
|
+
secondaryForeground: hexToHsl(theme.secondaryForeground),
|
|
75
|
+
muted: hexToHsl(theme.muted),
|
|
76
|
+
mutedForeground: hexToHsl(theme.mutedForeground),
|
|
77
|
+
destructive: hexToHsl(theme.destructive),
|
|
78
|
+
destructiveForeground: hexToHsl(theme.destructiveForeground),
|
|
79
|
+
success: hexToHsl(theme.success),
|
|
80
|
+
successForeground: hexToHsl(theme.successForeground),
|
|
81
|
+
warning: hexToHsl(theme.warning),
|
|
82
|
+
warningForeground: hexToHsl(theme.warningForeground),
|
|
83
|
+
chip: hexToHsl(theme.chip),
|
|
84
|
+
chipForeground: hexToHsl(theme.chipForeground),
|
|
85
|
+
border: hexToHsl(theme.border),
|
|
86
|
+
input: hexToHsl(theme.input),
|
|
87
|
+
ring: hexToHsl(theme.ring),
|
|
88
|
+
chart1: hexToHsl(theme.chart1),
|
|
89
|
+
chart2: hexToHsl(theme.chart2),
|
|
90
|
+
chart3: hexToHsl(theme.chart3),
|
|
91
|
+
chart4: hexToHsl(theme.chart4),
|
|
92
|
+
chart5: hexToHsl(theme.chart5),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply a theme to the DOM
|
|
98
|
+
*
|
|
99
|
+
* @param theme - The theme to apply
|
|
100
|
+
* @param targetElement - Optional element to apply theme to (defaults to document.documentElement)
|
|
101
|
+
*/
|
|
102
|
+
export function applyTheme(theme: Theme, targetElement?: HTMLElement): void {
|
|
103
|
+
const element = targetElement || document.documentElement;
|
|
104
|
+
const hslTheme = themeToHsl(theme);
|
|
105
|
+
|
|
106
|
+
// Set CSS variables as inline styles
|
|
107
|
+
element.style.setProperty("--background", hslTheme.background);
|
|
108
|
+
element.style.setProperty("--foreground", hslTheme.foreground);
|
|
109
|
+
element.style.setProperty("--card", hslTheme.card);
|
|
110
|
+
element.style.setProperty("--card-foreground", hslTheme.cardForeground);
|
|
111
|
+
element.style.setProperty("--popover", hslTheme.popover);
|
|
112
|
+
element.style.setProperty("--popover-foreground", hslTheme.popoverForeground);
|
|
113
|
+
element.style.setProperty("--primary", hslTheme.primary);
|
|
114
|
+
element.style.setProperty("--primary-foreground", hslTheme.primaryForeground);
|
|
115
|
+
element.style.setProperty("--accent", hslTheme.accent);
|
|
116
|
+
element.style.setProperty("--accent-foreground", hslTheme.accentForeground);
|
|
117
|
+
element.style.setProperty("--secondary", hslTheme.secondary);
|
|
118
|
+
element.style.setProperty(
|
|
119
|
+
"--secondary-foreground",
|
|
120
|
+
hslTheme.secondaryForeground
|
|
121
|
+
);
|
|
122
|
+
element.style.setProperty("--muted", hslTheme.muted);
|
|
123
|
+
element.style.setProperty("--muted-foreground", hslTheme.mutedForeground);
|
|
124
|
+
element.style.setProperty("--destructive", hslTheme.destructive);
|
|
125
|
+
element.style.setProperty(
|
|
126
|
+
"--destructive-foreground",
|
|
127
|
+
hslTheme.destructiveForeground
|
|
128
|
+
);
|
|
129
|
+
element.style.setProperty("--success", hslTheme.success);
|
|
130
|
+
element.style.setProperty("--success-foreground", hslTheme.successForeground);
|
|
131
|
+
element.style.setProperty("--warning", hslTheme.warning);
|
|
132
|
+
element.style.setProperty("--warning-foreground", hslTheme.warningForeground);
|
|
133
|
+
element.style.setProperty("--chip", hslTheme.chip);
|
|
134
|
+
element.style.setProperty("--chip-foreground", hslTheme.chipForeground);
|
|
135
|
+
element.style.setProperty("--border", hslTheme.border);
|
|
136
|
+
element.style.setProperty("--input", hslTheme.input);
|
|
137
|
+
element.style.setProperty("--ring", hslTheme.ring);
|
|
138
|
+
element.style.setProperty("--chart-1", hslTheme.chart1);
|
|
139
|
+
element.style.setProperty("--chart-2", hslTheme.chart2);
|
|
140
|
+
element.style.setProperty("--chart-3", hslTheme.chart3);
|
|
141
|
+
element.style.setProperty("--chart-4", hslTheme.chart4);
|
|
142
|
+
element.style.setProperty("--chart-5", hslTheme.chart5);
|
|
143
|
+
|
|
144
|
+
if (theme.radius) {
|
|
145
|
+
element.style.setProperty("--radius", theme.radius);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get the currently active theme (uses default)
|
|
151
|
+
*/
|
|
152
|
+
export function getCurrentTheme(): Theme {
|
|
153
|
+
return getDefaultTheme();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validate theme object structure
|
|
158
|
+
*/
|
|
159
|
+
export function validateTheme(theme: unknown): theme is Theme {
|
|
160
|
+
if (!theme || typeof theme !== "object") return false;
|
|
161
|
+
|
|
162
|
+
const t = theme as Record<string, unknown>;
|
|
163
|
+
|
|
164
|
+
// Check required fields
|
|
165
|
+
const requiredFields: string[] = [
|
|
166
|
+
"id",
|
|
167
|
+
"name",
|
|
168
|
+
"background",
|
|
169
|
+
"foreground",
|
|
170
|
+
"card",
|
|
171
|
+
"cardForeground",
|
|
172
|
+
"popover",
|
|
173
|
+
"popoverForeground",
|
|
174
|
+
"primary",
|
|
175
|
+
"primaryForeground",
|
|
176
|
+
"accent",
|
|
177
|
+
"accentForeground",
|
|
178
|
+
"secondary",
|
|
179
|
+
"secondaryForeground",
|
|
180
|
+
"muted",
|
|
181
|
+
"mutedForeground",
|
|
182
|
+
"destructive",
|
|
183
|
+
"destructiveForeground",
|
|
184
|
+
"success",
|
|
185
|
+
"successForeground",
|
|
186
|
+
"warning",
|
|
187
|
+
"warningForeground",
|
|
188
|
+
"chip",
|
|
189
|
+
"chipForeground",
|
|
190
|
+
"border",
|
|
191
|
+
"input",
|
|
192
|
+
"ring",
|
|
193
|
+
"chart1",
|
|
194
|
+
"chart2",
|
|
195
|
+
"chart3",
|
|
196
|
+
"chart4",
|
|
197
|
+
"chart5",
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const field of requiredFields) {
|
|
201
|
+
if (!(field in t)) {
|
|
202
|
+
console.error(`Theme validation failed: missing field "${field}"`);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clone a theme (for editing)
|
|
212
|
+
*/
|
|
213
|
+
export function cloneTheme(theme: Theme, newName?: string): Theme {
|
|
214
|
+
return {
|
|
215
|
+
...theme,
|
|
216
|
+
id: `custom-${Date.now()}`,
|
|
217
|
+
name: newName || `${theme.name} - copy`,
|
|
218
|
+
author: "Custom",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Deep comparison of two themes
|
|
224
|
+
*/
|
|
225
|
+
export function areThemesEqual(theme1: Theme, theme2: Theme): boolean {
|
|
226
|
+
if (!theme1 || !theme2) return false;
|
|
227
|
+
|
|
228
|
+
if (theme1.name !== theme2.name) return false;
|
|
229
|
+
if (theme1.description !== theme2.description) return false;
|
|
230
|
+
|
|
231
|
+
const colorFields: (keyof Theme)[] = [
|
|
232
|
+
"background",
|
|
233
|
+
"foreground",
|
|
234
|
+
"card",
|
|
235
|
+
"cardForeground",
|
|
236
|
+
"popover",
|
|
237
|
+
"popoverForeground",
|
|
238
|
+
"primary",
|
|
239
|
+
"primaryForeground",
|
|
240
|
+
"accent",
|
|
241
|
+
"accentForeground",
|
|
242
|
+
"secondary",
|
|
243
|
+
"secondaryForeground",
|
|
244
|
+
"muted",
|
|
245
|
+
"mutedForeground",
|
|
246
|
+
"destructive",
|
|
247
|
+
"destructiveForeground",
|
|
248
|
+
"success",
|
|
249
|
+
"successForeground",
|
|
250
|
+
"warning",
|
|
251
|
+
"warningForeground",
|
|
252
|
+
"chip",
|
|
253
|
+
"chipForeground",
|
|
254
|
+
"border",
|
|
255
|
+
"input",
|
|
256
|
+
"ring",
|
|
257
|
+
"chart1",
|
|
258
|
+
"chart2",
|
|
259
|
+
"chart3",
|
|
260
|
+
"chart4",
|
|
261
|
+
"chart5",
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
for (const field of colorFields) {
|
|
265
|
+
if (theme1[field] !== theme2[field]) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Export theme as JSON string
|
|
275
|
+
*/
|
|
276
|
+
export function exportTheme(theme: Theme): string {
|
|
277
|
+
return JSON.stringify(theme, null, 2);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Import theme from JSON string
|
|
282
|
+
*/
|
|
283
|
+
export function importTheme(jsonString: string): {
|
|
284
|
+
success: boolean;
|
|
285
|
+
theme?: Theme;
|
|
286
|
+
error?: string;
|
|
287
|
+
} {
|
|
288
|
+
try {
|
|
289
|
+
const theme = JSON.parse(jsonString);
|
|
290
|
+
|
|
291
|
+
if (!validateTheme(theme)) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
error:
|
|
295
|
+
"Invalid theme format. Please check that all required fields are present.",
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
success: true,
|
|
301
|
+
theme,
|
|
302
|
+
};
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: error instanceof Error ? error.message : "Failed to parse JSON",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
package/src/utils/cn.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to merge Tailwind CSS classes with proper precedence.
|
|
6
|
+
* Combines clsx for conditional classes with tailwind-merge for deduplication.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* cn("px-4 py-2", "px-6") // => "py-2 px-6"
|
|
10
|
+
* cn("text-red-500", isActive && "text-blue-500") // => conditional
|
|
11
|
+
*/
|
|
12
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
13
|
+
return twMerge(clsx(inputs));
|
|
14
|
+
}
|