@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.4 → 0.1.6
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/CHANGELOG.md +1 -1
- package/dist/index.d.ts +131 -131
- package/dist/index.esm.js +148 -148
- package/dist/index.js +148 -148
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/accessibility-demo.tsx +271 -0
- package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
- package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
- package/src/components/ui/advanced-transition-system.tsx +395 -0
- package/src/components/ui/animation/animated-container.tsx +166 -0
- package/src/components/ui/animation/index.ts +19 -0
- package/src/components/ui/animation/staggered-container.tsx +68 -0
- package/src/components/ui/animation-demo.tsx +250 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
- package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
- package/src/components/ui/button.tsx +36 -0
- package/src/components/ui/card.tsx +207 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/color-preview.tsx +411 -0
- package/src/components/ui/data-display/chart.tsx +653 -0
- package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
- package/src/components/ui/data-display/data-grid.tsx +680 -0
- package/src/components/ui/data-display/list.tsx +456 -0
- package/src/components/ui/data-display/table.tsx +482 -0
- package/src/components/ui/data-display/timeline.tsx +441 -0
- package/src/components/ui/data-display/tree.tsx +602 -0
- package/src/components/ui/data-display/types.ts +536 -0
- package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
- package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
- package/src/components/ui/feedback/alert.tsx +157 -0
- package/src/components/ui/feedback/progress.tsx +292 -0
- package/src/components/ui/feedback/skeleton.tsx +185 -0
- package/src/components/ui/feedback/toast.tsx +280 -0
- package/src/components/ui/feedback/types.ts +125 -0
- package/src/components/ui/font-preview.tsx +288 -0
- package/src/components/ui/form-demo.tsx +553 -0
- package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
- package/src/components/ui/input.tsx +35 -0
- package/src/components/ui/label.tsx +16 -0
- package/src/components/ui/layout-demo.tsx +367 -0
- package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
- package/src/components/ui/layouts/desktop-layout.tsx +224 -0
- package/src/components/ui/layouts/index.ts +10 -0
- package/src/components/ui/layouts/mobile-layout.tsx +162 -0
- package/src/components/ui/layouts/tablet-layout.tsx +197 -0
- package/src/components/ui/mobile-form-validation.tsx +451 -0
- package/src/components/ui/mobile-input-demo.tsx +201 -0
- package/src/components/ui/mobile-input.tsx +281 -0
- package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
- package/src/components/ui/navigation/breadcrumb.tsx +158 -0
- package/src/components/ui/navigation/index.ts +36 -0
- package/src/components/ui/navigation/menu.tsx +374 -0
- package/src/components/ui/navigation/navigation-demo.tsx +324 -0
- package/src/components/ui/navigation/pagination.tsx +272 -0
- package/src/components/ui/navigation/sidebar.tsx +383 -0
- package/src/components/ui/navigation/stepper.tsx +303 -0
- package/src/components/ui/navigation/tabs.tsx +205 -0
- package/src/components/ui/navigation/types.ts +299 -0
- package/src/components/ui/overlay/backdrop.tsx +81 -0
- package/src/components/ui/overlay/focus-manager.tsx +143 -0
- package/src/components/ui/overlay/index.ts +36 -0
- package/src/components/ui/overlay/modal.tsx +270 -0
- package/src/components/ui/overlay/overlay-manager.tsx +110 -0
- package/src/components/ui/overlay/popover.tsx +462 -0
- package/src/components/ui/overlay/portal.tsx +79 -0
- package/src/components/ui/overlay/tooltip.tsx +303 -0
- package/src/components/ui/overlay/types.ts +196 -0
- package/src/components/ui/performance-demo.tsx +596 -0
- package/src/components/ui/semantic-input-system-demo.tsx +502 -0
- package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
- package/src/components/ui/tablet-layout.tsx +192 -0
- package/src/components/ui/theme-customizer.tsx +386 -0
- package/src/components/ui/theme-preview.tsx +310 -0
- package/src/components/ui/theme-switcher.tsx +264 -0
- package/src/components/ui/theme-toggle.tsx +38 -0
- package/src/components/ui/token-demo.tsx +195 -0
- package/src/components/ui/touch-demo.tsx +462 -0
- package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
- package/src/components/ui/touch-friendly-interface.tsx +296 -0
- package/src/hooks/index.ts +190 -0
- package/src/hooks/use-accessibility-support.ts +518 -0
- package/src/hooks/use-adaptive-layout.ts +289 -0
- package/src/hooks/use-advanced-patterns.ts +294 -0
- package/src/hooks/use-advanced-transition-system.ts +393 -0
- package/src/hooks/use-animation-profile.ts +288 -0
- package/src/hooks/use-battery-animations.ts +384 -0
- package/src/hooks/use-battery-conscious-loading.ts +475 -0
- package/src/hooks/use-battery-optimization.ts +330 -0
- package/src/hooks/use-battery-status.ts +299 -0
- package/src/hooks/use-component-performance.ts +344 -0
- package/src/hooks/use-device-loading-states.ts +459 -0
- package/src/hooks/use-device.tsx +110 -0
- package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
- package/src/hooks/use-form-feedback.ts +403 -0
- package/src/hooks/use-form-performance.ts +513 -0
- package/src/hooks/use-frame-rate.ts +251 -0
- package/src/hooks/use-gestures.ts +338 -0
- package/src/hooks/use-hardware-acceleration.ts +341 -0
- package/src/hooks/use-input-accessibility.ts +455 -0
- package/src/hooks/use-input-performance.ts +506 -0
- package/src/hooks/use-layout-performance.ts +319 -0
- package/src/hooks/use-loading-accessibility.ts +535 -0
- package/src/hooks/use-loading-performance.ts +473 -0
- package/src/hooks/use-memory-usage.ts +287 -0
- package/src/hooks/use-mobile-form-layout.ts +464 -0
- package/src/hooks/use-mobile-form-validation.ts +518 -0
- package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
- package/src/hooks/use-mobile-layout.ts +302 -0
- package/src/hooks/use-mobile-optimization.ts +406 -0
- package/src/hooks/use-mobile-skeleton.ts +402 -0
- package/src/hooks/use-mobile-touch.ts +414 -0
- package/src/hooks/use-performance-throttling.ts +348 -0
- package/src/hooks/use-performance.ts +316 -0
- package/src/hooks/use-reusable-architecture.ts +414 -0
- package/src/hooks/use-semantic-input-types.ts +357 -0
- package/src/hooks/use-semantic-input.ts +565 -0
- package/src/hooks/use-tablet-layout.ts +384 -0
- package/src/hooks/use-touch-friendly-input.ts +524 -0
- package/src/hooks/use-touch-friendly-interface.ts +331 -0
- package/src/hooks/use-touch-optimization.ts +375 -0
- package/src/index.ts +279 -279
- package/src/lib/utils.ts +6 -0
- package/src/themes/README.md +272 -0
- package/src/themes/ThemeContext.tsx +31 -0
- package/src/themes/ThemeProvider.tsx +232 -0
- package/src/themes/accessibility/index.ts +27 -0
- package/src/themes/accessibility.ts +259 -0
- package/src/themes/aria-patterns.ts +420 -0
- package/src/themes/base-themes.ts +55 -0
- package/src/themes/colorManager.ts +380 -0
- package/src/themes/examples/dark-theme.ts +154 -0
- package/src/themes/examples/minimal-theme.ts +108 -0
- package/src/themes/focus-management.ts +701 -0
- package/src/themes/fontLoader.ts +201 -0
- package/src/themes/high-contrast.ts +621 -0
- package/src/themes/index.ts +19 -0
- package/src/themes/inheritance.ts +227 -0
- package/src/themes/keyboard-navigation.ts +550 -0
- package/src/themes/motion-reduction.ts +662 -0
- package/src/themes/navigation.ts +238 -0
- package/src/themes/screen-reader.ts +645 -0
- package/src/themes/systemThemeDetector.ts +182 -0
- package/src/themes/themeCSSUpdater.ts +262 -0
- package/src/themes/themePersistence.ts +238 -0
- package/src/themes/themes/default.ts +586 -0
- package/src/themes/themes/harvey.ts +554 -0
- package/src/themes/themes/stan-design.ts +683 -0
- package/src/themes/types.ts +460 -0
- package/src/themes/useSystemTheme.ts +48 -0
- package/src/themes/useTheme.ts +87 -0
- package/src/themes/validation.ts +462 -0
- package/src/tokens/index.ts +34 -0
- package/src/tokens/tokenExporter.ts +397 -0
- package/src/tokens/tokenGenerator.ts +276 -0
- package/src/tokens/tokenManager.ts +248 -0
- package/src/tokens/tokenValidator.ts +543 -0
- package/src/tokens/types.ts +78 -0
- package/src/utils/bundle-analyzer.ts +260 -0
- package/src/utils/bundle-splitting.ts +483 -0
- package/src/utils/lazy-loading.ts +441 -0
- package/src/utils/performance-monitor.ts +513 -0
- package/src/utils/tree-shaking.ts +274 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTheme } from '../../themes';
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from './card';
|
|
4
|
+
import { Badge } from './badge';
|
|
5
|
+
|
|
6
|
+
export interface ThemePreviewProps {
|
|
7
|
+
themeName?: string;
|
|
8
|
+
showColors?: boolean;
|
|
9
|
+
showFonts?: boolean;
|
|
10
|
+
showNavigation?: boolean;
|
|
11
|
+
showSpacing?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ThemePreview: React.FC<ThemePreviewProps> = ({
|
|
16
|
+
themeName,
|
|
17
|
+
showColors = true,
|
|
18
|
+
showFonts = true,
|
|
19
|
+
showNavigation = true,
|
|
20
|
+
showSpacing = true,
|
|
21
|
+
className = ''
|
|
22
|
+
}) => {
|
|
23
|
+
const { getTheme, currentTheme } = useTheme();
|
|
24
|
+
const targetTheme = themeName || currentTheme;
|
|
25
|
+
const themeConfig = getTheme(targetTheme);
|
|
26
|
+
|
|
27
|
+
if (!themeConfig) {
|
|
28
|
+
return (
|
|
29
|
+
<div className={`p-4 text-center text-gray-500 ${className}`}>
|
|
30
|
+
Theme "{targetTheme}" not found
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const renderColorPreview = () => {
|
|
36
|
+
if (!showColors) return null;
|
|
37
|
+
|
|
38
|
+
const { colors } = themeConfig;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="space-y-4">
|
|
42
|
+
<h4 className="text-lg font-semibold">Colors</h4>
|
|
43
|
+
|
|
44
|
+
{/* Primary Colors */}
|
|
45
|
+
<div>
|
|
46
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
47
|
+
Primary Colors
|
|
48
|
+
</h5>
|
|
49
|
+
<div className="grid grid-cols-5 gap-2">
|
|
50
|
+
{Object.entries(colors.primary).map(([shade, color]) => (
|
|
51
|
+
<div key={shade} className="text-center">
|
|
52
|
+
<div
|
|
53
|
+
className="w-12 h-12 rounded-lg border border-gray-200 dark:border-gray-700 mb-1"
|
|
54
|
+
style={{ backgroundColor: color }}
|
|
55
|
+
/>
|
|
56
|
+
<span className="text-xs text-gray-600 dark:text-gray-400">{shade}</span>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Semantic Colors */}
|
|
63
|
+
<div>
|
|
64
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
65
|
+
Semantic Colors
|
|
66
|
+
</h5>
|
|
67
|
+
<div className="grid grid-cols-2 gap-2">
|
|
68
|
+
{Object.entries(colors.semantic).map(([name, color]) => (
|
|
69
|
+
<div key={name} className="flex items-center gap-2">
|
|
70
|
+
<div
|
|
71
|
+
className="w-6 h-6 rounded border border-gray-200 dark:border-gray-700"
|
|
72
|
+
style={{ backgroundColor: color }}
|
|
73
|
+
/>
|
|
74
|
+
<span className="text-sm text-gray-600 dark:text-gray-400 capitalize">
|
|
75
|
+
{name.replace(/([A-Z])/g, ' $1').trim()}
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Neutral Colors */}
|
|
83
|
+
<div>
|
|
84
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
85
|
+
Neutral Colors
|
|
86
|
+
</h5>
|
|
87
|
+
<div className="grid grid-cols-5 gap-2">
|
|
88
|
+
{Object.entries(colors.neutral).map(([shade, color]) => (
|
|
89
|
+
<div key={shade} className="text-center">
|
|
90
|
+
<div
|
|
91
|
+
className="w-12 h-12 rounded-lg border border-gray-200 dark:border-gray-700 mb-1"
|
|
92
|
+
style={{ backgroundColor: color }}
|
|
93
|
+
/>
|
|
94
|
+
<span className="text-xs text-gray-600 dark:text-gray-400">{shade}</span>
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const renderFontPreview = () => {
|
|
104
|
+
if (!showFonts) return null;
|
|
105
|
+
|
|
106
|
+
const { fonts } = themeConfig;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="space-y-4">
|
|
110
|
+
<h4 className="text-lg font-semibold">Typography</h4>
|
|
111
|
+
|
|
112
|
+
{/* Primary Font */}
|
|
113
|
+
<div>
|
|
114
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
115
|
+
Primary Font: {fonts.primary.family}
|
|
116
|
+
</h5>
|
|
117
|
+
<div className="space-y-2">
|
|
118
|
+
{Object.entries(fonts.primary.sizes).map(([size, value]) => (
|
|
119
|
+
<div key={size} className="flex items-center gap-4">
|
|
120
|
+
<span className="text-xs text-gray-500 w-12">{size}:</span>
|
|
121
|
+
<span
|
|
122
|
+
className="font-primary"
|
|
123
|
+
style={{
|
|
124
|
+
fontSize: value,
|
|
125
|
+
fontWeight: fonts.primary.weights[0] || 400
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
The quick brown fox jumps over the lazy dog
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
))}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{/* Font Weights */}
|
|
136
|
+
<div>
|
|
137
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
138
|
+
Font Weights
|
|
139
|
+
</h5>
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
{fonts.primary.weights.map((weight) => (
|
|
142
|
+
<div key={weight} className="flex items-center gap-4">
|
|
143
|
+
<span className="text-xs text-gray-500 w-12">{weight}:</span>
|
|
144
|
+
<span
|
|
145
|
+
className="font-primary"
|
|
146
|
+
style={{
|
|
147
|
+
fontSize: '1rem',
|
|
148
|
+
fontWeight: weight
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
The quick brown fox jumps over the lazy dog
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const renderNavigationPreview = () => {
|
|
162
|
+
if (!showNavigation) return null;
|
|
163
|
+
|
|
164
|
+
const { navigation } = themeConfig;
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div className="space-y-4">
|
|
168
|
+
<h4 className="text-lg font-semibold">Navigation</h4>
|
|
169
|
+
|
|
170
|
+
<div className="grid grid-cols-2 gap-4">
|
|
171
|
+
<div>
|
|
172
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
173
|
+
Configuration
|
|
174
|
+
</h5>
|
|
175
|
+
<div className="space-y-2 text-sm">
|
|
176
|
+
<div className="flex justify-between">
|
|
177
|
+
<span className="text-gray-600 dark:text-gray-400">Layout:</span>
|
|
178
|
+
<Badge variant="outline">{navigation.layout}</Badge>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="flex justify-between">
|
|
181
|
+
<span className="text-gray-600 dark:text-gray-400">Style:</span>
|
|
182
|
+
<Badge variant="outline">{navigation.style}</Badge>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="flex justify-between">
|
|
185
|
+
<span className="text-gray-600 dark:text-gray-400">Behavior:</span>
|
|
186
|
+
<Badge variant="outline">{navigation.behavior}</Badge>
|
|
187
|
+
</div>
|
|
188
|
+
<div className="flex justify-between">
|
|
189
|
+
<span className="text-gray-600 dark:text-gray-400">Responsive:</span>
|
|
190
|
+
<Badge variant="outline">{navigation.responsive}</Badge>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div>
|
|
196
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
197
|
+
Animations
|
|
198
|
+
</h5>
|
|
199
|
+
<div className="space-y-2 text-sm">
|
|
200
|
+
<div className="flex justify-between">
|
|
201
|
+
<span className="text-gray-600 dark:text-gray-400">Duration:</span>
|
|
202
|
+
<span className="text-gray-900 dark:text-gray-100">
|
|
203
|
+
{navigation.animations.duration.normal}
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
<div className="flex justify-between">
|
|
207
|
+
<span className="text-gray-600 dark:text-gray-400">Easing:</span>
|
|
208
|
+
<span className="text-gray-900 dark:text-gray-100">
|
|
209
|
+
{navigation.animations.easing.ease}
|
|
210
|
+
</span>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="flex justify-between">
|
|
213
|
+
<span className="text-gray-600 dark:text-gray-400">Stagger:</span>
|
|
214
|
+
<span className="text-gray-900 dark:text-gray-100">
|
|
215
|
+
{navigation.animations.stagger.enabled ? 'Enabled' : 'Disabled'}
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const renderSpacingPreview = () => {
|
|
226
|
+
if (!showSpacing) return null;
|
|
227
|
+
|
|
228
|
+
const { spacing } = themeConfig;
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div className="space-y-4">
|
|
232
|
+
<h4 className="text-lg font-semibold">Spacing</h4>
|
|
233
|
+
|
|
234
|
+
<div>
|
|
235
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
236
|
+
Scale
|
|
237
|
+
</h5>
|
|
238
|
+
<div className="space-y-2">
|
|
239
|
+
{Object.entries(spacing.scale).map(([size, value]) => (
|
|
240
|
+
<div key={size} className="flex items-center gap-4">
|
|
241
|
+
<span className="text-xs text-gray-500 w-12">{size}:</span>
|
|
242
|
+
<div
|
|
243
|
+
className="bg-blue-500 rounded"
|
|
244
|
+
style={{
|
|
245
|
+
width: value,
|
|
246
|
+
height: '1rem'
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
249
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">{value}</span>
|
|
250
|
+
</div>
|
|
251
|
+
))}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div>
|
|
256
|
+
<h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
257
|
+
Component Spacing
|
|
258
|
+
</h5>
|
|
259
|
+
<div className="space-y-2">
|
|
260
|
+
{Object.entries(spacing.component).map(([component, spacingConfig]) => (
|
|
261
|
+
<div key={component} className="border border-gray-200 dark:border-gray-700 rounded p-2">
|
|
262
|
+
<h6 className="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 capitalize">
|
|
263
|
+
{component}
|
|
264
|
+
</h6>
|
|
265
|
+
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
266
|
+
{Object.entries(spacingConfig).map(([prop, value]) => (
|
|
267
|
+
<div key={prop} className="text-center">
|
|
268
|
+
<div className="text-gray-500 capitalize">{prop}</div>
|
|
269
|
+
<div className="text-gray-900 dark:text-gray-100">{String(value)}</div>
|
|
270
|
+
</div>
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
))}
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<div className={`theme-preview ${className}`}>
|
|
283
|
+
<Card>
|
|
284
|
+
<CardHeader>
|
|
285
|
+
<CardTitle className="flex items-center gap-2">
|
|
286
|
+
<span className="text-2xl">
|
|
287
|
+
{targetTheme === 'stan-design' ? '⚡' :
|
|
288
|
+
targetTheme === 'enterprise' ? '🏢' :
|
|
289
|
+
targetTheme === 'harvey' ? '🎨' : '🎯'}
|
|
290
|
+
</span>
|
|
291
|
+
{themeConfig.meta.name} Theme Preview
|
|
292
|
+
</CardTitle>
|
|
293
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
294
|
+
{themeConfig.meta.description}
|
|
295
|
+
</p>
|
|
296
|
+
<div className="flex gap-2">
|
|
297
|
+
<Badge variant="outline">{themeConfig.meta.category}</Badge>
|
|
298
|
+
<Badge variant="secondary">v{themeConfig.meta.version}</Badge>
|
|
299
|
+
</div>
|
|
300
|
+
</CardHeader>
|
|
301
|
+
<CardContent className="space-y-6">
|
|
302
|
+
{renderColorPreview()}
|
|
303
|
+
{renderFontPreview()}
|
|
304
|
+
{renderNavigationPreview()}
|
|
305
|
+
{renderSpacingPreview()}
|
|
306
|
+
</CardContent>
|
|
307
|
+
</Card>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button } from './button';
|
|
3
|
+
import { useTheme } from '../../themes';
|
|
4
|
+
import type { MultiThemeConfig } from '../../themes';
|
|
5
|
+
|
|
6
|
+
export interface ThemeSwitcherProps {
|
|
7
|
+
themes?: Record<string, MultiThemeConfig>;
|
|
8
|
+
showPreview?: boolean;
|
|
9
|
+
showSystemTheme?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
variant?: 'default' | 'compact' | 'detailed';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ThemeSwitcher: React.FC<ThemeSwitcherProps> = ({
|
|
15
|
+
showPreview = true,
|
|
16
|
+
showSystemTheme = true,
|
|
17
|
+
className = '',
|
|
18
|
+
variant = 'default'
|
|
19
|
+
}) => {
|
|
20
|
+
const { currentTheme, availableThemes, setTheme, getTheme } = useTheme();
|
|
21
|
+
const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>('light');
|
|
22
|
+
const [showThemePreview, setShowThemePreview] = useState(false);
|
|
23
|
+
|
|
24
|
+
// Detect system theme preference
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!showSystemTheme) return;
|
|
27
|
+
|
|
28
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
29
|
+
const updateSystemTheme = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
30
|
+
setSystemTheme(e.matches ? 'dark' : 'light');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
updateSystemTheme(mediaQuery);
|
|
34
|
+
mediaQuery.addEventListener('change', updateSystemTheme);
|
|
35
|
+
|
|
36
|
+
return () => mediaQuery.removeEventListener('change', updateSystemTheme);
|
|
37
|
+
}, [showSystemTheme]);
|
|
38
|
+
|
|
39
|
+
const getThemeDisplayName = (theme: string) => {
|
|
40
|
+
const themeNames: Record<string, string> = {
|
|
41
|
+
'stan-design': 'Stan Design',
|
|
42
|
+
'enterprise': 'Enterprise',
|
|
43
|
+
'harvey': 'Harvey Creative'
|
|
44
|
+
};
|
|
45
|
+
return themeNames[theme] || theme;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getThemeEmoji = (theme: string) => {
|
|
49
|
+
const themeEmojis: Record<string, string> = {
|
|
50
|
+
'stan-design': '⚡',
|
|
51
|
+
'enterprise': '🏢',
|
|
52
|
+
'harvey': '🎨'
|
|
53
|
+
};
|
|
54
|
+
return themeEmojis[theme] || '🎯';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const getThemeDescription = (theme: string) => {
|
|
58
|
+
const themeConfig = getTheme(theme);
|
|
59
|
+
return themeConfig?.meta?.description || 'Custom theme';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getThemeCategory = (theme: string) => {
|
|
63
|
+
const themeConfig = getTheme(theme);
|
|
64
|
+
return themeConfig?.meta?.category || 'custom';
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleThemeSwitch = (themeName: string) => {
|
|
68
|
+
setTheme(themeName);
|
|
69
|
+
// Close preview if open
|
|
70
|
+
if (showThemePreview) {
|
|
71
|
+
setShowThemePreview(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleSystemThemeToggle = () => {
|
|
76
|
+
// For now, we'll just toggle between light/dark variants
|
|
77
|
+
// In a real implementation, this would switch to a system-aware theme
|
|
78
|
+
const currentConfig = getTheme(currentTheme);
|
|
79
|
+
if (currentConfig) {
|
|
80
|
+
// This is a placeholder - in practice, you'd have system-aware themes
|
|
81
|
+
console.log('System theme toggle - would switch to system-aware variant');
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const renderCompactSwitcher = () => (
|
|
86
|
+
<div className="flex items-center gap-2">
|
|
87
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">Theme:</span>
|
|
88
|
+
<div className="flex gap-1">
|
|
89
|
+
{availableThemes.map((theme) => (
|
|
90
|
+
<Button
|
|
91
|
+
key={theme}
|
|
92
|
+
variant={currentTheme === theme ? "default" : "outline"}
|
|
93
|
+
size="sm"
|
|
94
|
+
onClick={() => handleThemeSwitch(theme)}
|
|
95
|
+
className="text-xs"
|
|
96
|
+
>
|
|
97
|
+
{getThemeEmoji(theme)} {getThemeDisplayName(theme)}
|
|
98
|
+
</Button>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const renderDetailedSwitcher = () => (
|
|
105
|
+
<div className="space-y-4">
|
|
106
|
+
<div className="flex items-center justify-between">
|
|
107
|
+
<h3 className="text-lg font-semibold">Theme Selection</h3>
|
|
108
|
+
<Button
|
|
109
|
+
variant="outline"
|
|
110
|
+
size="sm"
|
|
111
|
+
onClick={() => setShowThemePreview(!showThemePreview)}
|
|
112
|
+
>
|
|
113
|
+
{showThemePreview ? 'Hide' : 'Show'} Preview
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
118
|
+
{availableThemes.map((theme) => {
|
|
119
|
+
const isActive = currentTheme === theme;
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
key={theme}
|
|
124
|
+
className={`p-4 rounded-lg border-2 transition-all duration-200 cursor-pointer ${
|
|
125
|
+
isActive
|
|
126
|
+
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
|
127
|
+
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
|
128
|
+
}`}
|
|
129
|
+
onClick={() => handleThemeSwitch(theme)}
|
|
130
|
+
>
|
|
131
|
+
<div className="flex items-center gap-3 mb-3">
|
|
132
|
+
<span className="text-2xl">{getThemeEmoji(theme)}</span>
|
|
133
|
+
<div>
|
|
134
|
+
<h4 className="font-semibold text-gray-900 dark:text-gray-100">
|
|
135
|
+
{getThemeDisplayName(theme)}
|
|
136
|
+
</h4>
|
|
137
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
138
|
+
{getThemeCategory(theme)}
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
|
|
144
|
+
{getThemeDescription(theme)}
|
|
145
|
+
</p>
|
|
146
|
+
|
|
147
|
+
{isActive && (
|
|
148
|
+
<div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
|
|
149
|
+
<div className="w-2 h-2 bg-blue-600 dark:bg-blue-400 rounded-full"></div>
|
|
150
|
+
<span className="text-sm font-medium">Active</span>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
})}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{showSystemTheme && (
|
|
159
|
+
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
160
|
+
<div className="flex items-center justify-between">
|
|
161
|
+
<div>
|
|
162
|
+
<h4 className="font-medium text-gray-900 dark:text-gray-100">
|
|
163
|
+
System Theme Detection
|
|
164
|
+
</h4>
|
|
165
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
166
|
+
Current system preference: {systemTheme}
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
<Button
|
|
170
|
+
variant="outline"
|
|
171
|
+
size="sm"
|
|
172
|
+
onClick={handleSystemThemeToggle}
|
|
173
|
+
>
|
|
174
|
+
Follow System
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const renderDefaultSwitcher = () => (
|
|
183
|
+
<div className="space-y-3">
|
|
184
|
+
<div className="flex items-center gap-2">
|
|
185
|
+
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Theme:</span>
|
|
186
|
+
<div className="flex gap-2">
|
|
187
|
+
{availableThemes.map((theme) => (
|
|
188
|
+
<Button
|
|
189
|
+
key={theme}
|
|
190
|
+
variant={currentTheme === theme ? "default" : "outline"}
|
|
191
|
+
size="sm"
|
|
192
|
+
onClick={() => handleThemeSwitch(theme)}
|
|
193
|
+
className="text-sm"
|
|
194
|
+
>
|
|
195
|
+
{getThemeEmoji(theme)} {getThemeDisplayName(theme)}
|
|
196
|
+
</Button>
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{showPreview && (
|
|
202
|
+
<div className="flex items-center gap-2">
|
|
203
|
+
<Button
|
|
204
|
+
variant="ghost"
|
|
205
|
+
size="sm"
|
|
206
|
+
onClick={() => setShowThemePreview(!showThemePreview)}
|
|
207
|
+
className="text-xs"
|
|
208
|
+
>
|
|
209
|
+
{showThemePreview ? 'Hide' : 'Show'} Theme Details
|
|
210
|
+
</Button>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{showThemePreview && (
|
|
215
|
+
<div className="mt-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
216
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
217
|
+
{availableThemes.map((theme) => {
|
|
218
|
+
const isActive = currentTheme === theme;
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
key={theme}
|
|
223
|
+
className={`p-2 rounded border text-xs ${
|
|
224
|
+
isActive
|
|
225
|
+
? 'border-blue-300 bg-blue-50 dark:bg-blue-900/20'
|
|
226
|
+
: 'border-gray-200 dark:border-gray-700'
|
|
227
|
+
}`}
|
|
228
|
+
>
|
|
229
|
+
<div className="flex items-center gap-2 mb-1">
|
|
230
|
+
<span>{getThemeEmoji(theme)}</span>
|
|
231
|
+
<span className="font-medium">{getThemeDisplayName(theme)}</span>
|
|
232
|
+
{isActive && (
|
|
233
|
+
<span className="text-blue-600 dark:text-blue-400 text-xs">●</span>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
237
|
+
{getThemeDescription(theme)}
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const renderContent = () => {
|
|
249
|
+
switch (variant) {
|
|
250
|
+
case 'compact':
|
|
251
|
+
return renderCompactSwitcher();
|
|
252
|
+
case 'detailed':
|
|
253
|
+
return renderDetailedSwitcher();
|
|
254
|
+
default:
|
|
255
|
+
return renderDefaultSwitcher();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div className={`theme-switcher ${className}`}>
|
|
261
|
+
{renderContent()}
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button } from './button';
|
|
3
|
+
import { Moon, Sun } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export const ThemeToggle: React.FC = () => {
|
|
6
|
+
const [isDark, setIsDark] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// Check initial theme
|
|
10
|
+
const isDarkMode = document.documentElement.classList.contains('dark');
|
|
11
|
+
setIsDark(isDarkMode);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const toggleTheme = () => {
|
|
15
|
+
const newDarkMode = !isDark;
|
|
16
|
+
setIsDark(newDarkMode);
|
|
17
|
+
|
|
18
|
+
if (newDarkMode) {
|
|
19
|
+
document.documentElement.classList.add('dark');
|
|
20
|
+
localStorage.setItem('theme', 'dark');
|
|
21
|
+
} else {
|
|
22
|
+
document.documentElement.classList.remove('dark');
|
|
23
|
+
localStorage.setItem('theme', 'light');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Button
|
|
29
|
+
variant="ghost"
|
|
30
|
+
size="sm"
|
|
31
|
+
onClick={toggleTheme}
|
|
32
|
+
className="h-8 w-8 px-0"
|
|
33
|
+
aria-label={`Switch to ${isDark ? 'light' : 'dark'} mode`}
|
|
34
|
+
>
|
|
35
|
+
{isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
36
|
+
</Button>
|
|
37
|
+
);
|
|
38
|
+
};
|