Generated by Spark: Create composite components in schema editor

This commit is contained in:
2026-01-17 11:27:03 +00:00
committed by GitHub
parent eb7660864d
commit 19a44c4010
12 changed files with 969 additions and 132 deletions

View File

@@ -0,0 +1,262 @@
# Composite Components - Quick Reference
## What are Composite Components?
Composite components are pre-assembled, reusable UI sections that combine multiple smaller components (atoms and molecules) into cohesive, functional units (organisms).
## Schema Editor Composites
### Core Panels
#### SchemaEditorToolbar
**Purpose**: Top action bar with import/export controls
**Location**: `src/components/organisms/SchemaEditorToolbar.tsx`
```tsx
<SchemaEditorToolbar
onImport={() => {}}
onExport={() => {}}
onCopy={() => {}}
onPreview={() => {}}
onClear={() => {}}
/>
```
#### SchemaEditorSidebar
**Purpose**: Left panel with component palette
**Location**: `src/components/organisms/SchemaEditorSidebar.tsx`
```tsx
<SchemaEditorSidebar
onDragStart={(component, e) => {}}
/>
```
#### SchemaEditorCanvas
**Purpose**: Central canvas for rendering components
**Location**: `src/components/organisms/SchemaEditorCanvas.tsx`
```tsx
<SchemaEditorCanvas
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => setHoveredId(null)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
/>
```
#### SchemaEditorPropertiesPanel
**Purpose**: Right panel with component tree and property editor
**Location**: `src/components/organisms/SchemaEditorPropertiesPanel.tsx`
```tsx
<SchemaEditorPropertiesPanel
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
selectedComponent={selectedComponent}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => setHoveredId(null)}
onDragStart={handleTreeDragStart}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
```
### Supporting Composites
#### EmptyCanvasState
**Purpose**: Empty state when no components exist
**Location**: `src/components/organisms/EmptyCanvasState.tsx`
```tsx
<EmptyCanvasState
onAddFirstComponent={() => {}}
onImportSchema={() => {}}
/>
```
#### SchemaEditorStatusBar
**Purpose**: Bottom status bar showing metrics
**Location**: `src/components/organisms/SchemaEditorStatusBar.tsx`
```tsx
<SchemaEditorStatusBar
componentCount={components.length}
selectedComponentType={selectedComponent?.type}
hasUnsavedChanges={false}
/>
```
#### SchemaCodeViewer
**Purpose**: View generated JSON schema
**Location**: `src/components/organisms/SchemaCodeViewer.tsx`
```tsx
<SchemaCodeViewer
components={components}
schema={schema}
/>
```
### Complete Layout
#### SchemaEditorLayout
**Purpose**: Orchestrates all panels into complete editor
**Location**: `src/components/organisms/SchemaEditorLayout.tsx`
```tsx
<SchemaEditorLayout
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
selectedComponent={selectedComponent}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => setHoveredId(null)}
onComponentDragStart={handleComponentDragStart}
onTreeDragStart={handleTreeDragStart}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onUpdate={handleUpdate}
onDelete={handleDelete}
onImport={handleImport}
onExport={handleExport}
onCopy={handleCopy}
onPreview={handlePreview}
onClear={clearAll}
/>
```
## When to Use
### Use Composite Components When:
✅ Building complete features (full page editors, dashboards)
✅ Need to enforce consistent layouts
✅ Want to abstract complexity from page components
✅ Creating reusable feature modules
### Use Individual Components When:
✅ Building custom layouts
✅ Need fine-grained control
✅ Creating new composite components
✅ Testing specific functionality
## Benefits
1. **Reduced Duplication**: Reuse complex layouts across pages
2. **Consistency**: Same look and behavior everywhere
3. **Maintainability**: Change once, update everywhere
4. **Testability**: Test complete features in isolation
5. **Documentation**: Self-documenting through composition
## Creating New Composites
### Step 1: Identify the Pattern
Look for repeated component combinations in your code.
### Step 2: Create the Organism
```tsx
// src/components/organisms/MyComposite.tsx
interface MyCompositeProps {
// data props
items: Item[]
selectedId: string | null
// action props
onSelect: (id: string) => void
onUpdate: (item: Item) => void
}
export function MyComposite({
items,
selectedId,
onSelect,
onUpdate
}: MyCompositeProps) {
return (
<div className="flex flex-col">
<MyHeader />
<MySidebar items={items} onSelect={onSelect} />
<MyContent
item={items.find(i => i.id === selectedId)}
onUpdate={onUpdate}
/>
</div>
)
}
```
### Step 3: Export from Index
```tsx
// src/components/organisms/index.ts
export { MyComposite } from './MyComposite'
```
### Step 4: Document
Add usage examples and prop descriptions.
## Common Patterns
### Panel Wrapper Pattern
```tsx
function MyPanel({ children }: { children: React.ReactNode }) {
return (
<div className="border-r border-border bg-card">
<div className="p-4 border-b">
<h2>Panel Title</h2>
</div>
<div className="p-4">
{children}
</div>
</div>
)
}
```
### Action Bar Pattern
```tsx
function MyActionBar({ onAction1, onAction2 }: ActionBarProps) {
return (
<div className="flex items-center gap-2 p-2 border-b">
<Button onClick={onAction1}>Action 1</Button>
<Button onClick={onAction2}>Action 2</Button>
</div>
)
}
```
### Split View Pattern
```tsx
function MySplitView({ left, right }: SplitViewProps) {
return (
<div className="flex h-full">
<div className="w-1/2 border-r">{left}</div>
<div className="w-1/2">{right}</div>
</div>
)
}
```
## Best Practices
1. **Keep Props Focused**: Each composite should have a clear, single purpose
2. **Expose Necessary Callbacks**: Don't hide important events
3. **Support Composition**: Allow children or render props when needed
4. **Document Extensively**: Provide examples and use cases
5. **Test Thoroughly**: Test with various prop combinations
6. **Keep LOC < 150**: Break down if it gets too large
## Related Docs
- [Atomic Design Principles](./atomic-design.md)
- [Schema Editor Architecture](./schema-editor-composite-components.md)
- [Component Guidelines](./component-guidelines.md)

