@promakeai/inspector 0.2.1 → 1.0.0

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 (170) hide show
  1. package/dist/inspector.css +1 -0
  2. package/dist/inspector.js +8740 -0
  3. package/dist/packages/inspector/src/App.d.ts +5 -0
  4. package/dist/packages/inspector/src/App.d.ts.map +1 -0
  5. package/dist/packages/inspector/src/__tests__/App.test.d.ts +5 -0
  6. package/dist/packages/inspector/src/__tests__/App.test.d.ts.map +1 -0
  7. package/dist/packages/inspector/src/components/Badge.d.ts +9 -0
  8. package/dist/packages/inspector/src/components/Badge.d.ts.map +1 -0
  9. package/dist/packages/inspector/src/components/ControlBox/ContentArea.d.ts +10 -0
  10. package/dist/packages/inspector/src/components/ControlBox/ContentArea.d.ts.map +1 -0
  11. package/dist/packages/inspector/src/components/ControlBox/PromptInput.d.ts +12 -0
  12. package/dist/packages/inspector/src/components/ControlBox/PromptInput.d.ts.map +1 -0
  13. package/dist/packages/inspector/src/components/ControlBox/index.d.ts +21 -0
  14. package/dist/packages/inspector/src/components/ControlBox/index.d.ts.map +1 -0
  15. package/dist/packages/inspector/src/components/ImageEditor/UploadBox.d.ts +10 -0
  16. package/dist/packages/inspector/src/components/ImageEditor/UploadBox.d.ts.map +1 -0
  17. package/dist/packages/inspector/src/components/ImageEditor/index.d.ts +11 -0
  18. package/dist/packages/inspector/src/components/ImageEditor/index.d.ts.map +1 -0
  19. package/dist/packages/inspector/src/components/Overlay.d.ts +11 -0
  20. package/dist/packages/inspector/src/components/Overlay.d.ts.map +1 -0
  21. package/dist/packages/inspector/src/components/StyleEditor/BorderSection.d.ts +13 -0
  22. package/dist/packages/inspector/src/components/StyleEditor/BorderSection.d.ts.map +1 -0
  23. package/dist/packages/inspector/src/components/StyleEditor/ColorPicker.d.ts +13 -0
  24. package/dist/packages/inspector/src/components/StyleEditor/ColorPicker.d.ts.map +1 -0
  25. package/dist/packages/inspector/src/components/StyleEditor/DisplaySection.d.ts +13 -0
  26. package/dist/packages/inspector/src/components/StyleEditor/DisplaySection.d.ts.map +1 -0
  27. package/dist/packages/inspector/src/components/StyleEditor/ImageSection.d.ts +13 -0
  28. package/dist/packages/inspector/src/components/StyleEditor/ImageSection.d.ts.map +1 -0
  29. package/dist/packages/inspector/src/components/StyleEditor/LayoutSection.d.ts +13 -0
  30. package/dist/packages/inspector/src/components/StyleEditor/LayoutSection.d.ts.map +1 -0
  31. package/dist/packages/inspector/src/components/StyleEditor/NumberInput.d.ts +17 -0
  32. package/dist/packages/inspector/src/components/StyleEditor/NumberInput.d.ts.map +1 -0
  33. package/dist/packages/inspector/src/components/StyleEditor/SliderInput.d.ts +16 -0
  34. package/dist/packages/inspector/src/components/StyleEditor/SliderInput.d.ts.map +1 -0
  35. package/dist/packages/inspector/src/components/StyleEditor/SpacingSection.d.ts +13 -0
  36. package/dist/packages/inspector/src/components/StyleEditor/SpacingSection.d.ts.map +1 -0
  37. package/dist/packages/inspector/src/components/StyleEditor/TextSection.d.ts +13 -0
  38. package/dist/packages/inspector/src/components/StyleEditor/TextSection.d.ts.map +1 -0
  39. package/dist/packages/inspector/src/components/StyleEditor/index.d.ts +12 -0
  40. package/dist/packages/inspector/src/components/StyleEditor/index.d.ts.map +1 -0
  41. package/dist/packages/inspector/src/components/TextEditor/index.d.ts +11 -0
  42. package/dist/packages/inspector/src/components/TextEditor/index.d.ts.map +1 -0
  43. package/dist/packages/inspector/src/components/ui/CustomCollapsible.d.ts +26 -0
  44. package/dist/packages/inspector/src/components/ui/CustomCollapsible.d.ts.map +1 -0
  45. package/dist/packages/inspector/src/components/ui/button.d.ts +9 -0
  46. package/dist/packages/inspector/src/components/ui/button.d.ts.map +1 -0
  47. package/dist/packages/inspector/src/components/ui/color-picker.d.ts +10 -0
  48. package/dist/packages/inspector/src/components/ui/color-picker.d.ts.map +1 -0
  49. package/dist/packages/inspector/src/components/ui/input.d.ts +6 -0
  50. package/dist/packages/inspector/src/components/ui/input.d.ts.map +1 -0
  51. package/dist/packages/inspector/src/components/ui/popover.d.ts +8 -0
  52. package/dist/packages/inspector/src/components/ui/popover.d.ts.map +1 -0
  53. package/dist/packages/inspector/src/components/ui/select.d.ts +16 -0
  54. package/dist/packages/inspector/src/components/ui/select.d.ts.map +1 -0
  55. package/dist/packages/inspector/src/components/ui/slider.d.ts +5 -0
  56. package/dist/packages/inspector/src/components/ui/slider.d.ts.map +1 -0
  57. package/dist/packages/inspector/src/components/ui/textarea.d.ts +4 -0
  58. package/dist/packages/inspector/src/components/ui/textarea.d.ts.map +1 -0
  59. package/dist/packages/inspector/src/components/ui/tooltip.d.ts +8 -0
  60. package/dist/packages/inspector/src/components/ui/tooltip.d.ts.map +1 -0
  61. package/dist/packages/inspector/src/core/highlighter.d.ts +40 -0
  62. package/dist/packages/inspector/src/core/highlighter.d.ts.map +1 -0
  63. package/dist/packages/inspector/src/hooks/useMessageBridge.d.ts +9 -0
  64. package/dist/packages/inspector/src/hooks/useMessageBridge.d.ts.map +1 -0
  65. package/dist/packages/inspector/src/hooks/useStylePreview.d.ts +11 -0
  66. package/dist/packages/inspector/src/hooks/useStylePreview.d.ts.map +1 -0
  67. package/dist/packages/inspector/src/index.d.ts +16 -0
  68. package/dist/packages/inspector/src/index.d.ts.map +1 -0
  69. package/dist/packages/inspector/src/lib/utils.d.ts +3 -0
  70. package/dist/packages/inspector/src/lib/utils.d.ts.map +1 -0
  71. package/dist/packages/inspector/src/plugin.d.ts +4 -0
  72. package/dist/packages/inspector/src/plugin.d.ts.map +1 -0
  73. package/dist/packages/inspector/src/store/useInspectorStore.d.ts +13 -0
  74. package/dist/packages/inspector/src/store/useInspectorStore.d.ts.map +1 -0
  75. package/dist/packages/inspector/src/styles.d.ts +5 -0
  76. package/dist/packages/inspector/src/styles.d.ts.map +1 -0
  77. package/dist/packages/inspector/src/utils/colorUtils.d.ts +49 -0
  78. package/dist/packages/inspector/src/utils/colorUtils.d.ts.map +1 -0
  79. package/dist/packages/inspector/src/utils/elementNames.d.ts +7 -0
  80. package/dist/packages/inspector/src/utils/elementNames.d.ts.map +1 -0
  81. package/dist/packages/inspector/src/utils/elementUtils.d.ts +28 -0
  82. package/dist/packages/inspector/src/utils/elementUtils.d.ts.map +1 -0
  83. package/dist/packages/inspector/src/utils/errorTracker.d.ts +48 -0
  84. package/dist/packages/inspector/src/utils/errorTracker.d.ts.map +1 -0
  85. package/dist/packages/inspector/src/utils/inputStyles.d.ts +23 -0
  86. package/dist/packages/inspector/src/utils/inputStyles.d.ts.map +1 -0
  87. package/dist/packages/inspector/src/utils/styleUtils.d.ts +27 -0
  88. package/dist/packages/inspector/src/utils/styleUtils.d.ts.map +1 -0
  89. package/dist/packages/inspector/src/utils/tailwindMapper.d.ts +9 -0
  90. package/dist/packages/inspector/src/utils/tailwindMapper.d.ts.map +1 -0
  91. package/dist/packages/inspector/src/utils/urlTracker.d.ts +27 -0
  92. package/dist/packages/inspector/src/utils/urlTracker.d.ts.map +1 -0
  93. package/dist/packages/inspector/tsconfig.tsbuildinfo +1 -0
  94. package/dist/plugin.js +10 -1813
  95. package/package.json +86 -76
  96. package/src/App.tsx +912 -0
  97. package/src/__tests__/App.test.tsx +373 -0
  98. package/src/assets/fonts/Satoshi-Variable.woff +0 -0
  99. package/src/assets/fonts/Satoshi-Variable.woff2 +0 -0
  100. package/src/components/Badge.tsx +118 -0
  101. package/src/components/ControlBox/ContentArea.tsx +13 -0
  102. package/src/components/ControlBox/PromptInput.module.css +66 -0
  103. package/src/components/ControlBox/PromptInput.tsx +104 -0
  104. package/src/components/ControlBox/index.module.css +81 -0
  105. package/src/components/ControlBox/index.tsx +409 -0
  106. package/src/components/ImageEditor/UploadBox.module.css +69 -0
  107. package/src/components/ImageEditor/UploadBox.tsx +113 -0
  108. package/src/components/ImageEditor/index.module.css +11 -0
  109. package/src/components/ImageEditor/index.tsx +84 -0
  110. package/src/components/Overlay.tsx +157 -0
  111. package/src/components/StyleEditor/BorderSection.tsx +147 -0
  112. package/src/components/StyleEditor/ColorPicker.tsx +182 -0
  113. package/src/components/StyleEditor/DisplaySection.tsx +349 -0
  114. package/src/components/StyleEditor/ImageSection.tsx +105 -0
  115. package/src/components/StyleEditor/LayoutSection.tsx +63 -0
  116. package/src/components/StyleEditor/NumberInput.tsx +138 -0
  117. package/src/components/StyleEditor/SliderInput.tsx +121 -0
  118. package/src/components/StyleEditor/SpacingSection.tsx +365 -0
  119. package/src/components/StyleEditor/TextSection.tsx +381 -0
  120. package/src/components/StyleEditor/index.module.css +133 -0
  121. package/src/components/StyleEditor/index.tsx +612 -0
  122. package/src/components/StyleEditor/shared.module.css +193 -0
  123. package/src/components/TextEditor/index.module.css +31 -0
  124. package/src/components/TextEditor/index.tsx +166 -0
  125. package/src/components/ui/CustomCollapsible.tsx +159 -0
  126. package/src/components/ui/button.module.css +141 -0
  127. package/src/components/ui/button.tsx +73 -0
  128. package/src/components/ui/color-picker.module.css +112 -0
  129. package/src/components/ui/color-picker.tsx +146 -0
  130. package/src/components/ui/input.module.css +49 -0
  131. package/src/components/ui/input.tsx +34 -0
  132. package/src/components/ui/popover.module.css +42 -0
  133. package/src/components/ui/popover.tsx +59 -0
  134. package/src/components/ui/select.module.css +160 -0
  135. package/src/components/ui/select.tsx +216 -0
  136. package/src/components/ui/slider.module.css +75 -0
  137. package/src/components/ui/slider.tsx +60 -0
  138. package/src/components/ui/textarea.module.css +30 -0
  139. package/src/components/ui/textarea.tsx +23 -0
  140. package/src/components/ui/tooltip.module.css +11 -0
  141. package/src/components/ui/tooltip.tsx +37 -0
  142. package/src/core/highlighter.ts +197 -0
  143. package/src/hooks/useMessageBridge.ts +49 -0
  144. package/src/hooks/useStylePreview.ts +332 -0
  145. package/src/index.ts +20 -0
  146. package/src/lib/utils.ts +5 -0
  147. package/src/plugin.ts +11 -0
  148. package/src/store/useInspectorStore.ts +235 -0
  149. package/src/styles/fonts.css +15 -0
  150. package/src/styles/global.css +138 -0
  151. package/src/styles/variables.css +151 -0
  152. package/src/styles.ts +5 -0
  153. package/src/utils/colorUtils.ts +133 -0
  154. package/src/utils/elementNames.ts +103 -0
  155. package/src/utils/elementUtils.ts +90 -0
  156. package/src/utils/errorTracker.ts +186 -0
  157. package/src/utils/inputStyles.ts +30 -0
  158. package/src/utils/styleUtils.ts +226 -0
  159. package/src/utils/tailwindMapper.ts +554 -0
  160. package/src/utils/urlTracker.ts +75 -0
  161. package/src/vite-env.d.ts +7 -0
  162. package/README.md +0 -866
  163. package/dist/hook.d.ts +0 -115
  164. package/dist/hook.d.ts.map +0 -1
  165. package/dist/hook.js +0 -288
  166. package/dist/plugin.d.ts +0 -44
  167. package/dist/plugin.d.ts.map +0 -1
  168. package/dist/types.d.ts +0 -139
  169. package/dist/types.d.ts.map +0 -1
  170. package/dist/types.js +0 -7
