@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,482 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { persist } from 'zustand/middleware';
|
|
3
|
+
import { computeDerivedTokens, type FontTheme } from '@thesage/tokens';
|
|
4
|
+
import {
|
|
5
|
+
generateColorScale,
|
|
6
|
+
getOptimalForeground,
|
|
7
|
+
} from '../colors';
|
|
8
|
+
|
|
9
|
+
export type CustomizationMode = 'simple' | 'advanced';
|
|
10
|
+
|
|
11
|
+
export interface ColorPalette {
|
|
12
|
+
/* Metadata */
|
|
13
|
+
name?: string; // Name of the source palette
|
|
14
|
+
description?: string; // Description of the source palette
|
|
15
|
+
|
|
16
|
+
/* Colors */
|
|
17
|
+
primary: string; // Base hex
|
|
18
|
+
primaryForeground: string; // Calculated contrast
|
|
19
|
+
secondary?: string; // Optional secondary color
|
|
20
|
+
secondaryForeground?: string; // Calculated contrast
|
|
21
|
+
accent?: string; // Optional accent color
|
|
22
|
+
accentForeground?: string; // Calculated contrast
|
|
23
|
+
scale: Record<number, string>; // 50-900 tints/shades
|
|
24
|
+
derivedTokens: Record<string, string>; // Computed dependent tokens
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SavedPalette {
|
|
28
|
+
id: string; // Unique ID
|
|
29
|
+
name: string; // User-defined name
|
|
30
|
+
description: string; // Palette description
|
|
31
|
+
primary: string; // Primary color hex
|
|
32
|
+
accent: string; // Accent color hex
|
|
33
|
+
secondary?: string; // Optional secondary color
|
|
34
|
+
category: 'custom'; // Always 'custom' for user palettes
|
|
35
|
+
wcagAA: boolean; // Calculated accessibility
|
|
36
|
+
wcagAAA: boolean; // Calculated accessibility
|
|
37
|
+
createdAt: number; // Timestamp
|
|
38
|
+
mood: string[]; // Mood tags
|
|
39
|
+
bestFor?: string[]; // Best use cases
|
|
40
|
+
harmony?: string;
|
|
41
|
+
rationale?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SavedFontTheme extends FontTheme {
|
|
45
|
+
id: string; // Unique ID (overrides FontTheme.id)
|
|
46
|
+
createdAt: number; // Timestamp
|
|
47
|
+
category: 'custom'; // Always 'custom' for user font themes
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ColorCustomization {
|
|
51
|
+
mode: CustomizationMode;
|
|
52
|
+
palette: ColorPalette | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ThemeName = 'studio' | 'terra' | 'volt' | 'speedboat';
|
|
56
|
+
export type ColorMode = 'light' | 'dark';
|
|
57
|
+
|
|
58
|
+
interface CustomizerState {
|
|
59
|
+
// Motion settings
|
|
60
|
+
motion: number; // 0-10
|
|
61
|
+
prefersReducedMotion: boolean;
|
|
62
|
+
|
|
63
|
+
// Color customization
|
|
64
|
+
customizationMode: CustomizationMode;
|
|
65
|
+
customColors: {
|
|
66
|
+
[theme in ThemeName]?: {
|
|
67
|
+
[mode in ColorMode]?: ColorPalette;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Saved custom palettes
|
|
72
|
+
savedPalettes: SavedPalette[];
|
|
73
|
+
|
|
74
|
+
// Font theme customization
|
|
75
|
+
customFontThemes: {
|
|
76
|
+
[theme in ThemeName]?: {
|
|
77
|
+
[mode in ColorMode]?: FontTheme;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Saved custom font themes
|
|
82
|
+
savedFontThemes: SavedFontTheme[];
|
|
83
|
+
|
|
84
|
+
// Motion actions
|
|
85
|
+
setMotion: (level: number) => void;
|
|
86
|
+
setPrefersReducedMotion: (value: boolean) => void;
|
|
87
|
+
|
|
88
|
+
// Color customization actions
|
|
89
|
+
setCustomizationMode: (mode: CustomizationMode) => void;
|
|
90
|
+
|
|
91
|
+
setCustomPrimaryColor: (
|
|
92
|
+
theme: ThemeName,
|
|
93
|
+
mode: ColorMode,
|
|
94
|
+
hexColor: string
|
|
95
|
+
) => void;
|
|
96
|
+
|
|
97
|
+
setCustomSecondaryColor: (
|
|
98
|
+
theme: ThemeName,
|
|
99
|
+
mode: ColorMode,
|
|
100
|
+
hexColor: string
|
|
101
|
+
) => void;
|
|
102
|
+
|
|
103
|
+
setCustomAccentColor: (
|
|
104
|
+
theme: ThemeName,
|
|
105
|
+
mode: ColorMode,
|
|
106
|
+
hexColor: string
|
|
107
|
+
) => void;
|
|
108
|
+
|
|
109
|
+
applyColorPalette: (
|
|
110
|
+
theme: ThemeName,
|
|
111
|
+
mode: ColorMode,
|
|
112
|
+
colors: {
|
|
113
|
+
primary: string;
|
|
114
|
+
secondary?: string;
|
|
115
|
+
accent?: string;
|
|
116
|
+
name?: string;
|
|
117
|
+
description?: string;
|
|
118
|
+
}
|
|
119
|
+
) => void;
|
|
120
|
+
|
|
121
|
+
resetCustomColors: (theme: ThemeName, mode?: ColorMode) => void;
|
|
122
|
+
|
|
123
|
+
getActiveColorPalette: (theme: ThemeName, mode: ColorMode) => ColorPalette | null;
|
|
124
|
+
|
|
125
|
+
// Saved palette actions
|
|
126
|
+
savePalette: (palette: Omit<SavedPalette, 'id' | 'createdAt' | 'category'>) => void;
|
|
127
|
+
updatePalette: (id: string, updates: Partial<SavedPalette>) => void;
|
|
128
|
+
renamePalette: (id: string, newName: string) => void;
|
|
129
|
+
deletePalette: (id: string) => void;
|
|
130
|
+
reorderPalettes: (palettes: SavedPalette[]) => void;
|
|
131
|
+
getSavedPalettes: () => SavedPalette[];
|
|
132
|
+
|
|
133
|
+
// Font theme actions
|
|
134
|
+
applyFontTheme: (
|
|
135
|
+
theme: ThemeName,
|
|
136
|
+
mode: ColorMode,
|
|
137
|
+
fontTheme: FontTheme
|
|
138
|
+
) => void;
|
|
139
|
+
|
|
140
|
+
resetCustomFonts: (theme: ThemeName, mode?: ColorMode) => void;
|
|
141
|
+
|
|
142
|
+
getActiveFontTheme: (theme: ThemeName, mode: ColorMode) => FontTheme | null;
|
|
143
|
+
|
|
144
|
+
// Saved font theme actions
|
|
145
|
+
saveFontTheme: (fontTheme: Omit<SavedFontTheme, 'id' | 'createdAt' | 'category'>) => void;
|
|
146
|
+
updateFontTheme: (id: string, updates: Partial<SavedFontTheme>) => void;
|
|
147
|
+
renameFontTheme: (id: string, newName: string) => void;
|
|
148
|
+
deleteFontTheme: (id: string) => void;
|
|
149
|
+
reorderFontThemes: (fontThemes: SavedFontTheme[]) => void;
|
|
150
|
+
getSavedFontThemes: () => SavedFontTheme[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const useCustomizer = create<CustomizerState>()(
|
|
154
|
+
persist(
|
|
155
|
+
(set, get) => ({
|
|
156
|
+
motion: 5,
|
|
157
|
+
prefersReducedMotion: false,
|
|
158
|
+
customizationMode: 'simple',
|
|
159
|
+
customColors: {},
|
|
160
|
+
savedPalettes: [],
|
|
161
|
+
customFontThemes: {},
|
|
162
|
+
savedFontThemes: [],
|
|
163
|
+
|
|
164
|
+
setMotion: (level) => set({ motion: level }),
|
|
165
|
+
setPrefersReducedMotion: (value) => set({ prefersReducedMotion: value }),
|
|
166
|
+
setCustomizationMode: (mode) => set({ customizationMode: mode }),
|
|
167
|
+
|
|
168
|
+
setCustomPrimaryColor: (theme, mode, hexColor) => {
|
|
169
|
+
const state = get();
|
|
170
|
+
const currentPalette = state.customColors[theme]?.[mode];
|
|
171
|
+
|
|
172
|
+
// Generate complete color palette
|
|
173
|
+
const scale = generateColorScale(hexColor);
|
|
174
|
+
const primaryForeground = getOptimalForeground(hexColor);
|
|
175
|
+
|
|
176
|
+
// Compute all derived tokens based on dependency graph
|
|
177
|
+
const derivedTokens = computeDerivedTokens('--color-primary', hexColor, mode);
|
|
178
|
+
|
|
179
|
+
// In simple mode, generate secondary/accent from primary
|
|
180
|
+
const isSimple = state.customizationMode === 'simple';
|
|
181
|
+
|
|
182
|
+
const palette: ColorPalette = {
|
|
183
|
+
primary: hexColor,
|
|
184
|
+
primaryForeground,
|
|
185
|
+
secondary: isSimple ? undefined : currentPalette?.secondary,
|
|
186
|
+
secondaryForeground: isSimple ? undefined : currentPalette?.secondaryForeground,
|
|
187
|
+
accent: isSimple ? undefined : currentPalette?.accent,
|
|
188
|
+
accentForeground: isSimple ? undefined : currentPalette?.accentForeground,
|
|
189
|
+
scale,
|
|
190
|
+
derivedTokens,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
set((state) => ({
|
|
194
|
+
customColors: {
|
|
195
|
+
...state.customColors,
|
|
196
|
+
[theme]: {
|
|
197
|
+
...state.customColors[theme],
|
|
198
|
+
[mode]: palette,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
setCustomSecondaryColor: (theme, mode, hexColor) => {
|
|
205
|
+
const state = get();
|
|
206
|
+
const currentPalette = state.customColors[theme]?.[mode];
|
|
207
|
+
|
|
208
|
+
if (!currentPalette) return;
|
|
209
|
+
|
|
210
|
+
const secondaryForeground = getOptimalForeground(hexColor);
|
|
211
|
+
const derivedTokens = computeDerivedTokens('--color-secondary', hexColor, mode);
|
|
212
|
+
|
|
213
|
+
set((state) => ({
|
|
214
|
+
customColors: {
|
|
215
|
+
...state.customColors,
|
|
216
|
+
[theme]: {
|
|
217
|
+
...state.customColors[theme],
|
|
218
|
+
[mode]: {
|
|
219
|
+
...currentPalette,
|
|
220
|
+
secondary: hexColor,
|
|
221
|
+
secondaryForeground,
|
|
222
|
+
derivedTokens: {
|
|
223
|
+
...currentPalette.derivedTokens,
|
|
224
|
+
...derivedTokens,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
setCustomAccentColor: (theme, mode, hexColor) => {
|
|
233
|
+
const state = get();
|
|
234
|
+
const currentPalette = state.customColors[theme]?.[mode];
|
|
235
|
+
|
|
236
|
+
if (!currentPalette) return;
|
|
237
|
+
|
|
238
|
+
const accentForeground = getOptimalForeground(hexColor);
|
|
239
|
+
const derivedTokens = computeDerivedTokens('--color-accent', hexColor, mode);
|
|
240
|
+
|
|
241
|
+
set((state) => ({
|
|
242
|
+
customColors: {
|
|
243
|
+
...state.customColors,
|
|
244
|
+
[theme]: {
|
|
245
|
+
...state.customColors[theme],
|
|
246
|
+
[mode]: {
|
|
247
|
+
...currentPalette,
|
|
248
|
+
accent: hexColor,
|
|
249
|
+
accentForeground,
|
|
250
|
+
derivedTokens: {
|
|
251
|
+
...currentPalette.derivedTokens,
|
|
252
|
+
...derivedTokens,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
}));
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
applyColorPalette: (theme, mode, colors: {
|
|
261
|
+
primary: string;
|
|
262
|
+
secondary?: string;
|
|
263
|
+
accent?: string;
|
|
264
|
+
name?: string;
|
|
265
|
+
description?: string;
|
|
266
|
+
}) => {
|
|
267
|
+
// Generate complete color palette with all three colors in a single atomic update
|
|
268
|
+
const scale = generateColorScale(colors.primary);
|
|
269
|
+
const primaryForeground = getOptimalForeground(colors.primary);
|
|
270
|
+
|
|
271
|
+
// Compute all derived tokens
|
|
272
|
+
let derivedTokens = computeDerivedTokens('--color-primary', colors.primary, mode);
|
|
273
|
+
|
|
274
|
+
// Add secondary color if provided
|
|
275
|
+
const secondary = colors.secondary;
|
|
276
|
+
const secondaryForeground = secondary ? getOptimalForeground(secondary) : undefined;
|
|
277
|
+
if (secondary) {
|
|
278
|
+
const secondaryDerived = computeDerivedTokens('--color-secondary', secondary, mode);
|
|
279
|
+
derivedTokens = { ...derivedTokens, ...secondaryDerived };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Add accent color if provided
|
|
283
|
+
const accent = colors.accent;
|
|
284
|
+
const accentForeground = accent ? getOptimalForeground(accent) : undefined;
|
|
285
|
+
if (accent) {
|
|
286
|
+
const accentDerived = computeDerivedTokens('--color-accent', accent, mode);
|
|
287
|
+
derivedTokens = { ...derivedTokens, ...accentDerived };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const palette: ColorPalette = {
|
|
291
|
+
name: colors.name,
|
|
292
|
+
description: colors.description,
|
|
293
|
+
primary: colors.primary,
|
|
294
|
+
primaryForeground,
|
|
295
|
+
secondary,
|
|
296
|
+
secondaryForeground,
|
|
297
|
+
accent,
|
|
298
|
+
accentForeground,
|
|
299
|
+
scale,
|
|
300
|
+
derivedTokens,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
set((state) => ({
|
|
304
|
+
customColors: {
|
|
305
|
+
...state.customColors,
|
|
306
|
+
[theme]: {
|
|
307
|
+
...state.customColors[theme],
|
|
308
|
+
[mode]: palette,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
}));
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
resetCustomColors: (theme, mode) => {
|
|
315
|
+
if (mode) {
|
|
316
|
+
// Reset specific mode
|
|
317
|
+
set((state) => ({
|
|
318
|
+
customColors: {
|
|
319
|
+
...state.customColors,
|
|
320
|
+
[theme]: {
|
|
321
|
+
...state.customColors[theme],
|
|
322
|
+
[mode]: undefined,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
}));
|
|
326
|
+
} else {
|
|
327
|
+
// Reset entire theme
|
|
328
|
+
set((state) => {
|
|
329
|
+
const { [theme]: _, ...rest } = state.customColors;
|
|
330
|
+
return { customColors: rest };
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
getActiveColorPalette: (theme, mode) => {
|
|
336
|
+
return get().customColors[theme]?.[mode] || null;
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Saved palette management
|
|
340
|
+
savePalette: (paletteData) => {
|
|
341
|
+
const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
342
|
+
const newPalette: SavedPalette = {
|
|
343
|
+
...paletteData,
|
|
344
|
+
id,
|
|
345
|
+
category: 'custom',
|
|
346
|
+
createdAt: Date.now(),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
set((state) => ({
|
|
350
|
+
savedPalettes: [...state.savedPalettes, newPalette],
|
|
351
|
+
}));
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
updatePalette: (id, updates) => {
|
|
355
|
+
set((state) => ({
|
|
356
|
+
savedPalettes: state.savedPalettes.map((p) =>
|
|
357
|
+
p.id === id ? { ...p, ...updates } : p
|
|
358
|
+
),
|
|
359
|
+
}));
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
renamePalette: (id, newName) => {
|
|
363
|
+
set((state) => ({
|
|
364
|
+
savedPalettes: state.savedPalettes.map((p) =>
|
|
365
|
+
p.id === id ? { ...p, name: newName } : p
|
|
366
|
+
),
|
|
367
|
+
}));
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
deletePalette: (id) => {
|
|
371
|
+
set((state) => ({
|
|
372
|
+
savedPalettes: state.savedPalettes.filter((p) => p.id !== id),
|
|
373
|
+
}));
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
reorderPalettes: (palettes) => {
|
|
377
|
+
set({ savedPalettes: palettes });
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
getSavedPalettes: () => {
|
|
381
|
+
return get().savedPalettes;
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
// Font theme management
|
|
385
|
+
applyFontTheme: (theme, mode, fontTheme) => {
|
|
386
|
+
set((state) => ({
|
|
387
|
+
customFontThemes: {
|
|
388
|
+
...state.customFontThemes,
|
|
389
|
+
[theme]: {
|
|
390
|
+
...state.customFontThemes[theme],
|
|
391
|
+
[mode]: fontTheme,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
}));
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
resetCustomFonts: (theme, mode) => {
|
|
398
|
+
if (mode) {
|
|
399
|
+
// Reset specific mode
|
|
400
|
+
set((state) => ({
|
|
401
|
+
customFontThemes: {
|
|
402
|
+
...state.customFontThemes,
|
|
403
|
+
[theme]: {
|
|
404
|
+
...state.customFontThemes[theme],
|
|
405
|
+
[mode]: undefined,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
}));
|
|
409
|
+
} else {
|
|
410
|
+
// Reset entire theme
|
|
411
|
+
set((state) => {
|
|
412
|
+
const { [theme]: _, ...rest } = state.customFontThemes;
|
|
413
|
+
return { customFontThemes: rest };
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
getActiveFontTheme: (theme, mode) => {
|
|
419
|
+
return get().customFontThemes[theme]?.[mode] || null;
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Saved font theme management
|
|
423
|
+
saveFontTheme: (fontThemeData) => {
|
|
424
|
+
const id = `font-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
425
|
+
const newFontTheme: SavedFontTheme = {
|
|
426
|
+
...fontThemeData,
|
|
427
|
+
id,
|
|
428
|
+
category: 'custom',
|
|
429
|
+
createdAt: Date.now(),
|
|
430
|
+
isCustom: true,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
set((state) => ({
|
|
434
|
+
savedFontThemes: [...state.savedFontThemes, newFontTheme],
|
|
435
|
+
}));
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
updateFontTheme: (id, updates) => {
|
|
439
|
+
set((state) => ({
|
|
440
|
+
savedFontThemes: state.savedFontThemes.map((ft) =>
|
|
441
|
+
ft.id === id ? { ...ft, ...updates } : ft
|
|
442
|
+
),
|
|
443
|
+
}));
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
renameFontTheme: (id, newName) => {
|
|
447
|
+
set((state) => ({
|
|
448
|
+
savedFontThemes: state.savedFontThemes.map((ft) =>
|
|
449
|
+
ft.id === id ? { ...ft, name: newName } : ft
|
|
450
|
+
),
|
|
451
|
+
}));
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
deleteFontTheme: (id) => {
|
|
455
|
+
set((state) => ({
|
|
456
|
+
savedFontThemes: state.savedFontThemes.filter((ft) => ft.id !== id),
|
|
457
|
+
}));
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
reorderFontThemes: (fontThemes) => {
|
|
461
|
+
set({ savedFontThemes: fontThemes });
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
getSavedFontThemes: () => {
|
|
465
|
+
return get().savedFontThemes;
|
|
466
|
+
},
|
|
467
|
+
}),
|
|
468
|
+
{
|
|
469
|
+
name: 'ecosystem-customizer',
|
|
470
|
+
version: 4,
|
|
471
|
+
partialize: (state) => ({
|
|
472
|
+
motion: state.motion,
|
|
473
|
+
prefersReducedMotion: state.prefersReducedMotion,
|
|
474
|
+
customizationMode: state.customizationMode,
|
|
475
|
+
customColors: state.customColors,
|
|
476
|
+
savedPalettes: state.savedPalettes,
|
|
477
|
+
customFontThemes: state.customFontThemes,
|
|
478
|
+
savedFontThemes: state.savedFontThemes,
|
|
479
|
+
}),
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Store
|
|
3
|
+
* Manages theme and color mode state with localStorage persistence
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
import { persist } from 'zustand/middleware';
|
|
8
|
+
import type { ThemeName, ColorMode } from '@thesage/tokens';
|
|
9
|
+
|
|
10
|
+
// Re-export types for convenience
|
|
11
|
+
export type { ThemeName, ColorMode };
|
|
12
|
+
|
|
13
|
+
interface ThemeState {
|
|
14
|
+
// Current theme and mode
|
|
15
|
+
theme: ThemeName;
|
|
16
|
+
mode: ColorMode;
|
|
17
|
+
|
|
18
|
+
// Actions
|
|
19
|
+
setTheme: (theme: ThemeName) => void;
|
|
20
|
+
setMode: (mode: ColorMode) => void;
|
|
21
|
+
toggleMode: () => void;
|
|
22
|
+
|
|
23
|
+
// Computed
|
|
24
|
+
themeConfig: { name: ThemeName; mode: ColorMode };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useThemeStore = create<ThemeState>()(
|
|
28
|
+
persist(
|
|
29
|
+
(set, get) => ({
|
|
30
|
+
// Defaults
|
|
31
|
+
theme: 'volt',
|
|
32
|
+
mode: 'dark',
|
|
33
|
+
|
|
34
|
+
// Actions
|
|
35
|
+
setTheme: (theme) => set({ theme }),
|
|
36
|
+
setMode: (mode) => set({ mode }),
|
|
37
|
+
toggleMode: () =>
|
|
38
|
+
set((state) => ({ mode: state.mode === 'light' ? 'dark' : 'light' })),
|
|
39
|
+
|
|
40
|
+
// Computed
|
|
41
|
+
get themeConfig() {
|
|
42
|
+
const state = get();
|
|
43
|
+
return { name: state.theme, mode: state.mode };
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
{
|
|
47
|
+
name: 'ecosystem-theme',
|
|
48
|
+
// Only persist theme and mode
|
|
49
|
+
partialize: (state) => ({
|
|
50
|
+
theme: state.theme,
|
|
51
|
+
mode: state.mode,
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Parser
|
|
3
|
+
*
|
|
4
|
+
* Lightweight syntax parser for automatic code tokenization.
|
|
5
|
+
* Converts plain code strings into syntax-highlighted token arrays.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { parseCode } from '@thesage/ui';
|
|
10
|
+
*
|
|
11
|
+
* const tokens = parseCode('const greeting = "Hello World";');
|
|
12
|
+
* // Use with CollapsibleCodeBlock:
|
|
13
|
+
* <CollapsibleCodeBlock
|
|
14
|
+
* id="example"
|
|
15
|
+
* code={tokens}
|
|
16
|
+
* />
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export { tokenize, detectLanguage } from './tokenizer';
|
|
21
|
+
export type { SyntaxToken, SyntaxType, Language } from './types';
|
|
22
|
+
|
|
23
|
+
import { tokenize, detectLanguage } from './tokenizer';
|
|
24
|
+
import type { SyntaxToken, Language } from './types';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parses source code into syntax tokens for highlighting
|
|
28
|
+
*
|
|
29
|
+
* @param code - The source code to parse
|
|
30
|
+
* @param language - Optional language hint (auto-detected if not provided)
|
|
31
|
+
* @returns Array of syntax tokens ready for CollapsibleCodeBlock
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const code = `
|
|
36
|
+
* import { useState } from 'react';
|
|
37
|
+
*
|
|
38
|
+
* export function Counter() {
|
|
39
|
+
* const [count, setCount] = useState(0);
|
|
40
|
+
* return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
41
|
+
* }
|
|
42
|
+
* `;
|
|
43
|
+
*
|
|
44
|
+
* const tokens = parseCode(code);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function parseCode(code: string, language?: Language): SyntaxToken[] {
|
|
48
|
+
const lang = language || detectLanguage(code);
|
|
49
|
+
return tokenize(code, lang);
|
|
50
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Parser Patterns
|
|
3
|
+
* Regex patterns for tokenizing TypeScript/JavaScript/JSX code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Token pattern configuration
|
|
8
|
+
* Order matters - patterns are tested in sequence
|
|
9
|
+
*/
|
|
10
|
+
export const TOKEN_PATTERNS = [
|
|
11
|
+
// Single-line comments
|
|
12
|
+
{ type: 'comment' as const, pattern: /\/\/.*$/gm },
|
|
13
|
+
|
|
14
|
+
// Multi-line comments
|
|
15
|
+
{ type: 'comment' as const, pattern: /\/\*[\s\S]*?\*\//g },
|
|
16
|
+
|
|
17
|
+
// Template literals and strings
|
|
18
|
+
{ type: 'string' as const, pattern: /`(?:\\.|[^`\\])*`/g },
|
|
19
|
+
{ type: 'string' as const, pattern: /"(?:\\.|[^"\\])*"/g },
|
|
20
|
+
{ type: 'string' as const, pattern: /'(?:\\.|[^'\\])*'/g },
|
|
21
|
+
|
|
22
|
+
// JSX/TSX tags
|
|
23
|
+
{ type: 'tag' as const, pattern: /<\/?[A-Z][a-zA-Z0-9]*(?=[\s>])/g },
|
|
24
|
+
{ type: 'tag' as const, pattern: /<\/?[a-z][a-zA-Z0-9-]*(?=[\s>])/g },
|
|
25
|
+
|
|
26
|
+
// Keywords
|
|
27
|
+
{
|
|
28
|
+
type: 'keyword' as const,
|
|
29
|
+
pattern: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|throw|try|catch|finally|new|typeof|instanceof|void|delete|async|await|yield|export|import|from|default|class|extends|implements|interface|type|enum|namespace|declare|public|private|protected|static|readonly|abstract|as|is|in|of|null|undefined)\b/g
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Booleans
|
|
33
|
+
{ type: 'boolean' as const, pattern: /\b(true|false)\b/g },
|
|
34
|
+
|
|
35
|
+
// Numbers
|
|
36
|
+
{ type: 'number' as const, pattern: /\b\d+\.?\d*(?:e[+-]?\d+)?(?:n)?\b/gi },
|
|
37
|
+
{ type: 'number' as const, pattern: /\b0x[0-9a-f]+\b/gi },
|
|
38
|
+
|
|
39
|
+
// Function calls
|
|
40
|
+
{ type: 'function' as const, pattern: /\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*\()/g },
|
|
41
|
+
|
|
42
|
+
// Class names (PascalCase identifiers)
|
|
43
|
+
{ type: 'className' as const, pattern: /\b[A-Z][a-zA-Z0-9]*\b/g },
|
|
44
|
+
|
|
45
|
+
// JSX attributes
|
|
46
|
+
{ type: 'attribute' as const, pattern: /\b[a-z][a-zA-Z0-9]*(?=\s*=)/g },
|
|
47
|
+
|
|
48
|
+
// Object properties (after dot)
|
|
49
|
+
{ type: 'property' as const, pattern: /(?<=\.)[a-zA-Z_$][a-zA-Z0-9_$]*/g },
|
|
50
|
+
|
|
51
|
+
// Operators
|
|
52
|
+
{
|
|
53
|
+
type: 'operator' as const,
|
|
54
|
+
pattern: /[+\-*/%=!<>&|^~?:]+|&&|\|\||\.\.\.|\?\?|===|!==|==|!=|<=|>=|<<|>>|>>>/g
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Punctuation
|
|
58
|
+
{ type: 'punctuation' as const, pattern: /[{}[\](),.;]/g },
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Characters that should be treated as whitespace
|
|
63
|
+
*/
|
|
64
|
+
export const WHITESPACE = /\s+/g;
|