@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,192 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
import { useDevice } from "../../hooks/use-device"
|
|
4
|
+
|
|
5
|
+
interface TabletLayoutProps {
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
className?: string
|
|
8
|
+
orientation?: 'auto' | 'portrait' | 'landscape'
|
|
9
|
+
adaptive?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tablet-optimized layout container that adapts to orientation changes
|
|
14
|
+
* Provides optimal layouts for both portrait and landscape tablet modes
|
|
15
|
+
*/
|
|
16
|
+
export const TabletLayout: React.FC<TabletLayoutProps> = ({
|
|
17
|
+
children,
|
|
18
|
+
className,
|
|
19
|
+
orientation = 'auto',
|
|
20
|
+
adaptive = true
|
|
21
|
+
}) => {
|
|
22
|
+
const device = useDevice()
|
|
23
|
+
|
|
24
|
+
const getLayoutClasses = () => {
|
|
25
|
+
const classes = ['coach-stan-tablet-layout']
|
|
26
|
+
|
|
27
|
+
// Add device-specific classes
|
|
28
|
+
if (device.isTablet) {
|
|
29
|
+
classes.push('tablet-optimized')
|
|
30
|
+
|
|
31
|
+
// Handle orientation-specific layouts
|
|
32
|
+
const targetOrientation = orientation === 'auto' ? device.orientation : orientation
|
|
33
|
+
classes.push(`orientation-${targetOrientation}`)
|
|
34
|
+
|
|
35
|
+
if (adaptive) {
|
|
36
|
+
classes.push('adaptive-layout')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return classes
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={cn(getLayoutClasses(), className)}>
|
|
45
|
+
{children}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface TabletGridProps {
|
|
51
|
+
children: React.ReactNode
|
|
52
|
+
columns?: {
|
|
53
|
+
portrait: number
|
|
54
|
+
landscape: number
|
|
55
|
+
}
|
|
56
|
+
gap?: string
|
|
57
|
+
className?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Responsive grid component optimized for tablet orientations
|
|
62
|
+
*/
|
|
63
|
+
export const TabletGrid: React.FC<TabletGridProps> = ({
|
|
64
|
+
children,
|
|
65
|
+
columns = { portrait: 1, landscape: 2 },
|
|
66
|
+
gap = '1rem',
|
|
67
|
+
className
|
|
68
|
+
}) => {
|
|
69
|
+
const device = useDevice()
|
|
70
|
+
|
|
71
|
+
const gridStyle: React.CSSProperties = {
|
|
72
|
+
display: 'grid',
|
|
73
|
+
gap,
|
|
74
|
+
gridTemplateColumns: device.orientation === 'portrait'
|
|
75
|
+
? `repeat(${columns.portrait}, 1fr)`
|
|
76
|
+
: `repeat(${columns.landscape}, 1fr)`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
className={cn('coach-stan-tablet-grid', className)}
|
|
82
|
+
style={gridStyle}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface TabletSidebarProps {
|
|
90
|
+
children: React.ReactNode
|
|
91
|
+
side?: 'left' | 'right'
|
|
92
|
+
collapsible?: boolean
|
|
93
|
+
width?: string
|
|
94
|
+
className?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Tablet-optimized sidebar that adapts to orientation
|
|
99
|
+
* In portrait mode, sidebar can collapse or stack vertically
|
|
100
|
+
*/
|
|
101
|
+
export const TabletSidebar: React.FC<TabletSidebarProps> = ({
|
|
102
|
+
children,
|
|
103
|
+
side = 'left',
|
|
104
|
+
collapsible = false,
|
|
105
|
+
width = '280px',
|
|
106
|
+
className
|
|
107
|
+
}) => {
|
|
108
|
+
const device = useDevice()
|
|
109
|
+
|
|
110
|
+
const getSidebarClasses = () => {
|
|
111
|
+
const classes = ['coach-stan-tablet-sidebar']
|
|
112
|
+
|
|
113
|
+
if (device.isTablet) {
|
|
114
|
+
classes.push('tablet-optimized')
|
|
115
|
+
|
|
116
|
+
if (device.orientation === 'portrait') {
|
|
117
|
+
classes.push('orientation-portrait')
|
|
118
|
+
} else {
|
|
119
|
+
classes.push('orientation-landscape')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (side === 'right') {
|
|
124
|
+
classes.push('sidebar-right')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return classes
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sidebarStyle: React.CSSProperties = {
|
|
131
|
+
width: device.orientation === 'portrait' && collapsible ? '60px' : width,
|
|
132
|
+
transition: 'width 0.3s ease'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
className={cn(getSidebarClasses(), className)}
|
|
138
|
+
style={sidebarStyle}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</div>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface TabletStackProps {
|
|
146
|
+
children: React.ReactNode
|
|
147
|
+
direction?: 'auto' | 'horizontal' | 'vertical'
|
|
148
|
+
className?: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Responsive stack component that adapts to tablet orientation
|
|
153
|
+
*/
|
|
154
|
+
export const TabletStack: React.FC<TabletStackProps> = ({
|
|
155
|
+
children,
|
|
156
|
+
direction = 'auto',
|
|
157
|
+
className
|
|
158
|
+
}) => {
|
|
159
|
+
const device = useDevice()
|
|
160
|
+
|
|
161
|
+
const getStackClasses = () => {
|
|
162
|
+
const classes = ['coach-stan-tablet-stack']
|
|
163
|
+
|
|
164
|
+
if (device.isTablet) {
|
|
165
|
+
classes.push('tablet-optimized')
|
|
166
|
+
|
|
167
|
+
const targetDirection = direction === 'auto'
|
|
168
|
+
? (device.orientation === 'portrait' ? 'vertical' : 'horizontal')
|
|
169
|
+
: direction
|
|
170
|
+
|
|
171
|
+
classes.push(`stack-${targetDirection}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return classes
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const stackStyle: React.CSSProperties = {
|
|
178
|
+
display: 'flex',
|
|
179
|
+
flexDirection: device.orientation === 'portrait' ? 'column' : 'row',
|
|
180
|
+
gap: device.orientation === 'portrait' ? '1rem' : '1.5rem',
|
|
181
|
+
transition: 'flex-direction 0.3s ease'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div
|
|
186
|
+
className={cn(getStackClasses(), className)}
|
|
187
|
+
style={stackStyle}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useTheme } from '../../themes';
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from './card';
|
|
4
|
+
import { Button } from './button';
|
|
5
|
+
import { Input } from './input';
|
|
6
|
+
import { ThemePreview } from './theme-preview';
|
|
7
|
+
import type { CompleteThemeConfig } from '../../themes/types';
|
|
8
|
+
|
|
9
|
+
export interface ThemeCustomizerProps {
|
|
10
|
+
initialTheme?: string;
|
|
11
|
+
onThemeChange?: (theme: CompleteThemeConfig) => void;
|
|
12
|
+
showPreview?: boolean;
|
|
13
|
+
allowCustomization?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CustomizationState {
|
|
18
|
+
colors: {
|
|
19
|
+
primary: string;
|
|
20
|
+
secondary: string;
|
|
21
|
+
};
|
|
22
|
+
fonts: {
|
|
23
|
+
primary: string;
|
|
24
|
+
secondary: string;
|
|
25
|
+
};
|
|
26
|
+
spacing: {
|
|
27
|
+
scale: number;
|
|
28
|
+
};
|
|
29
|
+
shadows: {
|
|
30
|
+
intensity: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({
|
|
35
|
+
initialTheme = 'stan-design',
|
|
36
|
+
onThemeChange,
|
|
37
|
+
showPreview = true,
|
|
38
|
+
allowCustomization = true,
|
|
39
|
+
className = ''
|
|
40
|
+
}) => {
|
|
41
|
+
const { getTheme } = useTheme();
|
|
42
|
+
const [customizationState, setCustomizationState] = useState<CustomizationState>({
|
|
43
|
+
colors: {
|
|
44
|
+
primary: '#3b82f6',
|
|
45
|
+
secondary: '#10b981'
|
|
46
|
+
},
|
|
47
|
+
fonts: {
|
|
48
|
+
primary: 'Inter',
|
|
49
|
+
secondary: 'Roboto'
|
|
50
|
+
},
|
|
51
|
+
spacing: {
|
|
52
|
+
scale: 1.25
|
|
53
|
+
},
|
|
54
|
+
shadows: {
|
|
55
|
+
intensity: 0.1
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const [activeTab, setActiveTab] = useState<'colors' | 'fonts' | 'spacing' | 'shadows' | 'preview'>('colors');
|
|
60
|
+
const [customTheme, setCustomTheme] = useState<CompleteThemeConfig | null>(null);
|
|
61
|
+
|
|
62
|
+
// Initialize with the initial theme
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const theme = getTheme(initialTheme);
|
|
65
|
+
if (theme) {
|
|
66
|
+
setCustomTheme(theme);
|
|
67
|
+
}
|
|
68
|
+
}, [initialTheme]); // Removed getTheme from dependencies as it's stable
|
|
69
|
+
|
|
70
|
+
const handleCustomizationChange = useCallback((key: keyof CustomizationState, subKey: string, value: any) => {
|
|
71
|
+
setCustomizationState(prev => ({
|
|
72
|
+
...prev,
|
|
73
|
+
[key]: {
|
|
74
|
+
...prev[key],
|
|
75
|
+
[subKey]: value
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
const generateCustomTheme = useCallback(() => {
|
|
81
|
+
if (!customTheme) return;
|
|
82
|
+
|
|
83
|
+
// Create a deep copy of the current theme
|
|
84
|
+
const newTheme: CompleteThemeConfig = JSON.parse(JSON.stringify(customTheme));
|
|
85
|
+
|
|
86
|
+
// Apply customizations
|
|
87
|
+
// Generate color scales based on the selected primary color
|
|
88
|
+
const primaryColor = customizationState.colors.primary;
|
|
89
|
+
const secondaryColor = customizationState.colors.secondary;
|
|
90
|
+
|
|
91
|
+
// Simple color scale generation (in a real app, you'd use a color library)
|
|
92
|
+
const generateColorScale = (baseColor: string) => {
|
|
93
|
+
return {
|
|
94
|
+
50: `${baseColor}0a`,
|
|
95
|
+
100: `${baseColor}1a`,
|
|
96
|
+
200: `${baseColor}33`,
|
|
97
|
+
300: `${baseColor}4d`,
|
|
98
|
+
400: `${baseColor}66`,
|
|
99
|
+
500: baseColor,
|
|
100
|
+
600: `${baseColor}99`,
|
|
101
|
+
700: `${baseColor}b3`,
|
|
102
|
+
800: `${baseColor}cc`,
|
|
103
|
+
900: `${baseColor}e6`
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Update primary colors
|
|
108
|
+
newTheme.colors.primary = generateColorScale(primaryColor);
|
|
109
|
+
|
|
110
|
+
// Update secondary colors if they exist
|
|
111
|
+
if (newTheme.colors.secondary) {
|
|
112
|
+
newTheme.colors.secondary = generateColorScale(secondaryColor);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update fonts
|
|
116
|
+
if (newTheme.fonts.primary) {
|
|
117
|
+
newTheme.fonts.primary.family = customizationState.fonts.primary;
|
|
118
|
+
}
|
|
119
|
+
if (newTheme.fonts.secondary) {
|
|
120
|
+
newTheme.fonts.secondary.family = customizationState.fonts.secondary;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Update spacing scale (we'll adjust the existing scale)
|
|
124
|
+
if (newTheme.spacing && newTheme.spacing.scale) {
|
|
125
|
+
// This is a simplified approach - in reality you'd want to recalculate all spacing values
|
|
126
|
+
// For now, we'll just note that the scale factor has changed
|
|
127
|
+
console.log('Spacing scale factor updated to:', customizationState.spacing.scale);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Update shadow intensity (we'll adjust the existing shadows)
|
|
131
|
+
if (newTheme.shadows) {
|
|
132
|
+
// This is a simplified approach - in reality you'd want to recalculate all shadow values
|
|
133
|
+
// For now, we'll just note that the intensity has changed
|
|
134
|
+
console.log('Shadow intensity updated to:', customizationState.shadows.intensity);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setCustomTheme(newTheme);
|
|
138
|
+
|
|
139
|
+
if (onThemeChange) {
|
|
140
|
+
onThemeChange(newTheme);
|
|
141
|
+
}
|
|
142
|
+
}, [customTheme, customizationState, onThemeChange]);
|
|
143
|
+
|
|
144
|
+
const exportTheme = useCallback(() => {
|
|
145
|
+
if (!customTheme) return;
|
|
146
|
+
|
|
147
|
+
const themeData = JSON.stringify(customTheme, null, 2);
|
|
148
|
+
const blob = new Blob([themeData], { type: 'application/json' });
|
|
149
|
+
const url = URL.createObjectURL(blob);
|
|
150
|
+
const a = document.createElement('a');
|
|
151
|
+
a.href = url;
|
|
152
|
+
a.download = `${customTheme.meta.name.toLowerCase().replace(/\s+/g, '-')}-theme.json`;
|
|
153
|
+
document.body.appendChild(a);
|
|
154
|
+
a.click();
|
|
155
|
+
document.body.removeChild(a);
|
|
156
|
+
URL.revokeObjectURL(url);
|
|
157
|
+
}, [customTheme]);
|
|
158
|
+
|
|
159
|
+
const resetTheme = useCallback(() => {
|
|
160
|
+
const originalTheme = getTheme(initialTheme);
|
|
161
|
+
if (originalTheme) {
|
|
162
|
+
setCustomTheme(originalTheme);
|
|
163
|
+
setCustomizationState({
|
|
164
|
+
colors: {
|
|
165
|
+
primary: '#3b82f6',
|
|
166
|
+
secondary: '#10b981'
|
|
167
|
+
},
|
|
168
|
+
fonts: {
|
|
169
|
+
primary: 'Inter',
|
|
170
|
+
secondary: 'Roboto'
|
|
171
|
+
},
|
|
172
|
+
spacing: {
|
|
173
|
+
scale: 1.25
|
|
174
|
+
},
|
|
175
|
+
shadows: {
|
|
176
|
+
intensity: 0.1
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}, [initialTheme, getTheme]);
|
|
181
|
+
|
|
182
|
+
if (!customTheme) {
|
|
183
|
+
return (
|
|
184
|
+
<div className={`p-4 text-center text-gray-500 ${className}`}>
|
|
185
|
+
Loading theme...
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className={`theme-customizer ${className}`}>
|
|
192
|
+
<Card>
|
|
193
|
+
<CardHeader>
|
|
194
|
+
<CardTitle className="flex items-center gap-2">
|
|
195
|
+
🎨 Theme Customizer
|
|
196
|
+
</CardTitle>
|
|
197
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
198
|
+
Customize and preview themes in real-time
|
|
199
|
+
</p>
|
|
200
|
+
</CardHeader>
|
|
201
|
+
<CardContent>
|
|
202
|
+
{/* Tab Navigation */}
|
|
203
|
+
<div className="flex space-x-1 mb-6 border-b">
|
|
204
|
+
{allowCustomization && (
|
|
205
|
+
<>
|
|
206
|
+
<Button
|
|
207
|
+
variant={activeTab === 'colors' ? 'default' : 'ghost'}
|
|
208
|
+
size="sm"
|
|
209
|
+
onClick={() => setActiveTab('colors')}
|
|
210
|
+
>
|
|
211
|
+
Colors
|
|
212
|
+
</Button>
|
|
213
|
+
<Button
|
|
214
|
+
variant={activeTab === 'fonts' ? 'default' : 'ghost'}
|
|
215
|
+
size="sm"
|
|
216
|
+
onClick={() => setActiveTab('fonts')}
|
|
217
|
+
>
|
|
218
|
+
Fonts
|
|
219
|
+
</Button>
|
|
220
|
+
<Button
|
|
221
|
+
variant={activeTab === 'spacing' ? 'default' : 'ghost'}
|
|
222
|
+
size="sm"
|
|
223
|
+
onClick={() => setActiveTab('spacing')}
|
|
224
|
+
>
|
|
225
|
+
Spacing
|
|
226
|
+
</Button>
|
|
227
|
+
<Button
|
|
228
|
+
variant={activeTab === 'shadows' ? 'default' : 'ghost'}
|
|
229
|
+
size="sm"
|
|
230
|
+
onClick={() => setActiveTab('shadows')}
|
|
231
|
+
>
|
|
232
|
+
Shadows
|
|
233
|
+
</Button>
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
{showPreview && (
|
|
237
|
+
<Button
|
|
238
|
+
variant={activeTab === 'preview' ? 'default' : 'ghost'}
|
|
239
|
+
size="sm"
|
|
240
|
+
onClick={() => setActiveTab('preview')}
|
|
241
|
+
>
|
|
242
|
+
Preview
|
|
243
|
+
</Button>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Tab Content */}
|
|
248
|
+
<div className="min-h-[400px]">
|
|
249
|
+
{activeTab === 'colors' && allowCustomization && (
|
|
250
|
+
<div className="space-y-4">
|
|
251
|
+
<h3 className="text-lg font-semibold">Color Customization</h3>
|
|
252
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
253
|
+
<div>
|
|
254
|
+
<label className="block text-sm font-medium mb-2">Primary Color</label>
|
|
255
|
+
<Input
|
|
256
|
+
type="color"
|
|
257
|
+
value={customizationState.colors.primary}
|
|
258
|
+
onChange={(e) => handleCustomizationChange('colors', 'primary', e.target.value)}
|
|
259
|
+
className="w-full h-12"
|
|
260
|
+
/>
|
|
261
|
+
<Input
|
|
262
|
+
type="text"
|
|
263
|
+
value={customizationState.colors.primary}
|
|
264
|
+
onChange={(e) => handleCustomizationChange('colors', 'primary', e.target.value)}
|
|
265
|
+
placeholder="#3b82f6"
|
|
266
|
+
className="mt-2"
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
<div>
|
|
270
|
+
<label className="block text-sm font-medium mb-2">Secondary Color</label>
|
|
271
|
+
<Input
|
|
272
|
+
type="color"
|
|
273
|
+
value={customizationState.colors.secondary}
|
|
274
|
+
onChange={(e) => handleCustomizationChange('colors', 'secondary', e.target.value)}
|
|
275
|
+
className="w-full h-12"
|
|
276
|
+
/>
|
|
277
|
+
<Input
|
|
278
|
+
type="text"
|
|
279
|
+
value={customizationState.colors.secondary}
|
|
280
|
+
onChange={(e) => handleCustomizationChange('colors', 'secondary', e.target.value)}
|
|
281
|
+
placeholder="#10b981"
|
|
282
|
+
className="mt-2"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
|
|
290
|
+
{activeTab === 'fonts' && allowCustomization && (
|
|
291
|
+
<div className="space-y-4">
|
|
292
|
+
<h3 className="text-lg font-semibold">Font Customization</h3>
|
|
293
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
294
|
+
<div>
|
|
295
|
+
<label className="block text-sm font-medium mb-2">Primary Font</label>
|
|
296
|
+
<Input
|
|
297
|
+
value={customizationState.fonts.primary}
|
|
298
|
+
onChange={(e) => handleCustomizationChange('fonts', 'primary', e.target.value)}
|
|
299
|
+
placeholder="Inter"
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
<div>
|
|
303
|
+
<label className="block text-sm font-medium mb-2">Secondary Font</label>
|
|
304
|
+
<Input
|
|
305
|
+
value={customizationState.fonts.secondary}
|
|
306
|
+
onChange={(e) => handleCustomizationChange('fonts', 'secondary', e.target.value)}
|
|
307
|
+
placeholder="Roboto"
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{activeTab === 'spacing' && allowCustomization && (
|
|
315
|
+
<div className="space-y-4">
|
|
316
|
+
<h3 className="text-lg font-semibold">Spacing Customization</h3>
|
|
317
|
+
<div className="grid grid-cols-1 gap-4">
|
|
318
|
+
<div>
|
|
319
|
+
<label className="block text-sm font-medium mb-2">Scale Factor</label>
|
|
320
|
+
<Input
|
|
321
|
+
type="number"
|
|
322
|
+
value={customizationState.spacing.scale}
|
|
323
|
+
onChange={(e) => handleCustomizationChange('spacing', 'scale', Number(e.target.value))}
|
|
324
|
+
min="1.1"
|
|
325
|
+
max="2.0"
|
|
326
|
+
step="0.05"
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{activeTab === 'shadows' && allowCustomization && (
|
|
334
|
+
<div className="space-y-4">
|
|
335
|
+
<h3 className="text-lg font-semibold">Shadow Customization</h3>
|
|
336
|
+
<div className="grid grid-cols-1 gap-4">
|
|
337
|
+
<div>
|
|
338
|
+
<label className="block text-sm font-medium mb-2">Shadow Intensity</label>
|
|
339
|
+
<Input
|
|
340
|
+
type="range"
|
|
341
|
+
min="0"
|
|
342
|
+
max="0.5"
|
|
343
|
+
step="0.01"
|
|
344
|
+
value={customizationState.shadows.intensity}
|
|
345
|
+
onChange={(e) => handleCustomizationChange('shadows', 'intensity', Number(e.target.value))}
|
|
346
|
+
className="w-full"
|
|
347
|
+
/>
|
|
348
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
349
|
+
{customizationState.shadows.intensity}
|
|
350
|
+
</span>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
|
|
356
|
+
{activeTab === 'preview' && showPreview && (
|
|
357
|
+
<div>
|
|
358
|
+
<h3 className="text-lg font-semibold mb-4">Theme Preview</h3>
|
|
359
|
+
<ThemePreview themeName={customTheme.meta.name} />
|
|
360
|
+
</div>
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
{/* Action Buttons */}
|
|
365
|
+
{allowCustomization && (
|
|
366
|
+
<div className="flex justify-between items-center pt-6 border-t">
|
|
367
|
+
<div className="flex gap-2">
|
|
368
|
+
<Button variant="outline" onClick={resetTheme}>
|
|
369
|
+
Reset
|
|
370
|
+
</Button>
|
|
371
|
+
<Button onClick={generateCustomTheme}>
|
|
372
|
+
Apply Changes
|
|
373
|
+
</Button>
|
|
374
|
+
</div>
|
|
375
|
+
<div className="flex gap-2">
|
|
376
|
+
<Button variant="secondary" onClick={exportTheme}>
|
|
377
|
+
Export Theme
|
|
378
|
+
</Button>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
</CardContent>
|
|
383
|
+
</Card>
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
};
|