@shohojdhara/atomix 0.3.4 → 0.3.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/README.md +101 -199
- package/atomix.config.ts +241 -0
- package/dist/atomix.css +269 -189
- package/dist/atomix.css.map +1 -0
- package/dist/atomix.min.css +15179 -11
- package/dist/atomix.min.css.map +1 -0
- package/dist/charts.d.ts +1929 -0
- package/dist/charts.js +6477 -0
- package/dist/charts.js.map +1 -0
- package/dist/core.d.ts +1289 -0
- package/dist/core.js +3373 -0
- package/dist/core.js.map +1 -0
- package/dist/forms.d.ts +1085 -0
- package/dist/forms.js +2466 -0
- package/dist/forms.js.map +1 -0
- package/dist/heavy.d.ts +636 -0
- package/dist/heavy.js +4566 -0
- package/dist/heavy.js.map +1 -0
- package/dist/index.d.ts +5171 -4792
- package/dist/index.esm.js +6098 -4563
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +6291 -4747
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.d.ts +300 -0
- package/dist/layout.js +336 -0
- package/dist/layout.js.map +1 -0
- package/dist/theme.d.ts +2122 -0
- package/dist/theme.js +6084 -0
- package/dist/theme.js.map +1 -0
- package/package.json +59 -27
- package/scripts/atomix-cli.js +544 -16
- package/scripts/cli/__tests__/cli-commands.test.js +204 -0
- package/scripts/cli/__tests__/utils.test.js +201 -0
- package/scripts/cli/__tests__/vitest.config.js +26 -0
- package/scripts/cli/interactive-init.js +1 -1
- package/scripts/cli/token-manager.js +32 -7
- package/scripts/cli/utils.js +347 -0
- package/src/components/Accordion/Accordion.stories.tsx +50 -17
- package/src/components/Accordion/Accordion.tsx +5 -54
- package/src/components/Accordion/index.ts +1 -1
- package/src/components/AtomixGlass/AtomixGlass.tsx +65 -31
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +11 -4
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
- package/src/components/Avatar/Avatar.stories.tsx +7 -0
- package/src/components/Avatar/Avatar.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +91 -13
- package/src/components/Badge/Badge.tsx +3 -3
- package/src/components/Block/Block.stories.tsx +7 -23
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +3 -3
- package/src/components/Button/Button.stories.tsx +141 -22
- package/src/components/Button/ButtonGroup.stories.tsx +315 -0
- package/src/components/Button/ButtonGroup.tsx +67 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Callout/Callout.stories.tsx +8 -6
- package/src/components/Card/Card.stories.tsx +82 -28
- package/src/components/Card/ElevationCard.tsx +1 -1
- package/src/components/Chart/AnimatedChart.tsx +19 -18
- package/src/components/Chart/AreaChart.tsx +5 -2
- package/src/components/Chart/BarChart.tsx +1 -1
- package/src/components/Chart/BubbleChart.tsx +6 -6
- package/src/components/Chart/CandlestickChart.tsx +0 -1
- package/src/components/Chart/Chart.stories.tsx +5 -7
- package/src/components/Chart/Chart.tsx +0 -16
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/ChartToolbar.tsx +1 -0
- package/src/components/Chart/DonutChart.tsx +0 -1
- package/src/components/Chart/FunnelChart.tsx +1 -2
- package/src/components/Chart/GaugeChart.tsx +0 -1
- package/src/components/Chart/HeatmapChart.tsx +0 -1
- package/src/components/Chart/LineChart.tsx +0 -1
- package/src/components/Chart/MultiAxisChart.tsx +0 -1
- package/src/components/Chart/PieChart.tsx +0 -1
- package/src/components/Chart/RadarChart.tsx +19 -13
- package/src/components/Chart/ScatterChart.tsx +3 -4
- package/src/components/Chart/TreemapChart.tsx +2 -1
- package/src/components/Chart/WaterfallChart.tsx +0 -2
- package/src/components/Chart/types.ts +12 -2
- package/src/components/Chart/utils.ts +4 -3
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
- package/src/components/DataTable/DataTable.stories.tsx +23 -16
- package/src/components/DataTable/DataTable.tsx +3 -3
- package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
- package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
- package/src/components/Dropdown/Dropdown.tsx +12 -9
- package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
- package/src/components/Footer/Footer.stories.tsx +8 -6
- package/src/components/Footer/FooterLink.tsx +9 -2
- package/src/components/Footer/FooterSection.tsx +3 -3
- package/src/components/Form/Checkbox.stories.tsx +7 -0
- package/src/components/Form/Checkbox.tsx +3 -3
- package/src/components/Form/Form.stories.tsx +7 -0
- package/src/components/Form/FormGroup.stories.tsx +9 -1
- package/src/components/Form/Input.stories.tsx +69 -16
- package/src/components/Form/Input.tsx +4 -2
- package/src/components/Form/Radio.stories.tsx +9 -1
- package/src/components/Form/Radio.tsx +3 -3
- package/src/components/Form/Select.stories.tsx +9 -1
- package/src/components/Form/Select.tsx +3 -3
- package/src/components/Form/Textarea.stories.tsx +10 -2
- package/src/components/Form/Textarea.tsx +4 -2
- package/src/components/Hero/Hero.stories.tsx +7 -0
- package/src/components/List/List.stories.tsx +10 -3
- package/src/components/List/List.tsx +3 -3
- package/src/components/List/ListGroup.tsx +3 -1
- package/src/components/Messages/Messages.stories.tsx +8 -7
- package/src/components/Modal/Modal.stories.tsx +17 -6
- package/src/components/Modal/Modal.tsx +3 -3
- package/src/components/Navigation/Menu/MegaMenu.tsx +9 -3
- package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
- package/src/components/Navigation/Menu/Menu.tsx +9 -3
- package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +188 -111
- package/src/components/Pagination/Pagination.tsx +88 -7
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +2 -2
- package/src/components/Popover/Popover.stories.tsx +191 -115
- package/src/components/Popover/Popover.tsx +4 -4
- package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
- package/src/components/Progress/Progress.stories.tsx +79 -49
- package/src/components/Progress/Progress.tsx +6 -2
- package/src/components/Rating/Rating.stories.tsx +109 -84
- package/src/components/Rating/Rating.tsx +5 -2
- package/src/components/River/River.stories.tsx +194 -114
- package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
- package/src/components/Slider/Slider.stories.tsx +7 -0
- package/src/components/Slider/Slider.tsx +10 -9
- package/src/components/Spinner/Spinner.stories.tsx +15 -11
- package/src/components/Spinner/Spinner.tsx +3 -3
- package/src/components/Steps/Steps.stories.tsx +132 -98
- package/src/components/Tabs/Tabs.stories.tsx +163 -112
- package/src/components/Tabs/Tabs.tsx +3 -3
- package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
- package/src/components/Todo/Todo.stories.tsx +38 -12
- package/src/components/Toggle/Toggle.stories.tsx +61 -28
- package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/Upload/Upload.stories.tsx +122 -84
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
- package/src/components/index.ts +6 -2
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +2 -2
- package/src/lib/composables/useAtomixGlass.ts +2 -3
- package/src/lib/composables/useChartPerformance.ts +102 -78
- package/src/lib/composables/useChartScale.ts +10 -0
- package/src/lib/composables/useHero.ts +9 -2
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -3
- package/src/lib/composables/useNavbar.ts +0 -10
- package/src/lib/composables/useSideMenu.ts +1 -0
- package/src/lib/composables/useVideoPlayer.ts +3 -2
- package/src/lib/config/loader.ts +57 -14
- package/src/lib/constants/components.ts +10 -0
- package/src/lib/hooks/index.ts +0 -1
- package/src/lib/hooks/useComponentCustomization.ts +11 -15
- package/src/lib/hooks/usePerformanceMonitor.ts +149 -0
- package/src/lib/patterns/index.ts +2 -2
- package/src/lib/patterns/slots.tsx +2 -2
- package/src/lib/theme/README.md +174 -0
- package/src/lib/theme/adapters/index.ts +31 -0
- package/src/lib/theme/adapters/themeAdapter.ts +287 -0
- package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
- package/src/lib/theme/config/configLoader.ts +254 -0
- package/src/lib/theme/config/loader.ts +37 -48
- package/src/lib/theme/config/types.ts +2 -2
- package/src/lib/theme/config/validator.ts +15 -91
- package/src/lib/theme/{constants.ts → constants/constants.ts} +0 -18
- package/src/lib/theme/constants/index.ts +8 -0
- package/src/lib/theme/core/ThemeRegistry.ts +19 -6
- package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
- package/src/lib/theme/core/composeTheme.ts +155 -0
- package/src/lib/theme/core/createTheme.ts +94 -0
- package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +10 -6
- package/src/lib/theme/core/index.ts +5 -19
- package/src/lib/theme/devtools/Comparator.tsx +346 -22
- package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
- package/src/lib/theme/devtools/Inspector.tsx +335 -51
- package/src/lib/theme/devtools/LiveEditor.tsx +489 -112
- package/src/lib/theme/devtools/Preview.tsx +471 -221
- package/src/lib/theme/{core → devtools}/ThemeValidator.ts +6 -3
- package/src/lib/theme/devtools/index.ts +14 -4
- package/src/lib/theme/devtools/useHistory.ts +130 -0
- package/src/lib/theme/errors/index.ts +12 -0
- package/src/lib/theme/generators/cssFile.ts +79 -0
- package/src/lib/theme/generators/generateCSS.ts +89 -0
- package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +4 -14
- package/src/lib/theme/generators/index.ts +19 -0
- package/src/lib/theme/i18n/rtl.ts +7 -7
- package/src/lib/theme/index.ts +120 -15
- package/src/lib/theme/runtime/ThemeApplicator.ts +53 -95
- package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +4 -4
- package/src/lib/theme/runtime/ThemeProvider.tsx +456 -179
- package/src/lib/theme/runtime/index.ts +1 -2
- package/src/lib/theme/runtime/useTheme.ts +1 -2
- package/src/lib/theme/test/testTheme.ts +385 -0
- package/src/lib/theme/tokens/index.ts +12 -0
- package/src/lib/theme/tokens/tokens.ts +721 -0
- package/src/lib/theme/types.ts +6 -42
- package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
- package/src/lib/theme/utils/index.ts +11 -0
- package/src/lib/theme/utils/injectCSS.ts +90 -0
- package/src/lib/theme/utils/themeHelpers.ts +78 -0
- package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +1 -1
- package/src/lib/theme-tools.ts +8 -9
- package/src/lib/types/components.ts +93 -34
- package/src/lib/types/partProps.ts +0 -16
- package/src/lib/utils/componentUtils.ts +1 -1
- package/src/lib/utils/fontPreloader.ts +148 -0
- package/src/lib/utils/index.ts +11 -0
- package/src/lib/utils/memoryMonitor.ts +189 -0
- package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
- package/src/styles/01-settings/_settings.fonts.scss +2 -5
- package/src/styles/02-tools/_tools.button.scss +66 -79
- package/src/styles/06-components/_components.atomix-glass.scss +13 -3
- package/src/styles/06-components/_components.navbar.scss +0 -6
- package/src/styles/06-components/_components.pagination.scss +88 -0
- package/scripts/build-themes.js +0 -208
- package/scripts/sync-theme-config.js +0 -309
- package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -1263
- package/src/lib/theme/composeTheme.ts +0 -370
- package/src/lib/theme/core/ThemeCache.ts +0 -283
- package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
- package/src/lib/theme/core/ThemeEngine.ts +0 -657
- package/src/lib/theme/createThemeFromConfig.ts +0 -132
- package/src/lib/theme/devtools/CLI.ts +0 -364
- package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
- package/src/lib/theme/runtime/ThemeManager.ts +0 -442
- package/src/styles/03-generic/_generated-root.css +0 -5
- package/src/themes/README.md +0 -442
- package/src/themes/themes.config.js +0 -35
- /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
- /package/src/lib/theme/{errors.ts → errors/errors.ts} +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Theme Live Editor Component
|
|
3
3
|
*
|
|
4
4
|
* React component for live editing themes in development
|
|
5
|
+
* Enhanced with undo/redo, keyboard shortcuts, resizable layout, and better color pickers
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import React, { useState, useCallback } from 'react';
|
|
8
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
8
9
|
import type { Theme } from '../types';
|
|
9
|
-
import {
|
|
10
|
+
import { createThemeObject } from '../core/createThemeObject';
|
|
10
11
|
import { ThemePreview } from './Preview';
|
|
12
|
+
import { useHistory } from './useHistory';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Live editor props
|
|
@@ -23,6 +25,92 @@ export interface ThemeLiveEditorProps {
|
|
|
23
25
|
style?: React.CSSProperties;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Color format type
|
|
30
|
+
*/
|
|
31
|
+
type ColorFormat = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert color to different formats
|
|
35
|
+
*/
|
|
36
|
+
function convertColorFormat(color: string, format: ColorFormat): string {
|
|
37
|
+
// Remove whitespace
|
|
38
|
+
color = color.trim();
|
|
39
|
+
|
|
40
|
+
// If already in target format, return as is
|
|
41
|
+
if (format === 'hex' && color.startsWith('#')) return color;
|
|
42
|
+
if (format === 'rgb' && color.startsWith('rgb(')) return color;
|
|
43
|
+
if (format === 'rgba' && color.startsWith('rgba(')) return color;
|
|
44
|
+
if (format === 'hsl' && color.startsWith('hsl(')) return color;
|
|
45
|
+
if (format === 'hsla' && color.startsWith('hsla(')) return color;
|
|
46
|
+
|
|
47
|
+
// Create a temporary element to parse color
|
|
48
|
+
const temp = document.createElement('div');
|
|
49
|
+
temp.style.color = color;
|
|
50
|
+
document.body.appendChild(temp);
|
|
51
|
+
const computed = window.getComputedStyle(temp).color;
|
|
52
|
+
document.body.removeChild(temp);
|
|
53
|
+
|
|
54
|
+
// Parse rgb values
|
|
55
|
+
const rgbMatch = computed.match(/\d+/g);
|
|
56
|
+
if (!rgbMatch || rgbMatch.length < 3) return color;
|
|
57
|
+
|
|
58
|
+
const r = parseInt(rgbMatch[0], 10);
|
|
59
|
+
const g = parseInt(rgbMatch[1] || '0', 10);
|
|
60
|
+
const b = parseInt(rgbMatch[2] || '0', 10);
|
|
61
|
+
const a = rgbMatch[3] ? parseFloat(rgbMatch[3]) : 1;
|
|
62
|
+
|
|
63
|
+
switch (format) {
|
|
64
|
+
case 'hex':
|
|
65
|
+
return `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`;
|
|
66
|
+
case 'rgb':
|
|
67
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
68
|
+
case 'rgba':
|
|
69
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
70
|
+
case 'hsl':
|
|
71
|
+
case 'hsla':
|
|
72
|
+
// Convert RGB to HSL (simplified)
|
|
73
|
+
const hsl = rgbToHsl(r, g, b);
|
|
74
|
+
return format === 'hsl'
|
|
75
|
+
? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`
|
|
76
|
+
: `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
|
|
77
|
+
default:
|
|
78
|
+
return color;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Convert RGB to HSL
|
|
84
|
+
*/
|
|
85
|
+
function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
|
|
86
|
+
r /= 255;
|
|
87
|
+
g /= 255;
|
|
88
|
+
b /= 255;
|
|
89
|
+
|
|
90
|
+
const max = Math.max(r, g, b);
|
|
91
|
+
const min = Math.min(r, g, b);
|
|
92
|
+
let h = 0;
|
|
93
|
+
let s = 0;
|
|
94
|
+
const l = (max + min) / 2;
|
|
95
|
+
|
|
96
|
+
if (max !== min) {
|
|
97
|
+
const d = max - min;
|
|
98
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
99
|
+
|
|
100
|
+
switch (max) {
|
|
101
|
+
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
|
|
102
|
+
case g: h = ((b - r) / d + 2) / 6; break;
|
|
103
|
+
case b: h = ((r - g) / d + 4) / 6; break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
h: Math.round(h * 360),
|
|
109
|
+
s: Math.round(s * 100),
|
|
110
|
+
l: Math.round(l * 100),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
26
114
|
/**
|
|
27
115
|
* Theme Live Editor Component
|
|
28
116
|
*
|
|
@@ -34,45 +122,119 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
34
122
|
className,
|
|
35
123
|
style,
|
|
36
124
|
}) => {
|
|
37
|
-
const
|
|
125
|
+
const {
|
|
126
|
+
state: theme,
|
|
127
|
+
setState: setThemeHistory,
|
|
128
|
+
undo,
|
|
129
|
+
redo,
|
|
130
|
+
canUndo,
|
|
131
|
+
canRedo,
|
|
132
|
+
} = useHistory<Theme>({
|
|
133
|
+
initialState: initialTheme,
|
|
134
|
+
maxHistorySize: 50,
|
|
135
|
+
});
|
|
136
|
+
|
|
38
137
|
const [jsonInput, setJsonInput] = useState<string>(JSON.stringify(initialTheme, null, 2));
|
|
39
138
|
const [error, setError] = useState<string | null>(null);
|
|
40
139
|
const [editMode, setEditMode] = useState<'visual' | 'json'>('visual');
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
140
|
+
const [colorFormat, setColorFormat] = useState<ColorFormat>('hex');
|
|
141
|
+
const [resizerPosition, setResizerPosition] = useState<number>(50); // Percentage
|
|
142
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
143
|
+
const editorRef = useRef<HTMLDivElement>(null);
|
|
144
|
+
const previewRef = useRef<HTMLDivElement>(null);
|
|
145
|
+
|
|
146
|
+
// Load saved layout preference
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const saved = localStorage.getItem('atomix-editor-layout');
|
|
149
|
+
if (saved) {
|
|
150
|
+
const position = parseFloat(saved);
|
|
151
|
+
if (!isNaN(position) && position > 0 && position < 100) {
|
|
152
|
+
setResizerPosition(position);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
// Save layout preference
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
localStorage.setItem('atomix-editor-layout', resizerPosition.toString());
|
|
160
|
+
}, [resizerPosition]);
|
|
161
|
+
|
|
162
|
+
const updateTheme = useCallback((newTheme: Theme, addToHistory = true) => {
|
|
163
|
+
if (addToHistory) {
|
|
164
|
+
setThemeHistory(newTheme);
|
|
165
|
+
} else {
|
|
166
|
+
// Direct update without history (for JSON editor typing)
|
|
167
|
+
setJsonInput(JSON.stringify(newTheme, null, 2));
|
|
168
|
+
}
|
|
45
169
|
onChange?.(newTheme);
|
|
46
170
|
setError(null);
|
|
47
|
-
}, [onChange]);
|
|
171
|
+
}, [onChange, setThemeHistory]);
|
|
172
|
+
|
|
173
|
+
// Sync JSON input with theme history
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
setJsonInput(JSON.stringify(theme, null, 2));
|
|
176
|
+
}, [theme]);
|
|
48
177
|
|
|
49
178
|
const handleJsonChange = useCallback((value: string) => {
|
|
50
179
|
setJsonInput(value);
|
|
51
180
|
try {
|
|
52
181
|
const parsed = JSON.parse(value);
|
|
53
|
-
const newTheme =
|
|
54
|
-
|
|
55
|
-
onChange?.(newTheme);
|
|
56
|
-
setError(null);
|
|
182
|
+
const newTheme = createThemeObject(parsed);
|
|
183
|
+
updateTheme(newTheme, false); // Don't add to history on every keystroke
|
|
57
184
|
} catch (err) {
|
|
58
185
|
setError(err instanceof Error ? err.message : 'Invalid JSON');
|
|
59
186
|
}
|
|
60
|
-
}, [
|
|
187
|
+
}, [updateTheme]);
|
|
188
|
+
|
|
189
|
+
// Debounced JSON update to history
|
|
190
|
+
const jsonUpdateTimeoutRef = useRef<NodeJS.Timeout>();
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (error) return;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(jsonInput);
|
|
196
|
+
const newTheme = createThemeObject(parsed);
|
|
197
|
+
|
|
198
|
+
// Clear existing timeout
|
|
199
|
+
if (jsonUpdateTimeoutRef.current) {
|
|
200
|
+
clearTimeout(jsonUpdateTimeoutRef.current);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add to history after 1 second of no typing
|
|
204
|
+
jsonUpdateTimeoutRef.current = setTimeout(() => {
|
|
205
|
+
setThemeHistory(newTheme);
|
|
206
|
+
}, 1000);
|
|
207
|
+
} catch {
|
|
208
|
+
// Invalid JSON, don't update
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return () => {
|
|
212
|
+
if (jsonUpdateTimeoutRef.current) {
|
|
213
|
+
clearTimeout(jsonUpdateTimeoutRef.current);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}, [jsonInput, error, setThemeHistory]);
|
|
61
217
|
|
|
62
|
-
const handleColorChange = useCallback((path: string, value: string) => {
|
|
218
|
+
const handleColorChange = useCallback((path: string, value: string | number) => {
|
|
63
219
|
const newTheme = { ...theme };
|
|
64
220
|
const keys = path.split('.');
|
|
65
221
|
let current: any = newTheme;
|
|
66
222
|
|
|
67
223
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
68
|
-
|
|
69
|
-
|
|
224
|
+
const key = keys[i];
|
|
225
|
+
if (key && !current[key]) {
|
|
226
|
+
current[key] = {};
|
|
227
|
+
}
|
|
228
|
+
if (key) {
|
|
229
|
+
current = current[key];
|
|
70
230
|
}
|
|
71
|
-
current = current[keys[i]];
|
|
72
231
|
}
|
|
73
232
|
|
|
74
|
-
|
|
75
|
-
|
|
233
|
+
const lastKey = keys[keys.length - 1];
|
|
234
|
+
if (lastKey) {
|
|
235
|
+
current[lastKey] = value;
|
|
236
|
+
}
|
|
237
|
+
updateTheme(createThemeObject(newTheme));
|
|
76
238
|
}, [theme, updateTheme]);
|
|
77
239
|
|
|
78
240
|
const exportTheme = useCallback(() => {
|
|
@@ -90,114 +252,234 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
90
252
|
navigator.clipboard?.writeText(jsonInput);
|
|
91
253
|
}, [jsonInput]);
|
|
92
254
|
|
|
255
|
+
// Keyboard shortcuts
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
258
|
+
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
259
|
+
const ctrlKey = isMac ? e.metaKey : e.ctrlKey;
|
|
260
|
+
|
|
261
|
+
if (ctrlKey && e.key === 'z' && !e.shiftKey) {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
if (canUndo) undo();
|
|
264
|
+
} else if (ctrlKey && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
if (canRedo) redo();
|
|
267
|
+
} else if (ctrlKey && e.key === 's') {
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
exportTheme();
|
|
270
|
+
} else if (ctrlKey && e.key === '/') {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
setEditMode(prev => prev === 'visual' ? 'json' : 'visual');
|
|
273
|
+
} else if (e.key === 'Escape') {
|
|
274
|
+
setError(null);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
279
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
280
|
+
}, [canUndo, canRedo, undo, redo, exportTheme]);
|
|
281
|
+
|
|
282
|
+
// Resizer handlers
|
|
283
|
+
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
setIsResizing(true);
|
|
286
|
+
}, []);
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (!isResizing) return;
|
|
290
|
+
|
|
291
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
292
|
+
if (!editorRef.current || !previewRef.current) return;
|
|
293
|
+
|
|
294
|
+
const container = editorRef.current.parentElement;
|
|
295
|
+
if (!container) return;
|
|
296
|
+
|
|
297
|
+
const containerRect = container.getBoundingClientRect();
|
|
298
|
+
const newPosition = ((e.clientX - containerRect.left) / containerRect.width) * 100;
|
|
299
|
+
|
|
300
|
+
// Constrain between 20% and 80%
|
|
301
|
+
const constrainedPosition = Math.max(20, Math.min(80, newPosition));
|
|
302
|
+
setResizerPosition(constrainedPosition);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handleMouseUp = () => {
|
|
306
|
+
setIsResizing(false);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
310
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
311
|
+
|
|
312
|
+
return () => {
|
|
313
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
314
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
315
|
+
};
|
|
316
|
+
}, [isResizing]);
|
|
317
|
+
|
|
318
|
+
const getColorValue = useCallback((color: string): string => {
|
|
319
|
+
return convertColorFormat(color, colorFormat);
|
|
320
|
+
}, [colorFormat]);
|
|
321
|
+
|
|
93
322
|
return (
|
|
94
323
|
<div className={`atomix-theme-live-editor ${className || ''}`} style={style}>
|
|
95
324
|
<div className="editor-header">
|
|
96
325
|
<h2>Live Theme Editor</h2>
|
|
97
326
|
<div className="editor-controls">
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</
|
|
327
|
+
<div className="history-controls">
|
|
328
|
+
<button
|
|
329
|
+
className="history-button"
|
|
330
|
+
onClick={undo}
|
|
331
|
+
disabled={!canUndo}
|
|
332
|
+
title="Undo (Ctrl+Z)"
|
|
333
|
+
>
|
|
334
|
+
↶ Undo
|
|
335
|
+
</button>
|
|
336
|
+
<button
|
|
337
|
+
className="history-button"
|
|
338
|
+
onClick={redo}
|
|
339
|
+
disabled={!canRedo}
|
|
340
|
+
title="Redo (Ctrl+Shift+Z)"
|
|
341
|
+
>
|
|
342
|
+
↷ Redo
|
|
343
|
+
</button>
|
|
344
|
+
</div>
|
|
345
|
+
<div className="mode-controls">
|
|
346
|
+
<button
|
|
347
|
+
className={`mode-button ${editMode === 'visual' ? 'active' : ''}`}
|
|
348
|
+
onClick={() => setEditMode('visual')}
|
|
349
|
+
title="Visual Editor (Ctrl+/)"
|
|
350
|
+
>
|
|
351
|
+
Visual
|
|
352
|
+
</button>
|
|
353
|
+
<button
|
|
354
|
+
className={`mode-button ${editMode === 'json' ? 'active' : ''}`}
|
|
355
|
+
onClick={() => setEditMode('json')}
|
|
356
|
+
title="JSON Editor (Ctrl+/)"
|
|
357
|
+
>
|
|
358
|
+
JSON
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
<div className="action-controls">
|
|
362
|
+
<button className="export-button" onClick={exportTheme} title="Export (Ctrl+S)">
|
|
363
|
+
Export
|
|
364
|
+
</button>
|
|
365
|
+
<button className="copy-button" onClick={copyToClipboard} title="Copy JSON">
|
|
366
|
+
Copy JSON
|
|
367
|
+
</button>
|
|
368
|
+
</div>
|
|
116
369
|
</div>
|
|
117
370
|
</div>
|
|
118
371
|
|
|
119
|
-
<div className="editor-content">
|
|
120
|
-
<div
|
|
372
|
+
<div className="editor-content" ref={editorRef}>
|
|
373
|
+
<div
|
|
374
|
+
className="editor-panel"
|
|
375
|
+
style={{ width: `${resizerPosition}%` }}
|
|
376
|
+
>
|
|
121
377
|
{editMode === 'visual' ? (
|
|
122
378
|
<div className="visual-editor">
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
value=
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
379
|
+
<div className="editor-section">
|
|
380
|
+
<h3>Colors</h3>
|
|
381
|
+
<div className="color-format-selector">
|
|
382
|
+
<label>Color Format:</label>
|
|
383
|
+
<select
|
|
384
|
+
value={colorFormat}
|
|
385
|
+
onChange={(e) => setColorFormat(e.target.value as ColorFormat)}
|
|
386
|
+
>
|
|
387
|
+
<option value="hex">HEX</option>
|
|
388
|
+
<option value="rgb">RGB</option>
|
|
389
|
+
<option value="rgba">RGBA</option>
|
|
390
|
+
<option value="hsl">HSL</option>
|
|
391
|
+
<option value="hsla">HSLA</option>
|
|
392
|
+
</select>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
{/* Primary Color */}
|
|
396
|
+
<div className="editor-field">
|
|
397
|
+
<label>Primary Color</label>
|
|
398
|
+
<div className="color-input-group">
|
|
399
|
+
<input
|
|
400
|
+
type="color"
|
|
401
|
+
value={theme.palette.primary.main}
|
|
402
|
+
onChange={(e) => handleColorChange('palette.primary.main', e.target.value)}
|
|
403
|
+
/>
|
|
404
|
+
<input
|
|
405
|
+
type="text"
|
|
406
|
+
value={getColorValue(theme.palette.primary.main)}
|
|
407
|
+
onChange={(e) => {
|
|
408
|
+
const converted = convertColorFormat(e.target.value, 'hex');
|
|
409
|
+
handleColorChange('palette.primary.main', converted);
|
|
410
|
+
}}
|
|
411
|
+
placeholder="#7AFFD7"
|
|
412
|
+
/>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
416
|
+
{/* Secondary Color */}
|
|
417
|
+
<div className="editor-field">
|
|
418
|
+
<label>Secondary Color</label>
|
|
419
|
+
<div className="color-input-group">
|
|
420
|
+
<input
|
|
421
|
+
type="color"
|
|
422
|
+
value={theme.palette.secondary.main}
|
|
423
|
+
onChange={(e) => handleColorChange('palette.secondary.main', e.target.value)}
|
|
424
|
+
/>
|
|
425
|
+
<input
|
|
426
|
+
type="text"
|
|
427
|
+
value={getColorValue(theme.palette.secondary.main)}
|
|
428
|
+
onChange={(e) => {
|
|
429
|
+
const converted = convertColorFormat(e.target.value, 'hex');
|
|
430
|
+
handleColorChange('palette.secondary.main', converted);
|
|
431
|
+
}}
|
|
432
|
+
placeholder="#FF5733"
|
|
433
|
+
/>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
{/* Background Colors */}
|
|
438
|
+
<h3>Background</h3>
|
|
439
|
+
<div className="editor-field">
|
|
440
|
+
<label>Default Background</label>
|
|
441
|
+
<div className="color-input-group">
|
|
442
|
+
<input
|
|
443
|
+
type="color"
|
|
444
|
+
value={theme.palette.background.default}
|
|
445
|
+
onChange={(e) => handleColorChange('palette.background.default', e.target.value)}
|
|
446
|
+
/>
|
|
447
|
+
<input
|
|
448
|
+
type="text"
|
|
449
|
+
value={getColorValue(theme.palette.background.default)}
|
|
450
|
+
onChange={(e) => {
|
|
451
|
+
const converted = convertColorFormat(e.target.value, 'hex');
|
|
452
|
+
handleColorChange('palette.background.default', converted);
|
|
453
|
+
}}
|
|
454
|
+
/>
|
|
455
|
+
</div>
|
|
140
456
|
</div>
|
|
141
457
|
</div>
|
|
142
458
|
|
|
143
|
-
{/*
|
|
144
|
-
<div className="editor-
|
|
145
|
-
<
|
|
146
|
-
<div className="
|
|
147
|
-
<
|
|
148
|
-
type="color"
|
|
149
|
-
value={theme.palette.secondary.main}
|
|
150
|
-
onChange={(e) => handleColorChange('palette.secondary.main', e.target.value)}
|
|
151
|
-
/>
|
|
459
|
+
{/* Typography */}
|
|
460
|
+
<div className="editor-section">
|
|
461
|
+
<h3>Typography</h3>
|
|
462
|
+
<div className="editor-field">
|
|
463
|
+
<label>Font Family</label>
|
|
152
464
|
<input
|
|
153
465
|
type="text"
|
|
154
|
-
value={theme.
|
|
155
|
-
onChange={(e) => handleColorChange('
|
|
156
|
-
placeholder="
|
|
466
|
+
value={theme.typography.fontFamily}
|
|
467
|
+
onChange={(e) => handleColorChange('typography.fontFamily', e.target.value)}
|
|
468
|
+
placeholder="Inter, sans-serif"
|
|
157
469
|
/>
|
|
158
470
|
</div>
|
|
159
|
-
</div>
|
|
160
471
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<div className="editor-field">
|
|
164
|
-
<label>Default Background</label>
|
|
165
|
-
<div className="color-input-group">
|
|
472
|
+
<div className="editor-field">
|
|
473
|
+
<label>Base Font Size (px)</label>
|
|
166
474
|
<input
|
|
167
|
-
type="
|
|
168
|
-
value={theme.
|
|
169
|
-
onChange={(e) => handleColorChange('
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
type="text"
|
|
173
|
-
value={theme.palette.background.default}
|
|
174
|
-
onChange={(e) => handleColorChange('palette.background.default', e.target.value)}
|
|
475
|
+
type="number"
|
|
476
|
+
value={theme.typography.fontSize}
|
|
477
|
+
onChange={(e) => handleColorChange('typography.fontSize', parseInt(e.target.value))}
|
|
478
|
+
min="10"
|
|
479
|
+
max="24"
|
|
175
480
|
/>
|
|
176
481
|
</div>
|
|
177
482
|
</div>
|
|
178
|
-
|
|
179
|
-
{/* Typography */}
|
|
180
|
-
<h3>Typography</h3>
|
|
181
|
-
<div className="editor-field">
|
|
182
|
-
<label>Font Family</label>
|
|
183
|
-
<input
|
|
184
|
-
type="text"
|
|
185
|
-
value={theme.typography.fontFamily}
|
|
186
|
-
onChange={(e) => handleColorChange('typography.fontFamily', e.target.value)}
|
|
187
|
-
placeholder="Inter, sans-serif"
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div className="editor-field">
|
|
192
|
-
<label>Base Font Size (px)</label>
|
|
193
|
-
<input
|
|
194
|
-
type="number"
|
|
195
|
-
value={theme.typography.fontSize}
|
|
196
|
-
onChange={(e) => handleColorChange('typography.fontSize', parseInt(e.target.value))}
|
|
197
|
-
min="10"
|
|
198
|
-
max="24"
|
|
199
|
-
/>
|
|
200
|
-
</div>
|
|
201
483
|
</div>
|
|
202
484
|
) : (
|
|
203
485
|
<div className="json-editor">
|
|
@@ -209,13 +491,23 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
209
491
|
{error && (
|
|
210
492
|
<div className="error-message">
|
|
211
493
|
❌ {error}
|
|
494
|
+
<button className="error-dismiss" onClick={() => setError(null)}>×</button>
|
|
212
495
|
</div>
|
|
213
496
|
)}
|
|
214
497
|
</div>
|
|
215
498
|
)}
|
|
216
499
|
</div>
|
|
217
500
|
|
|
218
|
-
<div
|
|
501
|
+
<div
|
|
502
|
+
className={`resizer ${isResizing ? 'resizing' : ''}`}
|
|
503
|
+
onMouseDown={handleResizeStart}
|
|
504
|
+
/>
|
|
505
|
+
|
|
506
|
+
<div
|
|
507
|
+
className="preview-panel"
|
|
508
|
+
ref={previewRef}
|
|
509
|
+
style={{ width: `${100 - resizerPosition}%` }}
|
|
510
|
+
>
|
|
219
511
|
<h3>Live Preview</h3>
|
|
220
512
|
<ThemePreview
|
|
221
513
|
theme={theme}
|
|
@@ -251,10 +543,19 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
251
543
|
}
|
|
252
544
|
|
|
253
545
|
.editor-controls {
|
|
546
|
+
display: flex;
|
|
547
|
+
gap: 12px;
|
|
548
|
+
align-items: center;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.history-controls,
|
|
552
|
+
.mode-controls,
|
|
553
|
+
.action-controls {
|
|
254
554
|
display: flex;
|
|
255
555
|
gap: 8px;
|
|
256
556
|
}
|
|
257
557
|
|
|
558
|
+
.history-button,
|
|
258
559
|
.mode-button,
|
|
259
560
|
.export-button,
|
|
260
561
|
.copy-button {
|
|
@@ -267,12 +568,18 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
267
568
|
transition: all 0.2s;
|
|
268
569
|
}
|
|
269
570
|
|
|
571
|
+
.history-button:hover:not(:disabled),
|
|
270
572
|
.mode-button:hover,
|
|
271
573
|
.export-button:hover,
|
|
272
574
|
.copy-button:hover {
|
|
273
575
|
background: #f5f5f5;
|
|
274
576
|
}
|
|
275
577
|
|
|
578
|
+
.history-button:disabled {
|
|
579
|
+
opacity: 0.5;
|
|
580
|
+
cursor: not-allowed;
|
|
581
|
+
}
|
|
582
|
+
|
|
276
583
|
.mode-button.active {
|
|
277
584
|
background: #2196f3;
|
|
278
585
|
color: white;
|
|
@@ -292,16 +599,38 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
292
599
|
}
|
|
293
600
|
|
|
294
601
|
.editor-content {
|
|
295
|
-
display:
|
|
296
|
-
|
|
297
|
-
gap: 24px;
|
|
298
|
-
padding: 24px;
|
|
602
|
+
display: flex;
|
|
603
|
+
position: relative;
|
|
299
604
|
min-height: 600px;
|
|
300
605
|
}
|
|
301
606
|
|
|
302
607
|
.editor-panel,
|
|
303
608
|
.preview-panel {
|
|
304
609
|
overflow-y: auto;
|
|
610
|
+
padding: 24px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.resizer {
|
|
614
|
+
width: 4px;
|
|
615
|
+
background: #e0e0e0;
|
|
616
|
+
cursor: col-resize;
|
|
617
|
+
position: relative;
|
|
618
|
+
flex-shrink: 0;
|
|
619
|
+
transition: background 0.2s;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.resizer:hover,
|
|
623
|
+
.resizer.resizing {
|
|
624
|
+
background: #2196f3;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.resizer::before {
|
|
628
|
+
content: '';
|
|
629
|
+
position: absolute;
|
|
630
|
+
left: -2px;
|
|
631
|
+
right: -2px;
|
|
632
|
+
top: 0;
|
|
633
|
+
bottom: 0;
|
|
305
634
|
}
|
|
306
635
|
|
|
307
636
|
.editor-panel h3,
|
|
@@ -314,11 +643,39 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
314
643
|
}
|
|
315
644
|
|
|
316
645
|
.visual-editor {
|
|
646
|
+
display: flex;
|
|
647
|
+
flex-direction: column;
|
|
648
|
+
gap: 24px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.editor-section {
|
|
317
652
|
display: flex;
|
|
318
653
|
flex-direction: column;
|
|
319
654
|
gap: 16px;
|
|
320
655
|
}
|
|
321
656
|
|
|
657
|
+
.color-format-selector {
|
|
658
|
+
display: flex;
|
|
659
|
+
align-items: center;
|
|
660
|
+
gap: 8px;
|
|
661
|
+
margin-bottom: 8px;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.color-format-selector label {
|
|
665
|
+
font-size: 14px;
|
|
666
|
+
font-weight: 500;
|
|
667
|
+
color: #666;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.color-format-selector select {
|
|
671
|
+
padding: 6px 12px;
|
|
672
|
+
border: 1px solid #e0e0e0;
|
|
673
|
+
border-radius: 4px;
|
|
674
|
+
font-size: 14px;
|
|
675
|
+
background: white;
|
|
676
|
+
cursor: pointer;
|
|
677
|
+
}
|
|
678
|
+
|
|
322
679
|
.editor-field {
|
|
323
680
|
display: flex;
|
|
324
681
|
flex-direction: column;
|
|
@@ -380,14 +737,34 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
|
|
|
380
737
|
border-radius: 4px;
|
|
381
738
|
margin-top: 8px;
|
|
382
739
|
font-size: 14px;
|
|
740
|
+
display: flex;
|
|
741
|
+
justify-content: space-between;
|
|
742
|
+
align-items: center;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.error-dismiss {
|
|
746
|
+
background: none;
|
|
747
|
+
border: none;
|
|
748
|
+
color: #d32f2f;
|
|
749
|
+
font-size: 20px;
|
|
750
|
+
cursor: pointer;
|
|
751
|
+
padding: 0;
|
|
752
|
+
width: 24px;
|
|
753
|
+
height: 24px;
|
|
754
|
+
display: flex;
|
|
755
|
+
align-items: center;
|
|
756
|
+
justify-content: center;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.error-dismiss:hover {
|
|
760
|
+
background: rgba(211, 47, 47, 0.1);
|
|
761
|
+
border-radius: 50%;
|
|
383
762
|
}
|
|
384
763
|
|
|
385
764
|
.preview-panel {
|
|
386
765
|
border-left: 1px solid #e0e0e0;
|
|
387
|
-
padding-left: 24px;
|
|
388
766
|
}
|
|
389
767
|
`}</style>
|
|
390
768
|
</div>
|
|
391
769
|
);
|
|
392
770
|
};
|
|
393
|
-
|