@growgroup/visual-editor 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +52 -0
  3. package/README.md +143 -0
  4. package/dist/editor.css +2 -0
  5. package/package.json +94 -0
  6. package/src/components/DeckSlideRender.tsx +21 -0
  7. package/src/components/SaveNote.tsx +21 -0
  8. package/src/components/auth/AuthProvider.tsx +44 -0
  9. package/src/components/ui/accordion.tsx +57 -0
  10. package/src/components/ui/alert-dialog.tsx +141 -0
  11. package/src/components/ui/badge.tsx +36 -0
  12. package/src/components/ui/button.tsx +56 -0
  13. package/src/components/ui/card.tsx +76 -0
  14. package/src/components/ui/checkbox.tsx +30 -0
  15. package/src/components/ui/collapsible.tsx +11 -0
  16. package/src/components/ui/context-menu.tsx +200 -0
  17. package/src/components/ui/dialog.tsx +122 -0
  18. package/src/components/ui/dropdown-menu.tsx +201 -0
  19. package/src/components/ui/input.tsx +22 -0
  20. package/src/components/ui/label.tsx +26 -0
  21. package/src/components/ui/popover.tsx +33 -0
  22. package/src/components/ui/scroll-area.tsx +48 -0
  23. package/src/components/ui/scrubbable-label.tsx +95 -0
  24. package/src/components/ui/select.tsx +159 -0
  25. package/src/components/ui/separator.tsx +31 -0
  26. package/src/components/ui/slider.tsx +28 -0
  27. package/src/components/ui/switch.tsx +29 -0
  28. package/src/components/ui/tabs.tsx +55 -0
  29. package/src/components/ui/textarea.tsx +22 -0
  30. package/src/components/ui/tooltip.tsx +32 -0
  31. package/src/components/viewer/useDeck.ts +58 -0
  32. package/src/editor/EditorContext.tsx +632 -0
  33. package/src/editor/EditorToolbar.tsx +446 -0
  34. package/src/editor/FrontendVisualEditor.tsx +2595 -0
  35. package/src/editor/autosave.ts +34 -0
  36. package/src/editor/components/AiPromptPopover.tsx +378 -0
  37. package/src/editor/components/BreakpointGuides.tsx +323 -0
  38. package/src/editor/components/BreakpointSelector.tsx +180 -0
  39. package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
  40. package/src/editor/components/CssEditorDialog.tsx +168 -0
  41. package/src/editor/components/EditorCanvas.tsx +987 -0
  42. package/src/editor/components/EditorContextMenu.tsx +515 -0
  43. package/src/editor/components/EditorFooter.tsx +81 -0
  44. package/src/editor/components/EditorHeader.tsx +524 -0
  45. package/src/editor/components/EditorLayerPanel.tsx +1197 -0
  46. package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
  47. package/src/editor/components/EditorSidebar.tsx +226 -0
  48. package/src/editor/components/FigmaColorPicker.tsx +1577 -0
  49. package/src/editor/components/HtmlEditorDialog.tsx +71 -0
  50. package/src/editor/components/HtmlImportDialog.tsx +346 -0
  51. package/src/editor/components/JsEditorDialog.tsx +175 -0
  52. package/src/editor/components/LayerTreeItem.tsx +494 -0
  53. package/src/editor/components/MasterComponentEditor.tsx +846 -0
  54. package/src/editor/components/MediaLibraryDialog.tsx +447 -0
  55. package/src/editor/components/PageSettingsDialog.tsx +776 -0
  56. package/src/editor/components/VariablePicker.tsx +355 -0
  57. package/src/editor/components/VariablesDialog.tsx +564 -0
  58. package/src/editor/components/VariantEditor.tsx +687 -0
  59. package/src/editor/components/VariantMatrix.tsx +507 -0
  60. package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
  61. package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
  62. package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
  63. package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
  64. package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
  65. package/src/editor/components/component-panel/VariantList.tsx +260 -0
  66. package/src/editor/components/component-panel/index.ts +12 -0
  67. package/src/editor/components/index.ts +48 -0
  68. package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
  69. package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
  70. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
  71. package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
  72. package/src/editor/components/multi-page/PageLabel.tsx +34 -0
  73. package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
  74. package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
  75. package/src/editor/components/multi-page/index.ts +7 -0
  76. package/src/editor/components/ppt/PptChrome.tsx +2508 -0
  77. package/src/editor/components/ppt/PptComments.tsx +589 -0
  78. package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
  79. package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
  80. package/src/editor/components/ppt/PptNotes.tsx +246 -0
  81. package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
  82. package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
  83. package/src/editor/components/property-panel/BorderSection.tsx +104 -0
  84. package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
  85. package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
  86. package/src/editor/components/property-panel/EffectSection.tsx +157 -0
  87. package/src/editor/components/property-panel/FillSection.tsx +69 -0
  88. package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
  89. package/src/editor/components/property-panel/ImageSection.tsx +206 -0
  90. package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
  91. package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
  92. package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
  93. package/src/editor/components/property-panel/LinkSection.tsx +103 -0
  94. package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
  95. package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
  96. package/src/editor/components/property-panel/TypographySection.tsx +230 -0
  97. package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
  98. package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
  99. package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
  100. package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
  101. package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
  102. package/src/editor/components/property-panel/index.ts +24 -0
  103. package/src/editor/components/property-panel/unit-utils.ts +261 -0
  104. package/src/editor/components/shell/EditorTopBar.tsx +392 -0
  105. package/src/editor/components/shell/LeftPanel.tsx +47 -0
  106. package/src/editor/components/shell/PagesPanel.tsx +118 -0
  107. package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
  108. package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
  109. package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
  110. package/src/editor/components/variables-panel/index.ts +7 -0
  111. package/src/editor/constants.ts +468 -0
  112. package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
  113. package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
  114. package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
  115. package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
  116. package/src/editor/contexts/EditorRefsContext.tsx +204 -0
  117. package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
  118. package/src/editor/contexts/EditorToolContext.tsx +58 -0
  119. package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
  120. package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
  121. package/src/editor/contexts/EditorViewContext.tsx +101 -0
  122. package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
  123. package/src/editor/contexts/index.ts +17 -0
  124. package/src/editor/editor-skin.css +122 -0
  125. package/src/editor/hooks/index.ts +38 -0
  126. package/src/editor/hooks/useAiReplace.ts +397 -0
  127. package/src/editor/hooks/useAltMeasure.ts +67 -0
  128. package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
  129. package/src/editor/hooks/useCanvasControls.ts +1222 -0
  130. package/src/editor/hooks/useComponentEditMode.ts +658 -0
  131. package/src/editor/hooks/useComponentInstances.ts +515 -0
  132. package/src/editor/hooks/useContextMenuHandler.ts +157 -0
  133. package/src/editor/hooks/useCoordinateTransform.ts +207 -0
  134. package/src/editor/hooks/useDragResize.ts +1317 -0
  135. package/src/editor/hooks/useDrawingMode.ts +472 -0
  136. package/src/editor/hooks/useEditorColors.ts +144 -0
  137. package/src/editor/hooks/useEditorMessages.ts +108 -0
  138. package/src/editor/hooks/useElementActions.ts +1754 -0
  139. package/src/editor/hooks/useElementSelection.ts +1573 -0
  140. package/src/editor/hooks/useFocusManagement.ts +249 -0
  141. package/src/editor/hooks/useGoogleFonts.ts +305 -0
  142. package/src/editor/hooks/useIframeInitializer.ts +584 -0
  143. package/src/editor/hooks/useIframeSetup.ts +360 -0
  144. package/src/editor/hooks/useImageUpload.ts +270 -0
  145. package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
  146. package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
  147. package/src/editor/hooks/useMarqueeSelection.ts +388 -0
  148. package/src/editor/hooks/useMediaLibrary.ts +129 -0
  149. package/src/editor/hooks/usePageSettingsManager.ts +739 -0
  150. package/src/editor/hooks/useResizablePanel.ts +163 -0
  151. package/src/editor/hooks/useRichPaste.ts +255 -0
  152. package/src/editor/hooks/useTouchGestures.ts +197 -0
  153. package/src/editor/index.ts +27 -0
  154. package/src/editor/types/page-settings.ts +513 -0
  155. package/src/editor/types.ts +382 -0
  156. package/src/editor/utils/align-elements.ts +169 -0
  157. package/src/editor/utils/api-json.ts +21 -0
  158. package/src/editor/utils/aspect-lock.ts +50 -0
  159. package/src/editor/utils/component-renderer.ts +702 -0
  160. package/src/editor/utils/component-sync.ts +845 -0
  161. package/src/editor/utils/crop-mode.ts +265 -0
  162. package/src/editor/utils/dom-utils.ts +2238 -0
  163. package/src/editor/utils/element-effects.ts +218 -0
  164. package/src/editor/utils/eyedropper.ts +35 -0
  165. package/src/editor/utils/figma-export.ts +560 -0
  166. package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
  167. package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
  168. package/src/editor/utils/figma-paste.ts +2262 -0
  169. package/src/editor/utils/flex-reorder.ts +231 -0
  170. package/src/editor/utils/flex-utils.ts +1230 -0
  171. package/src/editor/utils/geometry.ts +232 -0
  172. package/src/editor/utils/grid-layout.ts +210 -0
  173. package/src/editor/utils/html-utils.ts +470 -0
  174. package/src/editor/utils/index.ts +11 -0
  175. package/src/editor/utils/ink-style.ts +57 -0
  176. package/src/editor/utils/inline-format.ts +236 -0
  177. package/src/editor/utils/measure-distance.ts +120 -0
  178. package/src/editor/utils/override-apply.ts +526 -0
  179. package/src/editor/utils/override-detection.ts +613 -0
  180. package/src/editor/utils/paste-processors.ts +609 -0
  181. package/src/editor/utils/paste-sanitizer.ts +244 -0
  182. package/src/editor/utils/restore-flow.ts +188 -0
  183. package/src/editor/utils/shape-library.ts +171 -0
  184. package/src/editor/utils/slide-root.ts +29 -0
  185. package/src/editor/utils/smart-guides.ts +218 -0
  186. package/src/editor/utils/style-utils.ts +685 -0
  187. package/src/editor/utils/table-edit.ts +151 -0
  188. package/src/editor/utils/tailwind-mappings.ts +778 -0
  189. package/src/editor/utils/tailwind-utils.ts +804 -0
  190. package/src/editor/utils/text-highlight.ts +216 -0
  191. package/src/editor/utils/theme-to-variables.ts +341 -0
  192. package/src/editor/utils/variant-resolver.ts +456 -0
  193. package/src/editor/utils/viewport-utils.ts +295 -0
  194. package/src/hooks/useEditorHistory.ts +195 -0
  195. package/src/hooks/useEditorShortcuts.ts +260 -0
  196. package/src/index.ts +40 -0
  197. package/src/io.ts +134 -0
  198. package/src/lib/agent/slide-agent/types.ts +547 -0
  199. package/src/lib/agent/website-agent/types.ts +805 -0
  200. package/src/lib/api/auth-fetch.ts +58 -0
  201. package/src/lib/deck.ts +69 -0
  202. package/src/lib/export.ts +47 -0
  203. package/src/lib/firebase/config.ts +29 -0
  204. package/src/lib/firebase/css-variables.ts +378 -0
  205. package/src/lib/firebase/editor-components.ts +677 -0
  206. package/src/lib/firebase/storage.ts +469 -0
  207. package/src/lib/utils.ts +6 -0
  208. package/src/styles/editor.css +52 -0
  209. package/src/styles/skin.css +122 -0
  210. package/src/types/css-variables.ts +645 -0
  211. package/src/types/editor-components.ts +944 -0
  212. package/src/types/editor.ts +420 -0
  213. package/src/types/page-master.ts +197 -0
  214. package/src/types/page.ts +419 -0
  215. package/src/types/slide.ts +402 -0
  216. package/src/types/website-theme.ts +788 -0
  217. package/src/types/website.ts +374 -0
  218. package/src/vendor/firebase-firestore.ts +13 -0
  219. package/src/vendor/firebase-functions.ts +17 -0
