@shohojdhara/atomix 0.3.2 → 0.3.4

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.
Files changed (84) hide show
  1. package/README.md +58 -21
  2. package/dist/atomix.css +96 -121
  3. package/dist/atomix.min.css +3 -3
  4. package/dist/index.d.ts +7937 -7765
  5. package/dist/index.esm.js +3677 -4031
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +3648 -3952
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/package.json +44 -16
  12. package/scripts/atomix-cli.js +1764 -0
  13. package/scripts/build-themes.js +208 -0
  14. package/scripts/cli/interactive-init.js +520 -0
  15. package/scripts/cli/migration-tools.js +603 -0
  16. package/scripts/cli/theme-bridge.js +129 -0
  17. package/scripts/cli/token-manager.js +519 -0
  18. package/scripts/sync-theme-config.js +309 -0
  19. package/src/components/Button/Button.tsx +36 -1
  20. package/src/components/List/ListGroup.tsx +1 -2
  21. package/src/components/Popover/Popover.tsx +2 -2
  22. package/src/components/Tooltip/Tooltip.stories.tsx +49 -12
  23. package/src/components/Tooltip/Tooltip.tsx +32 -58
  24. package/src/lib/composables/useTooltip.ts +285 -0
  25. package/src/lib/config/index.ts +275 -0
  26. package/src/lib/config/loader.ts +105 -0
  27. package/src/lib/constants/cssVariables.ts +390 -0
  28. package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +151 -0
  29. package/src/lib/hooks/index.ts +19 -0
  30. package/src/lib/hooks/useComponentCustomization.ts +175 -0
  31. package/src/lib/index.ts +14 -1
  32. package/src/lib/patterns/__tests__/slots.test.ts +108 -0
  33. package/src/lib/patterns/index.ts +35 -0
  34. package/src/lib/patterns/slots.tsx +421 -0
  35. package/src/lib/theme/composeTheme.ts +0 -5
  36. package/src/lib/theme/config/index.ts +1 -1
  37. package/src/lib/theme/config/loader.ts +75 -41
  38. package/src/lib/theme/config/types.ts +21 -7
  39. package/src/lib/theme/config/validator.ts +1 -1
  40. package/src/lib/theme/constants.ts +12 -2
  41. package/src/lib/theme/createTheme.ts +2 -135
  42. package/src/lib/theme/createThemeFromConfig.ts +132 -0
  43. package/src/lib/theme/cssVariableMapper.ts +261 -0
  44. package/src/lib/theme/devtools/CLI.ts +161 -76
  45. package/src/lib/theme/devtools/Comparator.tsx +343 -0
  46. package/src/lib/theme/devtools/IMPROVEMENTS.md +429 -0
  47. package/src/lib/theme/devtools/Inspector.tsx +21 -6
  48. package/src/lib/theme/devtools/LiveEditor.tsx +393 -0
  49. package/src/lib/theme/devtools/README.md +433 -0
  50. package/src/lib/theme/devtools/index.ts +12 -11
  51. package/src/lib/theme/generateCSSVariables.ts +79 -38
  52. package/src/lib/theme/index.ts +45 -246
  53. package/src/lib/theme/runtime/ThemeApplicator.ts +252 -0
  54. package/src/lib/theme/runtime/ThemeManager.test.ts +17 -1
  55. package/src/lib/theme/runtime/ThemeManager.ts +7 -7
  56. package/src/lib/theme/themeUtils.ts +27 -5
  57. package/src/lib/theme/types.ts +59 -1
  58. package/src/lib/theme-tools.ts +125 -0
  59. package/src/lib/types/components.ts +260 -72
  60. package/src/lib/types/partProps.ts +426 -0
  61. package/src/lib/utils/__tests__/componentUtils.test.ts +144 -0
  62. package/src/lib/utils/componentUtils.ts +163 -0
  63. package/src/lib/utils/index.ts +17 -57
  64. package/src/styles/01-settings/_settings.colors.scss +10 -10
  65. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  66. package/src/styles/01-settings/_settings.tooltip.scss +1 -1
  67. package/src/styles/03-generic/_generated-root.css +5 -0
  68. package/src/styles/06-components/_components.navbar.scss +12 -5
  69. package/src/styles/06-components/_components.tooltip.scss +31 -81
  70. package/src/themes/README.md +442 -0
  71. package/src/themes/themes.config.js +35 -0
  72. package/src/lib/theme/errors.test.ts +0 -207
  73. package/src/lib/theme/generators/CSSGenerator.ts +0 -311
  74. package/src/lib/theme/generators/ConfigGenerator.ts +0 -287
  75. package/src/lib/theme/generators/TypeGenerator.ts +0 -228
  76. package/src/lib/theme/generators/index.ts +0 -21
  77. package/src/lib/theme/monitoring/ThemeAnalytics.ts +0 -409
  78. package/src/lib/theme/monitoring/index.ts +0 -17
  79. package/src/lib/theme/overrides/ComponentOverrides.ts +0 -243
  80. package/src/lib/theme/overrides/index.ts +0 -15
  81. package/src/lib/theme/studio/ThemeStudio.tsx +0 -312
  82. package/src/lib/theme/studio/index.ts +0 -8
  83. package/src/lib/theme/whitelabel/WhiteLabelManager.ts +0 -364
  84. package/src/lib/theme/whitelabel/index.ts +0 -13
