@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.5 → 0.1.7
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 +7 -0
- package/dist/index.esm.js +18 -1
- package/dist/index.js +18 -1
- package/dist/setupTests.d.ts +124 -0
- package/dist/setupTests.esm.js +122 -0
- package/dist/setupTests.js +122 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/index.css +1046 -0
- package/src/index.ts +18 -0
- package/src/plugins/theme-css-generator.ts +354 -0
- package/src/setupTests.ts +124 -0
- package/src/stories/README.md +39 -0
- package/src/stories/components/ThemeDebugger.tsx +143 -0
- package/src/stories/index.ts +29 -0
- package/src/stories/storybook-theme-imports.css +51 -0
- package/src/styles/base/fonts.css +30 -0
- package/src/styles/base/generated-theme-variables.css +573 -0
- package/src/styles/base/index.css +7 -0
- package/src/styles/base/reset.css +48 -0
- package/src/styles/base/theme.css +1068 -0
- package/src/styles/base/typography.css +68 -0
- package/src/styles/base/variables.css +5 -0
- package/src/styles/components/CLAUDE.md +62 -0
- package/src/styles/components/base/badge.css +428 -0
- package/src/styles/components/base/button.css +774 -0
- package/src/styles/components/base/card.css +601 -0
- package/src/styles/components/base/checkbox.css +442 -0
- package/src/styles/components/base/index.css +9 -0
- package/src/styles/components/base/input.css +887 -0
- package/src/styles/components/base/label.css +296 -0
- package/src/styles/components/data-display/chart.css +353 -0
- package/src/styles/components/data-display/data-grid.css +619 -0
- package/src/styles/components/data-display/index.css +9 -0
- package/src/styles/components/data-display/list.css +560 -0
- package/src/styles/components/data-display/table.css +498 -0
- package/src/styles/components/data-display/timeline.css +764 -0
- package/src/styles/components/data-display/tree.css +881 -0
- package/src/styles/components/feedback/alert.css +358 -0
- package/src/styles/components/feedback/index.css +7 -0
- package/src/styles/components/feedback/progress.css +435 -0
- package/src/styles/components/feedback/skeleton.css +337 -0
- package/src/styles/components/feedback/toast.css +564 -0
- package/src/styles/components/index.css +17 -0
- package/src/styles/components/navigation/breadcrumb.css +465 -0
- package/src/styles/components/navigation/index.css +9 -0
- package/src/styles/components/navigation/menu.css +572 -0
- package/src/styles/components/navigation/pagination.css +635 -0
- package/src/styles/components/navigation/sidebar.css +807 -0
- package/src/styles/components/navigation/stepper.css +519 -0
- package/src/styles/components/navigation/tabs.css +404 -0
- package/src/styles/components/overlay/backdrop.css +243 -0
- package/src/styles/components/overlay/index.css +8 -0
- package/src/styles/components/overlay/modal.css +482 -0
- package/src/styles/components/overlay/popover.css +607 -0
- package/src/styles/components/overlay/portal.css +213 -0
- package/src/styles/components/overlay/tooltip.css +488 -0
- package/src/styles/generated-theme-variables.css +573 -0
- package/src/styles/index.css +5 -0
- package/src/styles/layers/index.css +54 -0
- package/src/styles/layers/overrides.css +108 -0
- package/src/styles/layers/validation.css +159 -0
- package/src/styles/layers/validation.js +310 -0
- package/src/styles/themes/default.css +450 -0
- package/src/styles/themes/enterprise.css +370 -0
- package/src/styles/themes/harvey.css +436 -0
- package/src/styles/themes/index.css +4 -0
- package/src/styles/themes/stan-design.css +572 -0
- package/src/styles/utilities/advanced-transition-system.css +467 -0
- package/src/styles/utilities/battery-conscious-animations.css +289 -0
- package/src/styles/utilities/enterprise-mobile-experience.css +817 -0
- package/src/styles/utilities/hardware-acceleration.css +121 -0
- package/src/styles/utilities/index.css +20 -0
- package/src/styles/utilities/mobile-skeleton-loading.css +596 -0
- package/src/styles/utilities/semantic-input-system.css +451 -0
- package/src/styles/utilities/touch-friendly-interface.css +247 -0
- package/src/styles/utilities/touch-optimization.css +165 -0
- package/src/test-utils/index.ts +7 -0
- package/src/test-utils/theme-testing.tsx +219 -0
- package/src/testing/test-automation.ts +627 -0
- package/src/testing/test-utils.tsx +367 -0
package/src/index.ts
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* - 30 theme system components
|
|
10
10
|
* - 131 TypeScript types
|
|
11
11
|
* - 4 design tokens
|
|
12
|
+
* - 0 build plugins
|
|
13
|
+
* - 0 test utilities
|
|
14
|
+
* - 2 testing tools
|
|
15
|
+
* - 0 development tools
|
|
16
|
+
* - Complete CSS system (59+ files)
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
// UI Components
|
|
@@ -169,6 +174,19 @@ export { TokenGenerator } from './tokens/tokenGenerator';
|
|
|
169
174
|
export { TokenManager } from './tokens/tokenManager';
|
|
170
175
|
export { TokenValidator } from './tokens/tokenValidator';
|
|
171
176
|
|
|
177
|
+
// Build Plugins
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// Test Utilities
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// Testing Framework
|
|
184
|
+
export { TestAutomation } from './testing/test-automation';
|
|
185
|
+
export { TestCLI } from './testing/test-automation';
|
|
186
|
+
|
|
187
|
+
// Development Tools
|
|
188
|
+
|
|
189
|
+
|
|
172
190
|
// Types
|
|
173
191
|
export type { NavigationBaseProps } from './components/ui/navigation/types';
|
|
174
192
|
export type { NavigationItem } from './components/ui/navigation/types';
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { resolve } from 'path'
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
3
|
+
import type { Plugin } from 'vite'
|
|
4
|
+
import type { MultiThemeConfig } from '../themes/types'
|
|
5
|
+
import { defaultThemes } from '../themes/base-themes'
|
|
6
|
+
|
|
7
|
+
export default function themeCSSGenerator(): Plugin {
|
|
8
|
+
let config: any
|
|
9
|
+
|
|
10
|
+
// Helper function to safely convert values to strings
|
|
11
|
+
const valueToString = (value: any): string => {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.join(', ')
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'object' && value !== null) {
|
|
16
|
+
return JSON.stringify(value)
|
|
17
|
+
}
|
|
18
|
+
return String(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Helper function to create CSS variable name
|
|
22
|
+
const createCSSVarName = (path: string[]): string => {
|
|
23
|
+
return `--cs-${path.join('-').replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Generate @font-face declarations from font configuration
|
|
27
|
+
const generateFontFaces = (themeObj: MultiThemeConfig): string => {
|
|
28
|
+
let css = ''
|
|
29
|
+
|
|
30
|
+
if (!themeObj.fonts) return css
|
|
31
|
+
|
|
32
|
+
Object.entries(themeObj.fonts).forEach(([, fontConfig]) => {
|
|
33
|
+
if (fontConfig.source?.type === 'custom' && fontConfig.source.files?.woff2) {
|
|
34
|
+
const { family, source, weights, display } = fontConfig
|
|
35
|
+
|
|
36
|
+
if (weights && Array.isArray(weights) && weights.length > 0) {
|
|
37
|
+
const minWeight = Math.min(...weights)
|
|
38
|
+
const maxWeight = Math.max(...weights)
|
|
39
|
+
|
|
40
|
+
css += `\n@font-face {\n`
|
|
41
|
+
css += ` font-family: '${family}';\n`
|
|
42
|
+
css += ` src: url('${source.files.woff2}') format('woff2-variations');\n`
|
|
43
|
+
css += ` font-weight: ${minWeight} ${maxWeight};\n`
|
|
44
|
+
css += ` font-display: ${display || 'swap'};\n`
|
|
45
|
+
css += `}\n`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return css
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// NEW: Extract breakpoints from anywhere in the theme object
|
|
54
|
+
const extractBreakpoints = (obj: any): any => {
|
|
55
|
+
let breakpoints: any = {}
|
|
56
|
+
|
|
57
|
+
const searchForBreakpoints = (currentObj: any, currentPath: string[] = []) => {
|
|
58
|
+
if (typeof currentObj !== 'object' || currentObj === null) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Object.entries(currentObj).forEach(([key, value]) => {
|
|
63
|
+
if (key === 'breakpoints' && typeof value === 'object' && value !== null) {
|
|
64
|
+
// Found breakpoints, extract them
|
|
65
|
+
Object.entries(value).forEach(([bpKey, bpValue]) => {
|
|
66
|
+
if (typeof bpValue === 'string' || typeof bpValue === 'number') {
|
|
67
|
+
breakpoints[bpKey] = bpValue
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
71
|
+
// Recursively search deeper
|
|
72
|
+
searchForBreakpoints(value, [...currentPath, key])
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
searchForBreakpoints(obj)
|
|
78
|
+
return breakpoints
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// NEW: Generate breakpoint CSS variables and @custom-media queries
|
|
82
|
+
const generateBreakpointVariables = (breakpoints: any): string => {
|
|
83
|
+
let css = ''
|
|
84
|
+
if (Object.keys(breakpoints).length === 0) { return css }
|
|
85
|
+
|
|
86
|
+
css += ` /* Breakpoint Variables */\n`
|
|
87
|
+
Object.entries(breakpoints).forEach(([key, value]) => {
|
|
88
|
+
css += ` --cs-breakpoints-${key}: ${value};\n`
|
|
89
|
+
})
|
|
90
|
+
css += '\n'
|
|
91
|
+
|
|
92
|
+
// NEW: Generate @custom-media queries for use in media queries
|
|
93
|
+
css += ` /* Custom Media Queries for Breakpoints */\n`
|
|
94
|
+
Object.entries(breakpoints).forEach(([key, value]) => {
|
|
95
|
+
css += ` @custom-media --bp-${key} (min-width: ${value});\n`
|
|
96
|
+
})
|
|
97
|
+
css += '\n'
|
|
98
|
+
|
|
99
|
+
return css
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Recursively generate CSS variables from theme object
|
|
103
|
+
const generateCSSVariables = (obj: any, path: string[] = []): string => {
|
|
104
|
+
let css = ''
|
|
105
|
+
|
|
106
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
107
|
+
return css
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
111
|
+
const currentPath = [...path, key]
|
|
112
|
+
|
|
113
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
114
|
+
// Generate CSS variable for primitive values
|
|
115
|
+
const cssVarName = createCSSVarName(currentPath)
|
|
116
|
+
css += ` ${cssVarName}: ${value};\n`
|
|
117
|
+
} else if (Array.isArray(value)) {
|
|
118
|
+
// Handle arrays (like font weights, tags)
|
|
119
|
+
const cssVarName = createCSSVarName(currentPath)
|
|
120
|
+
css += ` ${cssVarName}: ${valueToString(value)};\n`
|
|
121
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
122
|
+
// Recursively process nested objects
|
|
123
|
+
css += generateCSSVariables(value, currentPath)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return css
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Generate CSS from theme object using structured traversal
|
|
131
|
+
const generateSingleThemeCSS = (themeName: string, themeObj: MultiThemeConfig): string => {
|
|
132
|
+
// Always generate light + dark mode CSS
|
|
133
|
+
return generateLightDarkCSS(themeName, themeObj)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate light and dark mode CSS
|
|
137
|
+
const generateLightDarkCSS = (themeName: string, themeObj: MultiThemeConfig): string => {
|
|
138
|
+
let css = `/* ${themeName} Theme - Light & Dark Modes */\n`
|
|
139
|
+
|
|
140
|
+
// Generate @font-face declarations
|
|
141
|
+
css += generateFontFaces(themeObj)
|
|
142
|
+
|
|
143
|
+
// NEW: Extract breakpoints from anywhere in the theme
|
|
144
|
+
const breakpoints = extractBreakpoints(themeObj)
|
|
145
|
+
|
|
146
|
+
// Generate light mode variables (default)
|
|
147
|
+
css += `:root {\n`
|
|
148
|
+
css += ` /* Light Mode Variables */\n`
|
|
149
|
+
|
|
150
|
+
// NEW: Generate breakpoint variables first
|
|
151
|
+
css += generateBreakpointVariables(breakpoints)
|
|
152
|
+
|
|
153
|
+
// Generate all other CSS variables
|
|
154
|
+
css += generateCSSVariables(themeObj)
|
|
155
|
+
css += '}\n\n'
|
|
156
|
+
|
|
157
|
+
// Generate dark mode variables
|
|
158
|
+
css += `.dark {\n`
|
|
159
|
+
css += ` /* Dark Mode Variables */\n`
|
|
160
|
+
|
|
161
|
+
if (themeObj.modes?.dark?.colors && Object.keys(themeObj.modes.dark.colors).length > 0) {
|
|
162
|
+
// Use custom dark mode colors if defined
|
|
163
|
+
css += generateDarkModeColors(themeObj.modes.dark.colors)
|
|
164
|
+
} else {
|
|
165
|
+
// Generate automatic dark mode by adapting colors
|
|
166
|
+
css += generateDarkModeVariables(themeObj)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
css += '}\n'
|
|
170
|
+
return css
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Generate dark mode colors from explicit color definitions
|
|
174
|
+
const generateDarkModeColors = (darkColors: any): string => {
|
|
175
|
+
let css = ''
|
|
176
|
+
|
|
177
|
+
Object.entries(darkColors).forEach(([colorKey, colorValue]) => {
|
|
178
|
+
if (typeof colorValue === 'object' && colorValue !== null) {
|
|
179
|
+
// Handle nested color objects (like surface, text, etc.)
|
|
180
|
+
Object.entries(colorValue).forEach(([subKey, subValue]) => {
|
|
181
|
+
if (typeof subValue === 'string' || typeof subValue === 'number') {
|
|
182
|
+
css += ` --cs-colors-${colorKey}-${subKey}: ${subValue};\n`
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
} else if (typeof colorValue === 'string' || typeof colorValue === 'number') {
|
|
186
|
+
// Handle direct color values
|
|
187
|
+
css += ` --cs-colors-${colorKey}: ${colorValue};\n`
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return css
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generate dark mode variables by adapting light mode colors
|
|
195
|
+
const generateDarkModeVariables = (themeObj: MultiThemeConfig): string => {
|
|
196
|
+
// Create a dark variant of the theme by adapting colors
|
|
197
|
+
const darkTheme = adaptThemeForDarkMode(themeObj)
|
|
198
|
+
|
|
199
|
+
// Generate CSS variables only for the properties that exist in the dark theme
|
|
200
|
+
let css = ''
|
|
201
|
+
|
|
202
|
+
if (darkTheme.colors) {
|
|
203
|
+
// Generate CSS variables for colors
|
|
204
|
+
Object.entries(darkTheme.colors).forEach(([colorKey, colorValue]) => {
|
|
205
|
+
if (typeof colorValue === 'object' && colorValue !== null) {
|
|
206
|
+
// Handle nested color objects (like surface, text, etc.)
|
|
207
|
+
Object.entries(colorValue).forEach(([subKey, subValue]) => {
|
|
208
|
+
if (typeof subValue === 'string' || typeof subValue === 'number') {
|
|
209
|
+
css += ` --cs-colors-${colorKey}-${subKey}: ${subValue};\n`
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
} else if (typeof colorValue === 'string' || typeof colorValue === 'number') {
|
|
213
|
+
// Handle direct color values
|
|
214
|
+
css += ` --cs-colors-${colorKey}: ${colorValue};\n`
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Add some basic dark mode variables for testing
|
|
220
|
+
css += ` --cs-colors-surface-background: #0f172a;\n`
|
|
221
|
+
css += ` --cs-colors-surface-surface: #1e293b;\n`
|
|
222
|
+
css += ` --cs-colors-surface-border: #334155;\n`
|
|
223
|
+
css += ` --cs-colors-surface-divider: #475569;\n`
|
|
224
|
+
css += ` --cs-colors-text-primary: #f8fafc;\n`
|
|
225
|
+
css += ` --cs-colors-text-secondary: #cbd5e1;\n`
|
|
226
|
+
css += ` --cs-colors-text-muted: #94a3b8;\n`
|
|
227
|
+
|
|
228
|
+
return css
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Adapt theme for dark mode
|
|
232
|
+
const adaptThemeForDarkMode = (_themeObj: MultiThemeConfig): Partial<MultiThemeConfig> => {
|
|
233
|
+
// This would implement your dark mode color adaptation logic
|
|
234
|
+
// You can use the existing ColorManager or implement custom logic
|
|
235
|
+
return {
|
|
236
|
+
colors: {
|
|
237
|
+
surface: {
|
|
238
|
+
background: '#0f172a', // Dark background
|
|
239
|
+
surface: '#1e293b', // Dark surface
|
|
240
|
+
border: '#334155', // Dark border
|
|
241
|
+
divider: '#475569' // Dark divider
|
|
242
|
+
},
|
|
243
|
+
text: {
|
|
244
|
+
primary: '#f8fafc', // Light text
|
|
245
|
+
secondary: '#cbd5e1', // Muted text
|
|
246
|
+
muted: '#94a3b8', // Muted text
|
|
247
|
+
inverse: '#0f172a', // Dark text for light backgrounds
|
|
248
|
+
onPrimary: '#0f172a', // Text on primary color
|
|
249
|
+
onSecondary: '#0f172a', // Text on secondary color
|
|
250
|
+
onSurface: '#f8fafc' // Text on surface
|
|
251
|
+
}
|
|
252
|
+
// ... other color adaptations
|
|
253
|
+
} as any // Type assertion to bypass strict typing for now
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Generate CSS for all themes using structured approach
|
|
258
|
+
const generateAllThemesCSS = () => {
|
|
259
|
+
try {
|
|
260
|
+
// Create themes directory if it doesn't exist
|
|
261
|
+
const themesDir = resolve(config.root, 'src/styles/themes')
|
|
262
|
+
if (!existsSync(themesDir)) {
|
|
263
|
+
mkdirSync(themesDir, { recursive: true })
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let indexCSS = '/* Theme Index - Import all themes */\n'
|
|
267
|
+
let defaultThemeCSS = '/* Default Theme Variables (Coach-Stan) */\n'
|
|
268
|
+
|
|
269
|
+
// Use defaultThemes instead of hardcoded themeFiles
|
|
270
|
+
for (const [themeKey, themeObj] of Object.entries(defaultThemes)) {
|
|
271
|
+
try {
|
|
272
|
+
// Generate CSS for this specific theme
|
|
273
|
+
const themeCSS = generateSingleThemeCSS(themeObj.meta.name, themeObj)
|
|
274
|
+
|
|
275
|
+
// Write individual theme file
|
|
276
|
+
const themeFileName = `${themeKey}.css`
|
|
277
|
+
const themeOutputPath = resolve(themesDir, themeFileName)
|
|
278
|
+
writeFileSync(themeOutputPath, themeCSS, 'utf-8')
|
|
279
|
+
|
|
280
|
+
console.log(`✅ Generated dual-mode CSS for theme: ${themeObj.meta.name} -> ${themeFileName}`)
|
|
281
|
+
|
|
282
|
+
// Add import to index file
|
|
283
|
+
indexCSS += `@import './${themeFileName}';\n`
|
|
284
|
+
|
|
285
|
+
// Set stan-design theme as default
|
|
286
|
+
if (themeKey === 'stan-design') {
|
|
287
|
+
defaultThemeCSS += themeCSS
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(`❌ Error processing ${themeKey}:`, error)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Write theme index file
|
|
296
|
+
const indexPath = resolve(config.root, 'src/styles/themes/index.css')
|
|
297
|
+
writeFileSync(indexPath, indexCSS, 'utf-8')
|
|
298
|
+
|
|
299
|
+
// Write default theme file (for backward compatibility)
|
|
300
|
+
const defaultPath = resolve(config.root, 'src/styles/generated-theme-variables.css')
|
|
301
|
+
writeFileSync(defaultPath, defaultThemeCSS, 'utf-8')
|
|
302
|
+
|
|
303
|
+
console.log('✅ Theme CSS Generator: Generated all dual-mode theme files successfully')
|
|
304
|
+
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('❌ Theme CSS Generator Error:', error)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Main function to generate theme CSS
|
|
311
|
+
const generateThemeCSS = () => {
|
|
312
|
+
try {
|
|
313
|
+
// Check if themes directory exists
|
|
314
|
+
const themesSourceDir = resolve(config.root, 'src/themes/themes')
|
|
315
|
+
|
|
316
|
+
if (!existsSync(themesSourceDir)) {
|
|
317
|
+
console.warn('⚠️ Theme CSS Generator: themes/themes/ directory not found')
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Generate CSS for all themes
|
|
322
|
+
generateAllThemesCSS()
|
|
323
|
+
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('❌ Theme CSS Generator Error:', error)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
name: 'theme-css-generator',
|
|
331
|
+
|
|
332
|
+
configResolved(resolvedConfig) {
|
|
333
|
+
config = resolvedConfig
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
buildStart() {
|
|
337
|
+
generateThemeCSS()
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
handleHotUpdate({ file }) {
|
|
341
|
+
// Use defaultThemes keys instead of hardcoded file paths
|
|
342
|
+
const themeKeys = Object.keys(defaultThemes)
|
|
343
|
+
const shouldRegenerate = themeKeys.some(themeKey =>
|
|
344
|
+
file.includes(`themes/${themeKey}.ts`) ||
|
|
345
|
+
file.includes('inheritance.ts') ||
|
|
346
|
+
file.includes('types.ts')
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if (shouldRegenerate) {
|
|
350
|
+
generateThemeCSS()
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
|
|
3
|
+
// Mock localStorage for Jest environment
|
|
4
|
+
const localStorageMock = {
|
|
5
|
+
getItem: jest.fn(),
|
|
6
|
+
setItem: jest.fn(),
|
|
7
|
+
removeItem: jest.fn(),
|
|
8
|
+
clear: jest.fn(),
|
|
9
|
+
length: 0,
|
|
10
|
+
key: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(window, 'localStorage', {
|
|
14
|
+
value: localStorageMock,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Mock sessionStorage for Jest environment
|
|
19
|
+
const sessionStorageMock = {
|
|
20
|
+
getItem: jest.fn(),
|
|
21
|
+
setItem: jest.fn(),
|
|
22
|
+
removeItem: jest.fn(),
|
|
23
|
+
clear: jest.fn(),
|
|
24
|
+
length: 0,
|
|
25
|
+
key: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Object.defineProperty(window, 'sessionStorage', {
|
|
29
|
+
value: sessionStorageMock,
|
|
30
|
+
writable: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Mock performance API for Jest environment
|
|
34
|
+
Object.defineProperty(window, 'performance', {
|
|
35
|
+
value: {
|
|
36
|
+
now: jest.fn(() => Date.now()),
|
|
37
|
+
memory: {
|
|
38
|
+
usedJSHeapSize: 1000000,
|
|
39
|
+
totalJSHeapSize: 2000000,
|
|
40
|
+
jsHeapSizeLimit: 3000000,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
writable: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Mock ResizeObserver for Jest environment
|
|
47
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
48
|
+
observe: jest.fn(),
|
|
49
|
+
unobserve: jest.fn(),
|
|
50
|
+
disconnect: jest.fn(),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// Mock IntersectionObserver for Jest environment
|
|
54
|
+
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
|
55
|
+
observe: jest.fn(),
|
|
56
|
+
unobserve: jest.fn(),
|
|
57
|
+
disconnect: jest.fn(),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Mock React 18 createRoot for Jest environment
|
|
61
|
+
const mockCreateRoot = jest.fn(() => ({
|
|
62
|
+
render: jest.fn(),
|
|
63
|
+
unmount: jest.fn(),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(window, 'createRoot', {
|
|
67
|
+
value: mockCreateRoot,
|
|
68
|
+
writable: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Suppress console warnings for expected test behavior
|
|
72
|
+
const originalWarn = console.warn;
|
|
73
|
+
const originalError = console.error;
|
|
74
|
+
|
|
75
|
+
beforeAll(() => {
|
|
76
|
+
// Suppress specific warnings that are expected in tests
|
|
77
|
+
console.warn = jest.fn((...args) => {
|
|
78
|
+
const message = args[0];
|
|
79
|
+
if (
|
|
80
|
+
typeof message === 'string' && (
|
|
81
|
+
message.includes('Failed to read theme from storage') ||
|
|
82
|
+
message.includes('Failed to store theme preference') ||
|
|
83
|
+
message.includes('Failed to read system theme preference from storage')
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
return; // Suppress expected storage warnings
|
|
87
|
+
}
|
|
88
|
+
originalWarn(...args);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Suppress specific errors that are expected in tests
|
|
92
|
+
console.error = jest.fn((...args) => {
|
|
93
|
+
const message = args[0];
|
|
94
|
+
if (
|
|
95
|
+
typeof message === 'string' && (
|
|
96
|
+
message.includes('Warning: An update to ThemeProvider inside a test was not wrapped in act') ||
|
|
97
|
+
message.includes('createRoot') ||
|
|
98
|
+
message.includes('Target container is not a DOM element')
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
101
|
+
return; // Suppress expected React act warnings and createRoot errors
|
|
102
|
+
}
|
|
103
|
+
originalError(...args);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
afterAll(() => {
|
|
108
|
+
// Restore original console methods
|
|
109
|
+
console.warn = originalWarn;
|
|
110
|
+
console.error = originalError;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Reset mocks before each test
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
localStorageMock.getItem.mockClear();
|
|
116
|
+
localStorageMock.setItem.mockClear();
|
|
117
|
+
localStorageMock.removeItem.mockClear();
|
|
118
|
+
localStorageMock.clear.mockClear();
|
|
119
|
+
|
|
120
|
+
sessionStorageMock.getItem.mockClear();
|
|
121
|
+
sessionStorageMock.setItem.mockClear();
|
|
122
|
+
sessionStorageMock.removeItem.mockClear();
|
|
123
|
+
sessionStorageMock.clear.mockClear();
|
|
124
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Storybook Stories
|
|
2
|
+
|
|
3
|
+
This directory contains all the Storybook stories for the Roger UI Bank design system.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `components/` - Component stories organized by category
|
|
8
|
+
- `ui/` - Basic UI component stories
|
|
9
|
+
- `layouts/` - Layout component stories
|
|
10
|
+
- `feedback/` - Feedback component stories
|
|
11
|
+
- `navigation/` - Navigation component stories
|
|
12
|
+
- `overlay/` - Overlay component stories
|
|
13
|
+
- `data-display/` - Data display component stories
|
|
14
|
+
- `themes/` - Theme documentation and examples
|
|
15
|
+
- `decorators/` - Story decorators for common functionality
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
1. Start Storybook: `npm run storybook`
|
|
20
|
+
2. Navigate to http://localhost:6006
|
|
21
|
+
3. Use the theme switcher in the toolbar to see different themes
|
|
22
|
+
4. Explore component variations and documentation
|
|
23
|
+
|
|
24
|
+
## Adding New Stories
|
|
25
|
+
|
|
26
|
+
1. Create a new story file in the appropriate category
|
|
27
|
+
2. Import the component and create stories
|
|
28
|
+
3. Add theme decorator for multi-theme support
|
|
29
|
+
4. Include comprehensive documentation
|
|
30
|
+
5. Test with all available themes
|
|
31
|
+
|
|
32
|
+
## Theme Support
|
|
33
|
+
|
|
34
|
+
All components support three themes:
|
|
35
|
+
- **Stan Design** - Modern, professional design
|
|
36
|
+
- **Enterprise** - Corporate, business-focused design
|
|
37
|
+
- **Harvey Creative** - Creative, vibrant design
|
|
38
|
+
|
|
39
|
+
Use the theme decorator to ensure proper theme integration in your stories.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useTheme } from '../../themes';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Theme Debugger Component for Storybook
|
|
6
|
+
* Shows current theme state and CSS variables for debugging
|
|
7
|
+
*/
|
|
8
|
+
export const ThemeDebugger: React.FC = () => {
|
|
9
|
+
const { currentTheme, currentThemeConfig, availableThemes } = useTheme();
|
|
10
|
+
const [cssVariables, setCssVariables] = useState<Record<string, string>>({});
|
|
11
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
12
|
+
|
|
13
|
+
// Extract CSS variables from document root
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const extractCSSVariables = () => {
|
|
16
|
+
const root = document.documentElement;
|
|
17
|
+
const variables: Record<string, string> = {};
|
|
18
|
+
|
|
19
|
+
// Get all CSS custom properties
|
|
20
|
+
const styles = getComputedStyle(root);
|
|
21
|
+
|
|
22
|
+
// Method 1: Try to get all CSS properties
|
|
23
|
+
try {
|
|
24
|
+
for (let i = 0; i < styles.length; i++) {
|
|
25
|
+
const property = styles[i];
|
|
26
|
+
if (property.startsWith('--cs-')) {
|
|
27
|
+
variables[property] = styles.getPropertyValue(property);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.warn('Could not iterate through styles:', error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Method 2: Try to get specific known CSS variables
|
|
35
|
+
const knownVariables = [
|
|
36
|
+
'--cs-primary-500', '--cs-secondary-500', '--cs-success', '--cs-warning', '--cs-error', '--cs-info',
|
|
37
|
+
'--cs-text-primary', '--cs-text-secondary', '--cs-surface-bg', '--cs-border',
|
|
38
|
+
'--cs-fonts-primary-family', '--cs-fonts-primary-sizes-md', '--cs-spacing-4'
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
knownVariables.forEach(varName => {
|
|
42
|
+
try {
|
|
43
|
+
const value = styles.getPropertyValue(varName);
|
|
44
|
+
if (value && value.trim() !== '') {
|
|
45
|
+
variables[varName] = value;
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Variable doesn't exist
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Method 3: Check if variables are set via inline styles
|
|
53
|
+
const inlineVars = root.style.cssText.match(/--cs-[^:]+:[^;]+/g) || [];
|
|
54
|
+
inlineVars.forEach(varDeclaration => {
|
|
55
|
+
const [name, value] = varDeclaration.split(':');
|
|
56
|
+
if (name && value) {
|
|
57
|
+
variables[name.trim()] = value.trim();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log('Extracted CSS variables:', variables);
|
|
62
|
+
setCssVariables(variables);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
extractCSSVariables();
|
|
66
|
+
|
|
67
|
+
// Listen for theme changes
|
|
68
|
+
const handleThemeChange = () => {
|
|
69
|
+
setTimeout(extractCSSVariables, 200); // Increased delay to ensure CSS is updated
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
window.addEventListener('themeChange', handleThemeChange);
|
|
73
|
+
|
|
74
|
+
// Also check periodically for changes
|
|
75
|
+
const interval = setInterval(extractCSSVariables, 1000);
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
window.removeEventListener('themeChange', handleThemeChange);
|
|
79
|
+
clearInterval(interval);
|
|
80
|
+
};
|
|
81
|
+
}, [currentTheme]);
|
|
82
|
+
|
|
83
|
+
if (!isVisible) {
|
|
84
|
+
return (
|
|
85
|
+
<button
|
|
86
|
+
onClick={() => setIsVisible(true)}
|
|
87
|
+
className="fixed bottom-4 right-4 bg-blue-600 text-white px-3 py-2 rounded-lg text-sm z-50"
|
|
88
|
+
>
|
|
89
|
+
🐛 Debug Theme
|
|
90
|
+
</button>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="fixed bottom-4 right-4 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg p-4 max-w-md max-h-96 overflow-auto shadow-lg z-50">
|
|
96
|
+
<div className="flex items-center justify-between mb-3">
|
|
97
|
+
<h3 className="font-semibold text-gray-900 dark:text-white">Theme Debugger</h3>
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => setIsVisible(false)}
|
|
100
|
+
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
101
|
+
>
|
|
102
|
+
✕
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div className="space-y-3 text-sm">
|
|
107
|
+
<div>
|
|
108
|
+
<strong>Current Theme:</strong> {currentTheme}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div>
|
|
112
|
+
<strong>Available Themes:</strong> {availableThemes.join(', ')}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{currentThemeConfig && (
|
|
116
|
+
<div>
|
|
117
|
+
<strong>Theme Config:</strong>
|
|
118
|
+
<pre className="mt-1 p-2 bg-gray-100 dark:bg-gray-700 rounded text-xs overflow-auto max-h-20">
|
|
119
|
+
{JSON.stringify({
|
|
120
|
+
name: currentThemeConfig.meta?.name,
|
|
121
|
+
description: currentThemeConfig.meta?.description,
|
|
122
|
+
category: currentThemeConfig.meta?.category,
|
|
123
|
+
primaryColor: currentThemeConfig.colors?.primary?.[500],
|
|
124
|
+
fontFamily: currentThemeConfig.fonts?.primary?.family
|
|
125
|
+
}, null, 2)}
|
|
126
|
+
</pre>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
<div>
|
|
131
|
+
<strong>CSS Variables ({Object.keys(cssVariables).length}):</strong>
|
|
132
|
+
<div className="mt-1 max-h-32 overflow-auto">
|
|
133
|
+
{Object.entries(cssVariables).map(([variable, value]) => (
|
|
134
|
+
<div key={variable} className="text-xs font-mono">
|
|
135
|
+
<span className="text-blue-600">{variable}:</span> {value}
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
};
|