View File

@@ -0,0 +1,212 @@
# Schema Editor Composite Components
This document describes the composite component architecture for the Schema Editor feature.
## Component Hierarchy
The Schema Editor follows the Atomic Design methodology:
### Atoms (Smallest units)
- `ComponentPaletteItem` - Individual draggable component in the palette
- `ComponentTreeNode` - Individual node in the component tree
- `PropertyEditorField` - Individual property input field
### Molecules (Simple combinations)
- `ComponentPalette` - Tabbed palette of draggable components
- `ComponentTree` - Hierarchical tree view of components
- `PropertyEditor` - Form for editing component properties
- `CanvasRenderer` - Visual canvas for rendering UI components
### Organisms (Complex combinations)
- `SchemaEditorToolbar` - Top toolbar with import/export/preview actions
- `SchemaEditorSidebar` - Left sidebar containing component palette
- `SchemaEditorCanvas` - Central canvas area with component rendering
- `SchemaEditorPropertiesPanel` - Right panel with tree + property editor
- `SchemaEditorLayout` - Complete editor layout orchestrating all panels
- `EmptyCanvasState` - Empty state displayed when no components exist
- `SchemaEditorStatusBar` - Bottom status bar showing component count
- `SchemaCodeViewer` - JSON/preview viewer for schema output
### Pages (Full features)
- `SchemaEditorPage` - Complete schema editor feature with all hooks and state
## Benefits of This Architecture
### 1. Separation of Concerns
Each component has a single, clear responsibility:
- **Toolbar**: Actions (import, export, preview)
- **Sidebar**: Component palette
- **Canvas**: Visual rendering
- **Properties Panel**: Editing selected component
- **Layout**: Orchestration
### 2. Reusability
Composite components can be used independently:
```tsx
// Use just the toolbar elsewhere
<SchemaEditorToolbar
onImport={handleImport}
onExport={handleExport}
// ...
/>
// Use just the canvas
<SchemaEditorCanvas
components={components}
selectedId={selectedId}
// ...
/>
```
### 3. Testability
Each component can be tested in isolation with mock props.
### 4. Maintainability
- Each file is <150 LOC (as per project guidelines)
- Clear dependencies between components
- Easy to locate and modify specific functionality
### 5. Composability
The `SchemaEditorLayout` component orchestrates all the panels, but you could create alternative layouts:
```tsx
// Simple layout without sidebar
<div>
<SchemaEditorToolbar {...toolbarProps} />
<div className="flex">
<SchemaEditorCanvas {...canvasProps} />
<SchemaEditorPropertiesPanel {...panelProps} />
</div>
</div>
// Or minimal layout with just canvas
<SchemaEditorCanvas {...canvasProps} />
```
## Component Props Pattern
Each composite component follows a consistent prop pattern:
### Data Props
Props representing the current state (read-only):
- `components: UIComponent[]`
- `selectedId: string | null`
- `hoveredId: string | null`
### Action Props
Props for modifying state (callbacks):
- `onSelect: (id: string | null) => void`
- `onUpdate: (updates: Partial<UIComponent>) => void`
- `onDelete: () => void`
### Drag & Drop Props
Props specifically for drag-and-drop functionality:
- `draggedOverId: string | null`
- `dropPosition: 'before' | 'after' | 'inside' | null`
- `onDragStart: (...) => void`
- `onDragOver: (...) => void`
- `onDrop: (...) => void`
## Usage Example
```tsx
import { SchemaEditorLayout } from '@/components/organisms'
import { useSchemaEditor } from '@/hooks/ui/use-schema-editor'
import { useDragDrop } from '@/hooks/ui/use-drag-drop'
function MySchemaEditor() {
const {
components,
selectedId,
// ... other state
} = useSchemaEditor()
const {
draggedOverId,
dropPosition,
// ... drag handlers
} = useDragDrop()
return (
<SchemaEditorLayout
components={components}
selectedId={selectedId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
// ... all other props
/>
)
}
```
## Extension Points
### Adding New Panels
Create a new organism component and add it to the layout:
```tsx
// New component
export function SchemaEditorMetricsPanel({ ... }) {
return <div>Metrics content</div>
}
// Add to layout
<SchemaEditorLayout>
{/* existing panels */}
<SchemaEditorMetricsPanel />
</SchemaEditorLayout>
```
### Custom Toolbars
Create alternative toolbar components:
```tsx
export function SchemaEditorCompactToolbar({ ... }) {
// Simplified toolbar with fewer buttons
}
```
### Alternative Layouts
Create new layout compositions:
```tsx
export function SchemaEditorSplitLayout({ ... }) {
// Different arrangement of the same panels
}
```
## File Organization
```
src/components/
├── atoms/
│ ├── ComponentPaletteItem.tsx
│ ├── ComponentTreeNode.tsx
│ └── PropertyEditorField.tsx
├── molecules/
│ ├── ComponentPalette.tsx
│ ├── ComponentTree.tsx
│ ├── PropertyEditor.tsx
│ └── CanvasRenderer.tsx
├── organisms/
│ ├── SchemaEditorToolbar.tsx
│ ├── SchemaEditorSidebar.tsx
│ ├── SchemaEditorCanvas.tsx
│ ├── SchemaEditorPropertiesPanel.tsx
│ ├── SchemaEditorLayout.tsx
│ ├── EmptyCanvasState.tsx
│ ├── SchemaEditorStatusBar.tsx
│ └── SchemaCodeViewer.tsx
└── SchemaEditorPage.tsx
```
## Future Enhancements
1. **Add SchemaEditorPreviewPanel** - Live preview of the schema
2. **Add SchemaEditorHistoryPanel** - Undo/redo history
3. **Add SchemaEditorTemplatesPanel** - Pre-built component templates
4. **Create SchemaEditorMobileLayout** - Responsive mobile layout
5. **Add SchemaEditorKeyboardShortcuts** - Keyboard navigation overlay
## Related Documentation
- [Component Definitions](../lib/component-definitions.ts)
- [JSON UI Types](../types/json-ui.ts)
- [Schema Editor Hook](../hooks/ui/use-schema-editor.ts)
- [Drag Drop Hook](../hooks/ui/use-drag-drop.ts)