@@ -0,0 +1,426 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * ComponentPropertyEditor - Editor for individual component properties
5
+ *
6
+ * Allows adding/editing ComponentProperty definitions for master components.
7
+ * Supports boolean, text, and instanceSwap property types.
8
+ */
9
+
10
+ import React, { useState, useCallback, useMemo } from 'react';
11
+ import { Button } from '../../components/ui/button';
12
+ import { Input } from '../../components/ui/input';
13
+ import { Label } from '../../components/ui/label';
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from '../../components/ui/select';
21
+ import { Checkbox } from '../../components/ui/checkbox';
22
+ import {
23
+ X,
24
+ Check,
25
+ ToggleLeft,
26
+ Type,
27
+ Repeat,
28
+ Plus,
29
+ Trash2,
30
+ } from 'lucide-react';
31
+ import { cn } from '../../lib/utils';
32
+ import type {
33
+ ComponentProperty,
34
+ ComponentPropertyType,
35
+ } from '../../types/editor-components';
36
+
37
+ // ============================================
38
+ // Types
39
+ // ============================================
40
+
41
+ interface AvailableElement {
42
+ id: string;
43
+ name: string;
44
+ }
45
+
46
+ interface ComponentPropertyEditorProps {
47
+ /** Property to edit (null for creating new) */
48
+ property: ComponentProperty | null;
49
+ /** Available elements that can be targeted by this property */
50
+ availableElements: AvailableElement[];
51
+ /** Available components for instance swap (optional) */
52
+ availableComponents?: { id: string; name: string }[];
53
+ /** Callback when property is saved */
54
+ onSave: (property: ComponentProperty) => void;
55
+ /** Callback when editing is cancelled */
56
+ onCancel: () => void;
57
+ }
58
+
59
+ // ============================================
60
+ // Constants
61
+ // ============================================
62
+
63
+ const PROPERTY_TYPE_OPTIONS: {
64
+ value: ComponentPropertyType;
65
+ label: string;
66
+ description: string;
67
+ icon: React.ReactNode;
68
+ }[] = [
69
+ {
70
+ value: 'boolean',
71
+ label: 'Boolean',
72
+ description: 'Toggle visibility of elements',
73
+ icon: <ToggleLeft className="h-4 w-4" />,
74
+ },
75
+ {
76
+ value: 'text',
77
+ label: 'Text',
78
+ description: 'Editable text content',
79
+ icon: <Type className="h-4 w-4" />,
80
+ },
81
+ {
82
+ value: 'instanceSwap',
83
+ label: 'Instance Swap',
84
+ description: 'Replace nested component',
85
+ icon: <Repeat className="h-4 w-4" />,
86
+ },
87
+ ];
88
+
89
+ // ============================================
90
+ // Helper Functions
91
+ // ============================================
92
+
93
+ function generatePropertyId(): string {
94
+ return `prop-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
95
+ }
96
+
97
+ // ============================================
98
+ // Component
99
+ // ============================================
100
+
101
+ export function ComponentPropertyEditor({
102
+ property,
103
+ availableElements,
104
+ availableComponents = [],
105
+ onSave,
106
+ onCancel,
107
+ }: ComponentPropertyEditorProps) {
108
+ // Form state
109
+ const [name, setName] = useState(property?.name ?? '');
110
+ const [type, setType] = useState<ComponentPropertyType>(property?.type ?? 'boolean');
111
+ const [description, setDescription] = useState(property?.description ?? '');
112
+ const [group, setGroup] = useState(property?.group ?? '');
113
+ const [required, setRequired] = useState(property?.required ?? false);
114
+ const [targetElementIds, setTargetElementIds] = useState<string[]>(
115
+ property?.targetElementIds ?? []
116
+ );
117
+ const [allowedComponentIds, setAllowedComponentIds] = useState<string[]>(
118
+ property?.allowedComponentIds ?? []
119
+ );
120
+ const [defaultValue, setDefaultValue] = useState<string | boolean>(
121
+ property?.defaultValue ?? (type === 'boolean' ? true : '')
122
+ );
123
+
124
+ const isNew = !property;
125
+
126
+ // Update default value when type changes
127
+ const handleTypeChange = useCallback((newType: ComponentPropertyType) => {
128
+ setType(newType);
129
+ // Reset default value based on type
130
+ if (newType === 'boolean') {
131
+ setDefaultValue(true);
132
+ } else {
133
+ setDefaultValue('');
134
+ }
135
+ // Clear instance swap specific fields if not instanceSwap
136
+ if (newType !== 'instanceSwap') {
137
+ setAllowedComponentIds([]);
138
+ }
139
+ }, []);
140
+
141
+ // Toggle target element selection
142
+ const toggleTargetElement = useCallback((elementId: string) => {
143
+ setTargetElementIds((prev) =>
144
+ prev.includes(elementId)
145
+ ? prev.filter((id) => id !== elementId)
146
+ : [...prev, elementId]
147
+ );
148
+ }, []);
149
+
150
+ // Toggle allowed component selection
151
+ const toggleAllowedComponent = useCallback((componentId: string) => {
152
+ setAllowedComponentIds((prev) =>
153
+ prev.includes(componentId)
154
+ ? prev.filter((id) => id !== componentId)
155
+ : [...prev, componentId]
156
+ );
157
+ }, []);
158
+
159
+ // Validation
160
+ const isValid = useMemo(() => {
161
+ if (!name.trim()) return false;
162
+ if (targetElementIds.length === 0) return false;
163
+ if (type === 'instanceSwap' && allowedComponentIds.length === 0) return false;
164
+ return true;
165
+ }, [name, targetElementIds, type, allowedComponentIds]);
166
+
167
+ // Handle save
168
+ const handleSave = useCallback(() => {
169
+ if (!isValid) return;
170
+
171
+ const newProperty: ComponentProperty = {
172
+ id: property?.id ?? generatePropertyId(),
173
+ name: name.trim(),
174
+ type,
175
+ defaultValue,
176
+ targetElementIds,
177
+ description: description.trim() || undefined,
178
+ group: group.trim() || undefined,
179
+ required,
180
+ ...(type === 'instanceSwap' && { allowedComponentIds }),
181
+ };
182
+
183
+ onSave(newProperty);
184
+ }, [
185
+ isValid,
186
+ property,
187
+ name,
188
+ type,
189
+ defaultValue,
190
+ targetElementIds,
191
+ description,
192
+ group,
193
+ required,
194
+ allowedComponentIds,
195
+ onSave,
196
+ ]);
197
+
198
+ return (
199
+ <div className="space-y-4 p-4 bg-[#1e1e1e] rounded-lg border border-[#444444]">
200
+ {/* Header */}
201
+ <div className="flex items-center justify-between">
202
+ <h4 className="text-sm font-medium text-gray-200">
203
+ {isNew ? 'Add Property' : 'Edit Property'}
204
+ </h4>
205
+ <Button
206
+ type="button"
207
+ variant="ghost"
208
+ size="icon"
209
+ className="h-6 w-6 text-gray-400 hover:text-gray-200"
210
+ onClick={onCancel}
211
+ aria-label="Close"
212
+ >
213
+ <X className="h-3.5 w-3.5" />
214
+ </Button>
215
+ </div>
216
+
217
+ {/* Property Type */}
218
+ <div className="grid gap-1.5">
219
+ <Label className="text-xs text-gray-400">Type</Label>
220
+ <Select value={type} onValueChange={(v) => handleTypeChange(v as ComponentPropertyType)}>
221
+ <SelectTrigger className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200">
222
+ <SelectValue />
223
+ </SelectTrigger>
224
+ <SelectContent className="bg-[#2c2c2c] border-[#444444]">
225
+ {PROPERTY_TYPE_OPTIONS.map((option) => (
226
+ <SelectItem
227
+ key={option.value}
228
+ value={option.value}
229
+ className="text-gray-200 focus:bg-[#444444] focus:text-white"
230
+ >
231
+ <div className="flex items-center gap-2">
232
+ {option.icon}
233
+ <div>
234
+ <span>{option.label}</span>
235
+ <span className="text-xs text-gray-500 ml-2">
236
+ - {option.description}
237
+ </span>
238
+ </div>
239
+ </div>
240
+ </SelectItem>
241
+ ))}
242
+ </SelectContent>
243
+ </Select>
244
+ </div>
245
+
246
+ {/* Property Name */}
247
+ <div className="grid gap-1.5">
248
+ <Label className="text-xs text-gray-400">Name</Label>
249
+ <Input
250
+ value={name}
251
+ onChange={(e) => setName(e.target.value)}
252
+ placeholder="Property name..."
253
+ className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
254
+ />
255
+ </div>
256
+
257
+ {/* Description */}
258
+ <div className="grid gap-1.5">
259
+ <Label className="text-xs text-gray-400">Description (optional)</Label>
260
+ <Input
261
+ value={description}
262
+ onChange={(e) => setDescription(e.target.value)}
263
+ placeholder="What this property controls..."
264
+ className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
265
+ />
266
+ </div>
267
+
268
+ {/* Group */}
269
+ <div className="grid gap-1.5">
270
+ <Label className="text-xs text-gray-400">Group (optional)</Label>
271
+ <Input
272
+ value={group}
273
+ onChange={(e) => setGroup(e.target.value)}
274
+ placeholder="Property group..."
275
+ className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
276
+ />
277
+ </div>
278
+
279
+ {/* Default Value */}
280
+ <div className="grid gap-1.5">
281
+ <Label className="text-xs text-gray-400">Default Value</Label>
282
+ {type === 'boolean' ? (
283
+ <div className="flex items-center gap-2">
284
+ <Checkbox
285
+ id="default-value-checkbox"
286
+ checked={defaultValue as boolean}
287
+ onCheckedChange={(checked) => setDefaultValue(checked === true)}
288
+ className="border-[#444444] data-[state=checked]:bg-[#0d99ff]"
289
+ />
290
+ <label
291
+ htmlFor="default-value-checkbox"
292
+ className="text-sm text-gray-300 cursor-pointer"
293
+ >
294
+ {defaultValue ? 'Visible (true)' : 'Hidden (false)'}
295
+ </label>
296
+ </div>
297
+ ) : (
298
+ <Input
299
+ value={defaultValue as string}
300
+ onChange={(e) => setDefaultValue(e.target.value)}
301
+ placeholder="Default value..."
302
+ className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
303
+ />
304
+ )}
305
+ </div>
306
+
307
+ {/* Required */}
308
+ <div className="flex items-center gap-2">
309
+ <Checkbox
310
+ id="required-checkbox"
311
+ checked={required}
312
+ onCheckedChange={(checked) => setRequired(checked === true)}
313
+ className="border-[#444444] data-[state=checked]:bg-[#0d99ff]"
314
+ />
315
+ <label
316
+ htmlFor="required-checkbox"
317
+ className="text-sm text-gray-300 cursor-pointer"
318
+ >
319
+ Required property
320
+ </label>
321
+ </div>
322
+
323
+ {/* Target Elements */}
324
+ <div className="grid gap-1.5">
325
+ <Label className="text-xs text-gray-400">
326
+ Target Elements
327
+ {targetElementIds.length > 0 && (
328
+ <span className="ml-1 text-[#4fb8ff]">({targetElementIds.length} selected)</span>
329
+ )}
330
+ </Label>
331
+ <div className="max-h-32 overflow-y-auto bg-[#2c2c2c] border border-[#444444] rounded-md p-2 space-y-1">
332
+ {availableElements.length === 0 ? (
333
+ <p className="text-xs text-gray-500 py-2 text-center">
334
+ No elements available
335
+ </p>
336
+ ) : (
337
+ availableElements.map((element) => (
338
+ <div
339
+ key={element.id}
340
+ className={cn(
341
+ 'flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors',
342
+ targetElementIds.includes(element.id)
343
+ ? 'bg-[#0d99ff]/20 border border-blue-600/40'
344
+ : 'hover:bg-[#444444]'
345
+ )}
346
+ onClick={() => toggleTargetElement(element.id)}
347
+ >
348
+ <Checkbox
349
+ checked={targetElementIds.includes(element.id)}
350
+ onCheckedChange={() => toggleTargetElement(element.id)}
351
+ className="border-[#4a4a4a] data-[state=checked]:bg-[#0d99ff]"
352
+ />
353
+ <span className="text-xs text-gray-300 truncate">{element.name}</span>
354
+ <span className="text-[10px] text-gray-500 ml-auto truncate max-w-20">
355
+ {element.id}
356
+ </span>
357
+ </div>
358
+ ))
359
+ )}
360
+ </div>
361
+ </div>
362
+
363
+ {/* Allowed Components (for Instance Swap) */}
364
+ {type === 'instanceSwap' && (
365
+ <div className="grid gap-1.5">
366
+ <Label className="text-xs text-gray-400">
367
+ Allowed Components
368
+ {allowedComponentIds.length > 0 && (
369
+ <span className="ml-1 text-[#4fb8ff]">({allowedComponentIds.length} selected)</span>
370
+ )}
371
+ </Label>
372
+ <div className="max-h-32 overflow-y-auto bg-[#2c2c2c] border border-[#444444] rounded-md p-2 space-y-1">
373
+ {availableComponents.length === 0 ? (
374
+ <p className="text-xs text-gray-500 py-2 text-center">
375
+ No components available for swap
376
+ </p>
377
+ ) : (
378
+ availableComponents.map((component) => (
379
+ <div
380
+ key={component.id}
381
+ className={cn(
382
+ 'flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors',
383
+ allowedComponentIds.includes(component.id)
384
+ ? 'bg-[#0d99ff]/20 border border-blue-600/40'
385
+ : 'hover:bg-[#444444]'
386
+ )}
387
+ onClick={() => toggleAllowedComponent(component.id)}
388
+ >
389
+ <Checkbox
390
+ checked={allowedComponentIds.includes(component.id)}
391
+ onCheckedChange={() => toggleAllowedComponent(component.id)}
392
+ className="border-[#4a4a4a] data-[state=checked]:bg-[#0d99ff]"
393
+ />
394
+ <span className="text-xs text-gray-300 truncate">{component.name}</span>
395
+ </div>
396
+ ))
397
+ )}
398
+ </div>
399
+ </div>
400
+ )}
401
+
402
+ {/* Actions */}
403
+ <div className="flex items-center justify-end gap-2 pt-2 border-t border-[#444444]">
404
+ <Button
405
+ variant="ghost"
406
+ size="sm"
407
+ onClick={onCancel}
408
+ className="text-gray-400 hover:text-gray-200"
409
+ >
410
+ Cancel
411
+ </Button>
412
+ <Button
413
+ size="sm"
414
+ onClick={handleSave}
415
+ disabled={!isValid}
416
+ className="bg-[#0d99ff] hover:bg-[#0c8ce9] text-white"
417
+ >
418
+ <Check className="h-3.5 w-3.5 mr-1" />
419
+ {isNew ? 'Add' : 'Update'}
420
+ </Button>
421
+ </div>
422
+ </div>
423
+ );
424
+ }
425
+
426
+ export default ComponentPropertyEditor;
@@ -0,0 +1,168 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CSSエディタダイアログ
5
+ * インポートされたCSSを編集するためのダイアログ
6
+ */
7
+
8
+ import { useState, useCallback, useEffect } from 'react';
9
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../components/ui/dialog';
10
+ import { Button } from '../../components/ui/button';
11
+ import { Textarea } from '../../components/ui/textarea';
12
+ import { Code, Trash2, RefreshCw } from 'lucide-react';
13
+
14
+ export interface CssEditorDialogProps {
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ initialCss: string;
18
+ onSave: (css: string) => void;
19
+ onClear?: () => void;
20
+ }
21
+
22
+ export function CssEditorDialog({
23
+ isOpen,
24
+ onClose,
25
+ initialCss,
26
+ onSave,
27
+ onClear,
28
+ }: CssEditorDialogProps) {
29
+ const [css, setCss] = useState(initialCss);
30
+ const [hasChanges, setHasChanges] = useState(false);
31
+
32
+ // ダイアログが開かれたときに初期値をセット
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ setCss(initialCss);
36
+ setHasChanges(false);
37
+ }
38
+ }, [isOpen, initialCss]);
39
+
40
+ // CSS変更ハンドラ
41
+ const handleCssChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
42
+ setCss(e.target.value);
43
+ setHasChanges(e.target.value !== initialCss);
44
+ }, [initialCss]);
45
+
46
+ // 保存ハンドラ
47
+ const handleSave = useCallback(() => {
48
+ onSave(css);
49
+ onClose();
50
+ }, [css, onSave, onClose]);
51
+
52
+ // クリアハンドラ
53
+ const handleClear = useCallback(() => {
54
+ if (onClear) {
55
+ onClear();
56
+ onClose();
57
+ }
58
+ }, [onClear, onClose]);
59
+
60
+ // リセットハンドラ
61
+ const handleReset = useCallback(() => {
62
+ setCss(initialCss);
63
+ setHasChanges(false);
64
+ }, [initialCss]);
65
+
66
+ // CSSの行数を計算
67
+ const lineCount = css.split('\n').length;
68
+
69
+ return (
70
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
71
+ <DialogContent className="sm:max-w-[900px] w-[90vw] h-[90vh] max-h-[90vh] bg-[#2c2c2c] border-[#444444] text-white flex flex-col">
72
+ <DialogHeader className="flex-shrink-0">
73
+ <DialogTitle className="text-white flex items-center gap-2">
74
+ <Code className="w-5 h-5" />
75
+ インポートCSS編集
76
+ </DialogTitle>
77
+ </DialogHeader>
78
+
79
+ <div className="flex-1 min-h-0 flex flex-col gap-3 overflow-hidden">
80
+ {/* 情報 */}
81
+ <div className="text-xs text-gray-400 flex items-center justify-between flex-shrink-0">
82
+ <span>
83
+ インポート時に抽出されたCSSを編集できます。
84
+ 変更はリアルタイムでプレビューに反映されます。
85
+ </span>
86
+ <span className="text-gray-500">
87
+ {lineCount}行 / {css.length}文字
88
+ </span>
89
+ </div>
90
+
91
+ {/* CSSエディタ */}
92
+ <div className="flex-1 min-h-0 overflow-hidden">
93
+ <Textarea
94
+ value={css}
95
+ onChange={handleCssChange}
96
+ className="w-full h-full bg-[#1e1e1e] border-[#444444] text-white font-mono text-sm resize-none"
97
+ placeholder={`.class-name {
98
+ property: value;
99
+ }
100
+
101
+ /* ホバー状態 */
102
+ .button:hover {
103
+ background-color: #0066ff;
104
+ }
105
+
106
+ /* メディアクエリ */
107
+ @media (max-width: 768px) {
108
+ .container {
109
+ padding: 1rem;
110
+ }
111
+ }`}
112
+ />
113
+ </div>
114
+
115
+ {/* CSSがない場合のヒント */}
116
+ {!initialCss && !css && (
117
+ <div className="text-xs text-gray-500 p-3 bg-[#1e1e1e] rounded border border-[#444444] flex-shrink-0">
118
+ 💡 ヒント: URLからHTMLをインポートすると、ページのCSSも自動的に抽出されます。
119
+ 手動でCSSを追加することもできます。
120
+ </div>
121
+ )}
122
+ </div>
123
+
124
+ <DialogFooter className="flex items-center justify-between sm:justify-between gap-2 flex-shrink-0">
125
+ <div className="flex items-center gap-2">
126
+ {onClear && initialCss && (
127
+ <Button
128
+ variant="ghost"
129
+ onClick={handleClear}
130
+ className="text-red-400 hover:text-red-300 hover:bg-red-500/20"
131
+ >
132
+ <Trash2 className="w-4 h-4 mr-2" />
133
+ CSSを削除
134
+ </Button>
135
+ )}
136
+ </div>
137
+ <div className="flex items-center gap-2">
138
+ {hasChanges && (
139
+ <Button
140
+ variant="ghost"
141
+ onClick={handleReset}
142
+ className="text-gray-400 hover:text-white"
143
+ >
144
+ <RefreshCw className="w-4 h-4 mr-2" />
145
+ リセット
146
+ </Button>
147
+ )}
148
+ <Button
149
+ variant="ghost"
150
+ onClick={onClose}
151
+ className="text-gray-400 hover:text-white"
152
+ >
153
+ キャンセル
154
+ </Button>
155
+ <Button
156
+ onClick={handleSave}
157
+ className="bg-[#0d99ff] hover:bg-[#0c8ce9]"
158
+ >
159
+ 適用
160
+ </Button>
161
+ </div>
162
+ </DialogFooter>
163
+ </DialogContent>
164
+ </Dialog>
165
+ );
166
+ }
167
+
168
+ export default CssEditorDialog;