diff --git a/src/components/SchemaEditorPage.tsx b/src/components/SchemaEditorPage.tsx index 617beb4..f753fa0 100644 --- a/src/components/SchemaEditorPage.tsx +++ b/src/components/SchemaEditorPage.tsx @@ -1,158 +1,5 @@ -import { useSchemaEditor } from '@/hooks/ui/use-schema-editor' -import { useDragDrop } from '@/hooks/ui/use-drag-drop' -import { useJsonExport } from '@/hooks/ui/use-json-export' -import { SchemaEditorLayout } from '@/components/organisms' -import { ComponentDefinition } from '@/lib/component-definitions' -import { UIComponent } from '@/types/json-ui' -import { toast } from 'sonner' -import { PageSchema } from '@/types/json-ui' +import { SchemaEditorWorkspace } from '@/components/schema-editor/SchemaEditorWorkspace' export function SchemaEditorPage() { - const { - components, - selectedId, - hoveredId, - setSelectedId, - setHoveredId, - findComponentById, - addComponent, - updateComponent, - deleteComponent, - moveComponent, - clearAll, - } = useSchemaEditor() - - const { - draggedItem, - dropTarget, - dropPosition, - handleDragStart, - handleDragEnd, - handleDragOver, - handleDragLeave, - handleDrop, - } = useDragDrop() - - const { exportToJson, copyToClipboard, importFromJson } = useJsonExport() - - const handleComponentDragStart = (component: ComponentDefinition, e: React.DragEvent) => { - const newComponent: UIComponent = { - id: `${component.type.toLowerCase()}-${Date.now()}`, - type: component.type, - props: component.defaultProps || {}, - children: component.canHaveChildren ? [] : undefined, - } - - handleDragStart({ - id: 'new', - type: 'component', - data: newComponent, - }, e) - } - - const handleComponentTreeDragStart = (id: string, e: React.DragEvent) => { - handleDragStart({ - id, - type: 'existing', - data: id, - }, e) - } - - const handleCanvasDrop = (targetId: string, e: React.DragEvent) => { - if (!draggedItem) return - - const position = dropPosition || 'inside' - - if (draggedItem.type === 'component') { - addComponent(draggedItem.data, targetId === 'root' ? undefined : targetId, position) - } else if (draggedItem.type === 'existing') { - if (draggedItem.data !== targetId) { - moveComponent(draggedItem.data, targetId, position) - } - } - - handleDrop(targetId, e) - } - - const handleExportJson = () => { - const schema: PageSchema = { - id: 'custom-page', - name: 'Custom Page', - layout: { type: 'single' }, - dataSources: [], - components, - } - exportToJson(schema, 'schema.json') - } - - const handleCopyJson = () => { - const schema: PageSchema = { - id: 'custom-page', - name: 'Custom Page', - layout: { type: 'single' }, - dataSources: [], - components, - } - copyToClipboard(schema) - } - - const handleImport = () => { - const input = document.createElement('input') - input.type = 'file' - input.accept = '.json' - input.onchange = (e: any) => { - const file = e.target?.files?.[0] - if (file) { - importFromJson(file, (data) => { - if (data.components) { - clearAll() - data.components.forEach((comp: UIComponent) => { - addComponent(comp) - }) - } - }) - } - } - input.click() - } - - const handlePreview = () => { - toast.info('Preview mode coming soon') - } - - const selectedComponent = selectedId ? findComponentById(selectedId) : null - - return ( - setHoveredId(null)} - onComponentDragStart={handleComponentDragStart} - onTreeDragStart={handleComponentTreeDragStart} - onDragOver={handleDragOver} - onDragLeave={handleDragLeave} - onDrop={handleCanvasDrop} - onUpdate={(updates) => { - if (selectedId) { - updateComponent(selectedId, updates) - } - }} - onDelete={() => { - if (selectedId) { - deleteComponent(selectedId) - } - }} - onImport={handleImport} - onExport={handleExportJson} - onCopy={handleCopyJson} - onPreview={handlePreview} - onClear={clearAll} - /> - ) + return } diff --git a/src/components/molecules/ComponentTree.tsx b/src/components/molecules/ComponentTree.tsx index 8c7f7db..5eebde6 100644 --- a/src/components/molecules/ComponentTree.tsx +++ b/src/components/molecules/ComponentTree.tsx @@ -1,11 +1,9 @@ import { useState, useCallback } from 'react' import { UIComponent } from '@/types/json-ui' -import { ComponentTreeNode } from '@/components/atoms/ComponentTreeNode' -import { PanelHeader } from '@/components/atoms' import { ScrollArea } from '@/components/ui/scroll-area' -import { Button } from '@/components/ui/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { Tree, CaretDown, CaretRight } from '@phosphor-icons/react' +import { ComponentTreeHeader } from '@/components/molecules/component-tree/ComponentTreeHeader' +import { ComponentTreeEmptyState } from '@/components/molecules/component-tree/ComponentTreeEmptyState' +import { ComponentTreeNodes } from '@/components/molecules/component-tree/ComponentTreeNodes' interface ComponentTreeProps { components: UIComponent[] @@ -40,8 +38,8 @@ export function ComponentTree({ const getAllComponentIds = useCallback((comps: UIComponent[]): string[] => { const ids: string[] = [] - const traverse = (components: UIComponent[]) => { - components.forEach((comp) => { + const traverse = (nodes: UIComponent[]) => { + nodes.forEach((comp) => { if (Array.isArray(comp.children) && comp.children.length > 0) { ids.push(comp.id) traverse(comp.children) @@ -73,90 +71,36 @@ export function ComponentTree({ }) }, []) - const renderTree = (comps: UIComponent[], depth = 0) => { - return comps.map((comp) => { - const hasChildren = Array.isArray(comp.children) && comp.children.length > 0 - const isExpanded = expandedIds.has(comp.id) - - return ( -
- onSelect(comp.id)} - onHover={() => onHover(comp.id)} - onHoverEnd={onHoverEnd} - onDragStart={(e) => onDragStart(comp.id, e)} - onDragOver={(e) => onDragOver(comp.id, e)} - onDragLeave={onDragLeave} - onDrop={(e) => onDrop(comp.id, e)} - depth={depth} - hasChildren={hasChildren} - isExpanded={isExpanded} - onToggleExpand={() => toggleExpand(comp.id)} - /> - {hasChildren && isExpanded && comp.children && ( -
{renderTree(comp.children, depth + 1)}
- )} -
- ) - }) - } - return (
-
-
- } - /> - {components.length > 0 && ( -
- - - - - Expand All - - - - - - Collapse All - -
- )} -
-
- + + {components.length === 0 ? ( -
- -

No components yet

-

Drag components from the palette

-
+ ) : ( -
{renderTree(components)}
+
+ +
)}
diff --git a/src/components/molecules/PropertyEditor.tsx b/src/components/molecules/PropertyEditor.tsx index 2803500..0cbd56b 100644 --- a/src/components/molecules/PropertyEditor.tsx +++ b/src/components/molecules/PropertyEditor.tsx @@ -1,10 +1,12 @@ import { UIComponent } from '@/types/json-ui' -import { PropertyEditorField } from '@/components/atoms/PropertyEditorField' -import { PanelHeader, Badge, IconButton, Stack, Text, EmptyStateIcon } from '@/components/atoms' import { ScrollArea } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' -import { Sliders, Trash } from '@phosphor-icons/react' import { getComponentDef } from '@/lib/component-definitions' +import { PropertyEditorEmptyState } from '@/components/molecules/property-editor/PropertyEditorEmptyState' +import { propertyEditorConfig } from '@/components/molecules/property-editor/propertyEditorConfig' +import { PropertyEditorHeader } from '@/components/molecules/property-editor/PropertyEditorHeader' +import { PropertyEditorSection } from '@/components/molecules/property-editor/PropertyEditorSection' +import { Stack } from '@/components/atoms' interface PropertyEditorProps { component: UIComponent | null @@ -14,22 +16,12 @@ interface PropertyEditorProps { export function PropertyEditor({ component, onUpdate, onDelete }: PropertyEditorProps) { if (!component) { - return ( -
- - } /> - - No component selected - Select a component to edit its properties - - -
- ) + return } const def = getComponentDef(component.type) - - const handlePropChange = (key: string, value: any) => { + + const handlePropChange = (key: string, value: unknown) => { onUpdate({ props: { ...component.props, @@ -38,132 +30,33 @@ export function PropertyEditor({ component, onUpdate, onDelete }: PropertyEditor }) } - const commonProps = [ - { name: 'className', label: 'CSS Classes', type: 'text' as const }, - ] - - const typeSpecificProps: Record> = { - Button: [ - { - name: 'variant', - label: 'Variant', - type: 'select', - options: [ - { label: 'Default', value: 'default' }, - { label: 'Destructive', value: 'destructive' }, - { label: 'Outline', value: 'outline' }, - { label: 'Secondary', value: 'secondary' }, - { label: 'Ghost', value: 'ghost' }, - { label: 'Link', value: 'link' }, - ] - }, - { name: 'children', label: 'Text', type: 'text' }, - { name: 'disabled', label: 'Disabled', type: 'boolean' }, - ], - Input: [ - { name: 'placeholder', label: 'Placeholder', type: 'text' }, - { name: 'type', label: 'Type', type: 'select', options: [ - { label: 'Text', value: 'text' }, - { label: 'Password', value: 'password' }, - { label: 'Email', value: 'email' }, - { label: 'Number', value: 'number' }, - ]}, - { name: 'disabled', label: 'Disabled', type: 'boolean' }, - ], - Heading: [ - { name: 'level', label: 'Level', type: 'select', options: [ - { label: 'H1', value: '1' }, - { label: 'H2', value: '2' }, - { label: 'H3', value: '3' }, - { label: 'H4', value: '4' }, - ]}, - { name: 'children', label: 'Text', type: 'text' }, - ], - Text: [ - { name: 'children', label: 'Content', type: 'textarea' }, - ], - Badge: [ - { name: 'variant', label: 'Variant', type: 'select', options: [ - { label: 'Default', value: 'default' }, - { label: 'Secondary', value: 'secondary' }, - { label: 'Destructive', value: 'destructive' }, - { label: 'Outline', value: 'outline' }, - ]}, - { name: 'children', label: 'Text', type: 'text' }, - ], - Progress: [ - { name: 'value', label: 'Value', type: 'number' }, - ], - Grid: [ - { name: 'columns', label: 'Columns', type: 'number' }, - { name: 'gap', label: 'Gap', type: 'number' }, - ], - } - - const props = typeSpecificProps[component.type] || [] + const props = propertyEditorConfig.typeSpecificProps[component.type] || [] return (
-
- - - {def?.label || component.type} - - #{component.id} - - } - icon={} - actions={ - } - variant="ghost" - size="sm" - onClick={onDelete} - className="text-destructive hover:text-destructive hover:bg-destructive/10" - /> - } - /> -
- + + - - - Component Properties - - {props.map((prop) => ( - - ))} - + - - - Common Properties - - {commonProps.map((prop) => ( - - ))} - +
diff --git a/src/components/molecules/component-tree/ComponentTreeEmptyState.tsx b/src/components/molecules/component-tree/ComponentTreeEmptyState.tsx new file mode 100644 index 0000000..125b8ed --- /dev/null +++ b/src/components/molecules/component-tree/ComponentTreeEmptyState.tsx @@ -0,0 +1,14 @@ +import { componentTreeConfig } from '@/components/molecules/component-tree/componentTreeConfig' +import { componentTreeIcons } from '@/components/molecules/component-tree/componentTreeIcons' + +export function ComponentTreeEmptyState() { + const Icon = componentTreeIcons[componentTreeConfig.icon as keyof typeof componentTreeIcons] + + return ( +
+ +

{componentTreeConfig.emptyState.title}

+

{componentTreeConfig.emptyState.description}

+
+ ) +} diff --git a/src/components/molecules/component-tree/ComponentTreeHeader.tsx b/src/components/molecules/component-tree/ComponentTreeHeader.tsx new file mode 100644 index 0000000..722481b --- /dev/null +++ b/src/components/molecules/component-tree/ComponentTreeHeader.tsx @@ -0,0 +1,65 @@ +import { PanelHeader } from '@/components/atoms' +import { Button } from '@/components/ui/button' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { componentTreeConfig } from '@/components/molecules/component-tree/componentTreeConfig' +import { componentTreeIcons } from '@/components/molecules/component-tree/componentTreeIcons' +import { CaretDown, CaretRight } from '@phosphor-icons/react' + +interface ComponentTreeHeaderProps { + componentsCount: number + onExpandAll: () => void + onCollapseAll: () => void +} + +export function ComponentTreeHeader({ + componentsCount, + onExpandAll, + onCollapseAll, +}: ComponentTreeHeaderProps) { + const Icon = componentTreeIcons[componentTreeConfig.icon as keyof typeof componentTreeIcons] + const subtitleLabel = componentsCount === 1 + ? componentTreeConfig.subtitle.singular + : componentTreeConfig.subtitle.plural + + return ( +
+
+ } + /> + {componentsCount > 0 && ( +
+ + + + + {componentTreeConfig.tooltips.expandAll} + + + + + + {componentTreeConfig.tooltips.collapseAll} + +
+ )} +
+
+ ) +} diff --git a/src/components/molecules/component-tree/ComponentTreeNodes.tsx b/src/components/molecules/component-tree/ComponentTreeNodes.tsx new file mode 100644 index 0000000..12bbf62 --- /dev/null +++ b/src/components/molecules/component-tree/ComponentTreeNodes.tsx @@ -0,0 +1,91 @@ +import { UIComponent } from '@/types/json-ui' +import { ComponentTreeNode } from '@/components/atoms/ComponentTreeNode' + +interface ComponentTreeNodesProps { + components: UIComponent[] + depth?: number + expandedIds: Set + selectedId: string | null + hoveredId: string | null + draggedOverId: string | null + dropPosition: 'before' | 'after' | 'inside' | null + onSelect: (id: string) => void + onHover: (id: string) => void + onHoverEnd: () => void + onDragStart: (id: string, e: React.DragEvent) => void + onDragOver: (id: string, e: React.DragEvent) => void + onDragLeave: (e: React.DragEvent) => void + onDrop: (id: string, e: React.DragEvent) => void + onToggleExpand: (id: string) => void +} + +export function ComponentTreeNodes({ + components, + depth = 0, + expandedIds, + selectedId, + hoveredId, + draggedOverId, + dropPosition, + onSelect, + onHover, + onHoverEnd, + onDragStart, + onDragOver, + onDragLeave, + onDrop, + onToggleExpand, +}: ComponentTreeNodesProps) { + return ( + <> + {components.map((comp) => { + const hasChildren = Array.isArray(comp.children) && comp.children.length > 0 + const isExpanded = expandedIds.has(comp.id) + + return ( +
+ onSelect(comp.id)} + onHover={() => onHover(comp.id)} + onHoverEnd={onHoverEnd} + onDragStart={(e) => onDragStart(comp.id, e)} + onDragOver={(e) => onDragOver(comp.id, e)} + onDragLeave={onDragLeave} + onDrop={(e) => onDrop(comp.id, e)} + depth={depth} + hasChildren={hasChildren} + isExpanded={isExpanded} + onToggleExpand={() => onToggleExpand(comp.id)} + /> + {hasChildren && isExpanded && comp.children && ( +
+ +
+ )} +
+ ) + })} + + ) +} diff --git a/src/components/molecules/component-tree/componentTreeConfig.ts b/src/components/molecules/component-tree/componentTreeConfig.ts new file mode 100644 index 0000000..a9b03db --- /dev/null +++ b/src/components/molecules/component-tree/componentTreeConfig.ts @@ -0,0 +1,20 @@ +import config from '@/data/schema-editor/component-tree.json' + +export interface ComponentTreeConfig { + icon: string + title: string + subtitle: { + singular: string + plural: string + } + tooltips: { + expandAll: string + collapseAll: string + } + emptyState: { + title: string + description: string + } +} + +export const componentTreeConfig = config as ComponentTreeConfig diff --git a/src/components/molecules/component-tree/componentTreeIcons.ts b/src/components/molecules/component-tree/componentTreeIcons.ts new file mode 100644 index 0000000..fc749d7 --- /dev/null +++ b/src/components/molecules/component-tree/componentTreeIcons.ts @@ -0,0 +1,5 @@ +import { Tree } from '@phosphor-icons/react' + +export const componentTreeIcons = { + tree: Tree, +} diff --git a/src/components/molecules/property-editor/PropertyEditorEmptyState.tsx b/src/components/molecules/property-editor/PropertyEditorEmptyState.tsx new file mode 100644 index 0000000..f0e1e85 --- /dev/null +++ b/src/components/molecules/property-editor/PropertyEditorEmptyState.tsx @@ -0,0 +1,19 @@ +import { EmptyStateIcon, Stack, Text } from '@/components/atoms' +import { propertyEditorConfig } from '@/components/molecules/property-editor/propertyEditorConfig' +import { propertyEditorIcons } from '@/components/molecules/property-editor/propertyEditorIcons' + +export function PropertyEditorEmptyState() { + const Icon = propertyEditorIcons[propertyEditorConfig.icon as keyof typeof propertyEditorIcons] + + return ( +
+ + } /> + + {propertyEditorConfig.emptyState.title} + {propertyEditorConfig.emptyState.description} + + +
+ ) +} diff --git a/src/components/molecules/property-editor/PropertyEditorHeader.tsx b/src/components/molecules/property-editor/PropertyEditorHeader.tsx new file mode 100644 index 0000000..7396a4b --- /dev/null +++ b/src/components/molecules/property-editor/PropertyEditorHeader.tsx @@ -0,0 +1,40 @@ +import { Badge, IconButton, PanelHeader, Stack, Text } from '@/components/atoms' +import { propertyEditorConfig } from '@/components/molecules/property-editor/propertyEditorConfig' +import { propertyEditorIcons } from '@/components/molecules/property-editor/propertyEditorIcons' +import { Trash } from '@phosphor-icons/react' + +interface PropertyEditorHeaderProps { + componentId: string + componentLabel: string + onDelete: () => void +} + +export function PropertyEditorHeader({ componentId, componentLabel, onDelete }: PropertyEditorHeaderProps) { + const Icon = propertyEditorIcons[propertyEditorConfig.icon as keyof typeof propertyEditorIcons] + + return ( +
+ + + {componentLabel} + + #{componentId} + + } + icon={} + actions={ + } + variant="ghost" + size="sm" + onClick={onDelete} + className="text-destructive hover:text-destructive hover:bg-destructive/10" + /> + } + /> +
+ ) +} diff --git a/src/components/molecules/property-editor/PropertyEditorSection.tsx b/src/components/molecules/property-editor/PropertyEditorSection.tsx new file mode 100644 index 0000000..16230ae --- /dev/null +++ b/src/components/molecules/property-editor/PropertyEditorSection.tsx @@ -0,0 +1,32 @@ +import { PropertyEditorField } from '@/components/atoms/PropertyEditorField' +import { Stack, Text } from '@/components/atoms' +import { PropertyEditorFieldDefinition } from '@/components/molecules/property-editor/propertyEditorConfig' +import { UIComponent } from '@/types/json-ui' + +interface PropertyEditorSectionProps { + title: string + fields: PropertyEditorFieldDefinition[] + component: UIComponent + onChange: (key: string, value: unknown) => void +} + +export function PropertyEditorSection({ title, fields, component, onChange }: PropertyEditorSectionProps) { + return ( + + + {title} + + {fields.map((field) => ( + + ))} + + ) +} diff --git a/src/components/molecules/property-editor/propertyEditorConfig.ts b/src/components/molecules/property-editor/propertyEditorConfig.ts new file mode 100644 index 0000000..93da11b --- /dev/null +++ b/src/components/molecules/property-editor/propertyEditorConfig.ts @@ -0,0 +1,32 @@ +import config from '@/data/schema-editor/property-editor.json' + +export type PropertyEditorFieldType = 'text' | 'select' | 'boolean' | 'textarea' | 'number' + +export interface PropertyEditorOption { + label: string + value: string +} + +export interface PropertyEditorFieldDefinition { + name: string + label: string + type: PropertyEditorFieldType + options?: PropertyEditorOption[] +} + +export interface PropertyEditorConfig { + icon: string + title: string + emptyState: { + title: string + description: string + } + sections: { + componentProps: string + commonProps: string + } + commonProps: PropertyEditorFieldDefinition[] + typeSpecificProps: Record +} + +export const propertyEditorConfig = config as PropertyEditorConfig diff --git a/src/components/molecules/property-editor/propertyEditorIcons.ts b/src/components/molecules/property-editor/propertyEditorIcons.ts new file mode 100644 index 0000000..ff62433 --- /dev/null +++ b/src/components/molecules/property-editor/propertyEditorIcons.ts @@ -0,0 +1,5 @@ +import { Sliders } from '@phosphor-icons/react' + +export const propertyEditorIcons = { + sliders: Sliders, +} diff --git a/src/components/schema-editor/SchemaEditorWorkspace.tsx b/src/components/schema-editor/SchemaEditorWorkspace.tsx new file mode 100644 index 0000000..119ba50 --- /dev/null +++ b/src/components/schema-editor/SchemaEditorWorkspace.tsx @@ -0,0 +1,158 @@ +import { useSchemaEditor } from '@/hooks/ui/use-schema-editor' +import { useDragDrop } from '@/hooks/ui/use-drag-drop' +import { useJsonExport } from '@/hooks/ui/use-json-export' +import { SchemaEditorLayout } from '@/components/organisms' +import { ComponentDefinition } from '@/lib/component-definitions' +import { UIComponent, PageSchema } from '@/types/json-ui' +import { toast } from 'sonner' +import { schemaEditorConfig } from '@/components/schema-editor/schemaEditorConfig' + +export function SchemaEditorWorkspace() { + const { + components, + selectedId, + hoveredId, + setSelectedId, + setHoveredId, + findComponentById, + addComponent, + updateComponent, + deleteComponent, + moveComponent, + clearAll, + } = useSchemaEditor() + + const { + draggedItem, + dropTarget, + dropPosition, + handleDragStart, + handleDragOver, + handleDragLeave, + handleDrop, + } = useDragDrop() + + const { exportToJson, copyToClipboard, importFromJson } = useJsonExport() + + const createSchema = (): PageSchema => ({ + id: schemaEditorConfig.schema.id, + name: schemaEditorConfig.schema.name, + layout: schemaEditorConfig.schema.layout, + dataSources: [], + components, + }) + + const handleComponentDragStart = (component: ComponentDefinition, e: React.DragEvent) => { + const newComponent: UIComponent = { + id: `${component.type.toLowerCase()}-${Date.now()}`, + type: component.type, + props: component.defaultProps || {}, + children: component.canHaveChildren ? [] : undefined, + } + + handleDragStart( + { + id: 'new', + type: 'component', + data: newComponent, + }, + e, + ) + } + + const handleComponentTreeDragStart = (id: string, e: React.DragEvent) => { + handleDragStart( + { + id, + type: 'existing', + data: id, + }, + e, + ) + } + + const handleCanvasDrop = (targetId: string, e: React.DragEvent) => { + if (!draggedItem) return + + const position = dropPosition || 'inside' + + if (draggedItem.type === 'component') { + addComponent(draggedItem.data, targetId === 'root' ? undefined : targetId, position) + } else if (draggedItem.type === 'existing') { + if (draggedItem.data !== targetId) { + moveComponent(draggedItem.data, targetId, position) + } + } + + handleDrop(targetId, e) + } + + const handleExportJson = () => { + exportToJson(createSchema(), schemaEditorConfig.export.fileName) + } + + const handleCopyJson = () => { + copyToClipboard(createSchema()) + } + + const handleImport = () => { + const input = document.createElement('input') + input.type = 'file' + input.accept = schemaEditorConfig.import.accept + input.onchange = (event: Event) => { + const target = event.target as HTMLInputElement + const file = target?.files?.[0] + if (file) { + importFromJson(file, (data) => { + if (data.components) { + clearAll() + data.components.forEach((comp: UIComponent) => { + addComponent(comp) + }) + } + }) + } + } + input.click() + } + + const handlePreview = () => { + toast.info(schemaEditorConfig.preview.message) + } + + const selectedComponent = selectedId ? findComponentById(selectedId) : null + + return ( + setHoveredId(null)} + onComponentDragStart={handleComponentDragStart} + onTreeDragStart={handleComponentTreeDragStart} + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleCanvasDrop} + onUpdate={(updates) => { + if (selectedId) { + updateComponent(selectedId, updates) + } + }} + onDelete={() => { + if (selectedId) { + deleteComponent(selectedId) + } + }} + onImport={handleImport} + onExport={handleExportJson} + onCopy={handleCopyJson} + onPreview={handlePreview} + onClear={clearAll} + /> + ) +} diff --git a/src/components/schema-editor/schemaEditorConfig.ts b/src/components/schema-editor/schemaEditorConfig.ts new file mode 100644 index 0000000..89e1022 --- /dev/null +++ b/src/components/schema-editor/schemaEditorConfig.ts @@ -0,0 +1,22 @@ +import config from '@/data/schema-editor/schema-editor-page.json' + +export interface SchemaEditorConfig { + schema: { + id: string + name: string + layout: { + type: string + } + } + export: { + fileName: string + } + import: { + accept: string + } + preview: { + message: string + } +} + +export const schemaEditorConfig = config as SchemaEditorConfig diff --git a/src/data/schema-editor/component-tree.json b/src/data/schema-editor/component-tree.json new file mode 100644 index 0000000..84732f4 --- /dev/null +++ b/src/data/schema-editor/component-tree.json @@ -0,0 +1,16 @@ +{ + "icon": "tree", + "title": "Component Tree", + "subtitle": { + "singular": "component", + "plural": "components" + }, + "tooltips": { + "expandAll": "Expand All", + "collapseAll": "Collapse All" + }, + "emptyState": { + "title": "No components yet", + "description": "Drag components from the palette" + } +} diff --git a/src/data/schema-editor/property-editor.json b/src/data/schema-editor/property-editor.json new file mode 100644 index 0000000..d9ca2f3 --- /dev/null +++ b/src/data/schema-editor/property-editor.json @@ -0,0 +1,91 @@ +{ + "icon": "sliders", + "title": "Properties", + "emptyState": { + "title": "No component selected", + "description": "Select a component to edit its properties" + }, + "sections": { + "componentProps": "Component Properties", + "commonProps": "Common Properties" + }, + "commonProps": [ + { + "name": "className", + "label": "CSS Classes", + "type": "text" + } + ], + "typeSpecificProps": { + "Button": [ + { + "name": "variant", + "label": "Variant", + "type": "select", + "options": [ + { "label": "Default", "value": "default" }, + { "label": "Destructive", "value": "destructive" }, + { "label": "Outline", "value": "outline" }, + { "label": "Secondary", "value": "secondary" }, + { "label": "Ghost", "value": "ghost" }, + { "label": "Link", "value": "link" } + ] + }, + { "name": "children", "label": "Text", "type": "text" }, + { "name": "disabled", "label": "Disabled", "type": "boolean" } + ], + "Input": [ + { "name": "placeholder", "label": "Placeholder", "type": "text" }, + { + "name": "type", + "label": "Type", + "type": "select", + "options": [ + { "label": "Text", "value": "text" }, + { "label": "Password", "value": "password" }, + { "label": "Email", "value": "email" }, + { "label": "Number", "value": "number" } + ] + }, + { "name": "disabled", "label": "Disabled", "type": "boolean" } + ], + "Heading": [ + { + "name": "level", + "label": "Level", + "type": "select", + "options": [ + { "label": "H1", "value": "1" }, + { "label": "H2", "value": "2" }, + { "label": "H3", "value": "3" }, + { "label": "H4", "value": "4" } + ] + }, + { "name": "children", "label": "Text", "type": "text" } + ], + "Text": [ + { "name": "children", "label": "Content", "type": "textarea" } + ], + "Badge": [ + { + "name": "variant", + "label": "Variant", + "type": "select", + "options": [ + { "label": "Default", "value": "default" }, + { "label": "Secondary", "value": "secondary" }, + { "label": "Destructive", "value": "destructive" }, + { "label": "Outline", "value": "outline" } + ] + }, + { "name": "children", "label": "Text", "type": "text" } + ], + "Progress": [ + { "name": "value", "label": "Value", "type": "number" } + ], + "Grid": [ + { "name": "columns", "label": "Columns", "type": "number" }, + { "name": "gap", "label": "Gap", "type": "number" } + ] + } +} diff --git a/src/data/schema-editor/schema-editor-page.json b/src/data/schema-editor/schema-editor-page.json new file mode 100644 index 0000000..00b8acd --- /dev/null +++ b/src/data/schema-editor/schema-editor-page.json @@ -0,0 +1,18 @@ +{ + "schema": { + "id": "custom-page", + "name": "Custom Page", + "layout": { + "type": "single" + } + }, + "export": { + "fileName": "schema.json" + }, + "import": { + "accept": ".json" + }, + "preview": { + "message": "Preview mode coming soon" + } +}