@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,82 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * ComponentSearch - Search input with filter for components
5
+ *
6
+ * Provides a search input field with a search icon and clear button.
7
+ * Matches the dark theme of the editor.
8
+ */
9
+
10
+ import React, { useRef, useCallback } from 'react';
11
+ import { Search, X } from 'lucide-react';
12
+ import { Input } from '../../../components/ui/input';
13
+ import { Button } from '../../../components/ui/button';
14
+
15
+ interface ComponentSearchProps {
16
+ /** Current search value */
17
+ value: string;
18
+ /** Callback when search value changes */
19
+ onChange: (value: string) => void;
20
+ /** Placeholder text */
21
+ placeholder?: string;
22
+ /** Auto-focus the input on mount */
23
+ autoFocus?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Search input component for filtering components.
28
+ * Supports keyboard navigation (Escape to clear).
29
+ */
30
+ export function ComponentSearch({
31
+ value,
32
+ onChange,
33
+ placeholder = 'コンポーネントを検索...',
34
+ autoFocus = false,
35
+ }: ComponentSearchProps) {
36
+ const inputRef = useRef<HTMLInputElement>(null);
37
+
38
+ const handleClear = useCallback(() => {
39
+ onChange('');
40
+ inputRef.current?.focus();
41
+ }, [onChange]);
42
+
43
+ const handleKeyDown = useCallback(
44
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
45
+ if (e.key === 'Escape' && value) {
46
+ e.stopPropagation();
47
+ handleClear();
48
+ }
49
+ },
50
+ [value, handleClear]
51
+ );
52
+
53
+ return (
54
+ <div className="relative">
55
+ <Search className="w-3.5 h-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" />
56
+ <Input
57
+ ref={inputRef}
58
+ type="text"
59
+ value={value}
60
+ onChange={(e) => onChange(e.target.value)}
61
+ onKeyDown={handleKeyDown}
62
+ placeholder={placeholder}
63
+ autoFocus={autoFocus}
64
+ className="h-7 text-xs pl-7 pr-7 bg-[#383838] border-[#444444] text-white placeholder:text-gray-500 focus-visible:ring-1 focus-visible:ring-[#0d99ff]"
65
+ aria-label="コンポーネントを検索"
66
+ />
67
+ {value && (
68
+ <Button
69
+ variant="ghost"
70
+ size="icon"
71
+ className="absolute right-1 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-500 hover:text-white hover:bg-transparent"
72
+ onClick={handleClear}
73
+ aria-label="検索をクリア"
74
+ >
75
+ <X className="w-3 h-3" />
76
+ </Button>
77
+ )}
78
+ </div>
79
+ );
80
+ }
81
+
82
+ export default ComponentSearch;
@@ -0,0 +1,260 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * VariantList - Display variants of a component
5
+ *
6
+ * Shows all variants of a component with thumbnails.
7
+ * Each variant can be dragged to the canvas separately.
8
+ */
9
+
10
+ import React, { useCallback, useRef, useEffect, useState } from 'react';
11
+ import { Layers } from 'lucide-react';
12
+ import type { MasterComponent, VariantDefinition } from '../../../types/editor-components';
13
+ import { createDOMElement } from '../../utils/component-renderer';
14
+
15
+ interface VariantListProps {
16
+ /** The component containing the variants */
17
+ component: MasterComponent;
18
+ /** Whether the variant list is expanded */
19
+ isExpanded: boolean;
20
+ }
21
+
22
+ /**
23
+ * Calculate the scale factor to fit the element within the container
24
+ */
25
+ function calculateScale(
26
+ elementWidth: number,
27
+ elementHeight: number,
28
+ containerWidth: number,
29
+ containerHeight: number,
30
+ padding: number = 4
31
+ ): number {
32
+ const availableWidth = containerWidth - padding * 2;
33
+ const availableHeight = containerHeight - padding * 2;
34
+
35
+ const scaleX = availableWidth / elementWidth;
36
+ const scaleY = availableHeight / elementHeight;
37
+
38
+ return Math.min(scaleX, scaleY, 1);
39
+ }
40
+
41
+ /**
42
+ * VariantThumbnail - Renders a preview of a specific variant
43
+ */
44
+ function VariantThumbnail({
45
+ variant,
46
+ componentId,
47
+ }: {
48
+ variant: VariantDefinition;
49
+ componentId: string;
50
+ }) {
51
+ const containerRef = useRef<HTMLDivElement>(null);
52
+ const [isReady, setIsReady] = useState(false);
53
+
54
+ useEffect(() => {
55
+ if (!containerRef.current || !variant.rootElement) return;
56
+
57
+ setIsReady(false);
58
+ const container = containerRef.current;
59
+ container.innerHTML = '';
60
+
61
+ try {
62
+ const domElement = createDOMElement(variant.rootElement);
63
+
64
+ const wrapper = document.createElement('div');
65
+ wrapper.style.cssText = `
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 100%;
70
+ height: 100%;
71
+ overflow: hidden;
72
+ pointer-events: none;
73
+ `;
74
+
75
+ const scaleContainer = document.createElement('div');
76
+ scaleContainer.style.cssText = `
77
+ transform-origin: center center;
78
+ transition: transform 0.15s ease;
79
+ `;
80
+
81
+ domElement.style.position = 'relative';
82
+ domElement.style.left = 'auto';
83
+ domElement.style.top = 'auto';
84
+ domElement.style.transform = 'none';
85
+ domElement.style.margin = '0';
86
+
87
+ scaleContainer.appendChild(domElement);
88
+ wrapper.appendChild(scaleContainer);
89
+ container.appendChild(wrapper);
90
+
91
+ requestAnimationFrame(() => {
92
+ const containerRect = container.getBoundingClientRect();
93
+ const elementRect = domElement.getBoundingClientRect();
94
+
95
+ if (elementRect.width > 0 && elementRect.height > 0) {
96
+ const scale = calculateScale(
97
+ elementRect.width,
98
+ elementRect.height,
99
+ containerRect.width,
100
+ containerRect.height,
101
+ 2
102
+ );
103
+ scaleContainer.style.transform = `scale(${scale})`;
104
+ }
105
+
106
+ setIsReady(true);
107
+ });
108
+ } catch (error) {
109
+ console.error('[VariantThumbnail] Error:', error);
110
+ }
111
+
112
+ return () => {
113
+ if (container) container.innerHTML = '';
114
+ };
115
+ }, [variant, componentId]);
116
+
117
+ return (
118
+ <div
119
+ ref={containerRef}
120
+ className={`w-full h-full bg-white transition-opacity duration-200 ${
121
+ isReady ? 'opacity-100' : 'opacity-0'
122
+ }`}
123
+ style={{
124
+ background: 'linear-gradient(45deg, #f8f8f8 25%, transparent 25%), linear-gradient(-45deg, #f8f8f8 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f8f8f8 75%), linear-gradient(-45deg, transparent 75%, #f8f8f8 75%)',
125
+ backgroundSize: '6px 6px',
126
+ backgroundPosition: '0 0, 0 3px, 3px -3px, -3px 0px',
127
+ }}
128
+ />
129
+ );
130
+ }
131
+
132
+ /**
133
+ * VariantItem - Single variant item with drag support
134
+ */
135
+ function VariantItem({
136
+ component,
137
+ variant,
138
+ isDefault,
139
+ }: {
140
+ component: MasterComponent;
141
+ variant: VariantDefinition;
142
+ isDefault: boolean;
143
+ }) {
144
+ const [isDragging, setIsDragging] = useState(false);
145
+
146
+ const handleDragStart = useCallback(
147
+ (e: React.DragEvent<HTMLDivElement>) => {
148
+ setIsDragging(true);
149
+
150
+ e.dataTransfer.setData(
151
+ 'application/x-editor-component',
152
+ JSON.stringify({
153
+ type: 'component',
154
+ componentId: component.id,
155
+ componentName: component.name,
156
+ variantId: variant.id,
157
+ variantName: variant.name,
158
+ })
159
+ );
160
+ e.dataTransfer.effectAllowed = 'copy';
161
+
162
+ // Custom drag image
163
+ const dragImage = document.createElement('div');
164
+ dragImage.className =
165
+ 'bg-[#2c2c2c] border border-purple-500 rounded px-2 py-1 text-xs text-white shadow-lg';
166
+ dragImage.textContent = `${component.name} - ${variant.name}`;
167
+ dragImage.style.position = 'absolute';
168
+ dragImage.style.top = '-1000px';
169
+ document.body.appendChild(dragImage);
170
+ e.dataTransfer.setDragImage(dragImage, 0, 0);
171
+
172
+ setTimeout(() => {
173
+ document.body.removeChild(dragImage);
174
+ }, 0);
175
+ },
176
+ [component, variant]
177
+ );
178
+
179
+ const handleDragEnd = useCallback(() => {
180
+ setIsDragging(false);
181
+ }, []);
182
+
183
+ // Get variant property values display
184
+ const propertyDisplay = variant.propertyValues
185
+ ? Object.entries(variant.propertyValues)
186
+ .map(([key, value]) => `${value}`)
187
+ .join(', ')
188
+ : null;
189
+
190
+ return (
191
+ <div
192
+ className={`
193
+ flex items-center gap-2 p-1.5 rounded border cursor-grab
194
+ transition-all duration-150
195
+ ${isDragging
196
+ ? 'opacity-50 border-purple-500 bg-purple-500/10'
197
+ : 'border-[#444444] bg-[#2a2a2a] hover:border-gray-500 hover:bg-[#333]'
198
+ }
199
+ `}
200
+ draggable
201
+ onDragStart={handleDragStart}
202
+ onDragEnd={handleDragEnd}
203
+ title={`${variant.name}${variant.description ? `: ${variant.description}` : ''}`}
204
+ >
205
+ {/* Thumbnail */}
206
+ <div className="w-10 h-8 rounded overflow-hidden flex-shrink-0 border border-[#444444]">
207
+ <VariantThumbnail variant={variant} componentId={component.id} />
208
+ </div>
209
+
210
+ {/* Info */}
211
+ <div className="flex-1 min-w-0">
212
+ <div className="flex items-center gap-1">
213
+ <span className="text-[9px] text-gray-300 truncate">
214
+ {variant.name}
215
+ </span>
216
+ {isDefault && (
217
+ <span className="text-[7px] text-[#4fb8ff] bg-[#0d99ff]/20 px-1 rounded">
218
+ デフォルト
219
+ </span>
220
+ )}
221
+ </div>
222
+ {propertyDisplay && (
223
+ <p className="text-[8px] text-gray-500 truncate">
224
+ {propertyDisplay}
225
+ </p>
226
+ )}
227
+ </div>
228
+ </div>
229
+ );
230
+ }
231
+
232
+ /**
233
+ * VariantList displays all variants of a component
234
+ */
235
+ export function VariantList({ component, isExpanded }: VariantListProps) {
236
+ if (!isExpanded || component.variants.length <= 1) {
237
+ return null;
238
+ }
239
+
240
+ return (
241
+ <div className="mt-1 space-y-1 animate-in fade-in slide-in-from-top-1 duration-200">
242
+ <div className="flex items-center gap-1 px-1 text-[9px] text-gray-500">
243
+ <Layers className="w-3 h-3" />
244
+ <span>バリアント</span>
245
+ </div>
246
+ <div className="space-y-1">
247
+ {component.variants.map((variant) => (
248
+ <VariantItem
249
+ key={variant.id}
250
+ component={component}
251
+ variant={variant}
252
+ isDefault={variant.id === component.defaultVariantId}
253
+ />
254
+ ))}
255
+ </div>
256
+ </div>
257
+ );
258
+ }
259
+
260
+ export default VariantList;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Component Panel exports
3
+ *
4
+ * Provides a Figma-like component panel for the frontend editor.
5
+ */
6
+
7
+ export { ComponentPanel, default } from './ComponentPanel';
8
+ export { ComponentSearch } from './ComponentSearch';
9
+ export { ComponentGrid } from './ComponentGrid';
10
+ export { ComponentItem } from './ComponentItem';
11
+ export { CategoryAccordion } from './CategoryAccordion';
12
+ export { VariantList } from './VariantList';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * エディタコンポーネントのエクスポート
3
+ */
4
+
5
+ export { EditorHeader } from './EditorHeader';
6
+ export { EditorCanvas } from './EditorCanvas';
7
+ export { EditorLayerPanel } from './EditorLayerPanel';
8
+ export { EditorPropertyPanel } from './EditorPropertyPanel';
9
+ export { EditorFooter } from './EditorFooter';
10
+ export { BreakpointSelector } from './BreakpointSelector';
11
+ export { BreakpointGuides } from './BreakpointGuides';
12
+ export { FigmaColorPicker, fillConfigToCss, parseCssToFillConfig } from './FigmaColorPicker';
13
+ export type { FillConfig, FillType, GradientStop } from './FigmaColorPicker';
14
+ export { EditorContextMenu } from './EditorContextMenu';
15
+ export type { ContextMenuPosition, ContextMenuProps } from './EditorContextMenu';
16
+ export { AiPromptPopover } from './AiPromptPopover';
17
+ export type { AiPromptPopoverProps } from './AiPromptPopover';
18
+ export { HtmlEditorDialog } from './HtmlEditorDialog';
19
+ export { HtmlImportDialog } from './HtmlImportDialog';
20
+ export type { HtmlImportDialogProps, HtmlImportResult } from './HtmlImportDialog';
21
+ export { CssEditorDialog } from './CssEditorDialog';
22
+ export type { CssEditorDialogProps } from './CssEditorDialog';
23
+ export { JsEditorDialog } from './JsEditorDialog';
24
+ export type { JsEditorDialogProps } from './JsEditorDialog';
25
+ export { PageSettingsDialog } from './PageSettingsDialog';
26
+ export type { PageSettingsDialogProps } from './PageSettingsDialog';
27
+ export { MediaLibraryDialog } from './MediaLibraryDialog';
28
+ export type { MediaLibraryDialogProps, MediaLibraryMode } from './MediaLibraryDialog';
29
+ export { VariablesDialog } from './VariablesDialog';
30
+ export { VariablePicker, VariableLinkButton } from './VariablePicker';
31
+ export { VariablesPanel } from './variables-panel';
32
+ export { ComponentPanel } from './component-panel';
33
+ export { MasterComponentEditor } from './MasterComponentEditor';
34
+ // 旧サイドバー(互換性のため残す)
35
+ export { EditorSidebar } from './EditorSidebar';
36
+
37
+ /** @deprecated Use EditorHeader instead */
38
+ export { EditorHeader as SlideEditorHeader } from './EditorHeader';
39
+ /** @deprecated Use EditorCanvas instead */
40
+ export { EditorCanvas as SlideEditorCanvas } from './EditorCanvas';
41
+ /** @deprecated Use EditorLayerPanel instead */
42
+ export { EditorLayerPanel as SlideEditorLayerPanel } from './EditorLayerPanel';
43
+ /** @deprecated Use EditorPropertyPanel instead */
44
+ export { EditorPropertyPanel as SlideEditorPropertyPanel } from './EditorPropertyPanel';
45
+ /** @deprecated Use EditorFooter instead */
46
+ export { EditorFooter as SlideEditorFooter } from './EditorFooter';
47
+ /** @deprecated Use EditorSidebar instead */
48
+ export { EditorSidebar as SlideEditorSidebar } from './EditorSidebar';
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import { memo, useCallback, useRef } from 'react';
4
+ import { Minus, Plus, Maximize } from 'lucide-react';
5
+ import { Button } from '../../../components/ui/button';
6
+ import { useMultiPageCanvas } from '../../contexts/MultiPageCanvasContext';
7
+
8
+ /**
9
+ * ズームコントロール(右下固定)
10
+ * - ズームイン/アウトボタン
11
+ * - ズーム値表示
12
+ * - フィットボタン (全ページを表示)
13
+ */
14
+ export const CanvasZoomControls = memo(function CanvasZoomControls() {
15
+ const { viewState, zoomIn, zoomOut, zoomToFit } = useMultiPageCanvas();
16
+ const containerRef = useRef<HTMLDivElement | null>(null);
17
+
18
+ const handleFitToView = useCallback(() => {
19
+ // 親のInfiniteCanvasコンテナサイズを取得
20
+ const canvas = containerRef.current?.closest('[data-infinite-canvas]');
21
+ if (canvas) {
22
+ const rect = canvas.getBoundingClientRect();
23
+ zoomToFit(rect.width, rect.height);
24
+ }
25
+ }, [zoomToFit]);
26
+
27
+ const zoomPercent = Math.round(viewState.canvasZoom * 100);
28
+
29
+ return (
30
+ <div
31
+ ref={containerRef}
32
+ className="absolute bottom-4 right-4 z-10 flex items-center gap-1 bg-[#2a2a2a] border border-[#404040] rounded-lg px-1 py-1 shadow-lg"
33
+ >
34
+ <Button
35
+ variant="ghost"
36
+ size="sm"
37
+ className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
38
+ onClick={zoomOut}
39
+ title="ズームアウト"
40
+ >
41
+ <Minus className="w-3.5 h-3.5" />
42
+ </Button>
43
+
44
+ <span className="text-xs text-gray-300 min-w-[40px] text-center tabular-nums select-none">
45
+ {zoomPercent}%
46
+ </span>
47
+
48
+ <Button
49
+ variant="ghost"
50
+ size="sm"
51
+ className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
52
+ onClick={zoomIn}
53
+ title="ズームイン"
54
+ >
55
+ <Plus className="w-3.5 h-3.5" />
56
+ </Button>
57
+
58
+ <div className="w-px h-4 bg-[#404040] mx-0.5" />
59
+
60
+ <Button
61
+ variant="ghost"
62
+ size="sm"
63
+ className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
64
+ onClick={handleFitToView}
65
+ title="全体にフィット (Ctrl+0)"
66
+ >
67
+ <Maximize className="w-3.5 h-3.5" />
68
+ </Button>
69
+ </div>
70
+ );
71
+ });
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+
3
+ import { memo, useRef, useEffect, useCallback } from 'react';
4
+ import { useMultiPageCanvas } from '../../contexts/MultiPageCanvasContext';
5
+ import { useInfiniteCanvas } from '../../hooks/useInfiniteCanvas';
6
+ import { PageFrameOverlay } from './PageFrameOverlay';
7
+ import { CanvasZoomControls } from './CanvasZoomControls';
8
+
9
+ /**
10
+ * InfiniteCanvas: 無限キャンバスコンテナ(レガシー)
11
+ *
12
+ * 全ページをフレームとして表示し、CSS transformでズーム/パンを実現。
13
+ * Note: マルチページ統合ビューはMultiPageCanvasViewを使用。
14
+ * このコンポーネントはサムネイル表示のみ(編集iframe含まず)。
15
+ */
16
+ export const InfiniteCanvas = memo(function InfiniteCanvas() {
17
+ const {
18
+ viewState,
19
+ pages,
20
+ switchPage,
21
+ zoomToFit,
22
+ } = useMultiPageCanvas();
23
+ const { canvasOffset, canvasZoom, activePageId } = viewState;
24
+ const containerRef = useRef<HTMLDivElement>(null);
25
+
26
+ // 無限キャンバスのズーム/パン操作
27
+ useInfiniteCanvas(containerRef);
28
+
29
+ // 初回表示時にフィットズーム
30
+ const hasInitialFitRef = useRef(false);
31
+ useEffect(() => {
32
+ if (hasInitialFitRef.current || pages.length === 0) return;
33
+ const container = containerRef.current;
34
+ if (!container) return;
35
+
36
+ const timer = setTimeout(() => {
37
+ const rect = container.getBoundingClientRect();
38
+ if (rect.width > 0 && rect.height > 0) {
39
+ zoomToFit(rect.width, rect.height);
40
+ hasInitialFitRef.current = true;
41
+ }
42
+ }, 100);
43
+ return () => clearTimeout(timer);
44
+ }, [pages.length, zoomToFit]);
45
+
46
+ const handlePageClick = useCallback((pageId: string) => {
47
+ switchPage(pageId);
48
+ }, [switchPage]);
49
+
50
+ return (
51
+ <div
52
+ ref={containerRef}
53
+ data-infinite-canvas="true"
54
+ className="absolute inset-0 overflow-hidden select-none"
55
+ style={{ backgroundColor: '#1a1a1a' }}
56
+ >
57
+ {/* ドットグリッド背景 */}
58
+ <div
59
+ className="absolute inset-0 pointer-events-none"
60
+ style={{
61
+ backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1px)`,
62
+ backgroundSize: `${24 * canvasZoom}px ${24 * canvasZoom}px`,
63
+ backgroundPosition: `${canvasOffset.x % (24 * canvasZoom)}px ${canvasOffset.y % (24 * canvasZoom)}px`,
64
+ }}
65
+ />
66
+
67
+ {/* CSS transform による無限キャンバス */}
68
+ <div
69
+ className="canvas-transform-layer"
70
+ style={{
71
+ transform: `translate(${canvasOffset.x}px, ${canvasOffset.y}px) scale(${canvasZoom})`,
72
+ transformOrigin: '0 0',
73
+ willChange: 'transform',
74
+ }}
75
+ >
76
+ {pages.map(page => (
77
+ <PageFrameOverlay
78
+ key={page.id}
79
+ page={page}
80
+ isActive={page.id === activePageId}
81
+ onClick={handlePageClick}
82
+ />
83
+ ))}
84
+ </div>
85
+
86
+ {/* ズームコントロール */}
87
+ <CanvasZoomControls />
88
+
89
+ {/* ページ数表示 */}
90
+ <div className="absolute top-4 left-4 z-10 text-xs text-gray-500 select-none">
91
+ {pages.length} pages
92
+ </div>
93
+ </div>
94
+ );
95
+ });