@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,426 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ComponentPropertyEditor - Editor for individual component properties
|
|
5
|
+
*
|
|
6
|
+
* Allows adding/editing ComponentProperty definitions for master components.
|
|
7
|
+
* Supports boolean, text, and instanceSwap property types.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
11
|
+
import { Button } from '../../components/ui/button';
|
|
12
|
+
import { Input } from '../../components/ui/input';
|
|
13
|
+
import { Label } from '../../components/ui/label';
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from '../../components/ui/select';
|
|
21
|
+
import { Checkbox } from '../../components/ui/checkbox';
|
|
22
|
+
import {
|
|
23
|
+
X,
|
|
24
|
+
Check,
|
|
25
|
+
ToggleLeft,
|
|
26
|
+
Type,
|
|
27
|
+
Repeat,
|
|
28
|
+
Plus,
|
|
29
|
+
Trash2,
|
|
30
|
+
} from 'lucide-react';
|
|
31
|
+
import { cn } from '../../lib/utils';
|
|
32
|
+
import type {
|
|
33
|
+
ComponentProperty,
|
|
34
|
+
ComponentPropertyType,
|
|
35
|
+
} from '../../types/editor-components';
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// Types
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
interface AvailableElement {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ComponentPropertyEditorProps {
|
|
47
|
+
/** Property to edit (null for creating new) */
|
|
48
|
+
property: ComponentProperty | null;
|
|
49
|
+
/** Available elements that can be targeted by this property */
|
|
50
|
+
availableElements: AvailableElement[];
|
|
51
|
+
/** Available components for instance swap (optional) */
|
|
52
|
+
availableComponents?: { id: string; name: string }[];
|
|
53
|
+
/** Callback when property is saved */
|
|
54
|
+
onSave: (property: ComponentProperty) => void;
|
|
55
|
+
/** Callback when editing is cancelled */
|
|
56
|
+
onCancel: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================
|
|
60
|
+
// Constants
|
|
61
|
+
// ============================================
|
|
62
|
+
|
|
63
|
+
const PROPERTY_TYPE_OPTIONS: {
|
|
64
|
+
value: ComponentPropertyType;
|
|
65
|
+
label: string;
|
|
66
|
+
description: string;
|
|
67
|
+
icon: React.ReactNode;
|
|
68
|
+
}[] = [
|
|
69
|
+
{
|
|
70
|
+
value: 'boolean',
|
|
71
|
+
label: 'Boolean',
|
|
72
|
+
description: 'Toggle visibility of elements',
|
|
73
|
+
icon: <ToggleLeft className="h-4 w-4" />,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
value: 'text',
|
|
77
|
+
label: 'Text',
|
|
78
|
+
description: 'Editable text content',
|
|
79
|
+
icon: <Type className="h-4 w-4" />,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
value: 'instanceSwap',
|
|
83
|
+
label: 'Instance Swap',
|
|
84
|
+
description: 'Replace nested component',
|
|
85
|
+
icon: <Repeat className="h-4 w-4" />,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// ============================================
|
|
90
|
+
// Helper Functions
|
|
91
|
+
// ============================================
|
|
92
|
+
|
|
93
|
+
function generatePropertyId(): string {
|
|
94
|
+
return `prop-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================
|
|
98
|
+
// Component
|
|
99
|
+
// ============================================
|
|
100
|
+
|
|
101
|
+
export function ComponentPropertyEditor({
|
|
102
|
+
property,
|
|
103
|
+
availableElements,
|
|
104
|
+
availableComponents = [],
|
|
105
|
+
onSave,
|
|
106
|
+
onCancel,
|
|
107
|
+
}: ComponentPropertyEditorProps) {
|
|
108
|
+
// Form state
|
|
109
|
+
const [name, setName] = useState(property?.name ?? '');
|
|
110
|
+
const [type, setType] = useState<ComponentPropertyType>(property?.type ?? 'boolean');
|
|
111
|
+
const [description, setDescription] = useState(property?.description ?? '');
|
|
112
|
+
const [group, setGroup] = useState(property?.group ?? '');
|
|
113
|
+
const [required, setRequired] = useState(property?.required ?? false);
|
|
114
|
+
const [targetElementIds, setTargetElementIds] = useState<string[]>(
|
|
115
|
+
property?.targetElementIds ?? []
|
|
116
|
+
);
|
|
117
|
+
const [allowedComponentIds, setAllowedComponentIds] = useState<string[]>(
|
|
118
|
+
property?.allowedComponentIds ?? []
|
|
119
|
+
);
|
|
120
|
+
const [defaultValue, setDefaultValue] = useState<string | boolean>(
|
|
121
|
+
property?.defaultValue ?? (type === 'boolean' ? true : '')
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const isNew = !property;
|
|
125
|
+
|
|
126
|
+
// Update default value when type changes
|
|
127
|
+
const handleTypeChange = useCallback((newType: ComponentPropertyType) => {
|
|
128
|
+
setType(newType);
|
|
129
|
+
// Reset default value based on type
|
|
130
|
+
if (newType === 'boolean') {
|
|
131
|
+
setDefaultValue(true);
|
|
132
|
+
} else {
|
|
133
|
+
setDefaultValue('');
|
|
134
|
+
}
|
|
135
|
+
// Clear instance swap specific fields if not instanceSwap
|
|
136
|
+
if (newType !== 'instanceSwap') {
|
|
137
|
+
setAllowedComponentIds([]);
|
|
138
|
+
}
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
// Toggle target element selection
|
|
142
|
+
const toggleTargetElement = useCallback((elementId: string) => {
|
|
143
|
+
setTargetElementIds((prev) =>
|
|
144
|
+
prev.includes(elementId)
|
|
145
|
+
? prev.filter((id) => id !== elementId)
|
|
146
|
+
: [...prev, elementId]
|
|
147
|
+
);
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
// Toggle allowed component selection
|
|
151
|
+
const toggleAllowedComponent = useCallback((componentId: string) => {
|
|
152
|
+
setAllowedComponentIds((prev) =>
|
|
153
|
+
prev.includes(componentId)
|
|
154
|
+
? prev.filter((id) => id !== componentId)
|
|
155
|
+
: [...prev, componentId]
|
|
156
|
+
);
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
// Validation
|
|
160
|
+
const isValid = useMemo(() => {
|
|
161
|
+
if (!name.trim()) return false;
|
|
162
|
+
if (targetElementIds.length === 0) return false;
|
|
163
|
+
if (type === 'instanceSwap' && allowedComponentIds.length === 0) return false;
|
|
164
|
+
return true;
|
|
165
|
+
}, [name, targetElementIds, type, allowedComponentIds]);
|
|
166
|
+
|
|
167
|
+
// Handle save
|
|
168
|
+
const handleSave = useCallback(() => {
|
|
169
|
+
if (!isValid) return;
|
|
170
|
+
|
|
171
|
+
const newProperty: ComponentProperty = {
|
|
172
|
+
id: property?.id ?? generatePropertyId(),
|
|
173
|
+
name: name.trim(),
|
|
174
|
+
type,
|
|
175
|
+
defaultValue,
|
|
176
|
+
targetElementIds,
|
|
177
|
+
description: description.trim() || undefined,
|
|
178
|
+
group: group.trim() || undefined,
|
|
179
|
+
required,
|
|
180
|
+
...(type === 'instanceSwap' && { allowedComponentIds }),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
onSave(newProperty);
|
|
184
|
+
}, [
|
|
185
|
+
isValid,
|
|
186
|
+
property,
|
|
187
|
+
name,
|
|
188
|
+
type,
|
|
189
|
+
defaultValue,
|
|
190
|
+
targetElementIds,
|
|
191
|
+
description,
|
|
192
|
+
group,
|
|
193
|
+
required,
|
|
194
|
+
allowedComponentIds,
|
|
195
|
+
onSave,
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div className="space-y-4 p-4 bg-[#1e1e1e] rounded-lg border border-[#444444]">
|
|
200
|
+
{/* Header */}
|
|
201
|
+
<div className="flex items-center justify-between">
|
|
202
|
+
<h4 className="text-sm font-medium text-gray-200">
|
|
203
|
+
{isNew ? 'Add Property' : 'Edit Property'}
|
|
204
|
+
</h4>
|
|
205
|
+
<Button
|
|
206
|
+
type="button"
|
|
207
|
+
variant="ghost"
|
|
208
|
+
size="icon"
|
|
209
|
+
className="h-6 w-6 text-gray-400 hover:text-gray-200"
|
|
210
|
+
onClick={onCancel}
|
|
211
|
+
aria-label="Close"
|
|
212
|
+
>
|
|
213
|
+
<X className="h-3.5 w-3.5" />
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Property Type */}
|
|
218
|
+
<div className="grid gap-1.5">
|
|
219
|
+
<Label className="text-xs text-gray-400">Type</Label>
|
|
220
|
+
<Select value={type} onValueChange={(v) => handleTypeChange(v as ComponentPropertyType)}>
|
|
221
|
+
<SelectTrigger className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200">
|
|
222
|
+
<SelectValue />
|
|
223
|
+
</SelectTrigger>
|
|
224
|
+
<SelectContent className="bg-[#2c2c2c] border-[#444444]">
|
|
225
|
+
{PROPERTY_TYPE_OPTIONS.map((option) => (
|
|
226
|
+
<SelectItem
|
|
227
|
+
key={option.value}
|
|
228
|
+
value={option.value}
|
|
229
|
+
className="text-gray-200 focus:bg-[#444444] focus:text-white"
|
|
230
|
+
>
|
|
231
|
+
<div className="flex items-center gap-2">
|
|
232
|
+
{option.icon}
|
|
233
|
+
<div>
|
|
234
|
+
<span>{option.label}</span>
|
|
235
|
+
<span className="text-xs text-gray-500 ml-2">
|
|
236
|
+
- {option.description}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</SelectItem>
|
|
241
|
+
))}
|
|
242
|
+
</SelectContent>
|
|
243
|
+
</Select>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
{/* Property Name */}
|
|
247
|
+
<div className="grid gap-1.5">
|
|
248
|
+
<Label className="text-xs text-gray-400">Name</Label>
|
|
249
|
+
<Input
|
|
250
|
+
value={name}
|
|
251
|
+
onChange={(e) => setName(e.target.value)}
|
|
252
|
+
placeholder="Property name..."
|
|
253
|
+
className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
{/* Description */}
|
|
258
|
+
<div className="grid gap-1.5">
|
|
259
|
+
<Label className="text-xs text-gray-400">Description (optional)</Label>
|
|
260
|
+
<Input
|
|
261
|
+
value={description}
|
|
262
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
263
|
+
placeholder="What this property controls..."
|
|
264
|
+
className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* Group */}
|
|
269
|
+
<div className="grid gap-1.5">
|
|
270
|
+
<Label className="text-xs text-gray-400">Group (optional)</Label>
|
|
271
|
+
<Input
|
|
272
|
+
value={group}
|
|
273
|
+
onChange={(e) => setGroup(e.target.value)}
|
|
274
|
+
placeholder="Property group..."
|
|
275
|
+
className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
{/* Default Value */}
|
|
280
|
+
<div className="grid gap-1.5">
|
|
281
|
+
<Label className="text-xs text-gray-400">Default Value</Label>
|
|
282
|
+
{type === 'boolean' ? (
|
|
283
|
+
<div className="flex items-center gap-2">
|
|
284
|
+
<Checkbox
|
|
285
|
+
id="default-value-checkbox"
|
|
286
|
+
checked={defaultValue as boolean}
|
|
287
|
+
onCheckedChange={(checked) => setDefaultValue(checked === true)}
|
|
288
|
+
className="border-[#444444] data-[state=checked]:bg-[#0d99ff]"
|
|
289
|
+
/>
|
|
290
|
+
<label
|
|
291
|
+
htmlFor="default-value-checkbox"
|
|
292
|
+
className="text-sm text-gray-300 cursor-pointer"
|
|
293
|
+
>
|
|
294
|
+
{defaultValue ? 'Visible (true)' : 'Hidden (false)'}
|
|
295
|
+
</label>
|
|
296
|
+
</div>
|
|
297
|
+
) : (
|
|
298
|
+
<Input
|
|
299
|
+
value={defaultValue as string}
|
|
300
|
+
onChange={(e) => setDefaultValue(e.target.value)}
|
|
301
|
+
placeholder="Default value..."
|
|
302
|
+
className="h-8 text-sm bg-[#2c2c2c] border-[#444444] text-gray-200 placeholder:text-gray-500"
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{/* Required */}
|
|
308
|
+
<div className="flex items-center gap-2">
|
|
309
|
+
<Checkbox
|
|
310
|
+
id="required-checkbox"
|
|
311
|
+
checked={required}
|
|
312
|
+
onCheckedChange={(checked) => setRequired(checked === true)}
|
|
313
|
+
className="border-[#444444] data-[state=checked]:bg-[#0d99ff]"
|
|
314
|
+
/>
|
|
315
|
+
<label
|
|
316
|
+
htmlFor="required-checkbox"
|
|
317
|
+
className="text-sm text-gray-300 cursor-pointer"
|
|
318
|
+
>
|
|
319
|
+
Required property
|
|
320
|
+
</label>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Target Elements */}
|
|
324
|
+
<div className="grid gap-1.5">
|
|
325
|
+
<Label className="text-xs text-gray-400">
|
|
326
|
+
Target Elements
|
|
327
|
+
{targetElementIds.length > 0 && (
|
|
328
|
+
<span className="ml-1 text-[#4fb8ff]">({targetElementIds.length} selected)</span>
|
|
329
|
+
)}
|
|
330
|
+
</Label>
|
|
331
|
+
<div className="max-h-32 overflow-y-auto bg-[#2c2c2c] border border-[#444444] rounded-md p-2 space-y-1">
|
|
332
|
+
{availableElements.length === 0 ? (
|
|
333
|
+
<p className="text-xs text-gray-500 py-2 text-center">
|
|
334
|
+
No elements available
|
|
335
|
+
</p>
|
|
336
|
+
) : (
|
|
337
|
+
availableElements.map((element) => (
|
|
338
|
+
<div
|
|
339
|
+
key={element.id}
|
|
340
|
+
className={cn(
|
|
341
|
+
'flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors',
|
|
342
|
+
targetElementIds.includes(element.id)
|
|
343
|
+
? 'bg-[#0d99ff]/20 border border-blue-600/40'
|
|
344
|
+
: 'hover:bg-[#444444]'
|
|
345
|
+
)}
|
|
346
|
+
onClick={() => toggleTargetElement(element.id)}
|
|
347
|
+
>
|
|
348
|
+
<Checkbox
|
|
349
|
+
checked={targetElementIds.includes(element.id)}
|
|
350
|
+
onCheckedChange={() => toggleTargetElement(element.id)}
|
|
351
|
+
className="border-[#4a4a4a] data-[state=checked]:bg-[#0d99ff]"
|
|
352
|
+
/>
|
|
353
|
+
<span className="text-xs text-gray-300 truncate">{element.name}</span>
|
|
354
|
+
<span className="text-[10px] text-gray-500 ml-auto truncate max-w-20">
|
|
355
|
+
{element.id}
|
|
356
|
+
</span>
|
|
357
|
+
</div>
|
|
358
|
+
))
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
{/* Allowed Components (for Instance Swap) */}
|
|
364
|
+
{type === 'instanceSwap' && (
|
|
365
|
+
<div className="grid gap-1.5">
|
|
366
|
+
<Label className="text-xs text-gray-400">
|
|
367
|
+
Allowed Components
|
|
368
|
+
{allowedComponentIds.length > 0 && (
|
|
369
|
+
<span className="ml-1 text-[#4fb8ff]">({allowedComponentIds.length} selected)</span>
|
|
370
|
+
)}
|
|
371
|
+
</Label>
|
|
372
|
+
<div className="max-h-32 overflow-y-auto bg-[#2c2c2c] border border-[#444444] rounded-md p-2 space-y-1">
|
|
373
|
+
{availableComponents.length === 0 ? (
|
|
374
|
+
<p className="text-xs text-gray-500 py-2 text-center">
|
|
375
|
+
No components available for swap
|
|
376
|
+
</p>
|
|
377
|
+
) : (
|
|
378
|
+
availableComponents.map((component) => (
|
|
379
|
+
<div
|
|
380
|
+
key={component.id}
|
|
381
|
+
className={cn(
|
|
382
|
+
'flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors',
|
|
383
|
+
allowedComponentIds.includes(component.id)
|
|
384
|
+
? 'bg-[#0d99ff]/20 border border-blue-600/40'
|
|
385
|
+
: 'hover:bg-[#444444]'
|
|
386
|
+
)}
|
|
387
|
+
onClick={() => toggleAllowedComponent(component.id)}
|
|
388
|
+
>
|
|
389
|
+
<Checkbox
|
|
390
|
+
checked={allowedComponentIds.includes(component.id)}
|
|
391
|
+
onCheckedChange={() => toggleAllowedComponent(component.id)}
|
|
392
|
+
className="border-[#4a4a4a] data-[state=checked]:bg-[#0d99ff]"
|
|
393
|
+
/>
|
|
394
|
+
<span className="text-xs text-gray-300 truncate">{component.name}</span>
|
|
395
|
+
</div>
|
|
396
|
+
))
|
|
397
|
+
)}
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
{/* Actions */}
|
|
403
|
+
<div className="flex items-center justify-end gap-2 pt-2 border-t border-[#444444]">
|
|
404
|
+
<Button
|
|
405
|
+
variant="ghost"
|
|
406
|
+
size="sm"
|
|
407
|
+
onClick={onCancel}
|
|
408
|
+
className="text-gray-400 hover:text-gray-200"
|
|
409
|
+
>
|
|
410
|
+
Cancel
|
|
411
|
+
</Button>
|
|
412
|
+
<Button
|
|
413
|
+
size="sm"
|
|
414
|
+
onClick={handleSave}
|
|
415
|
+
disabled={!isValid}
|
|
416
|
+
className="bg-[#0d99ff] hover:bg-[#0c8ce9] text-white"
|
|
417
|
+
>
|
|
418
|
+
<Check className="h-3.5 w-3.5 mr-1" />
|
|
419
|
+
{isNew ? 'Add' : 'Update'}
|
|
420
|
+
</Button>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export default ComponentPropertyEditor;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CSSエディタダイアログ
|
|
5
|
+
* インポートされたCSSを編集するためのダイアログ
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
9
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../components/ui/dialog';
|
|
10
|
+
import { Button } from '../../components/ui/button';
|
|
11
|
+
import { Textarea } from '../../components/ui/textarea';
|
|
12
|
+
import { Code, Trash2, RefreshCw } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
export interface CssEditorDialogProps {
|
|
15
|
+
isOpen: boolean;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
initialCss: string;
|
|
18
|
+
onSave: (css: string) => void;
|
|
19
|
+
onClear?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function CssEditorDialog({
|
|
23
|
+
isOpen,
|
|
24
|
+
onClose,
|
|
25
|
+
initialCss,
|
|
26
|
+
onSave,
|
|
27
|
+
onClear,
|
|
28
|
+
}: CssEditorDialogProps) {
|
|
29
|
+
const [css, setCss] = useState(initialCss);
|
|
30
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
31
|
+
|
|
32
|
+
// ダイアログが開かれたときに初期値をセット
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (isOpen) {
|
|
35
|
+
setCss(initialCss);
|
|
36
|
+
setHasChanges(false);
|
|
37
|
+
}
|
|
38
|
+
}, [isOpen, initialCss]);
|
|
39
|
+
|
|
40
|
+
// CSS変更ハンドラ
|
|
41
|
+
const handleCssChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
42
|
+
setCss(e.target.value);
|
|
43
|
+
setHasChanges(e.target.value !== initialCss);
|
|
44
|
+
}, [initialCss]);
|
|
45
|
+
|
|
46
|
+
// 保存ハンドラ
|
|
47
|
+
const handleSave = useCallback(() => {
|
|
48
|
+
onSave(css);
|
|
49
|
+
onClose();
|
|
50
|
+
}, [css, onSave, onClose]);
|
|
51
|
+
|
|
52
|
+
// クリアハンドラ
|
|
53
|
+
const handleClear = useCallback(() => {
|
|
54
|
+
if (onClear) {
|
|
55
|
+
onClear();
|
|
56
|
+
onClose();
|
|
57
|
+
}
|
|
58
|
+
}, [onClear, onClose]);
|
|
59
|
+
|
|
60
|
+
// リセットハンドラ
|
|
61
|
+
const handleReset = useCallback(() => {
|
|
62
|
+
setCss(initialCss);
|
|
63
|
+
setHasChanges(false);
|
|
64
|
+
}, [initialCss]);
|
|
65
|
+
|
|
66
|
+
// CSSの行数を計算
|
|
67
|
+
const lineCount = css.split('\n').length;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
|
71
|
+
<DialogContent className="sm:max-w-[900px] w-[90vw] h-[90vh] max-h-[90vh] bg-[#2c2c2c] border-[#444444] text-white flex flex-col">
|
|
72
|
+
<DialogHeader className="flex-shrink-0">
|
|
73
|
+
<DialogTitle className="text-white flex items-center gap-2">
|
|
74
|
+
<Code className="w-5 h-5" />
|
|
75
|
+
インポートCSS編集
|
|
76
|
+
</DialogTitle>
|
|
77
|
+
</DialogHeader>
|
|
78
|
+
|
|
79
|
+
<div className="flex-1 min-h-0 flex flex-col gap-3 overflow-hidden">
|
|
80
|
+
{/* 情報 */}
|
|
81
|
+
<div className="text-xs text-gray-400 flex items-center justify-between flex-shrink-0">
|
|
82
|
+
<span>
|
|
83
|
+
インポート時に抽出されたCSSを編集できます。
|
|
84
|
+
変更はリアルタイムでプレビューに反映されます。
|
|
85
|
+
</span>
|
|
86
|
+
<span className="text-gray-500">
|
|
87
|
+
{lineCount}行 / {css.length}文字
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* CSSエディタ */}
|
|
92
|
+
<div className="flex-1 min-h-0 overflow-hidden">
|
|
93
|
+
<Textarea
|
|
94
|
+
value={css}
|
|
95
|
+
onChange={handleCssChange}
|
|
96
|
+
className="w-full h-full bg-[#1e1e1e] border-[#444444] text-white font-mono text-sm resize-none"
|
|
97
|
+
placeholder={`.class-name {
|
|
98
|
+
property: value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ホバー状態 */
|
|
102
|
+
.button:hover {
|
|
103
|
+
background-color: #0066ff;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* メディアクエリ */
|
|
107
|
+
@media (max-width: 768px) {
|
|
108
|
+
.container {
|
|
109
|
+
padding: 1rem;
|
|
110
|
+
}
|
|
111
|
+
}`}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* CSSがない場合のヒント */}
|
|
116
|
+
{!initialCss && !css && (
|
|
117
|
+
<div className="text-xs text-gray-500 p-3 bg-[#1e1e1e] rounded border border-[#444444] flex-shrink-0">
|
|
118
|
+
💡 ヒント: URLからHTMLをインポートすると、ページのCSSも自動的に抽出されます。
|
|
119
|
+
手動でCSSを追加することもできます。
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<DialogFooter className="flex items-center justify-between sm:justify-between gap-2 flex-shrink-0">
|
|
125
|
+
<div className="flex items-center gap-2">
|
|
126
|
+
{onClear && initialCss && (
|
|
127
|
+
<Button
|
|
128
|
+
variant="ghost"
|
|
129
|
+
onClick={handleClear}
|
|
130
|
+
className="text-red-400 hover:text-red-300 hover:bg-red-500/20"
|
|
131
|
+
>
|
|
132
|
+
<Trash2 className="w-4 h-4 mr-2" />
|
|
133
|
+
CSSを削除
|
|
134
|
+
</Button>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
<div className="flex items-center gap-2">
|
|
138
|
+
{hasChanges && (
|
|
139
|
+
<Button
|
|
140
|
+
variant="ghost"
|
|
141
|
+
onClick={handleReset}
|
|
142
|
+
className="text-gray-400 hover:text-white"
|
|
143
|
+
>
|
|
144
|
+
<RefreshCw className="w-4 h-4 mr-2" />
|
|
145
|
+
リセット
|
|
146
|
+
</Button>
|
|
147
|
+
)}
|
|
148
|
+
<Button
|
|
149
|
+
variant="ghost"
|
|
150
|
+
onClick={onClose}
|
|
151
|
+
className="text-gray-400 hover:text-white"
|
|
152
|
+
>
|
|
153
|
+
キャンセル
|
|
154
|
+
</Button>
|
|
155
|
+
<Button
|
|
156
|
+
onClick={handleSave}
|
|
157
|
+
className="bg-[#0d99ff] hover:bg-[#0c8ce9]"
|
|
158
|
+
>
|
|
159
|
+
適用
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
</DialogFooter>
|
|
163
|
+
</DialogContent>
|
|
164
|
+
</Dialog>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default CssEditorDialog;
|