@@ -4,9 +4,6 @@
4
4
  * Command-line interface for theme management
5
5
  */
6
6
 
7
- import { generateConfigTemplate } from '../generators/ConfigGenerator';
8
- import { generateCSS } from '../generators/CSSGenerator';
9
- import { generateTypes } from '../generators/TypeGenerator';
10
7
  import { loadThemeConfig } from '../config/loader';
11
8
  import { validateConfig } from '../config/validator';
12
9
 
@@ -37,39 +34,49 @@ export class ThemeCLI {
37
34
  */
38
35
  private registerDefaultCommands(): void {
39
36
  this.register({
40
- name: 'init',
41
- description: 'Initialize theme configuration',
37
+ name: 'validate',
38
+ description: 'Validate theme configuration',
42
39
  options: {
43
- '--format': 'Output format (typescript, javascript, json)',
44
- '--examples': 'Include example themes',
40
+ '--config': 'Path to config file',
41
+ '--strict': 'Enable strict validation',
45
42
  },
46
- handler: this.handleInit.bind(this),
43
+ handler: this.handleValidate.bind(this),
47
44
  });
48
45
 
49
46
  this.register({
50
- name: 'validate',
51
- description: 'Validate theme configuration',
52
- handler: this.handleValidate.bind(this),
47
+ name: 'list',
48
+ description: 'List all available themes',
49
+ handler: this.handleList.bind(this),
50
+ });
51
+
52
+ this.register({
53
+ name: 'inspect',
54
+ description: 'Inspect a specific theme',
55
+ options: {
56
+ '--theme': 'Theme name to inspect',
57
+ '--json': 'Output as JSON',
58
+ },
59
+ handler: this.handleInspect.bind(this),
53
60
  });
54
61
 
55
62
  this.register({
56
- name: 'build',
57
- description: 'Build theme CSS files',
63
+ name: 'compare',
64
+ description: 'Compare two themes',
58
65
  options: {
59
- '--output': 'Output directory',
60
- '--minify': 'Minify output',
66
+ '--theme1': 'First theme name',
67
+ '--theme2': 'Second theme name',
61
68
  },
62
- handler: this.handleBuild.bind(this),
69
+ handler: this.handleCompare.bind(this),
63
70
  });
64
71
 
65
72
  this.register({
66
- name: 'types',
67
- description: 'Generate TypeScript types',
73
+ name: 'export',
74
+ description: 'Export theme to JSON',
68
75
  options: {
69
- '--output': 'Output file',
70
- '--module': 'Module name',
76
+ '--theme': 'Theme name to export',
77
+ '--output': 'Output file path',
71
78
  },
72
- handler: this.handleTypes.bind(this),
79
+ handler: this.handleExport.bind(this),
73
80
  });
74
81
 
75
82
  this.register({
@@ -139,29 +146,6 @@ export class ThemeCLI {
139
146
  return { args: parsedArgs, options };
140
147
  }
141
148
 
142
- /**
143
- * Handle init command
144
- */
145
- private handleInit(args: string[], options: Record<string, any>): void {
146
- const format = options.format || 'typescript';
147
- const includeExamples = options.examples !== false;
148
-
149
- const config = generateConfigTemplate({
150
- format: format as any,
151
- includeExamples,
152
- includeComments: true,
153
- });
154
-
155
- const filename = format === 'json' ? 'theme.config.json' :
156
- format === 'javascript' ? 'theme.config.js' :
157
- 'theme.config.ts';
158
-
159
- console.log(`Generating ${filename}...`);
160
- console.log(config);
161
- console.log(`\\nTheme configuration template generated!`);
162
- console.log(`Save this content to ${filename} in your project root.`);
163
- }
164
-
165
149
  /**
166
150
  * Handle validate command
167
151
  */
@@ -173,16 +157,16 @@ export class ThemeCLI {
173
157
  if (result.valid) {
174
158
  console.log('✅ Theme configuration is valid');
175
159
  if (result.warnings.length > 0) {
176
- console.log('\\n⚠️ Warnings:');
160
+ console.log('\n⚠️ Warnings:');
177
161
  result.warnings.forEach(warning => console.log(` - ${warning}`));
178
162
  }
179
163
  } else {
180
164
  console.log('❌ Theme configuration is invalid');
181
- console.log('\\nErrors:');
165
+ console.log('\nErrors:');
182
166
  result.errors.forEach(error => console.log(` - ${error}`));
183
-
167
+
184
168
  if (result.warnings.length > 0) {
185
- console.log('\\nWarnings:');
169
+ console.log('\nWarnings:');
186
170
  result.warnings.forEach(warning => console.log(` - ${warning}`));
187
171
  }
188
172
  process.exit(1);
@@ -194,49 +178,150 @@ export class ThemeCLI {
194
178
  }
195
179
 
196
180
  /**
197
- * Handle build command
181
+ * Handle list command
198
182
  */
199
- private handleBuild(args: string[], options: Record<string, any>): void {
200
- console.log('Building themes...');
201
- console.log('Note: This is a placeholder. Implement actual build logic based on your needs.');
202
-
183
+ private handleList(args: string[], options: Record<string, any>): void {
203
184
  try {
204
185
  const config = loadThemeConfig();
205
- console.log(`Found ${Object.keys(config.themes).length} themes to build`);
186
+ const themes = config.themes || {};
206
187
 
207
- // This would typically:
208
- // 1. Load each theme
209
- // 2. Generate CSS for CSS themes
210
- // 3. Execute createTheme for JS themes and generate CSS
211
- // 4. Write files to output directory
188
+ console.log('Available Themes:\n');
212
189
 
213
- console.log('✅ Build completed');
190
+ if (Object.keys(themes).length === 0) {
191
+ console.log('No themes found in configuration.');
192
+ return;
193
+ }
194
+
195
+ for (const [id, theme] of Object.entries(themes)) {
196
+ console.log(` ${id}`);
197
+ console.log(` Name: ${theme.name}`);
198
+ if (theme.description) {
199
+ console.log(` Description: ${theme.description}`);
200
+ }
201
+ if (theme.version) {
202
+ console.log(` Version: ${theme.version}`);
203
+ }
204
+ if (theme.status) {
205
+ console.log(` Status: ${theme.status}`);
206
+ }
207
+ console.log();
208
+ }
214
209
  } catch (error) {
215
- console.error('Build failed:', error);
210
+ console.error('Failed to list themes:', error);
216
211
  process.exit(1);
217
212
  }
218
213
  }
219
214
 
220
215
  /**
221
- * Handle types command
216
+ * Handle inspect command
222
217
  */
223
- private handleTypes(args: string[], options: Record<string, any>): void {
224
- console.log('Generating TypeScript types...');
225
- console.log('Note: This is a placeholder. Implement actual type generation logic.');
218
+ private handleInspect(args: string[], options: Record<string, any>): void {
219
+ const themeName = options.theme || args[0];
226
220
 
221
+ if (!themeName) {
222
+ console.error('Error: Theme name is required');
223
+ console.error('Usage: atomix-theme inspect --theme <theme-name>');
224
+ process.exit(1);
225
+ }
226
+
227
+ try {
228
+ const config = loadThemeConfig();
229
+ const theme = config.themes?.[themeName];
230
+
231
+ if (!theme) {
232
+ console.error(`Error: Theme "${themeName}" not found`);
233
+ process.exit(1);
234
+ }
235
+
236
+ if (options.json) {
237
+ console.log(JSON.stringify(theme, null, 2));
238
+ } else {
239
+ console.log(`Theme: ${themeName}\n`);
240
+ console.log(JSON.stringify(theme, null, 2));
241
+ }
242
+ } catch (error) {
243
+ console.error('Failed to inspect theme:', error);
244
+ process.exit(1);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Handle compare command
250
+ */
251
+ private handleCompare(args: string[], options: Record<string, any>): void {
252
+ const theme1 = options.theme1 || args[0];
253
+ const theme2 = options.theme2 || args[1];
254
+
255
+ if (!theme1 || !theme2) {
256
+ console.error('Error: Two theme names are required');
257
+ console.error('Usage: atomix-theme compare --theme1 <name1> --theme2 <name2>');
258
+ process.exit(1);
259
+ }
260
+
227
261
  try {
228
262
  const config = loadThemeConfig();
229
- const moduleName = options.module || 'CustomTheme';
263
+ const themeA = config.themes?.[theme1];
264
+ const themeB = config.themes?.[theme2];
265
+
266
+ if (!themeA) {
267
+ console.error(`Error: Theme "${theme1}" not found`);
268
+ process.exit(1);
269
+ }
270
+
271
+ if (!themeB) {
272
+ console.error(`Error: Theme "${theme2}" not found`);
273
+ process.exit(1);
274
+ }
275
+
276
+ console.log(`Comparing: ${theme1} vs ${theme2}\n`);
277
+ console.log('Differences:');
230
278
 
231
- // This would typically:
232
- // 1. Load themes
233
- // 2. Generate TypeScript definitions
234
- // 3. Write to output file
279
+ // Simple comparison (could be enhanced)
280
+ const keys = new Set([...Object.keys(themeA), ...Object.keys(themeB)]);
235
281
 
236
- console.log(`Generated types for module: ${moduleName}`);
237
- console.log('✅ Type generation completed');
282
+ for (const key of keys) {
283
+ const valueA = (themeA as any)[key];
284
+ const valueB = (themeB as any)[key];
285
+
286
+ if (JSON.stringify(valueA) !== JSON.stringify(valueB)) {
287
+ console.log(`\n ${key}:`);
288
+ console.log(` ${theme1}: ${JSON.stringify(valueA)}`);
289
+ console.log(` ${theme2}: ${JSON.stringify(valueB)}`);
290
+ }
291
+ }
238
292
  } catch (error) {
239
- console.error('Type generation failed:', error);
293
+ console.error('Failed to compare themes:', error);
294
+ process.exit(1);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Handle export command
300
+ */
301
+ private handleExport(args: string[], options: Record<string, any>): void {
302
+ const themeName = options.theme || args[0];
303
+ const outputPath = options.output || `${themeName}.json`;
304
+
305
+ if (!themeName) {
306
+ console.error('Error: Theme name is required');
307
+ console.error('Usage: atomix-theme export --theme <theme-name> [--output <path>]');
308
+ process.exit(1);
309
+ }
310
+
311
+ try {
312
+ const config = loadThemeConfig();
313
+ const theme = config.themes?.[themeName];
314
+
315
+ if (!theme) {
316
+ console.error(`Error: Theme "${themeName}" not found`);
317
+ process.exit(1);
318
+ }
319
+
320
+ const fs = require('fs');
321
+ fs.writeFileSync(outputPath, JSON.stringify(theme, null, 2));
322
+ console.log(`✅ Theme exported to: ${outputPath}`);
323
+ } catch (error) {
324
+ console.error('Failed to export theme:', error);
240
325
  process.exit(1);
241
326
  }
242
327
  }
@@ -245,13 +330,13 @@ export class ThemeCLI {
245
330
  * Handle help command
246
331
  */
247
332
  private handleHelp(args: string[], options: Record<string, any>): void {
248
- console.log('Atomix Theme CLI\\n');
249
- console.log('Usage: atomix-theme <command> [options]\\n');
333
+ console.log('Atomix Theme CLI\n');
334
+ console.log('Usage: atomix-theme <command> [options]\n');
250
335
  console.log('Commands:');
251
336
 
252
337
  for (const [name, command] of this.commands.entries()) {
253
338
  console.log(` ${name.padEnd(12)} ${command.description}`);
254
-
339
+
255
340
  if (command.options) {
256
341
  for (const [option, description] of Object.entries(command.options)) {
257
342
  console.log(` ${option.padEnd(16)} ${description}`);
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Theme Comparator Component
3
+ *
4
+ * React component for comparing two themes side-by-side
5
+ */
6
+
7
+ import React, { useMemo } from 'react';
8
+ import type { Theme } from '../types';
9
+
10
+ /**
11
+ * Theme comparator props
12
+ */
13
+ export interface ThemeComparatorProps {
14
+ /** First theme to compare */
15
+ themeA: Theme;
16
+ /** Second theme to compare */
17
+ themeB: Theme;
18
+ /** Show only differences */
19
+ showOnlyDifferences?: boolean;
20
+ /** CSS class name */
21
+ className?: string;
22
+ /** Inline styles */
23
+ style?: React.CSSProperties;
24
+ }
25
+
26
+ interface Difference {
27
+ path: string;
28
+ valueA: any;
29
+ valueB: any;
30
+ type: 'added' | 'removed' | 'changed';
31
+ }
32
+
33
+ /**
34
+ * Theme Comparator Component
35
+ *
36
+ * Compares two themes and highlights differences
37
+ */
38
+ export const ThemeComparator: React.FC<ThemeComparatorProps> = ({
39
+ themeA,
40
+ themeB,
41
+ showOnlyDifferences = false,
42
+ className,
43
+ style,
44
+ }) => {
45
+ const differences = useMemo(() => {
46
+ const diffs: Difference[] = [];
47
+
48
+ const compareObjects = (objA: any, objB: any, path: string = '') => {
49
+ const keysA = Object.keys(objA || {});
50
+ const keysB = Object.keys(objB || {});
51
+ const allKeys = new Set([...keysA, ...keysB]);
52
+
53
+ for (const key of allKeys) {
54
+ if (key === '__isJSTheme') continue;
55
+
56
+ const currentPath = path ? `${path}.${key}` : key;
57
+ const valueA = objA?.[key];
58
+ const valueB = objB?.[key];
59
+
60
+ if (valueA === undefined && valueB !== undefined) {
61
+ diffs.push({
62
+ path: currentPath,
63
+ valueA: undefined,
64
+ valueB,
65
+ type: 'added',
66
+ });
67
+ } else if (valueA !== undefined && valueB === undefined) {
68
+ diffs.push({
69
+ path: currentPath,
70
+ valueA,
71
+ valueB: undefined,
72
+ type: 'removed',
73
+ });
74
+ } else if (typeof valueA === 'object' && typeof valueB === 'object' && !Array.isArray(valueA) && !Array.isArray(valueB)) {
75
+ compareObjects(valueA, valueB, currentPath);
76
+ } else if (JSON.stringify(valueA) !== JSON.stringify(valueB)) {
77
+ diffs.push({
78
+ path: currentPath,
79
+ valueA,
80
+ valueB,
81
+ type: 'changed',
82
+ });
83
+ }
84
+ }
85
+ };
86
+
87
+ compareObjects(themeA, themeB);
88
+ return diffs;
89
+ }, [themeA, themeB]);
90
+
91
+ const formatValue = (value: any): string => {
92
+ if (value === undefined) return 'undefined';
93
+ if (value === null) return 'null';
94
+ if (typeof value === 'string') return `"${value}"`;
95
+ if (typeof value === 'object') return JSON.stringify(value, null, 2);
96
+ return String(value);
97
+ };
98
+
99
+ const getTypeColor = (type: Difference['type']): string => {
100
+ switch (type) {
101
+ case 'added': return '#4caf50';
102
+ case 'removed': return '#f44336';
103
+ case 'changed': return '#ff9800';
104
+ default: return '#666';
105
+ }
106
+ };
107
+
108
+ return (
109
+ <div className={`atomix-theme-comparator ${className || ''}`} style={style}>
110
+ <div className="comparator-header">
111
+ <div className="theme-column">
112
+ <h3>{themeA.name}</h3>
113
+ {themeA.version && <span className="version">v{themeA.version}</span>}
114
+ </div>
115
+ <div className="vs-divider">VS</div>
116
+ <div className="theme-column">
117
+ <h3>{themeB.name}</h3>
118
+ {themeB.version && <span className="version">v{themeB.version}</span>}
119
+ </div>
120
+ </div>
121
+
122
+ <div className="comparator-stats">
123
+ <div className="stat">
124
+ <span className="stat-value">{differences.length}</span>
125
+ <span className="stat-label">Total Differences</span>
126
+ </div>
127
+ <div className="stat">
128
+ <span className="stat-value" style={{ color: '#4caf50' }}>
129
+ {differences.filter(d => d.type === 'added').length}
130
+ </span>
131
+ <span className="stat-label">Added</span>
132
+ </div>
133
+ <div className="stat">
134
+ <span className="stat-value" style={{ color: '#f44336' }}>
135
+ {differences.filter(d => d.type === 'removed').length}
136
+ </span>
137
+ <span className="stat-label">Removed</span>
138
+ </div>
139
+ <div className="stat">
140
+ <span className="stat-value" style={{ color: '#ff9800' }}>
141
+ {differences.filter(d => d.type === 'changed').length}
142
+ </span>
143
+ <span className="stat-label">Changed</span>
144
+ </div>
145
+ </div>
146
+
147
+ {differences.length === 0 ? (
148
+ <div className="no-differences">
149
+ ✅ Themes are identical
150
+ </div>
151
+ ) : (
152
+ <div className="differences-list">
153
+ <h4>Differences</h4>
154
+ {differences.map((diff, index) => (
155
+ <div key={index} className="difference-item">
156
+ <div className="difference-header">
157
+ <span
158
+ className="difference-type"
159
+ style={{ backgroundColor: getTypeColor(diff.type) }}
160
+ >
161
+ {diff.type}
162
+ </span>
163
+ <code className="difference-path">{diff.path}</code>
164
+ </div>
165
+ <div className="difference-values">
166
+ <div className="value-column">
167
+ <div className="value-label">{themeA.name}</div>
168
+ <pre className="value-content">
169
+ {formatValue(diff.valueA)}
170
+ </pre>
171
+ </div>
172
+ <div className="value-divider">→</div>
173
+ <div className="value-column">
174
+ <div className="value-label">{themeB.name}</div>
175
+ <pre className="value-content">
176
+ {formatValue(diff.valueB)}
177
+ </pre>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ ))}
182
+ </div>
183
+ )}
184
+
185
+ <style>{`
186
+ .atomix-theme-comparator {
187
+ border: 1px solid #e0e0e0;
188
+ border-radius: 8px;
189
+ background: white;
190
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
191
+ padding: 24px;
192
+ }
193
+
194
+ .comparator-header {
195
+ display: grid;
196
+ grid-template-columns: 1fr auto 1fr;
197
+ gap: 24px;
198
+ align-items: center;
199
+ margin-bottom: 24px;
200
+ padding-bottom: 24px;
201
+ border-bottom: 2px solid #e0e0e0;
202
+ }
203
+
204
+ .theme-column {
205
+ text-align: center;
206
+ }
207
+
208
+ .theme-column h3 {
209
+ margin: 0;
210
+ font-size: 20px;
211
+ color: #333;
212
+ }
213
+
214
+ .version {
215
+ display: inline-block;
216
+ margin-top: 4px;
217
+ padding: 2px 8px;
218
+ background: #e3f2fd;
219
+ color: #1976d2;
220
+ border-radius: 4px;
221
+ font-size: 12px;
222
+ }
223
+
224
+ .vs-divider {
225
+ font-weight: bold;
226
+ font-size: 24px;
227
+ color: #666;
228
+ }
229
+
230
+ .comparator-stats {
231
+ display: grid;
232
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
233
+ gap: 16px;
234
+ margin-bottom: 24px;
235
+ }
236
+
237
+ .stat {
238
+ text-align: center;
239
+ padding: 16px;
240
+ background: #f5f5f5;
241
+ border-radius: 8px;
242
+ }
243
+
244
+ .stat-value {
245
+ display: block;
246
+ font-size: 32px;
247
+ font-weight: bold;
248
+ color: #2196f3;
249
+ }
250
+
251
+ .stat-label {
252
+ display: block;
253
+ font-size: 12px;
254
+ color: #666;
255
+ margin-top: 4px;
256
+ }
257
+
258
+ .no-differences {
259
+ text-align: center;
260
+ padding: 48px;
261
+ font-size: 18px;
262
+ color: #4caf50;
263
+ background: #e8f5e9;
264
+ border-radius: 8px;
265
+ }
266
+
267
+ .differences-list h4 {
268
+ margin: 0 0 16px 0;
269
+ font-size: 18px;
270
+ color: #333;
271
+ }
272
+
273
+ .difference-item {
274
+ margin-bottom: 16px;
275
+ border: 1px solid #e0e0e0;
276
+ border-radius: 8px;
277
+ overflow: hidden;
278
+ }
279
+
280
+ .difference-header {
281
+ display: flex;
282
+ align-items: center;
283
+ gap: 12px;
284
+ padding: 12px;
285
+ background: #f5f5f5;
286
+ }
287
+
288
+ .difference-type {
289
+ padding: 4px 8px;
290
+ border-radius: 4px;
291
+ font-size: 11px;
292
+ font-weight: bold;
293
+ color: white;
294
+ text-transform: uppercase;
295
+ }
296
+
297
+ .difference-path {
298
+ font-family: 'Monaco', 'Menlo', monospace;
299
+ font-size: 13px;
300
+ color: #1976d2;
301
+ }
302
+
303
+ .difference-values {
304
+ display: grid;
305
+ grid-template-columns: 1fr auto 1fr;
306
+ gap: 16px;
307
+ padding: 16px;
308
+ align-items: start;
309
+ }
310
+
311
+ .value-column {
312
+ min-width: 0;
313
+ }
314
+
315
+ .value-label {
316
+ font-size: 12px;
317
+ font-weight: bold;
318
+ color: #666;
319
+ margin-bottom: 8px;
320
+ }
321
+
322
+ .value-content {
323
+ margin: 0;
324
+ padding: 12px;
325
+ background: #f5f5f5;
326
+ border-radius: 4px;
327
+ font-family: 'Monaco', 'Menlo', monospace;
328
+ font-size: 12px;
329
+ overflow-x: auto;
330
+ white-space: pre-wrap;
331
+ word-break: break-all;
332
+ }
333
+
334
+ .value-divider {
335
+ font-size: 20px;
336
+ color: #666;
337
+ align-self: center;
338
+ }
339
+ `}</style>
340
+ </div>
341
+ );
342
+ };
343
+