View File

@@ -1,22 +1,9 @@
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 { ComponentPalette } from '@/components/molecules/ComponentPalette'
import { ComponentTree } from '@/components/molecules/ComponentTree'
import { PropertyEditor } from '@/components/molecules/PropertyEditor'
import { CanvasRenderer } from '@/components/molecules/CanvasRenderer'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { SchemaEditorLayout } from '@/components/organisms'
import { ComponentDefinition } from '@/lib/component-definitions'
import { UIComponent } from '@/types/json-ui'
import {
Download,
Upload,
Play,
Trash,
Copy,
Code,
} from '@phosphor-icons/react'
import { toast } from 'sonner'
import { PageSchema } from '@/types/json-ui'
@@ -136,123 +123,36 @@ export function SchemaEditorPage() {
const selectedComponent = selectedId ? findComponentById(selectedId) : null
return (
<div className="h-full flex flex-col bg-background">
<div className="border-b border-border px-6 py-3 bg-card">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
Schema Editor
</h1>
<p className="text-sm text-muted-foreground mt-1">
Build JSON UI schemas with drag-and-drop
</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={handleImport}
>
<Upload className="w-4 h-4 mr-2" />
Import
</Button>
<Button
variant="outline"
size="sm"
onClick={handleCopyJson}
>
<Copy className="w-4 h-4 mr-2" />
Copy JSON
</Button>
<Button
variant="outline"
size="sm"
onClick={handleExportJson}
>
<Download className="w-4 h-4 mr-2" />
Export
</Button>
<Separator orientation="vertical" className="h-6" />
<Button
variant="outline"
size="sm"
onClick={handlePreview}
>
<Play className="w-4 h-4 mr-2" />
Preview
</Button>
<Button
variant="outline"
size="sm"
onClick={clearAll}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash className="w-4 h-4 mr-2" />
Clear
</Button>
</div>
</div>
</div>
<div className="flex-1 flex overflow-hidden">
<div className="w-64 border-r border-border bg-card">
<ComponentPalette onDragStart={handleComponentDragStart} />
</div>
<div className="flex-1 flex flex-col">
<CanvasRenderer
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={dropTarget}
dropPosition={dropPosition}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => setHoveredId(null)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleCanvasDrop}
/>
</div>
<div className="w-80 border-l border-border bg-card flex flex-col">
<div className="flex-1 overflow-hidden">
<ComponentTree
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={dropTarget}
dropPosition={dropPosition}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => setHoveredId(null)}
onDragStart={handleComponentTreeDragStart}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleCanvasDrop}
/>
</div>
<Separator />
<div className="flex-1 overflow-hidden">
<PropertyEditor
component={selectedComponent}
onUpdate={(updates) => {
if (selectedId) {
updateComponent(selectedId, updates)
}
}}
onDelete={() => {
if (selectedId) {
deleteComponent(selectedId)
}
}}
/>
</div>
</div>
</div>
</div>
<SchemaEditorLayout
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={dropTarget}
dropPosition={dropPosition}
selectedComponent={selectedComponent}
onSelect={setSelectedId}
onHover={setHoveredId}
onHoverEnd={() => 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}
/>
)
}

