@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,413 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * InstanceOverrideSection
5
+ *
6
+ * Property panel section for managing component instance overrides.
7
+ * Displays current variant, list of overridden properties, and actions
8
+ * to reset overrides, go to main component, or detach instance.
9
+ */
10
+
11
+ import { useMemo, useCallback } from 'react';
12
+ import { Button } from '../../../components/ui/button';
13
+ import { Label } from '../../../components/ui/label';
14
+ import { Badge } from '../../../components/ui/badge';
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from '../../../components/ui/select';
22
+ import {
23
+ Collapsible,
24
+ CollapsibleContent,
25
+ CollapsibleTrigger,
26
+ } from '../../../components/ui/collapsible';
27
+ import {
28
+ AlertDialog,
29
+ AlertDialogAction,
30
+ AlertDialogCancel,
31
+ AlertDialogContent,
32
+ AlertDialogDescription,
33
+ AlertDialogFooter,
34
+ AlertDialogHeader,
35
+ AlertDialogTitle,
36
+ AlertDialogTrigger,
37
+ } from '../../../components/ui/alert-dialog';
38
+ import {
39
+ Tooltip,
40
+ TooltipContent,
41
+ TooltipProvider,
42
+ TooltipTrigger,
43
+ } from '../../../components/ui/tooltip';
44
+ import {
45
+ Component,
46
+ ChevronRight,
47
+ RotateCcw,
48
+ ExternalLink,
49
+ Unlink,
50
+ Type,
51
+ Palette,
52
+ Square,
53
+ Eye,
54
+ Image,
55
+ Code,
56
+ Box,
57
+ RefreshCw,
58
+ } from 'lucide-react';
59
+ import type {
60
+ ComponentInstance,
61
+ MasterComponent,
62
+ ComponentOverride,
63
+ OverrideType,
64
+ ComponentVariant,
65
+ } from '../../../types/editor-components';
66
+ import { getOverrideTypeDisplayName } from '../../utils/override-apply';
67
+ import { findElementById } from '../../utils/override-detection';
68
+
69
+ interface InstanceOverrideSectionProps {
70
+ /** The component instance being edited */
71
+ instance: ComponentInstance;
72
+ /** The master component this instance is based on */
73
+ master: MasterComponent;
74
+ /** Whether this section is open */
75
+ open: boolean;
76
+ /** Callback when section open state changes */
77
+ onOpenChange: (open: boolean) => void;
78
+ /** Callback to reset a specific override */
79
+ onResetOverride: (overrideId: string) => void;
80
+ /** Callback to reset all overrides */
81
+ onResetAll: () => void;
82
+ /** Callback to change the variant */
83
+ onVariantChange: (variantId: string) => void;
84
+ /** Callback to navigate to the main component */
85
+ onGoToMainComponent: () => void;
86
+ /** Callback to detach the instance from its master */
87
+ onDetachInstance: () => void;
88
+ }
89
+
90
+ /**
91
+ * Get icon for override type
92
+ */
93
+ function getOverrideIcon(type: OverrideType): React.ReactNode {
94
+ const iconClass = 'w-3 h-3';
95
+ switch (type) {
96
+ case 'text':
97
+ return <Type className={iconClass} />;
98
+ case 'fill':
99
+ return <Palette className={iconClass} />;
100
+ case 'stroke':
101
+ return <Square className={iconClass} />;
102
+ case 'visibility':
103
+ return <Eye className={iconClass} />;
104
+ case 'image':
105
+ return <Image className={iconClass} />;
106
+ case 'style':
107
+ case 'attribute':
108
+ return <Code className={iconClass} />;
109
+ case 'children':
110
+ case 'instanceSwap':
111
+ return <Box className={iconClass} />;
112
+ default:
113
+ return <Code className={iconClass} />;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Format override value for display
119
+ */
120
+ function formatOverrideValue(override: ComponentOverride): string {
121
+ const { type, value } = override;
122
+
123
+ if (typeof value === 'boolean') {
124
+ return value ? '表示' : '非表示';
125
+ }
126
+
127
+ if (typeof value === 'string') {
128
+ // Truncate long strings
129
+ if (value.length > 20) {
130
+ return value.substring(0, 20) + '...';
131
+ }
132
+ return value;
133
+ }
134
+
135
+ if (typeof value === 'object' && value !== null) {
136
+ if ('url' in value) {
137
+ const url = (value as { url: string }).url;
138
+ const filename = url.split('/').pop() || url;
139
+ return filename.length > 15 ? filename.substring(0, 15) + '...' : filename;
140
+ }
141
+ return 'カスタム値';
142
+ }
143
+
144
+ return String(value);
145
+ }
146
+
147
+ /**
148
+ * Get element display name from master component
149
+ */
150
+ function getElementDisplayName(
151
+ master: MasterComponent,
152
+ elementId: string,
153
+ variantId: string
154
+ ): string {
155
+ const variant = master.variants.find((v) => v.id === variantId);
156
+ if (!variant) return elementId;
157
+
158
+ const element = findElementById(variant.rootElement, elementId);
159
+ if (!element) return elementId;
160
+
161
+ return element.displayName || `${element.tagName}#${element.id.substring(0, 8)}`;
162
+ }
163
+
164
+ export function InstanceOverrideSection({
165
+ instance,
166
+ master,
167
+ open,
168
+ onOpenChange,
169
+ onResetOverride,
170
+ onResetAll,
171
+ onVariantChange,
172
+ onGoToMainComponent,
173
+ onDetachInstance,
174
+ }: InstanceOverrideSectionProps) {
175
+ // Get current variant
176
+ const currentVariant = useMemo<ComponentVariant | undefined>(() => {
177
+ return master.variants.find((v) => v.id === instance.variantId);
178
+ }, [master.variants, instance.variantId]);
179
+
180
+ // Group overrides by element
181
+ const groupedOverrides = useMemo(() => {
182
+ const groups = new Map<string, ComponentOverride[]>();
183
+
184
+ for (const override of instance.overrides) {
185
+ // Both targetElementId and elementPath are strings in our type definition
186
+ const elementId = override.targetElementId || override.elementPath || 'root';
187
+
188
+ if (!groups.has(elementId)) {
189
+ groups.set(elementId, []);
190
+ }
191
+ groups.get(elementId)!.push(override);
192
+ }
193
+
194
+ return groups;
195
+ }, [instance.overrides]);
196
+
197
+ // Handle variant change
198
+ const handleVariantChange = useCallback(
199
+ (variantId: string) => {
200
+ onVariantChange(variantId);
201
+ },
202
+ [onVariantChange]
203
+ );
204
+
205
+ const hasOverrides = instance.overrides.length > 0;
206
+
207
+ return (
208
+ <Collapsible open={open} onOpenChange={onOpenChange}>
209
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-xs font-medium text-gray-300 hover:text-white border-t border-[#444444]">
210
+ <span className="flex items-center gap-2">
211
+ <Component className="w-3.5 h-3.5" />
212
+ コンポーネントインスタンス
213
+ {hasOverrides && (
214
+ <Badge
215
+ variant="secondary"
216
+ className="h-4 px-1.5 text-[9px] bg-purple-600/30 text-purple-300 border-purple-500/30"
217
+ >
218
+ {instance.overrides.length}
219
+ </Badge>
220
+ )}
221
+ </span>
222
+ <ChevronRight
223
+ className={`w-3.5 h-3.5 transition-transform ${open ? 'rotate-90' : ''}`}
224
+ />
225
+ </CollapsibleTrigger>
226
+
227
+ <CollapsibleContent className="space-y-3 pb-3">
228
+ {/* Component Info */}
229
+ <div className="px-1">
230
+ <Label className="text-[10px] text-gray-500">メインコンポーネント</Label>
231
+ <div className="flex items-center gap-2 mt-1">
232
+ <span className="text-xs text-white truncate flex-1">
233
+ {master.name}
234
+ </span>
235
+ <TooltipProvider>
236
+ <Tooltip>
237
+ <TooltipTrigger asChild>
238
+ <Button
239
+ variant="ghost"
240
+ size="icon"
241
+ onClick={onGoToMainComponent}
242
+ className="h-6 w-6 text-gray-400 hover:text-white hover:bg-[#4a4a4a]"
243
+ >
244
+ <ExternalLink className="w-3 h-3" />
245
+ </Button>
246
+ </TooltipTrigger>
247
+ <TooltipContent side="bottom">
248
+ <p className="text-xs">メインコンポーネントを編集</p>
249
+ </TooltipContent>
250
+ </Tooltip>
251
+ </TooltipProvider>
252
+ </div>
253
+ </div>
254
+
255
+ {/* Variant Selector */}
256
+ {master.variants.length > 1 && (
257
+ <div className="px-1">
258
+ <Label className="text-[10px] text-gray-500">バリアント</Label>
259
+ <Select
260
+ value={instance.variantId}
261
+ onValueChange={handleVariantChange}
262
+ >
263
+ <SelectTrigger className="h-7 text-xs bg-[#383838] border-[#444444] text-white mt-1">
264
+ <SelectValue placeholder="バリアントを選択" />
265
+ </SelectTrigger>
266
+ <SelectContent>
267
+ {master.variants.map((variant) => (
268
+ <SelectItem key={variant.id} value={variant.id}>
269
+ {variant.name}
270
+ {variant.isDefault && (
271
+ <span className="ml-2 text-gray-400">(デフォルト)</span>
272
+ )}
273
+ </SelectItem>
274
+ ))}
275
+ </SelectContent>
276
+ </Select>
277
+ </div>
278
+ )}
279
+
280
+ {/* Overrides List */}
281
+ {hasOverrides && (
282
+ <div className="px-1 space-y-2">
283
+ <div className="flex items-center justify-between">
284
+ <Label className="text-[10px] text-gray-500">オーバーライド</Label>
285
+ <TooltipProvider>
286
+ <Tooltip>
287
+ <TooltipTrigger asChild>
288
+ <Button
289
+ variant="ghost"
290
+ size="sm"
291
+ onClick={onResetAll}
292
+ className="h-5 px-2 text-[10px] text-gray-400 hover:text-white hover:bg-[#4a4a4a]"
293
+ >
294
+ <RefreshCw className="w-3 h-3 mr-1" />
295
+ すべてリセット
296
+ </Button>
297
+ </TooltipTrigger>
298
+ <TooltipContent side="bottom">
299
+ <p className="text-xs">すべてのオーバーライドをリセット</p>
300
+ </TooltipContent>
301
+ </Tooltip>
302
+ </TooltipProvider>
303
+ </div>
304
+
305
+ <div className="space-y-1 max-h-48 overflow-y-auto">
306
+ {Array.from(groupedOverrides.entries()).map(([elementId, overrides]) => (
307
+ <div
308
+ key={elementId}
309
+ className="bg-[#2a2a2a] rounded-md p-2 border border-[#444444]"
310
+ >
311
+ <div className="text-[10px] text-gray-400 mb-1.5 truncate">
312
+ {getElementDisplayName(master, elementId, instance.variantId)}
313
+ </div>
314
+ <div className="space-y-1">
315
+ {overrides.map((override) => (
316
+ <div
317
+ key={override.id}
318
+ className="flex items-center justify-between gap-2 group"
319
+ >
320
+ <div className="flex items-center gap-2 min-w-0 flex-1">
321
+ {/* Purple indicator dot */}
322
+ <div className="w-1.5 h-1.5 rounded-full bg-purple-500 flex-shrink-0" />
323
+ {/* Override type icon */}
324
+ <span className="text-purple-400 flex-shrink-0">
325
+ {getOverrideIcon(override.type)}
326
+ </span>
327
+ {/* Override type name */}
328
+ <span className="text-[10px] text-gray-300 flex-shrink-0">
329
+ {getOverrideTypeDisplayName(override.type)}
330
+ </span>
331
+ {/* Override value */}
332
+ <span className="text-[10px] text-gray-500 truncate">
333
+ {formatOverrideValue(override)}
334
+ </span>
335
+ </div>
336
+ {/* Reset button */}
337
+ <TooltipProvider>
338
+ <Tooltip>
339
+ <TooltipTrigger asChild>
340
+ <Button
341
+ variant="ghost"
342
+ size="icon"
343
+ onClick={() => onResetOverride(override.id)}
344
+ className="h-5 w-5 text-gray-500 hover:text-white hover:bg-[#4a4a4a] opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0"
345
+ >
346
+ <RotateCcw className="w-2.5 h-2.5" />
347
+ </Button>
348
+ </TooltipTrigger>
349
+ <TooltipContent side="left">
350
+ <p className="text-xs">リセット</p>
351
+ </TooltipContent>
352
+ </Tooltip>
353
+ </TooltipProvider>
354
+ </div>
355
+ ))}
356
+ </div>
357
+ </div>
358
+ ))}
359
+ </div>
360
+ </div>
361
+ )}
362
+
363
+ {/* No overrides message */}
364
+ {!hasOverrides && (
365
+ <div className="px-1">
366
+ <p className="text-[10px] text-gray-500 italic">
367
+ オーバーライドはありません
368
+ </p>
369
+ </div>
370
+ )}
371
+
372
+ {/* Action Buttons */}
373
+ <div className="px-1 pt-2 border-t border-[#444444] space-y-2">
374
+ {/* Detach Instance Button */}
375
+ <AlertDialog>
376
+ <AlertDialogTrigger asChild>
377
+ <Button
378
+ variant="ghost"
379
+ className="w-full h-7 text-xs justify-start text-gray-400 hover:text-white hover:bg-[#4a4a4a]"
380
+ >
381
+ <Unlink className="w-3.5 h-3.5 mr-2" />
382
+ インスタンスを解除
383
+ </Button>
384
+ </AlertDialogTrigger>
385
+ <AlertDialogContent className="bg-[#2c2c2c] border-[#444444]">
386
+ <AlertDialogHeader>
387
+ <AlertDialogTitle className="text-white">
388
+ インスタンスを解除
389
+ </AlertDialogTitle>
390
+ <AlertDialogDescription className="text-gray-400">
391
+ この操作により、要素はメインコンポーネントから切り離されます。
392
+ メインコンポーネントへの変更は反映されなくなります。
393
+ この操作は元に戻せません。
394
+ </AlertDialogDescription>
395
+ </AlertDialogHeader>
396
+ <AlertDialogFooter>
397
+ <AlertDialogCancel className="bg-[#383838] border-[#4a4a4a] text-white hover:bg-[#4a4a4a]">
398
+ キャンセル
399
+ </AlertDialogCancel>
400
+ <AlertDialogAction
401
+ onClick={onDetachInstance}
402
+ className="bg-red-600 hover:bg-red-700"
403
+ >
404
+ 解除する
405
+ </AlertDialogAction>
406
+ </AlertDialogFooter>
407
+ </AlertDialogContent>
408
+ </AlertDialog>
409
+ </div>
410
+ </CollapsibleContent>
411
+ </Collapsible>
412
+ );
413
+ }
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+
3
+ import { Input } from '../../../components/ui/input';
4
+ import { Label } from '../../../components/ui/label';
5
+ import {
6
+ Collapsible,
7
+ CollapsibleContent,
8
+ CollapsibleTrigger,
9
+ } from '../../../components/ui/collapsible';
10
+ import { Layers, ChevronRight } from 'lucide-react';
11
+ import type { SelectedElementInfo } from '../../types';
12
+
13
+ interface LayoutSectionProps {
14
+ selectedElement: SelectedElementInfo;
15
+ open: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ onStyleChange: (styles: Record<string, string>) => void;
18
+ }
19
+
20
+ export function LayoutSection({
21
+ selectedElement,
22
+ open,
23
+ onOpenChange,
24
+ onStyleChange,
25
+ }: LayoutSectionProps) {
26
+ return (
27
+ <Collapsible open={open} onOpenChange={onOpenChange}>
28
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-xs font-medium text-gray-300 hover:text-white">
29
+ <span className="flex items-center gap-2">
30
+ <Layers className="w-3.5 h-3.5" />
31
+ レイアウト
32
+ </span>
33
+ <ChevronRight
34
+ className={`w-3.5 h-3.5 transition-transform ${open ? 'rotate-90' : ''}`}
35
+ />
36
+ </CollapsibleTrigger>
37
+ <CollapsibleContent className="space-y-2 pb-3">
38
+ <div className="grid grid-cols-2 gap-2">
39
+ <div>
40
+ <Label className="text-[10px] text-gray-500">X</Label>
41
+ <Input
42
+ type="number"
43
+ value={Math.round(selectedElement.x)}
44
+ onChange={(e) => onStyleChange({ left: `${e.target.value}px` })}
45
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
46
+ />
47
+ </div>
48
+ <div>
49
+ <Label className="text-[10px] text-gray-500">Y</Label>
50
+ <Input
51
+ type="number"
52
+ value={Math.round(selectedElement.y)}
53
+ onChange={(e) => onStyleChange({ top: `${e.target.value}px` })}
54
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
55
+ />
56
+ </div>
57
+ </div>
58
+ <div className="grid grid-cols-2 gap-2">
59
+ <div>
60
+ <Label className="text-[10px] text-gray-500">幅</Label>
61
+ <Input
62
+ type="number"
63
+ value={Math.round(selectedElement.width)}
64
+ onChange={(e) => onStyleChange({ width: `${e.target.value}px` })}
65
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
66
+ />
67
+ </div>
68
+ <div>
69
+ <Label className="text-[10px] text-gray-500">高さ</Label>
70
+ <Input
71
+ type="number"
72
+ value={Math.round(selectedElement.height)}
73
+ onChange={(e) => onStyleChange({ height: `${e.target.value}px` })}
74
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
75
+ />
76
+ </div>
77
+ </div>
78
+ <div className="grid grid-cols-2 gap-2">
79
+ <div>
80
+ <Label className="text-[10px] text-gray-500">角丸</Label>
81
+ <Input
82
+ type="number"
83
+ value={Math.round(selectedElement.borderRadius)}
84
+ onChange={(e) => onStyleChange({ borderRadius: `${e.target.value}px` })}
85
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
86
+ />
87
+ </div>
88
+ <div>
89
+ <Label className="text-[10px] text-gray-500">不透明度</Label>
90
+ <Input
91
+ type="number"
92
+ min={0}
93
+ max={100}
94
+ value={Math.round(selectedElement.opacity * 100)}
95
+ onChange={(e) =>
96
+ onStyleChange({ opacity: String(parseInt(e.target.value) / 100) })
97
+ }
98
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white"
99
+ />
100
+ </div>
101
+ </div>
102
+ </CollapsibleContent>
103
+ </Collapsible>
104
+ );
105
+ }
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import { Input } from '../../../components/ui/input';
4
+ import { Label } from '../../../components/ui/label';
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '../../../components/ui/select';
12
+ import {
13
+ Collapsible,
14
+ CollapsibleContent,
15
+ CollapsibleTrigger,
16
+ } from '../../../components/ui/collapsible';
17
+ import { Link, ChevronRight, ExternalLink } from 'lucide-react';
18
+ import type { SelectedElementInfo } from '../../types';
19
+
20
+ interface LinkSectionProps {
21
+ selectedElement: SelectedElementInfo;
22
+ open: boolean;
23
+ onOpenChange: (open: boolean) => void;
24
+ onAttributeChange: (attrs: Record<string, string>) => void;
25
+ }
26
+
27
+ const TARGET_OPTIONS = [
28
+ { value: '_self', label: '同じタブ' },
29
+ { value: '_blank', label: '新しいタブ' },
30
+ { value: '_parent', label: '親フレーム' },
31
+ { value: '_top', label: 'トップフレーム' },
32
+ ];
33
+
34
+ export function LinkSection({
35
+ selectedElement,
36
+ open,
37
+ onOpenChange,
38
+ onAttributeChange,
39
+ }: LinkSectionProps) {
40
+ if (!selectedElement.isLink) return null;
41
+
42
+ return (
43
+ <Collapsible open={open} onOpenChange={onOpenChange}>
44
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-xs font-medium text-gray-300 hover:text-white border-t border-[#444444]">
45
+ <span className="flex items-center gap-2">
46
+ <Link className="w-3.5 h-3.5" />
47
+ リンク
48
+ </span>
49
+ <ChevronRight
50
+ className={`w-3.5 h-3.5 transition-transform ${open ? 'rotate-90' : ''}`}
51
+ />
52
+ </CollapsibleTrigger>
53
+ <CollapsibleContent className="space-y-2 pb-3">
54
+ {/* URL */}
55
+ <div>
56
+ <Label className="text-[10px] text-gray-500">URL</Label>
57
+ <Input
58
+ type="text"
59
+ value={selectedElement.linkHref}
60
+ onChange={(e) => onAttributeChange({ href: e.target.value })}
61
+ placeholder="https://example.com"
62
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white placeholder:text-gray-500"
63
+ />
64
+ </div>
65
+
66
+ {/* Title */}
67
+ <div>
68
+ <Label className="text-[10px] text-gray-500">タイトル</Label>
69
+ <Input
70
+ type="text"
71
+ value={selectedElement.linkTitle}
72
+ onChange={(e) => onAttributeChange({ title: e.target.value })}
73
+ placeholder="リンクの説明"
74
+ className="h-7 text-xs bg-[#383838] border-[#444444] text-white placeholder:text-gray-500"
75
+ />
76
+ </div>
77
+
78
+ {/* Target */}
79
+ <div>
80
+ <Label className="text-[10px] text-gray-500">開き方</Label>
81
+ <Select
82
+ value={selectedElement.linkTarget || '_self'}
83
+ onValueChange={(value) => onAttributeChange({ target: value })}
84
+ >
85
+ <SelectTrigger className="h-7 text-xs bg-[#383838] border-[#444444] text-white">
86
+ <SelectValue />
87
+ </SelectTrigger>
88
+ <SelectContent>
89
+ {TARGET_OPTIONS.map((opt) => (
90
+ <SelectItem key={opt.value} value={opt.value}>
91
+ <span className="flex items-center gap-1.5">
92
+ {opt.value === '_blank' && <ExternalLink className="w-3 h-3" />}
93
+ {opt.label}
94
+ </span>
95
+ </SelectItem>
96
+ ))}
97
+ </SelectContent>
98
+ </Select>
99
+ </div>
100
+ </CollapsibleContent>
101
+ </Collapsible>
102
+ );
103
+ }