@@ -0,0 +1,612 @@
1
+ /**
2
+ * StyleEditor - Main style editing component with accordion sections
3
+ */
4
+
5
+ import { useState, useEffect } from "react";
6
+ import {
7
+ getElementOwnStyles,
8
+ convertToInlineStyle,
9
+ } from "../../utils/styleUtils";
10
+ import { useStylePreview } from "../../hooks/useStylePreview";
11
+ import { useMessageBridge } from "../../hooks/useMessageBridge";
12
+ import { mapStylesToTailwindClasses } from "../../utils/tailwindMapper";
13
+ import { useInspectorStore } from "../../store/useInspectorStore";
14
+ import type {
15
+ StyleChanges,
16
+ SelectedElementData,
17
+ } from "@promakeai/inspector-types";
18
+ import {
19
+ Collapsible,
20
+ CollapsibleTrigger,
21
+ CollapsibleContent,
22
+ } from "../ui/CustomCollapsible";
23
+ import { Button } from "../ui/button";
24
+ import { ChevronDown, Circle, Check } from "lucide-react";
25
+ import { LayoutSection } from "./LayoutSection";
26
+ import { DisplaySection } from "./DisplaySection";
27
+ import { ImageSection } from "./ImageSection";
28
+ import { TextSection } from "./TextSection";
29
+ import { BorderSection } from "./BorderSection";
30
+ import { SpacingSection } from "./SpacingSection";
31
+ import { shouldShowSection } from "../../utils/elementUtils";
32
+ import cssStyles from "./index.module.css";
33
+ import clsx from "clsx";
34
+
35
+ interface StyleEditorProps {
36
+ element: HTMLElement;
37
+ elementData: SelectedElementData;
38
+ onHasChangesChange?: (hasChanges: boolean) => void;
39
+ }
40
+
41
+ export function StyleEditor({
42
+ element,
43
+ elementData,
44
+ onHasChangesChange,
45
+ }: StyleEditorProps) {
46
+ const { theme, labels } = useInspectorStore();
47
+ const [styles, setStyles] = useState<StyleChanges>(() =>
48
+ getElementOwnStyles(element)
49
+ );
50
+ const [hasChanges, setHasChanges] = useState(false);
51
+ const [isSaving, setIsSaving] = useState(false);
52
+
53
+ // Collapsible states for each section
54
+ const [layoutOpen, setLayoutOpen] = useState(false);
55
+ const [displayOpen, setDisplayOpen] = useState(false);
56
+ const [imageOpen, setImageOpen] = useState(false);
57
+ const [textOpen, setTextOpen] = useState(false);
58
+ const [borderOpen, setBorderOpen] = useState(false);
59
+ const [spacingOpen, setSpacingOpen] = useState(false);
60
+
61
+ const { previewStyle, resetStyles, resetSingleProperty } =
62
+ useStylePreview(element);
63
+ const { sendToParent } = useMessageBridge();
64
+
65
+ // Store original styles for reset functionality
66
+ const originalStylesRef = useState(() => getElementOwnStyles(element))[0];
67
+
68
+ // Reset collapsible states when element changes
69
+ useEffect(() => {
70
+ setLayoutOpen(false);
71
+ setDisplayOpen(false);
72
+ setImageOpen(false);
73
+ setTextOpen(false);
74
+ setBorderOpen(false);
75
+ setSpacingOpen(false);
76
+ return () => {
77
+ setLayoutOpen(false);
78
+ setDisplayOpen(false);
79
+ setImageOpen(false);
80
+ setTextOpen(false);
81
+ setBorderOpen(false);
82
+ setSpacingOpen(false);
83
+ };
84
+ }, []);
85
+
86
+ // Notify parent when hasChanges state changes
87
+ useEffect(() => {
88
+ onHasChangesChange?.(hasChanges);
89
+ }, [hasChanges, onHasChangesChange]);
90
+
91
+ const handleStyleChange = (property: keyof StyleChanges, value: string) => {
92
+ const newStyles = { ...styles, [property]: value };
93
+ setStyles(newStyles);
94
+
95
+ // Real-time preview on DOM - apply only this property
96
+ previewStyle(property, value);
97
+
98
+ // Mark as having unsaved changes
99
+ setHasChanges(true);
100
+ };
101
+
102
+ const handleApply = async () => {
103
+ setIsSaving(true);
104
+
105
+ try {
106
+ // Get only changed styles by comparing with original
107
+ const changedStyles: Partial<StyleChanges> = {};
108
+ (Object.keys(styles) as Array<keyof StyleChanges>).forEach((key) => {
109
+ if (styles[key] !== originalStylesRef[key]) {
110
+ changedStyles[key] = styles[key];
111
+ }
112
+ });
113
+
114
+ // Convert only changed styles to inline style object
115
+ const inlineStyles = convertToInlineStyle(changedStyles as StyleChanges);
116
+
117
+ // Map only changed styles to Tailwind classes
118
+ const tailwindClasses = mapStylesToTailwindClasses(
119
+ changedStyles as StyleChanges
120
+ );
121
+
122
+ // Create detailed breakdown of applied styles (only changed ones)
123
+ const appliedStyles: Record<
124
+ string,
125
+ Record<string, string | undefined>
126
+ > = {
127
+ layout: {},
128
+ text: {},
129
+ border: {},
130
+ spacing: {},
131
+ display: {},
132
+ image: {},
133
+ };
134
+
135
+ // Categorize changed styles
136
+ if (changedStyles.backgroundColor !== undefined)
137
+ appliedStyles.layout.backgroundColor = changedStyles.backgroundColor;
138
+ if (changedStyles.height !== undefined)
139
+ appliedStyles.layout.height = changedStyles.height;
140
+ if (changedStyles.width !== undefined)
141
+ appliedStyles.layout.width = changedStyles.width;
142
+
143
+ if (changedStyles.color !== undefined)
144
+ appliedStyles.text.color = changedStyles.color;
145
+ if (changedStyles.fontSize !== undefined)
146
+ appliedStyles.text.fontSize = changedStyles.fontSize;
147
+ if (changedStyles.fontWeight !== undefined)
148
+ appliedStyles.text.fontWeight = changedStyles.fontWeight;
149
+ if (changedStyles.fontFamily !== undefined)
150
+ appliedStyles.text.fontFamily = changedStyles.fontFamily;
151
+ if (changedStyles.textAlign !== undefined)
152
+ appliedStyles.text.textAlign = changedStyles.textAlign;
153
+ if (changedStyles.textDecoration !== undefined)
154
+ appliedStyles.text.textDecoration = changedStyles.textDecoration;
155
+
156
+ if (changedStyles.borderRadius !== undefined)
157
+ appliedStyles.border.borderRadius = changedStyles.borderRadius;
158
+ if (changedStyles.borderWidth !== undefined)
159
+ appliedStyles.border.borderWidth = changedStyles.borderWidth;
160
+ if (changedStyles.borderColor !== undefined)
161
+ appliedStyles.border.borderColor = changedStyles.borderColor;
162
+ if (changedStyles.borderStyle !== undefined)
163
+ appliedStyles.border.borderStyle = changedStyles.borderStyle;
164
+
165
+ if (changedStyles.paddingVertical !== undefined)
166
+ appliedStyles.spacing.paddingVertical = changedStyles.paddingVertical;
167
+ if (changedStyles.paddingHorizontal !== undefined)
168
+ appliedStyles.spacing.paddingHorizontal =
169
+ changedStyles.paddingHorizontal;
170
+ if (changedStyles.paddingTop !== undefined)
171
+ appliedStyles.spacing.paddingTop = changedStyles.paddingTop;
172
+ if (changedStyles.paddingRight !== undefined)
173
+ appliedStyles.spacing.paddingRight = changedStyles.paddingRight;
174
+ if (changedStyles.paddingBottom !== undefined)
175
+ appliedStyles.spacing.paddingBottom = changedStyles.paddingBottom;
176
+ if (changedStyles.paddingLeft !== undefined)
177
+ appliedStyles.spacing.paddingLeft = changedStyles.paddingLeft;
178
+ if (changedStyles.marginVertical !== undefined)
179
+ appliedStyles.spacing.marginVertical = changedStyles.marginVertical;
180
+ if (changedStyles.marginHorizontal !== undefined)
181
+ appliedStyles.spacing.marginHorizontal = changedStyles.marginHorizontal;
182
+ if (changedStyles.marginTop !== undefined)
183
+ appliedStyles.spacing.marginTop = changedStyles.marginTop;
184
+ if (changedStyles.marginRight !== undefined)
185
+ appliedStyles.spacing.marginRight = changedStyles.marginRight;
186
+ if (changedStyles.marginBottom !== undefined)
187
+ appliedStyles.spacing.marginBottom = changedStyles.marginBottom;
188
+ if (changedStyles.marginLeft !== undefined)
189
+ appliedStyles.spacing.marginLeft = changedStyles.marginLeft;
190
+
191
+ if (changedStyles.display !== undefined)
192
+ appliedStyles.display.display = changedStyles.display;
193
+ if (changedStyles.opacity !== undefined)
194
+ appliedStyles.display.opacity = changedStyles.opacity;
195
+ if (changedStyles.flex !== undefined)
196
+ appliedStyles.display.flex = changedStyles.flex;
197
+ if (changedStyles.flexDirection !== undefined)
198
+ appliedStyles.display.flexDirection = changedStyles.flexDirection;
199
+ if (changedStyles.justifyContent !== undefined)
200
+ appliedStyles.display.justifyContent = changedStyles.justifyContent;
201
+ if (changedStyles.alignItems !== undefined)
202
+ appliedStyles.display.alignItems = changedStyles.alignItems;
203
+
204
+ if (changedStyles.objectFit !== undefined)
205
+ appliedStyles.image.objectFit = changedStyles.objectFit;
206
+
207
+ // Remove empty categories
208
+ Object.keys(appliedStyles).forEach((key) => {
209
+ if (Object.keys(appliedStyles[key]).length === 0) {
210
+ delete appliedStyles[key];
211
+ }
212
+ });
213
+
214
+ // Send only changed style data to parent for source code update
215
+ sendToParent("INSPECTOR_STYLE_UPDATED", {
216
+ element: elementData,
217
+ styles: changedStyles,
218
+ inlineStyles,
219
+ tailwindClasses,
220
+ appliedStyles,
221
+ });
222
+
223
+ // Mark as saved
224
+ setHasChanges(false);
225
+
226
+ return true; // Success
227
+ } catch (error) {
228
+ console.error("Failed to save styles:", error);
229
+ return false; // Failure
230
+ } finally {
231
+ setIsSaving(false);
232
+ }
233
+ };
234
+
235
+ // Expose handleApply to parent via window for close confirmation
236
+ useEffect(() => {
237
+ (window as any).__styleEditorSave = hasChanges ? handleApply : null;
238
+ return () => {
239
+ (window as any).__styleEditorSave = null;
240
+ };
241
+ }, [hasChanges, styles, elementData]);
242
+
243
+ const handleReset = () => {
244
+ // Reset to original styles
245
+ setStyles(originalStylesRef);
246
+ resetStyles();
247
+ setHasChanges(false);
248
+ };
249
+
250
+ const handleResetProperty = (property: keyof StyleChanges) => {
251
+ // Reset single property to original value
252
+ const originalValue = originalStylesRef[property];
253
+ setStyles((prev) => ({ ...prev, [property]: originalValue }));
254
+ resetSingleProperty(property);
255
+
256
+ // Check if there are any remaining changes
257
+ const updatedStyles = { ...styles, [property]: originalValue };
258
+ const hasRemainingChanges = Object.keys(updatedStyles).some(
259
+ (key) =>
260
+ updatedStyles[key as keyof StyleChanges] !==
261
+ originalStylesRef[key as keyof StyleChanges]
262
+ );
263
+ setHasChanges(hasRemainingChanges);
264
+ };
265
+
266
+ return (
267
+ <div className={cssStyles.container}>
268
+ {/* Collapsible sections - scrollable */}
269
+ <div
270
+ className={cssStyles.scrollArea}
271
+ style={{
272
+ scrollbarWidth: "thin",
273
+ scrollbarColor: `${theme.borderColor} transparent`,
274
+ }}
275
+ >
276
+ {/* Layout Section - Always show */}
277
+ {shouldShowSection(element, "layout") && (
278
+ <Collapsible open={layoutOpen} onOpenChange={setLayoutOpen}>
279
+ <CollapsibleTrigger asChild>
280
+ <div
281
+ className={cssStyles.collapsibleTrigger}
282
+ style={{
283
+ backgroundColor: theme.inputBackgroundColor,
284
+ border: `1px solid ${theme.inputBorderColor}`,
285
+ color: theme.textColor,
286
+ }}
287
+ onMouseEnter={(e) => {
288
+ if (!layoutOpen) {
289
+ e.currentTarget.style.backgroundColor =
290
+ theme.secondaryButtonHoverColor ||
291
+ theme.inputBackgroundColor;
292
+ }
293
+ }}
294
+ onMouseLeave={(e) => {
295
+ if (!layoutOpen) {
296
+ e.currentTarget.style.backgroundColor = "transparent";
297
+ }
298
+ }}
299
+ >
300
+ <span>{labels.layoutSectionTitle || "Layout"}</span>
301
+ <ChevronDown
302
+ className={clsx(
303
+ cssStyles.chevron,
304
+ layoutOpen && cssStyles.chevronOpen
305
+ )}
306
+ />
307
+ </div>
308
+ </CollapsibleTrigger>
309
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
310
+ <LayoutSection
311
+ styles={styles}
312
+ onChange={handleStyleChange}
313
+ originalStyles={originalStylesRef}
314
+ onResetProperty={handleResetProperty}
315
+ />
316
+ </CollapsibleContent>
317
+ </Collapsible>
318
+ )}
319
+
320
+ {/* Display Section - Always show */}
321
+ {shouldShowSection(element, "display") && (
322
+ <Collapsible open={displayOpen} onOpenChange={setDisplayOpen}>
323
+ <CollapsibleTrigger asChild>
324
+ <div
325
+ className={cssStyles.collapsibleTrigger}
326
+ style={{
327
+ backgroundColor: theme.inputBackgroundColor,
328
+ color: theme.textColor,
329
+ border: `1px solid ${theme.inputBorderColor}`,
330
+ }}
331
+ onMouseEnter={(e) => {
332
+ if (!displayOpen) {
333
+ e.currentTarget.style.backgroundColor =
334
+ theme.secondaryButtonHoverColor ||
335
+ theme.inputBackgroundColor;
336
+ }
337
+ }}
338
+ onMouseLeave={(e) => {
339
+ if (!displayOpen) {
340
+ e.currentTarget.style.backgroundColor = "transparent";
341
+ }
342
+ }}
343
+ >
344
+ <span>{labels.displaySectionTitle || "Display"}</span>
345
+ <ChevronDown
346
+ className={clsx(
347
+ cssStyles.chevron,
348
+ displayOpen && cssStyles.chevronOpen
349
+ )}
350
+ />
351
+ </div>
352
+ </CollapsibleTrigger>
353
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
354
+ <DisplaySection
355
+ styles={styles}
356
+ onChange={handleStyleChange}
357
+ originalStyles={originalStylesRef}
358
+ onResetProperty={handleResetProperty}
359
+ />
360
+ </CollapsibleContent>
361
+ </Collapsible>
362
+ )}
363
+
364
+ {/* Image Section - Only for img elements */}
365
+ {shouldShowSection(element, "image") && (
366
+ <Collapsible open={imageOpen} onOpenChange={setImageOpen}>
367
+ <CollapsibleTrigger asChild>
368
+ <div
369
+ className={cssStyles.collapsibleTrigger}
370
+ style={{
371
+ backgroundColor: theme.inputBackgroundColor,
372
+ color: theme.textColor,
373
+ border: `1px solid ${theme.inputBorderColor}`,
374
+ }}
375
+ onMouseEnter={(e) => {
376
+ if (!imageOpen) {
377
+ e.currentTarget.style.backgroundColor =
378
+ theme.secondaryButtonHoverColor ||
379
+ theme.inputBackgroundColor;
380
+ }
381
+ }}
382
+ onMouseLeave={(e) => {
383
+ if (!imageOpen) {
384
+ e.currentTarget.style.backgroundColor = "transparent";
385
+ }
386
+ }}
387
+ >
388
+ <span>{labels.imageSectionTitle || "Image"}</span>
389
+ <ChevronDown
390
+ className={clsx(
391
+ cssStyles.chevron,
392
+ imageOpen && cssStyles.chevronOpen
393
+ )}
394
+ />
395
+ </div>
396
+ </CollapsibleTrigger>
397
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
398
+ <ImageSection
399
+ styles={styles}
400
+ onChange={handleStyleChange}
401
+ originalStyles={originalStylesRef}
402
+ onResetProperty={handleResetProperty}
403
+ />
404
+ </CollapsibleContent>
405
+ </Collapsible>
406
+ )}
407
+
408
+ {/* Text Section - Conditional */}
409
+ {shouldShowSection(element, "text") && (
410
+ <Collapsible open={textOpen} onOpenChange={setTextOpen}>
411
+ <CollapsibleTrigger asChild>
412
+ <div
413
+ className={cssStyles.collapsibleTrigger}
414
+ style={{
415
+ backgroundColor: theme.inputBackgroundColor,
416
+ color: theme.textColor,
417
+ border: `1px solid ${theme.inputBorderColor}`,
418
+ }}
419
+ onMouseEnter={(e) => {
420
+ if (!textOpen) {
421
+ e.currentTarget.style.backgroundColor =
422
+ theme.secondaryButtonHoverColor ||
423
+ theme.inputBackgroundColor;
424
+ }
425
+ }}
426
+ onMouseLeave={(e) => {
427
+ if (!textOpen) {
428
+ e.currentTarget.style.backgroundColor = "transparent";
429
+ }
430
+ }}
431
+ >
432
+ <span>{labels.textSectionTitle || "Text"}</span>
433
+ <ChevronDown
434
+ className={clsx(
435
+ cssStyles.chevron,
436
+ textOpen && cssStyles.chevronOpen
437
+ )}
438
+ />
439
+ </div>
440
+ </CollapsibleTrigger>
441
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
442
+ <TextSection
443
+ styles={styles}
444
+ onChange={handleStyleChange}
445
+ originalStyles={originalStylesRef}
446
+ onResetProperty={handleResetProperty}
447
+ />
448
+ </CollapsibleContent>
449
+ </Collapsible>
450
+ )}
451
+
452
+ {/* Border Section - Always show */}
453
+ {shouldShowSection(element, "border") && (
454
+ <Collapsible open={borderOpen} onOpenChange={setBorderOpen}>
455
+ <CollapsibleTrigger asChild>
456
+ <div
457
+ className={cssStyles.collapsibleTrigger}
458
+ style={{
459
+ backgroundColor: theme.inputBackgroundColor,
460
+ color: theme.textColor,
461
+ border: `1px solid ${theme.inputBorderColor}`,
462
+ }}
463
+ onMouseEnter={(e) => {
464
+ if (!borderOpen) {
465
+ e.currentTarget.style.backgroundColor =
466
+ theme.secondaryButtonHoverColor ||
467
+ theme.inputBackgroundColor;
468
+ }
469
+ }}
470
+ onMouseLeave={(e) => {
471
+ if (!borderOpen) {
472
+ e.currentTarget.style.backgroundColor = "transparent";
473
+ }
474
+ }}
475
+ >
476
+ <span>{labels.borderSectionTitle || "Border"}</span>
477
+ <ChevronDown
478
+ className={clsx(
479
+ cssStyles.chevron,
480
+ borderOpen && cssStyles.chevronOpen
481
+ )}
482
+ />
483
+ </div>
484
+ </CollapsibleTrigger>
485
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
486
+ <BorderSection
487
+ styles={styles}
488
+ onChange={handleStyleChange}
489
+ originalStyles={originalStylesRef}
490
+ onResetProperty={handleResetProperty}
491
+ />
492
+ </CollapsibleContent>
493
+ </Collapsible>
494
+ )}
495
+
496
+ {/* Spacing Section - Always show */}
497
+ {shouldShowSection(element, "spacing") && (
498
+ <Collapsible open={spacingOpen} onOpenChange={setSpacingOpen}>
499
+ <CollapsibleTrigger asChild>
500
+ <div
501
+ className={cssStyles.collapsibleTrigger}
502
+ style={{
503
+ backgroundColor: theme.inputBackgroundColor,
504
+ color: theme.textColor,
505
+ border: `1px solid ${theme.inputBorderColor}`,
506
+ }}
507
+ onMouseEnter={(e) => {
508
+ if (!spacingOpen) {
509
+ e.currentTarget.style.backgroundColor =
510
+ theme.secondaryButtonHoverColor ||
511
+ theme.inputBackgroundColor;
512
+ }
513
+ }}
514
+ onMouseLeave={(e) => {
515
+ if (!spacingOpen) {
516
+ e.currentTarget.style.backgroundColor = "transparent";
517
+ }
518
+ }}
519
+ >
520
+ <span>{labels.spacingSectionTitle || "Spacing"}</span>
521
+ <ChevronDown
522
+ className={clsx(
523
+ cssStyles.chevron,
524
+ spacingOpen && cssStyles.chevronOpen
525
+ )}
526
+ />
527
+ </div>
528
+ </CollapsibleTrigger>
529
+ <CollapsibleContent className={cssStyles.collapsibleContent}>
530
+ <SpacingSection
531
+ styles={styles}
532
+ onChange={handleStyleChange}
533
+ originalStyles={originalStylesRef}
534
+ onResetProperty={handleResetProperty}
535
+ />
536
+ </CollapsibleContent>
537
+ </Collapsible>
538
+ )}
539
+ </div>
540
+
541
+ {/* Action buttons - fixed at bottom */}
542
+ <div
543
+ className={cssStyles.actionsContainer}
544
+ style={{ borderColor: theme.borderColor }}
545
+ >
546
+ {/* Status indicator */}
547
+ {hasChanges && (
548
+ <div
549
+ className={cssStyles.statusIndicator}
550
+ style={{ color: theme.warningColor }}
551
+ >
552
+ <Circle className={cssStyles.statusIcon} />
553
+ <span style={{ fontFamily: "Satoshi", fontSize: "var(--text-xs)" }}>
554
+ {labels.unsavedChangesText || "Unsaved"}
555
+ </span>
556
+ </div>
557
+ )}
558
+
559
+ {/* Button group */}
560
+ <div className={cssStyles.buttonGroup}>
561
+ {/* Reset button */}
562
+ {hasChanges && (
563
+ <Button
564
+ variant="secondary"
565
+ onClick={handleReset}
566
+ disabled={isSaving}
567
+ className={cssStyles.resetButton}
568
+ style={{
569
+ backgroundColor: theme.secondaryButtonColor,
570
+ color: theme.secondaryButtonTextColor,
571
+ }}
572
+ >
573
+ {labels.resetButton || "Reset"}
574
+ </Button>
575
+ )}
576
+
577
+ {/* Save button */}
578
+ <Button
579
+ variant="default"
580
+ onClick={handleApply}
581
+ disabled={!hasChanges || isSaving}
582
+ className={cssStyles.applyButton}
583
+ style={{
584
+ backgroundColor:
585
+ hasChanges && !isSaving
586
+ ? theme.buttonColor
587
+ : theme.secondaryButtonColor,
588
+ color:
589
+ hasChanges && !isSaving
590
+ ? theme.buttonTextColor
591
+ : theme.secondaryButtonTextColor,
592
+ }}
593
+ >
594
+ {isSaving ? (
595
+ <span className={cssStyles.buttonContent}>
596
+ <span className={cssStyles.savingSpinner} />
597
+ {labels.savingText || "Saving..."}
598
+ </span>
599
+ ) : hasChanges ? (
600
+ labels.saveButton || "Save"
601
+ ) : (
602
+ <span className={cssStyles.buttonContent}>
603
+ <Check className={cssStyles.buttonIcon} />
604
+ Saved
605
+ </span>
606
+ )}
607
+ </Button>
608
+ </div>
609
+ </div>
610
+ </div>
611
+ );
612
+ }