View File

@@ -0,0 +1,39 @@
import { Button } from '@/components/ui/button'
import { Plus, Folder } from '@phosphor-icons/react'
interface EmptyCanvasStateProps {
onAddFirstComponent?: () => void
onImportSchema?: () => void
}
export function EmptyCanvasState({ onAddFirstComponent, onImportSchema }: EmptyCanvasStateProps) {
return (
<div className="h-full flex flex-col items-center justify-center p-8 text-center bg-muted/20">
<div className="mb-6 relative">
<div className="w-24 h-24 rounded-full bg-primary/10 flex items-center justify-center">
<Folder className="w-12 h-12 text-primary" weight="duotone" />
</div>
</div>
<h3 className="text-xl font-semibold mb-2">Empty Canvas</h3>
<p className="text-sm text-muted-foreground max-w-md mb-6">
Start building your UI by dragging components from the left panel, or import an existing schema.
</p>
<div className="flex items-center gap-3">
{onImportSchema && (
<Button variant="outline" onClick={onImportSchema}>
<Folder className="w-4 h-4 mr-2" />
Import Schema
</Button>
)}
{onAddFirstComponent && (
<Button onClick={onAddFirstComponent}>
<Plus className="w-4 h-4 mr-2" />
Add Component
</Button>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,50 @@
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Code, Eye } from '@phosphor-icons/react'
import { UIComponent } from '@/types/json-ui'
interface SchemaCodeViewerProps {
components: UIComponent[]
schema: any
}
export function SchemaCodeViewer({ components, schema }: SchemaCodeViewerProps) {
const jsonString = JSON.stringify(schema, null, 2)
return (
<div className="h-full flex flex-col bg-card">
<div className="border-b border-border px-4 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Code className="w-5 h-5 text-primary" />
<h3 className="font-semibold">Schema Output</h3>
</div>
</div>
</div>
<Tabs defaultValue="json" className="flex-1 flex flex-col">
<TabsList className="w-full justify-start px-4 pt-2">
<TabsTrigger value="json">JSON</TabsTrigger>
<TabsTrigger value="preview">Preview</TabsTrigger>
</TabsList>
<TabsContent value="json" className="flex-1 m-0 mt-2">
<ScrollArea className="h-full">
<pre className="p-4 text-xs font-mono text-foreground">
{jsonString}
</pre>
</ScrollArea>
</TabsContent>
<TabsContent value="preview" className="flex-1 m-0 mt-2">
<div className="p-4">
<p className="text-sm text-muted-foreground">
Live preview coming soon
</p>
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,48 @@
import { CanvasRenderer } from '@/components/molecules/CanvasRenderer'
import { UIComponent } from '@/types/json-ui'
interface SchemaEditorCanvasProps {
components: UIComponent[]
selectedId: string | null
hoveredId: string | null
draggedOverId: string | null
dropPosition: 'before' | 'after' | 'inside' | null
onSelect: (id: string | null) => void
onHover: (id: string | null) => void
onHoverEnd: () => void
onDragOver: (id: string, e: React.DragEvent) => void
onDragLeave: (e: React.DragEvent) => void
onDrop: (targetId: string, e: React.DragEvent) => void
}
export function SchemaEditorCanvas({
components,
selectedId,
hoveredId,
draggedOverId,
dropPosition,
onSelect,
onHover,
onHoverEnd,
onDragOver,
onDragLeave,
onDrop,
}: SchemaEditorCanvasProps) {
return (
<div className="flex-1 flex flex-col">
<CanvasRenderer
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
onSelect={onSelect}
onHover={onHover}
onHoverEnd={onHoverEnd}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
/>
</div>
)
}

View File

@@ -0,0 +1,102 @@
import { UIComponent, PageSchema } from '@/types/json-ui'
import { ComponentDefinition } from '@/lib/component-definitions'
import { SchemaEditorToolbar } from './SchemaEditorToolbar'
import { SchemaEditorSidebar } from './SchemaEditorSidebar'
import { SchemaEditorCanvas } from './SchemaEditorCanvas'
import { SchemaEditorPropertiesPanel } from './SchemaEditorPropertiesPanel'
interface SchemaEditorLayoutProps {
components: UIComponent[]
selectedId: string | null
hoveredId: string | null
draggedOverId: string | null
dropPosition: 'before' | 'after' | 'inside' | null
selectedComponent: UIComponent | null
onSelect: (id: string | null) => void
onHover: (id: string | null) => void
onHoverEnd: () => void
onComponentDragStart: (component: ComponentDefinition, e: React.DragEvent) => void
onTreeDragStart: (id: string, e: React.DragEvent) => void
onDragOver: (id: string, e: React.DragEvent) => void
onDragLeave: (e: React.DragEvent) => void
onDrop: (targetId: string, e: React.DragEvent) => void
onUpdate: (updates: Partial<UIComponent>) => void
onDelete: () => void
onImport: () => void
onExport: () => void
onCopy: () => void
onPreview: () => void
onClear: () => void
}
export function SchemaEditorLayout({
components,
selectedId,
hoveredId,
draggedOverId,
dropPosition,
selectedComponent,
onSelect,
onHover,
onHoverEnd,
onComponentDragStart,
onTreeDragStart,
onDragOver,
onDragLeave,
onDrop,
onUpdate,
onDelete,
onImport,
onExport,
onCopy,
onPreview,
onClear,
}: SchemaEditorLayoutProps) {
return (
<div className="h-full flex flex-col bg-background">
<SchemaEditorToolbar
onImport={onImport}
onExport={onExport}
onCopy={onCopy}
onPreview={onPreview}
onClear={onClear}
/>
<div className="flex-1 flex overflow-hidden">
<SchemaEditorSidebar onDragStart={onComponentDragStart} />
<SchemaEditorCanvas
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
onSelect={onSelect}
onHover={onHover}
onHoverEnd={onHoverEnd}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
/>
<SchemaEditorPropertiesPanel
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
selectedComponent={selectedComponent}
onSelect={onSelect}
onHover={onHover}
onHoverEnd={onHoverEnd}
onDragStart={onTreeDragStart}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
onUpdate={onUpdate}
onDelete={onDelete}
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
import { ComponentTree } from '@/components/molecules/ComponentTree'
import { PropertyEditor } from '@/components/molecules/PropertyEditor'
import { Separator } from '@/components/ui/separator'
import { UIComponent } from '@/types/json-ui'
interface SchemaEditorPropertiesPanelProps {
components: UIComponent[]
selectedId: string | null
hoveredId: string | null
draggedOverId: string | null
dropPosition: 'before' | 'after' | 'inside' | null
selectedComponent: UIComponent | null
onSelect: (id: string | null) => void
onHover: (id: string | null) => void
onHoverEnd: () => void
onDragStart: (id: string, e: React.DragEvent) => void
onDragOver: (id: string, e: React.DragEvent) => void
onDragLeave: (e: React.DragEvent) => void
onDrop: (targetId: string, e: React.DragEvent) => void
onUpdate: (updates: Partial<UIComponent>) => void
onDelete: () => void
}
export function SchemaEditorPropertiesPanel({
components,
selectedId,
hoveredId,
draggedOverId,
dropPosition,
selectedComponent,
onSelect,
onHover,
onHoverEnd,
onDragStart,
onDragOver,
onDragLeave,
onDrop,
onUpdate,
onDelete,
}: SchemaEditorPropertiesPanelProps) {
return (
<div className="w-80 border-l border-border bg-card flex flex-col">
<div className="flex-1 overflow-hidden">
<ComponentTree
components={components}
selectedId={selectedId}
hoveredId={hoveredId}
draggedOverId={draggedOverId}
dropPosition={dropPosition}
onSelect={onSelect}
onHover={onHover}
onHoverEnd={onHoverEnd}
onDragStart={onDragStart}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
/>
</div>
<Separator />
<div className="flex-1 overflow-hidden">
<PropertyEditor
component={selectedComponent}
onUpdate={onUpdate}
onDelete={onDelete}
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,14 @@
import { ComponentPalette } from '@/components/molecules/ComponentPalette'
import { ComponentDefinition } from '@/lib/component-definitions'
interface SchemaEditorSidebarProps {
onDragStart: (component: ComponentDefinition, e: React.DragEvent) => void
}
export function SchemaEditorSidebar({ onDragStart }: SchemaEditorSidebarProps) {
return (
<div className="w-64 border-r border-border bg-card">
<ComponentPalette onDragStart={onDragStart} />
</div>
)
}

View File

@@ -0,0 +1,46 @@
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'
interface SchemaEditorStatusBarProps {
componentCount: number
selectedComponentType?: string
hasUnsavedChanges?: boolean
className?: string
}
export function SchemaEditorStatusBar({
componentCount,
selectedComponentType,
hasUnsavedChanges = false,
className
}: SchemaEditorStatusBarProps) {
return (
<div className={cn(
"border-t border-border px-4 py-2 bg-card flex items-center justify-between text-xs text-muted-foreground",
className
)}>
<div className="flex items-center gap-4">
<span>
<span className="font-medium text-foreground">{componentCount}</span> component{componentCount !== 1 ? 's' : ''}
</span>
{selectedComponentType && (
<div className="flex items-center gap-2">
<span>Selected:</span>
<Badge variant="secondary" className="text-xs font-mono">
{selectedComponentType}
</Badge>
</div>
)}
</div>
<div className="flex items-center gap-2">
{hasUnsavedChanges && (
<Badge variant="outline" className="text-xs">
Unsaved changes
</Badge>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,85 @@
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import {
Download,
Upload,
Play,
Trash,
Copy,
} from '@phosphor-icons/react'
interface SchemaEditorToolbarProps {
onImport: () => void
onExport: () => void
onCopy: () => void
onPreview: () => void
onClear: () => void
}
export function SchemaEditorToolbar({
onImport,
onExport,
onCopy,
onPreview,
onClear,
}: SchemaEditorToolbarProps) {
return (
<div className="border-b border-border px-6 py-3 bg-card">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
Schema Editor
</h1>
<p className="text-sm text-muted-foreground mt-1">
Build JSON UI schemas with drag-and-drop
</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={onImport}
>
<Upload className="w-4 h-4 mr-2" />
Import
</Button>
<Button
variant="outline"
size="sm"
onClick={onCopy}
>
<Copy className="w-4 h-4 mr-2" />
Copy JSON
</Button>
<Button
variant="outline"
size="sm"
onClick={onExport}
>
<Download className="w-4 h-4 mr-2" />
Export
</Button>
<Separator orientation="vertical" className="h-6" />
<Button
variant="outline"
size="sm"
onClick={onPreview}
>
<Play className="w-4 h-4 mr-2" />
Preview
</Button>
<Button
variant="outline"
size="sm"
onClick={onClear}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash className="w-4 h-4 mr-2" />
Clear
</Button>
</div>
</div>
</div>
)
}

View File

@@ -3,3 +3,11 @@ export { PageHeader } from './PageHeader'
export { ToolbarActions } from './ToolbarActions'
export { AppHeader } from './AppHeader'
export { TreeListPanel } from './TreeListPanel'
export { SchemaEditorToolbar } from './SchemaEditorToolbar'
export { SchemaEditorSidebar } from './SchemaEditorSidebar'
export { SchemaEditorCanvas } from './SchemaEditorCanvas'
export { SchemaEditorPropertiesPanel } from './SchemaEditorPropertiesPanel'
export { SchemaEditorLayout } from './SchemaEditorLayout'
export { EmptyCanvasState } from './EmptyCanvasState'
export { SchemaEditorStatusBar } from './SchemaEditorStatusBar'
export { SchemaCodeViewer } from './SchemaCodeViewer'