@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.
- package/LICENSE +21 -0
- package/NOTICE +52 -0
- package/README.md +143 -0
- package/dist/editor.css +2 -0
- package/package.json +94 -0
- package/src/components/DeckSlideRender.tsx +21 -0
- package/src/components/SaveNote.tsx +21 -0
- package/src/components/auth/AuthProvider.tsx +44 -0
- package/src/components/ui/accordion.tsx +57 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +11 -0
- package/src/components/ui/context-menu.tsx +200 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/scrubbable-label.tsx +95 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/viewer/useDeck.ts +58 -0
- package/src/editor/EditorContext.tsx +632 -0
- package/src/editor/EditorToolbar.tsx +446 -0
- package/src/editor/FrontendVisualEditor.tsx +2595 -0
- package/src/editor/autosave.ts +34 -0
- package/src/editor/components/AiPromptPopover.tsx +378 -0
- package/src/editor/components/BreakpointGuides.tsx +323 -0
- package/src/editor/components/BreakpointSelector.tsx +180 -0
- package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
- package/src/editor/components/CssEditorDialog.tsx +168 -0
- package/src/editor/components/EditorCanvas.tsx +987 -0
- package/src/editor/components/EditorContextMenu.tsx +515 -0
- package/src/editor/components/EditorFooter.tsx +81 -0
- package/src/editor/components/EditorHeader.tsx +524 -0
- package/src/editor/components/EditorLayerPanel.tsx +1197 -0
- package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
- package/src/editor/components/EditorSidebar.tsx +226 -0
- package/src/editor/components/FigmaColorPicker.tsx +1577 -0
- package/src/editor/components/HtmlEditorDialog.tsx +71 -0
- package/src/editor/components/HtmlImportDialog.tsx +346 -0
- package/src/editor/components/JsEditorDialog.tsx +175 -0
- package/src/editor/components/LayerTreeItem.tsx +494 -0
- package/src/editor/components/MasterComponentEditor.tsx +846 -0
- package/src/editor/components/MediaLibraryDialog.tsx +447 -0
- package/src/editor/components/PageSettingsDialog.tsx +776 -0
- package/src/editor/components/VariablePicker.tsx +355 -0
- package/src/editor/components/VariablesDialog.tsx +564 -0
- package/src/editor/components/VariantEditor.tsx +687 -0
- package/src/editor/components/VariantMatrix.tsx +507 -0
- package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
- package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
- package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
- package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
- package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
- package/src/editor/components/component-panel/VariantList.tsx +260 -0
- package/src/editor/components/component-panel/index.ts +12 -0
- package/src/editor/components/index.ts +48 -0
- package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
- package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
- package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
- package/src/editor/components/multi-page/PageLabel.tsx +34 -0
- package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
- package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
- package/src/editor/components/multi-page/index.ts +7 -0
- package/src/editor/components/ppt/PptChrome.tsx +2508 -0
- package/src/editor/components/ppt/PptComments.tsx +589 -0
- package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
- package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
- package/src/editor/components/ppt/PptNotes.tsx +246 -0
- package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
- package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
- package/src/editor/components/property-panel/BorderSection.tsx +104 -0
- package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
- package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
- package/src/editor/components/property-panel/EffectSection.tsx +157 -0
- package/src/editor/components/property-panel/FillSection.tsx +69 -0
- package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
- package/src/editor/components/property-panel/ImageSection.tsx +206 -0
- package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
- package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
- package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
- package/src/editor/components/property-panel/LinkSection.tsx +103 -0
- package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
- package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
- package/src/editor/components/property-panel/TypographySection.tsx +230 -0
- package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
- package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
- package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
- package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
- package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
- package/src/editor/components/property-panel/index.ts +24 -0
- package/src/editor/components/property-panel/unit-utils.ts +261 -0
- package/src/editor/components/shell/EditorTopBar.tsx +392 -0
- package/src/editor/components/shell/LeftPanel.tsx +47 -0
- package/src/editor/components/shell/PagesPanel.tsx +118 -0
- package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
- package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
- package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
- package/src/editor/components/variables-panel/index.ts +7 -0
- package/src/editor/constants.ts +468 -0
- package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
- package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
- package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
- package/src/editor/contexts/EditorRefsContext.tsx +204 -0
- package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
- package/src/editor/contexts/EditorToolContext.tsx +58 -0
- package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
- package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
- package/src/editor/contexts/EditorViewContext.tsx +101 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
- package/src/editor/contexts/index.ts +17 -0
- package/src/editor/editor-skin.css +122 -0
- package/src/editor/hooks/index.ts +38 -0
- package/src/editor/hooks/useAiReplace.ts +397 -0
- package/src/editor/hooks/useAltMeasure.ts +67 -0
- package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
- package/src/editor/hooks/useCanvasControls.ts +1222 -0
- package/src/editor/hooks/useComponentEditMode.ts +658 -0
- package/src/editor/hooks/useComponentInstances.ts +515 -0
- package/src/editor/hooks/useContextMenuHandler.ts +157 -0
- package/src/editor/hooks/useCoordinateTransform.ts +207 -0
- package/src/editor/hooks/useDragResize.ts +1317 -0
- package/src/editor/hooks/useDrawingMode.ts +472 -0
- package/src/editor/hooks/useEditorColors.ts +144 -0
- package/src/editor/hooks/useEditorMessages.ts +108 -0
- package/src/editor/hooks/useElementActions.ts +1754 -0
- package/src/editor/hooks/useElementSelection.ts +1573 -0
- package/src/editor/hooks/useFocusManagement.ts +249 -0
- package/src/editor/hooks/useGoogleFonts.ts +305 -0
- package/src/editor/hooks/useIframeInitializer.ts +584 -0
- package/src/editor/hooks/useIframeSetup.ts +360 -0
- package/src/editor/hooks/useImageUpload.ts +270 -0
- package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
- package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
- package/src/editor/hooks/useMarqueeSelection.ts +388 -0
- package/src/editor/hooks/useMediaLibrary.ts +129 -0
- package/src/editor/hooks/usePageSettingsManager.ts +739 -0
- package/src/editor/hooks/useResizablePanel.ts +163 -0
- package/src/editor/hooks/useRichPaste.ts +255 -0
- package/src/editor/hooks/useTouchGestures.ts +197 -0
- package/src/editor/index.ts +27 -0
- package/src/editor/types/page-settings.ts +513 -0
- package/src/editor/types.ts +382 -0
- package/src/editor/utils/align-elements.ts +169 -0
- package/src/editor/utils/api-json.ts +21 -0
- package/src/editor/utils/aspect-lock.ts +50 -0
- package/src/editor/utils/component-renderer.ts +702 -0
- package/src/editor/utils/component-sync.ts +845 -0
- package/src/editor/utils/crop-mode.ts +265 -0
- package/src/editor/utils/dom-utils.ts +2238 -0
- package/src/editor/utils/element-effects.ts +218 -0
- package/src/editor/utils/eyedropper.ts +35 -0
- package/src/editor/utils/figma-export.ts +560 -0
- package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
- package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
- package/src/editor/utils/figma-paste.ts +2262 -0
- package/src/editor/utils/flex-reorder.ts +231 -0
- package/src/editor/utils/flex-utils.ts +1230 -0
- package/src/editor/utils/geometry.ts +232 -0
- package/src/editor/utils/grid-layout.ts +210 -0
- package/src/editor/utils/html-utils.ts +470 -0
- package/src/editor/utils/index.ts +11 -0
- package/src/editor/utils/ink-style.ts +57 -0
- package/src/editor/utils/inline-format.ts +236 -0
- package/src/editor/utils/measure-distance.ts +120 -0
- package/src/editor/utils/override-apply.ts +526 -0
- package/src/editor/utils/override-detection.ts +613 -0
- package/src/editor/utils/paste-processors.ts +609 -0
- package/src/editor/utils/paste-sanitizer.ts +244 -0
- package/src/editor/utils/restore-flow.ts +188 -0
- package/src/editor/utils/shape-library.ts +171 -0
- package/src/editor/utils/slide-root.ts +29 -0
- package/src/editor/utils/smart-guides.ts +218 -0
- package/src/editor/utils/style-utils.ts +685 -0
- package/src/editor/utils/table-edit.ts +151 -0
- package/src/editor/utils/tailwind-mappings.ts +778 -0
- package/src/editor/utils/tailwind-utils.ts +804 -0
- package/src/editor/utils/text-highlight.ts +216 -0
- package/src/editor/utils/theme-to-variables.ts +341 -0
- package/src/editor/utils/variant-resolver.ts +456 -0
- package/src/editor/utils/viewport-utils.ts +295 -0
- package/src/hooks/useEditorHistory.ts +195 -0
- package/src/hooks/useEditorShortcuts.ts +260 -0
- package/src/index.ts +40 -0
- package/src/io.ts +134 -0
- package/src/lib/agent/slide-agent/types.ts +547 -0
- package/src/lib/agent/website-agent/types.ts +805 -0
- package/src/lib/api/auth-fetch.ts +58 -0
- package/src/lib/deck.ts +69 -0
- package/src/lib/export.ts +47 -0
- package/src/lib/firebase/config.ts +29 -0
- package/src/lib/firebase/css-variables.ts +378 -0
- package/src/lib/firebase/editor-components.ts +677 -0
- package/src/lib/firebase/storage.ts +469 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/editor.css +52 -0
- package/src/styles/skin.css +122 -0
- package/src/types/css-variables.ts +645 -0
- package/src/types/editor-components.ts +944 -0
- package/src/types/editor.ts +420 -0
- package/src/types/page-master.ts +197 -0
- package/src/types/page.ts +419 -0
- package/src/types/slide.ts +402 -0
- package/src/types/website-theme.ts +788 -0
- package/src/types/website.ts +374 -0
- package/src/vendor/firebase-firestore.ts +13 -0
- 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
|
+
}
|