From 46114eb347fb108d160fb51b1f1599808c6aa34c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:55:58 +0000 Subject: [PATCH 01/14] Initial plan From 20f239019fff3908d19b62d689b1b20a010e9439 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:07:19 +0000 Subject: [PATCH 02/14] Add specialized designers: FormDesigner and LayoutDesigner Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- packages/designer/SPECIALIZED_DESIGNERS.md | 249 +++++++++++++ packages/designer/src/components/Designer.tsx | 159 +++------ .../components/FilteredComponentPalette.tsx | 328 ++++++++++++++++++ .../designer/src/components/FormDesigner.tsx | 188 ++++++++++ .../src/components/GeneralDesigner.tsx | 123 +++++++ .../src/components/LayoutDesigner.tsx | 184 ++++++++++ packages/designer/src/index.ts | 9 + packages/designer/src/types/designer-modes.ts | 102 ++++++ 8 files changed, 1238 insertions(+), 104 deletions(-) create mode 100644 packages/designer/SPECIALIZED_DESIGNERS.md create mode 100644 packages/designer/src/components/FilteredComponentPalette.tsx create mode 100644 packages/designer/src/components/FormDesigner.tsx create mode 100644 packages/designer/src/components/GeneralDesigner.tsx create mode 100644 packages/designer/src/components/LayoutDesigner.tsx create mode 100644 packages/designer/src/types/designer-modes.ts diff --git a/packages/designer/SPECIALIZED_DESIGNERS.md b/packages/designer/SPECIALIZED_DESIGNERS.md new file mode 100644 index 000000000..a75596bbf --- /dev/null +++ b/packages/designer/SPECIALIZED_DESIGNERS.md @@ -0,0 +1,249 @@ +# Specialized Designers + +Object UI Designer now supports three specialized designer modes, each optimized for specific use cases: + +## ๐ŸŽฏ Designer Modes + +### 1. Form Designer (`mode="form"`) + +A specialized designer for creating object forms with validation and field management. + +**Features:** +- Form-specific component palette (inputs, selects, checkboxes, etc.) +- Validation rule editor +- Form layout helpers +- Field property configuration +- Optimized for form building workflows + +**Usage:** +```tsx +import { Designer } from '@object-ui/designer'; + +function App() { + return ( + console.log(schema)} + /> + ); +} +``` + +Or use the dedicated component: +```tsx +import { FormDesigner } from '@object-ui/designer'; + +function App() { + return ; +} +``` + +**Available Components:** +- **Form Fields**: input, textarea, select, checkbox, switch, label +- **Actions**: button +- **Layout**: div, card, stack, grid, separator +- **Display**: text, span, badge + +### 2. Layout Designer (`mode="layout"`) + +A specialized designer for creating page layouts and structures. + +**Features:** +- Layout-specific component palette +- Grid and flexbox layout helpers +- Responsive breakpoint controls +- Layout templates (planned) +- Optimized for page structure design + +**Usage:** +```tsx +import { Designer } from '@object-ui/designer'; + +function App() { + return ( + console.log(schema)} + /> + ); +} +``` + +Or use the dedicated component: +```tsx +import { LayoutDesigner } from '@object-ui/designer'; + +function App() { + return ; +} +``` + +**Available Components:** +- **Containers**: div, card, grid +- **Layout**: stack, separator +- **Navigation**: tabs, breadcrumb, menubar, pagination +- **Content**: text, span, image, button +- **Data Display**: table, badge, avatar + +### 3. General Designer (`mode="general"` or default) + +The full-featured, general-purpose designer with all components and capabilities. + +**Features:** +- Complete component library +- All advanced features (resize, undo/redo, etc.) +- Maximum flexibility +- Suitable for any UI design task + +**Usage:** +```tsx +import { Designer } from '@object-ui/designer'; + +function App() { + return ( + + ); +} +``` + +Or use the dedicated component: +```tsx +import { GeneralDesigner } from '@object-ui/designer'; + +function App() { + return ; +} +``` + +**Available Components:** +All components from the component registry, including: +- Layout, Form, Data Display, Feedback, Overlay, Navigation categories + +## ๐Ÿ”„ Migration Guide + +### From Previous Version + +The default `Designer` component now supports a `mode` prop but remains backward compatible: + +```tsx +// Old way (still works) +import { Designer } from '@object-ui/designer'; + + +// New way - explicit general mode + + +// New - specialized modes + + +``` + +All existing code will continue to work without changes. The default mode is `'general'`, which provides the same functionality as before. + +## ๐ŸŽจ Customization + +### Using Filtered Component Palette + +You can also use the `FilteredComponentPalette` component directly for custom designer layouts: + +```tsx +import { + DesignerProvider, + Canvas, + FilteredComponentPalette, + PropertyPanel +} from '@object-ui/designer'; + +function CustomDesigner() { + return ( + +
+ + + +
+
+ ); +} +``` + +## ๐Ÿ“‹ Component Comparison + +| Feature | Form Designer | Layout Designer | General Designer | +|---------|--------------|----------------|------------------| +| Form Components | โœ… Primary | โš ๏ธ Limited | โœ… Full | +| Layout Components | โš ๏ธ Limited | โœ… Primary | โœ… Full | +| Navigation | โŒ None | โœ… Full | โœ… Full | +| Data Display | โš ๏ธ Basic | โš ๏ธ Basic | โœ… Full | +| Feedback/Overlay | โŒ None | โŒ None | โœ… Full | +| Component Count | ~15 | ~15 | ~30+ | +| Complexity | Low | Medium | High | +| Use Case | Forms Only | Page Layouts | Everything | + +## ๐Ÿš€ Best Practices + +1. **Choose the Right Mode**: Use specialized designers when you know your use case: + - Building a form? Use `mode="form"` + - Designing a page structure? Use `mode="layout"` + - Need everything? Use `mode="general"` or omit the prop + +2. **Start Specialized, Upgrade Later**: Begin with a specialized designer for focused work, then export the schema and open it in the general designer if you need additional components. + +3. **Component Filtering**: The specialized designers limit available components to reduce cognitive load and improve the design experience for specific tasks. + +## ๐Ÿ“ Examples + +See the `examples/` directory for complete working examples: +- `examples/form-designer/` - Form builder example +- `examples/layout-designer/` - Page layout example +- `examples/general-designer/` - General purpose example + +## ๐Ÿ”ง TypeScript Support + +All designer modes are fully typed: + +```typescript +import type { DesignerMode, FormDesignerConfig, LayoutDesignerConfig } from '@object-ui/designer'; + +const mode: DesignerMode = 'form'; // 'form' | 'layout' | 'general' + +// Form-specific config +const formConfig: FormDesignerConfig = { + mode: 'form', + showValidationRules: true, + fieldTypes: ['input', 'select'] +}; + +// Layout-specific config +const layoutConfig: LayoutDesignerConfig = { + mode: 'layout', + showBreakpointControls: true, + layoutTypes: ['grid', 'flex'] +}; +``` + +## ๐Ÿ“š Related Documentation + +- [Main README](./README.md) - Full designer documentation +- [Drag and Resize Guide](./DRAG_AND_RESIZE_GUIDE.md) - Interaction details +- [Implementation Guide](./IMPLEMENTATION.zh-CN.md) - Architecture overview diff --git a/packages/designer/src/components/Designer.tsx b/packages/designer/src/components/Designer.tsx index 5dbff8430..a15eb7162 100644 --- a/packages/designer/src/components/Designer.tsx +++ b/packages/designer/src/components/Designer.tsx @@ -1,115 +1,66 @@ -import React, { useEffect } from 'react'; -import { DesignerProvider } from '../context/DesignerContext'; -import { LeftSidebar } from './LeftSidebar'; -import { Canvas } from './Canvas'; -import { PropertyPanel } from './PropertyPanel'; -import { useDesigner } from '../context/DesignerContext'; +/** + * Unified Designer Component + * + * This is the main designer entry point that supports three modes: + * - 'form': Specialized form designer + * - 'layout': Page layout designer + * - 'general': Full-featured general designer (default) + */ + +import React from 'react'; import type { SchemaNode } from '@object-ui/core'; +import type { DesignerMode } from '../types/designer-modes'; +import { FormDesigner } from './FormDesigner'; +import { LayoutDesigner } from './LayoutDesigner'; +import { GeneralDesigner, GeneralDesignerContent } from './GeneralDesigner'; +import { DesignerProvider } from '../context/DesignerContext'; interface DesignerProps { initialSchema?: SchemaNode; onSchemaChange?: (schema: SchemaNode) => void; + /** + * Designer mode - determines which specialized designer to use + * @default 'general' + */ + mode?: DesignerMode; } - +/** + * Legacy export for backward compatibility + * @deprecated Use GeneralDesignerContent or specify mode prop + */ export const DesignerContent: React.FC = () => { - const { - undo, - redo, - copyNode, - cutNode, - duplicateNode, - pasteNode, - removeNode, - selectedNodeId, - canUndo, - canRedo - } = useDesigner(); - - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - // Check if we're in an editable element - const target = e.target as HTMLElement; - const isEditing = - target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.tagName === 'SELECT' || - target.isContentEditable; - - // Undo: Ctrl+Z / Cmd+Z - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { - e.preventDefault(); - undo(); - } - // Redo: Ctrl+Y / Cmd+Shift+Z - else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { - if (canRedo) { - e.preventDefault(); - redo(); - } - } - // Copy: Ctrl+C / Cmd+C (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { - e.preventDefault(); - copyNode(selectedNodeId); - } - // Cut: Ctrl+X / Cmd+X (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { - e.preventDefault(); - cutNode(selectedNodeId); - } - // Paste: Ctrl+V / Cmd+V (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { - e.preventDefault(); - pasteNode(selectedNodeId); - } - // Duplicate: Ctrl+D / Cmd+D (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { - e.preventDefault(); - duplicateNode(selectedNodeId); - } - // Delete: Delete / Backspace (only when not editing) - else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { - e.preventDefault(); - removeNode(selectedNodeId); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); - - return ( -
- {/* removed, moved to parent */} - -
- {/* Left Sidebar - Combined Component Palette and Tree */} - {/* Responsive: w-72 on desktop, w-64 on tablet (md:), hidden on mobile with toggle option */} -
- -
- - {/* Main Canvas Area */} -
- -
- - {/* Right Sidebar - Property Panel */} - {/* Responsive: w-80 on desktop, w-72 on tablet (md:), can be toggled on small tablets */} -
- -
-
-
- ); + return ; }; -export const Designer: React.FC = ({ initialSchema, onSchemaChange }) => { - return ( - - - - ); +/** + * Main Designer component with mode support + * + * @example + * ```tsx + * // Form designer + * + * + * // Layout designer + * + * + * // General designer (default) + * + * ``` + */ +export const Designer: React.FC = ({ + initialSchema, + onSchemaChange, + mode = 'general' +}) => { + // Route to the appropriate specialized designer based on mode + switch (mode) { + case 'form': + return ; + case 'layout': + return ; + case 'general': + default: + return ; + } }; diff --git a/packages/designer/src/components/FilteredComponentPalette.tsx b/packages/designer/src/components/FilteredComponentPalette.tsx new file mode 100644 index 000000000..05bec1d91 --- /dev/null +++ b/packages/designer/src/components/FilteredComponentPalette.tsx @@ -0,0 +1,328 @@ +import React from 'react'; +import { ComponentRegistry } from '@object-ui/core'; +import { useDesigner } from '../context/DesignerContext'; +import { + Layout, + Type, + Image, + Square, + List, + Table, + CreditCard, + ToggleLeft, + CheckSquare, + MousePointer2, + Box, + Grid, + AlignJustify, + PanelLeft, + FileText, + Circle, + User, + MessageSquare, + Bell, + Zap, + BarChart3, + Menu, + ChevronRight, + Layers, + Columns3, + Minus, + X +} from 'lucide-react'; +import { cn } from '@object-ui/components'; +import { ScrollArea } from '@object-ui/components'; +import { enableTouchDrag, isTouchDevice } from '../utils/touchDragPolyfill'; + +interface FilteredComponentPaletteProps { + className?: string; + /** + * List of allowed component types. If provided, only these components will be shown. + */ + allowedComponents?: string[]; + /** + * Categories to display. If provided, only these categories will be shown. + */ + categories?: Record; + /** + * Title to display at the top of the palette + */ + title?: string; +} + +// Map component types to Lucide icons +const getIconForType = (type: string) => { + switch (type) { + // Layout + case 'div': + case 'container': return Box; + case 'card': return CreditCard; + case 'grid': return Grid; + case 'stack': return AlignJustify; + case 'separator': return Minus; + + // Form + case 'button': return MousePointer2; + case 'input': return Type; + case 'textarea': return FileText; + case 'checkbox': return CheckSquare; + case 'switch': return ToggleLeft; + case 'select': return List; + case 'label': return Type; + + // Data Display + case 'text': + case 'span': return Type; + case 'image': return Image; + case 'badge': return Circle; + case 'avatar': return User; + case 'table': return Table; + + // Feedback + case 'alert': return Bell; + case 'progress': return BarChart3; + case 'skeleton': return Layers; + case 'toast': return MessageSquare; + + // Overlay + case 'dialog': + case 'drawer': + case 'popover': + case 'tooltip': + case 'sheet': return PanelLeft; + + // Navigation + case 'tabs': return Columns3; + case 'breadcrumb': return ChevronRight; + case 'pagination': return Menu; + case 'menubar': return Menu; + + default: return Square; + } +}; + +// Default categories +const DEFAULT_CATEGORIES = { + 'Layout': ['div', 'card', 'stack', 'grid', 'separator'], + 'Form': ['input', 'button', 'checkbox', 'switch', 'select', 'textarea', 'label'], + 'Data Display': ['text', 'span', 'image', 'badge', 'avatar', 'table'], + 'Feedback': ['alert', 'progress', 'skeleton', 'toast'], + 'Overlay': ['dialog', 'drawer', 'popover', 'tooltip', 'sheet'], + 'Navigation': ['tabs', 'breadcrumb', 'pagination', 'menubar'] +}; + +// Component item with touch support +interface ComponentItemProps { + type: string; + config: any; + Icon: any; + isResizable: boolean; + onDragStart: (e: React.DragEvent, type: string) => void; + onDragEnd: () => void; +} + +const ComponentItem: React.FC = React.memo(({ + type, + config, + Icon, + isResizable, + onDragStart, + onDragEnd +}) => { + const itemRef = React.useRef(null); + const { setDraggingType } = useDesigner(); + + // Setup touch drag support + React.useEffect(() => { + if (!itemRef.current || !isTouchDevice()) return; + + const cleanup = enableTouchDrag(itemRef.current, { + dragData: { componentType: type }, + onDragStart: () => { + setDraggingType(type); + }, + onDragEnd: () => { + setDraggingType(null); + } + }); + + return cleanup; + }, [type, setDraggingType]); + + return ( +
onDragStart(e, type)} + onDragEnd={onDragEnd} + className={cn( + "group flex flex-col items-center justify-center gap-1.5 md:gap-2 p-2.5 md:p-3 rounded-xl border-2 border-transparent hover:border-indigo-200 hover:bg-gradient-to-br hover:from-indigo-50 hover:to-purple-50 hover:shadow-lg cursor-grab active:cursor-grabbing transition-all duration-200 bg-white relative overflow-hidden", + "h-20 md:h-24 hover:scale-105 active:scale-95 touch-none" + )} + aria-label={`${config.label || type}${isResizable ? ' (resizable)' : ''}`} + > + {/* Resizable badge indicator */} + {isResizable && ( + + )} + +
+ +
+ + {config.label || type} + +
+ ); +}); + +ComponentItem.displayName = 'ComponentItem'; + +export const FilteredComponentPalette: React.FC = React.memo(({ + className, + allowedComponents, + categories = DEFAULT_CATEGORIES, + title = 'Components' +}) => { + const { setDraggingType } = useDesigner(); + const allConfigs = ComponentRegistry.getAllConfigs(); + const [searchQuery, setSearchQuery] = React.useState(''); + + const handleDragStart = React.useCallback((e: React.DragEvent, type: string) => { + e.dataTransfer.setData('componentType', type); + e.dataTransfer.effectAllowed = 'copy'; + setDraggingType(type); + + // Create a custom drag image + const dragPreview = document.createElement('div'); + dragPreview.className = 'bg-blue-600 text-white px-3 py-1.5 rounded-md shadow-lg font-medium text-sm flex items-center gap-2'; + dragPreview.innerHTML = `${type}`; + document.body.appendChild(dragPreview); + e.dataTransfer.setDragImage(dragPreview, 0, 0); + setTimeout(() => document.body.removeChild(dragPreview), 0); + }, [setDraggingType]); + + const handleDragEnd = React.useCallback(() => { + setDraggingType(null); + }, [setDraggingType]); + + const renderComponentItem = React.useCallback((type: string) => { + const config = ComponentRegistry.getConfig(type); + if (!config) return null; // Skip if not found + + const Icon = getIconForType(type); + const isResizable = config.resizable || false; + + return ( + + ); + }, [handleDragStart, handleDragEnd]); + + // Filter components by search query + const filterBySearch = React.useCallback((types: string[]) => { + if (!searchQuery.trim()) return types; + + const query = searchQuery.toLowerCase(); + return types.filter(type => { + const config = ComponentRegistry.getConfig(type); + return type.toLowerCase().includes(query) || + config?.label?.toLowerCase().includes(query); + }); + }, [searchQuery]); + + // Filter available components based on category and allowedComponents + const getComponentsForCategory = React.useCallback((categoryComponents: string[]) => { + return categoryComponents.filter(type => { + // Check if component exists in registry + if (!ComponentRegistry.getConfig(type)) return false; + // Check if component is in allowedComponents list (if provided) + if (allowedComponents && !allowedComponents.includes(type)) return false; + return true; + }); + }, [allowedComponents]); + + return ( +
+
+ {title && ( +

{title}

+ )} +
+ setSearchQuery(e.target.value)} + className="w-full h-9 md:h-10 px-3 md:px-4 text-sm border-2 border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-400 transition-all placeholder:text-gray-400 font-medium shadow-sm hover:shadow-md" + /> + {searchQuery && ( + + )} +
+
+ + +
+ {Object.entries(categories).map(([category, types]) => { + const availableTypes = filterBySearch(getComponentsForCategory(types)); + if (availableTypes.length === 0) return null; + + return ( +
+

+ + {category} +

+
+ {availableTypes.map(renderComponentItem)} +
+
+ ); + })} + + {/* Fallback for uncategorized components (only if no categories filter provided) */} + {(() => { + const categorized = new Set(Object.values(categories).flat()); + const allTypes = Object.keys(allConfigs); + const filteredTypes = allowedComponents ? allTypes.filter(t => allowedComponents.includes(t)) : allTypes; + const uncategorized = filterBySearch(filteredTypes.filter(t => !categorized.has(t))); + + if (uncategorized.length === 0) return null; + + return ( +
+

+ + Other +

+
+ {uncategorized.map(renderComponentItem)} +
+
+ ); + })()} +
+
+
+ ); +}); + +FilteredComponentPalette.displayName = 'FilteredComponentPalette'; diff --git a/packages/designer/src/components/FormDesigner.tsx b/packages/designer/src/components/FormDesigner.tsx new file mode 100644 index 000000000..8ca536d4d --- /dev/null +++ b/packages/designer/src/components/FormDesigner.tsx @@ -0,0 +1,188 @@ +/** + * FormDesigner - Specialized designer for creating object forms + * + * This component provides a focused interface for designing forms with: + * - Form-specific components (inputs, selects, checkboxes, etc.) + * - Validation rule editor + * - Form layout options + * - Field property configuration + */ + +import React, { useEffect } from 'react'; +import { DesignerProvider } from '../context/DesignerContext'; +import { Canvas } from './Canvas'; +import { PropertyPanel } from './PropertyPanel'; +import { FilteredComponentPalette } from './FilteredComponentPalette'; +import { ComponentTree } from './ComponentTree'; +import { useDesigner } from '../context/DesignerContext'; +import type { SchemaNode } from '@object-ui/core'; +import type { FormDesignerConfig } from '../types/designer-modes'; + +interface FormDesignerProps { + initialSchema?: SchemaNode; + onSchemaChange?: (schema: SchemaNode) => void; + config?: Partial; +} + +// Form-specific component categories +const FORM_CATEGORIES = { + 'Form Fields': ['input', 'textarea', 'select', 'checkbox', 'switch', 'label'], + 'Form Actions': ['button'], + 'Form Layout': ['card', 'stack', 'grid', 'separator'], + 'Data Display': ['text', 'badge'] +}; + +// Allowed components for form designer +const FORM_COMPONENTS = [ + // Form fields + 'input', 'textarea', 'select', 'checkbox', 'switch', 'label', + // Actions + 'button', + // Layout + 'div', 'card', 'stack', 'grid', 'separator', + // Display + 'text', 'span', 'badge' +]; + +export const FormDesignerContent: React.FC = () => { + const { + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo + } = useDesigner(); + + // Keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Check if we're in an editable element + const target = e.target as HTMLElement; + const isEditing = + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable; + + // Undo: Ctrl+Z / Cmd+Z + if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { + e.preventDefault(); + undo(); + } + // Redo: Ctrl+Y / Cmd+Shift+Z + else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { + if (canRedo) { + e.preventDefault(); + redo(); + } + } + // Copy: Ctrl+C / Cmd+C (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { + e.preventDefault(); + copyNode(selectedNodeId); + } + // Cut: Ctrl+X / Cmd+X (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { + e.preventDefault(); + cutNode(selectedNodeId); + } + // Paste: Ctrl+V / Cmd+V (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { + e.preventDefault(); + pasteNode(selectedNodeId); + } + // Duplicate: Ctrl+D / Cmd+D (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { + e.preventDefault(); + duplicateNode(selectedNodeId); + } + // Delete: Delete / Backspace (only when not editing) + else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { + e.preventDefault(); + removeNode(selectedNodeId); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + + return ( +
+ {/* Header */} +
+

+ Form Designer +

+
+ Design forms with validation and field layouts +
+
+ +
+ {/* Left Sidebar - Form Fields & Component Tree */} +
+ {/* Component Palette */} +
+ +
+ + {/* Component Tree */} +
+ +
+
+ + {/* Main Canvas Area */} +
+ +
+ + {/* Right Sidebar - Property Panel */} +
+ +
+
+
+ ); +}; + +export const FormDesigner: React.FC = ({ + initialSchema, + onSchemaChange, + config +}) => { + // Default initial schema for forms + const defaultFormSchema: SchemaNode = { + type: 'div', + className: 'w-full max-w-2xl mx-auto p-8', + id: 'form-root', + body: [ + { + type: 'card', + title: 'New Form', + className: 'w-full', + body: [] + } + ] + }; + + return ( + + + + ); +}; diff --git a/packages/designer/src/components/GeneralDesigner.tsx b/packages/designer/src/components/GeneralDesigner.tsx new file mode 100644 index 000000000..b80ce9c65 --- /dev/null +++ b/packages/designer/src/components/GeneralDesigner.tsx @@ -0,0 +1,123 @@ +import React, { useEffect } from 'react'; +import { DesignerProvider } from '../context/DesignerContext'; +import { LeftSidebar } from './LeftSidebar'; +import { Canvas } from './Canvas'; +import { PropertyPanel } from './PropertyPanel'; +import { useDesigner } from '../context/DesignerContext'; +import type { SchemaNode } from '@object-ui/core'; + +interface GeneralDesignerProps { + initialSchema?: SchemaNode; + onSchemaChange?: (schema: SchemaNode) => void; +} + + +export const GeneralDesignerContent: React.FC = () => { + const { + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo + } = useDesigner(); + + // Keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Check if we're in an editable element + const target = e.target as HTMLElement; + const isEditing = + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable; + + // Undo: Ctrl+Z / Cmd+Z + if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { + e.preventDefault(); + undo(); + } + // Redo: Ctrl+Y / Cmd+Shift+Z + else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { + if (canRedo) { + e.preventDefault(); + redo(); + } + } + // Copy: Ctrl+C / Cmd+C (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { + e.preventDefault(); + copyNode(selectedNodeId); + } + // Cut: Ctrl+X / Cmd+X (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { + e.preventDefault(); + cutNode(selectedNodeId); + } + // Paste: Ctrl+V / Cmd+V (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { + e.preventDefault(); + pasteNode(selectedNodeId); + } + // Duplicate: Ctrl+D / Cmd+D (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { + e.preventDefault(); + duplicateNode(selectedNodeId); + } + // Delete: Delete / Backspace (only when not editing) + else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { + e.preventDefault(); + removeNode(selectedNodeId); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + + return ( +
+ {/* Header */} +
+

+ General Designer +

+
+ Full-featured designer for any UI component +
+
+ +
+ {/* Left Sidebar - Combined Component Palette and Tree */} + {/* Responsive: w-72 on desktop, w-64 on tablet (md:), hidden on mobile with toggle option */} +
+ +
+ + {/* Main Canvas Area */} +
+ +
+ + {/* Right Sidebar - Property Panel */} + {/* Responsive: w-80 on desktop, w-72 on tablet (md:), can be toggled on small tablets */} +
+ +
+
+
+ ); +}; + +export const GeneralDesigner: React.FC = ({ initialSchema, onSchemaChange }) => { + return ( + + + + ); +}; diff --git a/packages/designer/src/components/LayoutDesigner.tsx b/packages/designer/src/components/LayoutDesigner.tsx new file mode 100644 index 000000000..de3d9879d --- /dev/null +++ b/packages/designer/src/components/LayoutDesigner.tsx @@ -0,0 +1,184 @@ +/** + * LayoutDesigner - Specialized designer for creating page layouts + * + * This component provides a focused interface for designing page layouts with: + * - Layout-specific components (grid, containers, cards) + * - Responsive breakpoint controls + * - Layout templates + * - Spacing and alignment helpers + */ + +import React, { useEffect } from 'react'; +import { DesignerProvider } from '../context/DesignerContext'; +import { Canvas } from './Canvas'; +import { PropertyPanel } from './PropertyPanel'; +import { FilteredComponentPalette } from './FilteredComponentPalette'; +import { ComponentTree } from './ComponentTree'; +import { useDesigner } from '../context/DesignerContext'; +import type { SchemaNode } from '@object-ui/core'; +import type { LayoutDesignerConfig } from '../types/designer-modes'; + +interface LayoutDesignerProps { + initialSchema?: SchemaNode; + onSchemaChange?: (schema: SchemaNode) => void; + config?: Partial; +} + +// Layout-specific component categories +const LAYOUT_CATEGORIES = { + 'Containers': ['div', 'card', 'grid'], + 'Layout': ['stack', 'separator'], + 'Navigation': ['tabs', 'breadcrumb', 'menubar'], + 'Content': ['text', 'span', 'image', 'button'], + 'Data Display': ['table', 'badge', 'avatar'] +}; + +// Allowed components for layout designer +const LAYOUT_COMPONENTS = [ + // Containers + 'div', 'card', 'grid', + // Layout + 'stack', 'separator', + // Navigation + 'tabs', 'breadcrumb', 'menubar', 'pagination', + // Content + 'text', 'span', 'image', 'button', + // Data display + 'table', 'badge', 'avatar' +]; + +export const LayoutDesignerContent: React.FC = () => { + const { + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo + } = useDesigner(); + + // Keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Check if we're in an editable element + const target = e.target as HTMLElement; + const isEditing = + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable; + + // Undo: Ctrl+Z / Cmd+Z + if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { + e.preventDefault(); + undo(); + } + // Redo: Ctrl+Y / Cmd+Shift+Z + else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { + if (canRedo) { + e.preventDefault(); + redo(); + } + } + // Copy: Ctrl+C / Cmd+C (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { + e.preventDefault(); + copyNode(selectedNodeId); + } + // Cut: Ctrl+X / Cmd+X (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { + e.preventDefault(); + cutNode(selectedNodeId); + } + // Paste: Ctrl+V / Cmd+V (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { + e.preventDefault(); + pasteNode(selectedNodeId); + } + // Duplicate: Ctrl+D / Cmd+D (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { + e.preventDefault(); + duplicateNode(selectedNodeId); + } + // Delete: Delete / Backspace (only when not editing) + else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { + e.preventDefault(); + removeNode(selectedNodeId); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + + return ( +
+ {/* Header */} +
+

+ Page Layout Designer +

+
+ Design responsive page layouts and structures +
+
+ +
+ {/* Left Sidebar - Layout Components & Component Tree */} +
+ {/* Component Palette */} +
+ +
+ + {/* Component Tree */} +
+ +
+
+ + {/* Main Canvas Area */} +
+ +
+ + {/* Right Sidebar - Property Panel */} +
+ +
+
+
+ ); +}; + +export const LayoutDesigner: React.FC = ({ + initialSchema, + onSchemaChange, + config +}) => { + // Default initial schema for page layouts + const defaultLayoutSchema: SchemaNode = { + type: 'div', + className: 'h-full w-full flex flex-col', + id: 'layout-root', + body: [] + }; + + return ( + + + + ); +}; diff --git a/packages/designer/src/index.ts b/packages/designer/src/index.ts index 03221a6b8..68151f397 100644 --- a/packages/designer/src/index.ts +++ b/packages/designer/src/index.ts @@ -1,13 +1,22 @@ // Main Designer Component export { Designer, DesignerContent } from './components/Designer'; +// Specialized Designers +export { FormDesigner } from './components/FormDesigner'; +export { LayoutDesigner } from './components/LayoutDesigner'; +export { GeneralDesigner, GeneralDesignerContent } from './components/GeneralDesigner'; + // Context and Hooks export { DesignerProvider, useDesigner } from './context/DesignerContext'; export type { DesignerContextValue, ViewportMode, ResizingState } from './context/DesignerContext'; +// Designer Mode Types +export type { DesignerMode, DesignerConfig, FormDesignerConfig, LayoutDesignerConfig } from './types/designer-modes'; + // Individual Components (for custom layouts) export { Canvas } from './components/Canvas'; export { ComponentPalette } from './components/ComponentPalette'; +export { FilteredComponentPalette } from './components/FilteredComponentPalette'; export { PropertyPanel } from './components/PropertyPanel'; export { Toolbar } from './components/Toolbar'; export { ComponentTree } from './components/ComponentTree'; diff --git a/packages/designer/src/types/designer-modes.ts b/packages/designer/src/types/designer-modes.ts new file mode 100644 index 000000000..a7d82dd2a --- /dev/null +++ b/packages/designer/src/types/designer-modes.ts @@ -0,0 +1,102 @@ +/** + * Designer mode types for specialized designers + */ + +/** + * Available designer modes + * - 'form': Specialized form designer with validation and field management + * - 'layout': Page layout designer with grid/flex helpers + * - 'general': Full-featured general purpose designer + */ +export type DesignerMode = 'form' | 'layout' | 'general'; + +/** + * Configuration for specialized designers + */ +export interface DesignerConfig { + /** + * Designer mode + */ + mode: DesignerMode; + + /** + * Allowed component types for this designer mode + */ + allowedComponents?: string[]; + + /** + * Component categories to show in palette + */ + categories?: string[]; + + /** + * Whether to show component tree + * @default true + */ + showComponentTree?: boolean; + + /** + * Whether to show viewport controls + * @default true + */ + showViewportControls?: boolean; + + /** + * Whether to show zoom controls + * @default true + */ + showZoomControls?: boolean; + + /** + * Custom property panel fields + */ + propertyFields?: string[]; +} + +/** + * Form designer specific configuration + */ +export interface FormDesignerConfig extends DesignerConfig { + mode: 'form'; + + /** + * Whether to show validation rules editor + * @default true + */ + showValidationRules?: boolean; + + /** + * Whether to show form preview with data + * @default true + */ + showFormPreview?: boolean; + + /** + * Available field types + */ + fieldTypes?: string[]; +} + +/** + * Layout designer specific configuration + */ +export interface LayoutDesignerConfig extends DesignerConfig { + mode: 'layout'; + + /** + * Whether to show responsive breakpoint controls + * @default true + */ + showBreakpointControls?: boolean; + + /** + * Whether to show layout templates + * @default true + */ + showLayoutTemplates?: boolean; + + /** + * Available layout types + */ + layoutTypes?: string[]; +} From 500933e82cb891cfde2181d735b9a6e13473acd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:26:01 +0000 Subject: [PATCH 03/14] Add designer modes example and update documentation Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/designer-modes/README.md | 73 ++++++++++++++++++++++ examples/designer-modes/index.html | 13 ++++ examples/designer-modes/package.json | 38 +++++++++++ examples/designer-modes/postcss.config.js | 6 ++ examples/designer-modes/src/App.tsx | 69 ++++++++++++++++++++ examples/designer-modes/src/index.css | 27 ++++++++ examples/designer-modes/src/main.tsx | 10 +++ examples/designer-modes/tailwind.config.js | 13 ++++ examples/designer-modes/tsconfig.json | 25 ++++++++ examples/designer-modes/vite.config.ts | 12 ++++ packages/designer/README.md | 53 +++++++++++++++- 11 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 examples/designer-modes/README.md create mode 100644 examples/designer-modes/index.html create mode 100644 examples/designer-modes/package.json create mode 100644 examples/designer-modes/postcss.config.js create mode 100644 examples/designer-modes/src/App.tsx create mode 100644 examples/designer-modes/src/index.css create mode 100644 examples/designer-modes/src/main.tsx create mode 100644 examples/designer-modes/tailwind.config.js create mode 100644 examples/designer-modes/tsconfig.json create mode 100644 examples/designer-modes/vite.config.ts diff --git a/examples/designer-modes/README.md b/examples/designer-modes/README.md new file mode 100644 index 000000000..f43650384 --- /dev/null +++ b/examples/designer-modes/README.md @@ -0,0 +1,73 @@ +# Designer Modes Example + +This example demonstrates the three specialized designer modes in Object UI: + +## ๐ŸŽฏ Designer Modes + +### 1. Form Designer +Optimized for building forms with validation and field management. +- **Components**: Form fields, buttons, basic layout +- **Branding**: Emerald/Teal +- **Use Case**: Creating contact forms, registration forms, data entry forms + +### 2. Layout Designer +Optimized for designing page layouts and structures. +- **Components**: Containers, grids, navigation, content +- **Branding**: Blue/Indigo +- **Use Case**: Creating page structures, dashboards, landing pages + +### 3. General Designer +Full-featured designer with all available components. +- **Components**: All 30+ components +- **Branding**: Purple/Pink +- **Use Case**: Any UI design task requiring maximum flexibility + +## ๐Ÿš€ Running the Example + +```bash +# Install dependencies (from root) +pnpm install + +# Run the example +cd examples/designer-modes +pnpm dev +``` + +The application will start at `http://localhost:5173` + +## ๐ŸŽจ Features + +- **Mode Switching**: Toggle between the three designer modes using the top navigation +- **Live Preview**: See your changes in real-time as you design +- **Schema Export**: Check the console to see the generated schema JSON +- **Persistence**: Switch modes while maintaining your design (uses the same schema) + +## ๐Ÿ“ Usage + +1. **Start with a Mode**: Choose Form Designer for forms, Layout Designer for page structures, or General Designer for everything +2. **Drag Components**: Drag components from the left panel to the canvas +3. **Configure Properties**: Select a component and edit its properties in the right panel +4. **Switch Modes**: Try switching between modes to see different component sets +5. **Export Schema**: The schema is logged to the console on every change + +## ๐Ÿ”„ How It Works + +```tsx +import { Designer } from '@object-ui/designer'; + +function App() { + const [mode, setMode] = useState('general'); + + return ( + console.log(schema)} + /> + ); +} +``` + +## ๐Ÿ“š Related Documentation + +- [Specialized Designers Guide](../../packages/designer/SPECIALIZED_DESIGNERS.md) +- [Main Designer Documentation](../../packages/designer/README.md) diff --git a/examples/designer-modes/index.html b/examples/designer-modes/index.html new file mode 100644 index 000000000..4453e1451 --- /dev/null +++ b/examples/designer-modes/index.html @@ -0,0 +1,13 @@ + + + + + + + Designer Modes Demo - Object UI + + +
+ + + diff --git a/examples/designer-modes/package.json b/examples/designer-modes/package.json new file mode 100644 index 000000000..ce8c366a1 --- /dev/null +++ b/examples/designer-modes/package.json @@ -0,0 +1,38 @@ +{ + "name": "@examples/designer-modes", + "private": true, + "license": "MIT", + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@object-ui/core": "workspace:*", + "@object-ui/react": "workspace:*", + "@object-ui/components": "workspace:*", + "@object-ui/designer": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^5.1.2", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^5.0.0" + } +} diff --git a/examples/designer-modes/postcss.config.js b/examples/designer-modes/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/examples/designer-modes/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/designer-modes/src/App.tsx b/examples/designer-modes/src/App.tsx new file mode 100644 index 000000000..8e0d291f6 --- /dev/null +++ b/examples/designer-modes/src/App.tsx @@ -0,0 +1,69 @@ +import React, { useState } from 'react'; +import { Designer, type DesignerMode } from '@object-ui/designer'; +import type { SchemaNode } from '@object-ui/core'; + +function App() { + const [mode, setMode] = useState('general'); + const [schema, setSchema] = useState({ + type: 'div', + className: 'p-8', + body: [] + }); + + return ( +
+ {/* Mode Selector */} +
+

Object UI Designer Modes

+
+
+ + + +
+
+ + {/* Designer */} +
+ { + setSchema(newSchema); + console.log('Schema updated:', newSchema); + }} + /> +
+
+ ); +} + +export default App; diff --git a/examples/designer-modes/src/index.css b/examples/designer-modes/src/index.css new file mode 100644 index 000000000..32e805e2a --- /dev/null +++ b/examples/designer-modes/src/index.css @@ -0,0 +1,27 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + margin: 0; + display: flex; + min-width: 320px; + min-height: 100vh; +} + +#root { + width: 100%; + height: 100vh; +} diff --git a/examples/designer-modes/src/main.tsx b/examples/designer-modes/src/main.tsx new file mode 100644 index 000000000..9aa52ffd1 --- /dev/null +++ b/examples/designer-modes/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/examples/designer-modes/tailwind.config.js b/examples/designer-modes/tailwind.config.js new file mode 100644 index 000000000..cb1dc76fe --- /dev/null +++ b/examples/designer-modes/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + "../../packages/designer/src/**/*.{js,ts,jsx,tsx}", + "../../packages/components/src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/examples/designer-modes/tsconfig.json b/examples/designer-modes/tsconfig.json new file mode 100644 index 000000000..df2545fa1 --- /dev/null +++ b/examples/designer-modes/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/examples/designer-modes/vite.config.ts b/examples/designer-modes/vite.config.ts new file mode 100644 index 000000000..749728122 --- /dev/null +++ b/examples/designer-modes/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': '/src', + }, + }, +}); diff --git a/packages/designer/README.md b/packages/designer/README.md index 8f52eecf6..618aa669b 100644 --- a/packages/designer/README.md +++ b/packages/designer/README.md @@ -1,6 +1,24 @@ # @object-ui/designer -A professional drag-and-drop visual editor to generate Object UI schemas with advanced features including **component resizing**. +A professional drag-and-drop visual editor to generate Object UI schemas with advanced features including **component resizing** and **specialized designer modes**. + +## ๐ŸŽฏ Specialized Designer Modes + +The designer now supports three specialized modes optimized for different use cases: + +- **Form Designer** (`mode="form"`): Optimized for building forms with field validation and layouts +- **Layout Designer** (`mode="layout"`): Optimized for designing page structures and layouts +- **General Designer** (`mode="general"` or default): Full-featured designer with all components + +See [SPECIALIZED_DESIGNERS.md](./SPECIALIZED_DESIGNERS.md) for detailed information about each mode. + +### Quick Mode Comparison + +| Mode | Best For | Component Count | Complexity | +|------|----------|----------------|------------| +| Form | Contact forms, data entry | ~15 | Low | +| Layout | Page structures, dashboards | ~15 | Medium | +| General | Everything | 30+ | High | ## Features @@ -68,7 +86,7 @@ pnpm add @object-ui/designer @object-ui/react @object-ui/components ## Usage -### Basic Example +### Basic Example (General Designer) ```tsx import { Designer } from '@object-ui/designer'; @@ -91,6 +109,37 @@ function App() { } ``` +### Using Specialized Designer Modes + +```tsx +import { Designer } from '@object-ui/designer'; + +// Form Designer - for building forms +function FormBuilderApp() { + return ; +} + +// Layout Designer - for page layouts +function LayoutBuilderApp() { + return ; +} + +// General Designer - for everything (default) +function GeneralBuilderApp() { + return ; +} +``` + +Or use dedicated components: + +```tsx +import { FormDesigner, LayoutDesigner, GeneralDesigner } from '@object-ui/designer'; + +function App() { + return ; // or or +} +``` + ### With Initial Schema ```tsx From 59dda839075a5b48a481d8608c65b38c3ebf1f7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:31:09 +0000 Subject: [PATCH 04/14] Add tests and visual guide for specialized designers Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .../designer/DESIGNER_MODES_VISUAL_GUIDE.md | 290 ++++++++++++++++++ .../__tests__/specialized-designers.test.ts | 27 ++ 2 files changed, 317 insertions(+) create mode 100644 packages/designer/DESIGNER_MODES_VISUAL_GUIDE.md create mode 100644 packages/designer/src/__tests__/specialized-designers.test.ts diff --git a/packages/designer/DESIGNER_MODES_VISUAL_GUIDE.md b/packages/designer/DESIGNER_MODES_VISUAL_GUIDE.md new file mode 100644 index 000000000..a8e637f6a --- /dev/null +++ b/packages/designer/DESIGNER_MODES_VISUAL_GUIDE.md @@ -0,0 +1,290 @@ +# Visual Guide: Designer Modes Comparison + +## Overview + +Object UI Designer now offers three specialized modes, each optimized for specific design tasks. This guide provides a visual comparison to help you choose the right mode for your needs. + +--- + +## ๐ŸŽจ Designer Mode Comparison + +### 1. Form Designer (`mode="form"`) + +**Purpose**: Specialized for building forms with validation and field management + +**Visual Identity**: +- Header: Emerald/Teal gradient background +- Branding: `Form Designer` title +- Color Scheme: Green accents for form-focused workflow + +**Component Palette**: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Form Designer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Search: [ ] ๐Ÿ” โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ FORM FIELDS โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“โ”‚ โ”‚๐Ÿ“‹โ”‚ โ”‚โ˜‘๏ธโ”‚ โ”‚๐Ÿ”˜โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ input textarea checkbox โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ”€โ”‚ โ”‚๐Ÿ”ฝโ”‚ โ”‚๐Ÿท๏ธโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ switch select label โ”‚ +โ”‚ โ”‚ +โ”‚ FORM ACTIONS โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ”˜โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ button โ”‚ +โ”‚ โ”‚ +โ”‚ FORM LAYOUT โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“ฆโ”‚ โ”‚๐Ÿ“‡โ”‚ โ”‚โšกโ”‚ โ”‚โž–โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ card stack grid separator โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Typical Use Cases**: +- Contact forms +- User registration +- Data entry forms +- Survey forms +- Login/signup pages + +**Component Count**: ~15 components + +--- + +### 2. Layout Designer (`mode="layout"`) + +**Purpose**: Specialized for designing page layouts and structures + +**Visual Identity**: +- Header: Blue/Indigo gradient background +- Branding: `Page Layout Designer` title +- Color Scheme: Blue accents for structural design + +**Component Palette**: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Page Layout Designer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Search: [ ] ๐Ÿ” โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ CONTAINERS โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“ฆโ”‚ โ”‚๐Ÿ“‡โ”‚ โ”‚โšกโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ div card grid โ”‚ +โ”‚ โ”‚ +โ”‚ LAYOUT โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“šโ”‚ โ”‚โž–โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ stack separator โ”‚ +โ”‚ โ”‚ +โ”‚ NAVIGATION โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“‘โ”‚ โ”‚๐Ÿฅ–โ”‚ โ”‚โ˜ฐโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ tabs breadcrumb menubar โ”‚ +โ”‚ โ”‚ +โ”‚ CONTENT โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“โ”‚ โ”‚๐Ÿ–ผ๏ธโ”‚ โ”‚๐Ÿ”˜โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ text image button โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Typical Use Cases**: +- Dashboard layouts +- Landing pages +- Admin panel structures +- Multi-column layouts +- Navigation hierarchies + +**Component Count**: ~15 components + +--- + +### 3. General Designer (`mode="general"` or default) + +**Purpose**: Full-featured designer for any UI design task + +**Visual Identity**: +- Header: Purple/Pink gradient background +- Branding: `General Designer` title +- Color Scheme: Purple accents for versatility + +**Component Palette**: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ General Designer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Search: [ ] ๐Ÿ” โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ LAYOUT โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“ฆโ”‚ โ”‚๐Ÿ“‡โ”‚ โ”‚๐Ÿ“šโ”‚ โ”‚โšกโ”‚ โ”‚โž–โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ FORM โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“โ”‚ โ”‚๐Ÿ”˜โ”‚ โ”‚โ˜‘๏ธโ”‚ โ”‚๐Ÿ”€โ”‚ โ”‚๐Ÿ”ฝโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ DATA DISPLAY โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“„โ”‚ โ”‚๐Ÿ–ผ๏ธโ”‚ โ”‚๐Ÿท๏ธโ”‚ โ”‚๐Ÿ‘คโ”‚ โ”‚๐Ÿ“Šโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ FEEDBACK โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ””โ”‚ โ”‚๐Ÿ“Šโ”‚ โ”‚๐Ÿ’€โ”‚ โ”‚๐Ÿ’ฌโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ OVERLAY โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“ฑโ”‚ โ”‚๐Ÿ—ƒ๏ธโ”‚ โ”‚๐Ÿ’ญโ”‚ โ”‚๐Ÿ“Œโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ NAVIGATION โ”‚ +โ”‚ โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”Œโ”€โ”€โ” โ”‚ +โ”‚ โ”‚๐Ÿ“‘โ”‚ โ”‚๐Ÿฅ–โ”‚ โ”‚๐Ÿ“„โ”‚ โ”‚โ˜ฐโ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ””โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Typical Use Cases**: +- Complex applications +- Custom components +- Prototyping +- Full-featured UIs +- When you need maximum flexibility + +**Component Count**: 30+ components + +--- + +## ๐ŸŽฏ Quick Comparison Table + +| Feature | Form Designer | Layout Designer | General Designer | +|---------|--------------|-----------------|------------------| +| **Primary Use** | Forms | Page Layouts | Everything | +| **Complexity** | ๐ŸŸข Low | ๐ŸŸก Medium | ๐Ÿ”ด High | +| **Components** | 15 | 15 | 30+ | +| **Learning Curve** | Easiest | Easy | Moderate | +| **Specialization** | High | High | None | +| **Flexibility** | Limited | Limited | Maximum | + +--- + +## ๐Ÿ’ก Choosing the Right Mode + +### Use Form Designer When: +- โœ… Building forms exclusively +- โœ… You need validation-focused tools +- โœ… Working on data entry interfaces +- โœ… Team members are form specialists + +### Use Layout Designer When: +- โœ… Designing page structures +- โœ… Creating navigation systems +- โœ… Building dashboard layouts +- โœ… Focusing on responsive grids + +### Use General Designer When: +- โœ… Need all component types +- โœ… Building complex applications +- โœ… Prototyping various UI patterns +- โœ… Require maximum flexibility + +--- + +## ๐Ÿ”„ Switching Between Modes + +You can easily switch between modes in your code: + +```tsx +import { Designer } from '@object-ui/designer'; +import { useState } from 'react'; + +function App() { + const [mode, setMode] = useState<'form' | 'layout' | 'general'>('general'); + + return ( +
+ {/* Mode selector */} + + + {/* Designer with selected mode */} + +
+ ); +} +``` + +--- + +## ๐Ÿ“Š Component Distribution + +``` +Form Designer Components (15): +โ”œโ”€โ”€ Form Fields (7): input, textarea, select, checkbox, switch, label, button +โ”œโ”€โ”€ Layout (4): div, card, stack, grid, separator +โ””โ”€โ”€ Display (2): text, badge + +Layout Designer Components (15): +โ”œโ”€โ”€ Containers (3): div, card, grid +โ”œโ”€โ”€ Layout (2): stack, separator +โ”œโ”€โ”€ Navigation (4): tabs, breadcrumb, menubar, pagination +โ”œโ”€โ”€ Content (3): text, image, button +โ””โ”€โ”€ Display (3): table, badge, avatar + +General Designer Components (30+): +โ””โ”€โ”€ All categories: Layout, Form, Data Display, Feedback, Overlay, Navigation +``` + +--- + +## ๐ŸŽจ Visual Differentiation + +Each designer mode has distinct visual branding to help users quickly identify which mode they're using: + +| Mode | Header Color | Gradient | Icon Theme | +|------|-------------|----------|------------| +| Form | Emerald/Teal | `from-emerald-50 to-teal-50` | ๐ŸŸข Green | +| Layout | Blue/Indigo | `from-blue-50 to-indigo-50` | ๐Ÿ”ต Blue | +| General | Purple/Pink | `from-purple-50 to-pink-50` | ๐ŸŸฃ Purple | + +--- + +## ๐Ÿš€ Best Practices + +1. **Start Specialized**: Begin with Form or Layout designer for focused work +2. **Export and Upgrade**: Design in specialized mode, export schema, open in General if needed +3. **Team Workflow**: Assign Form Designer to form specialists, Layout Designer to UI designers +4. **Prototyping**: Use General Designer for initial exploration, then switch to specialized modes +5. **Component Consistency**: Specialized modes encourage using appropriate components for the task + +--- + +## ๐Ÿ“š Related Documentation + +- [Specialized Designers Guide](./SPECIALIZED_DESIGNERS.md) - Detailed API documentation +- [Main Designer README](./README.md) - General designer documentation +- [Examples](../../examples/designer-modes/) - Interactive demo application +- [Migration Guide](./SPECIALIZED_DESIGNERS.md#migration-guide) - Upgrading from previous versions diff --git a/packages/designer/src/__tests__/specialized-designers.test.ts b/packages/designer/src/__tests__/specialized-designers.test.ts new file mode 100644 index 000000000..b7d9a46fa --- /dev/null +++ b/packages/designer/src/__tests__/specialized-designers.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { FormDesigner } from '../components/FormDesigner'; +import { LayoutDesigner } from '../components/LayoutDesigner'; +import { GeneralDesigner } from '../components/GeneralDesigner'; +import { Designer } from '../components/Designer'; + +describe('Specialized Designers', () => { + it('should export FormDesigner component', () => { + expect(FormDesigner).toBeDefined(); + expect(typeof FormDesigner).toBe('function'); + }); + + it('should export LayoutDesigner component', () => { + expect(LayoutDesigner).toBeDefined(); + expect(typeof LayoutDesigner).toBe('function'); + }); + + it('should export GeneralDesigner component', () => { + expect(GeneralDesigner).toBeDefined(); + expect(typeof GeneralDesigner).toBe('function'); + }); + + it('should export unified Designer component', () => { + expect(Designer).toBeDefined(); + expect(typeof Designer).toBe('function'); + }); +}); From ecfec4da68769563608c185681d323da16220acb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:48:41 +0000 Subject: [PATCH 05/14] Refactor: Extract keyboard shortcuts into reusable hook Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .../designer/src/components/FormDesigner.tsx | 69 ++++----------- .../src/components/GeneralDesigner.tsx | 69 ++++----------- .../src/components/LayoutDesigner.tsx | 69 ++++----------- .../src/hooks/useKeyboardShortcuts.ts | 87 +++++++++++++++++++ packages/designer/src/index.ts | 1 + 5 files changed, 133 insertions(+), 162 deletions(-) create mode 100644 packages/designer/src/hooks/useKeyboardShortcuts.ts diff --git a/packages/designer/src/components/FormDesigner.tsx b/packages/designer/src/components/FormDesigner.tsx index 8ca536d4d..9a074404a 100644 --- a/packages/designer/src/components/FormDesigner.tsx +++ b/packages/designer/src/components/FormDesigner.tsx @@ -8,13 +8,14 @@ * - Field property configuration */ -import React, { useEffect } from 'react'; +import React from 'react'; import { DesignerProvider } from '../context/DesignerContext'; import { Canvas } from './Canvas'; import { PropertyPanel } from './PropertyPanel'; import { FilteredComponentPalette } from './FilteredComponentPalette'; import { ComponentTree } from './ComponentTree'; import { useDesigner } from '../context/DesignerContext'; +import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; import type { SchemaNode } from '@object-ui/core'; import type { FormDesignerConfig } from '../types/designer-modes'; @@ -58,59 +59,19 @@ export const FormDesignerContent: React.FC = () => { canRedo } = useDesigner(); - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - // Check if we're in an editable element - const target = e.target as HTMLElement; - const isEditing = - target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.tagName === 'SELECT' || - target.isContentEditable; - - // Undo: Ctrl+Z / Cmd+Z - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { - e.preventDefault(); - undo(); - } - // Redo: Ctrl+Y / Cmd+Shift+Z - else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { - if (canRedo) { - e.preventDefault(); - redo(); - } - } - // Copy: Ctrl+C / Cmd+C (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { - e.preventDefault(); - copyNode(selectedNodeId); - } - // Cut: Ctrl+X / Cmd+X (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { - e.preventDefault(); - cutNode(selectedNodeId); - } - // Paste: Ctrl+V / Cmd+V (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { - e.preventDefault(); - pasteNode(selectedNodeId); - } - // Duplicate: Ctrl+D / Cmd+D (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { - e.preventDefault(); - duplicateNode(selectedNodeId); - } - // Delete: Delete / Backspace (only when not editing) - else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { - e.preventDefault(); - removeNode(selectedNodeId); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + // Use shared keyboard shortcuts hook + useKeyboardShortcuts({ + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo, + }); return (
diff --git a/packages/designer/src/components/GeneralDesigner.tsx b/packages/designer/src/components/GeneralDesigner.tsx index b80ce9c65..88c27bc44 100644 --- a/packages/designer/src/components/GeneralDesigner.tsx +++ b/packages/designer/src/components/GeneralDesigner.tsx @@ -1,9 +1,10 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { DesignerProvider } from '../context/DesignerContext'; import { LeftSidebar } from './LeftSidebar'; import { Canvas } from './Canvas'; import { PropertyPanel } from './PropertyPanel'; import { useDesigner } from '../context/DesignerContext'; +import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; import type { SchemaNode } from '@object-ui/core'; interface GeneralDesignerProps { @@ -26,59 +27,19 @@ export const GeneralDesignerContent: React.FC = () => { canRedo } = useDesigner(); - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - // Check if we're in an editable element - const target = e.target as HTMLElement; - const isEditing = - target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.tagName === 'SELECT' || - target.isContentEditable; - - // Undo: Ctrl+Z / Cmd+Z - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { - e.preventDefault(); - undo(); - } - // Redo: Ctrl+Y / Cmd+Shift+Z - else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { - if (canRedo) { - e.preventDefault(); - redo(); - } - } - // Copy: Ctrl+C / Cmd+C (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { - e.preventDefault(); - copyNode(selectedNodeId); - } - // Cut: Ctrl+X / Cmd+X (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { - e.preventDefault(); - cutNode(selectedNodeId); - } - // Paste: Ctrl+V / Cmd+V (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { - e.preventDefault(); - pasteNode(selectedNodeId); - } - // Duplicate: Ctrl+D / Cmd+D (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { - e.preventDefault(); - duplicateNode(selectedNodeId); - } - // Delete: Delete / Backspace (only when not editing) - else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { - e.preventDefault(); - removeNode(selectedNodeId); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + // Use shared keyboard shortcuts hook + useKeyboardShortcuts({ + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo, + }); return (
diff --git a/packages/designer/src/components/LayoutDesigner.tsx b/packages/designer/src/components/LayoutDesigner.tsx index de3d9879d..9cb3b19be 100644 --- a/packages/designer/src/components/LayoutDesigner.tsx +++ b/packages/designer/src/components/LayoutDesigner.tsx @@ -8,13 +8,14 @@ * - Spacing and alignment helpers */ -import React, { useEffect } from 'react'; +import React from 'react'; import { DesignerProvider } from '../context/DesignerContext'; import { Canvas } from './Canvas'; import { PropertyPanel } from './PropertyPanel'; import { FilteredComponentPalette } from './FilteredComponentPalette'; import { ComponentTree } from './ComponentTree'; import { useDesigner } from '../context/DesignerContext'; +import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; import type { SchemaNode } from '@object-ui/core'; import type { LayoutDesignerConfig } from '../types/designer-modes'; @@ -61,59 +62,19 @@ export const LayoutDesignerContent: React.FC = () => { canRedo } = useDesigner(); - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - // Check if we're in an editable element - const target = e.target as HTMLElement; - const isEditing = - target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.tagName === 'SELECT' || - target.isContentEditable; - - // Undo: Ctrl+Z / Cmd+Z - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { - e.preventDefault(); - undo(); - } - // Redo: Ctrl+Y / Cmd+Shift+Z - else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { - if (canRedo) { - e.preventDefault(); - redo(); - } - } - // Copy: Ctrl+C / Cmd+C (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { - e.preventDefault(); - copyNode(selectedNodeId); - } - // Cut: Ctrl+X / Cmd+X (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { - e.preventDefault(); - cutNode(selectedNodeId); - } - // Paste: Ctrl+V / Cmd+V (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { - e.preventDefault(); - pasteNode(selectedNodeId); - } - // Duplicate: Ctrl+D / Cmd+D (only when not editing) - else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { - e.preventDefault(); - duplicateNode(selectedNodeId); - } - // Delete: Delete / Backspace (only when not editing) - else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { - e.preventDefault(); - removeNode(selectedNodeId); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); + // Use shared keyboard shortcuts hook + useKeyboardShortcuts({ + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo, + }); return (
diff --git a/packages/designer/src/hooks/useKeyboardShortcuts.ts b/packages/designer/src/hooks/useKeyboardShortcuts.ts new file mode 100644 index 000000000..861a4dcb9 --- /dev/null +++ b/packages/designer/src/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,87 @@ +/** + * Custom hook for handling keyboard shortcuts in designer components + */ +import { useEffect } from 'react'; + +interface UseKeyboardShortcutsOptions { + undo: () => void; + redo: () => void; + copyNode: (id: string) => void; + cutNode: (id: string) => void; + duplicateNode: (id: string) => void; + pasteNode: (parentId: string | null) => void; + removeNode: (id: string) => void; + selectedNodeId: string | null; + canUndo: boolean; + canRedo: boolean; +} + +/** + * Hook that sets up keyboard shortcuts for designer operations + * Handles Undo/Redo, Copy/Cut/Paste, Duplicate, and Delete + */ +export const useKeyboardShortcuts = ({ + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo, +}: UseKeyboardShortcutsOptions) => { + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // Check if we're in an editable element + const target = e.target as HTMLElement; + const isEditing = + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable; + + // Undo: Ctrl+Z / Cmd+Z + if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) { + e.preventDefault(); + undo(); + } + // Redo: Ctrl+Y / Cmd+Shift+Z + else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) { + if (canRedo) { + e.preventDefault(); + redo(); + } + } + // Copy: Ctrl+C / Cmd+C (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) { + e.preventDefault(); + copyNode(selectedNodeId); + } + // Cut: Ctrl+X / Cmd+X (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) { + e.preventDefault(); + cutNode(selectedNodeId); + } + // Paste: Ctrl+V / Cmd+V (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) { + e.preventDefault(); + pasteNode(selectedNodeId); + } + // Duplicate: Ctrl+D / Cmd+D (only when not editing) + else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) { + e.preventDefault(); + duplicateNode(selectedNodeId); + } + // Delete: Delete / Backspace (only when not editing) + else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) { + e.preventDefault(); + removeNode(selectedNodeId); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]); +}; diff --git a/packages/designer/src/index.ts b/packages/designer/src/index.ts index 68151f397..b766a4fde 100644 --- a/packages/designer/src/index.ts +++ b/packages/designer/src/index.ts @@ -9,6 +9,7 @@ export { GeneralDesigner, GeneralDesignerContent } from './components/GeneralDes // Context and Hooks export { DesignerProvider, useDesigner } from './context/DesignerContext'; export type { DesignerContextValue, ViewportMode, ResizingState } from './context/DesignerContext'; +export { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'; // Designer Mode Types export type { DesignerMode, DesignerConfig, FormDesignerConfig, LayoutDesignerConfig } from './types/designer-modes'; From 39b03a1e0fe54ebdcf78503baa6c9636c3e9bd4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:02:06 +0000 Subject: [PATCH 06/14] Add CanvasDesigner mode with free-form absolute positioning Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/designer-modes/src/App.tsx | 16 +- .../src/components/CanvasDesigner.tsx | 345 ++++++++++++++++++ packages/designer/src/components/Designer.tsx | 11 +- packages/designer/src/index.ts | 3 +- packages/designer/src/types/designer-modes.ts | 34 +- ....timestamp-1768467704562-21cdd04a7c5ed.mjs | 43 +++ pnpm-lock.yaml | 79 ++++ 7 files changed, 524 insertions(+), 7 deletions(-) create mode 100644 packages/designer/src/components/CanvasDesigner.tsx create mode 100644 packages/plugin-editor/vite.config.ts.timestamp-1768467704562-21cdd04a7c5ed.mjs diff --git a/examples/designer-modes/src/App.tsx b/examples/designer-modes/src/App.tsx index 8e0d291f6..e1dae7e80 100644 --- a/examples/designer-modes/src/App.tsx +++ b/examples/designer-modes/src/App.tsx @@ -25,7 +25,7 @@ function App() { : 'bg-gray-800 text-gray-300 hover:bg-gray-700' }`} > - Form Designer + Form +
diff --git a/packages/designer/src/components/CanvasDesigner.tsx b/packages/designer/src/components/CanvasDesigner.tsx new file mode 100644 index 000000000..b1796e037 --- /dev/null +++ b/packages/designer/src/components/CanvasDesigner.tsx @@ -0,0 +1,345 @@ +/** + * CanvasDesigner - Free-form canvas designer with absolute positioning + * + * This component provides a canvas-like interface where components can be: + * - Dragged to any position (absolute positioning) + * - Resized freely + * - Positioned with x,y coordinates + * - Arranged on a visual canvas + */ + +import React, { useCallback, useState, useRef } from 'react'; +import { DesignerProvider } from '../context/DesignerContext'; +import { PropertyPanel } from './PropertyPanel'; +import { FilteredComponentPalette } from './FilteredComponentPalette'; +import { ComponentTree } from './ComponentTree'; +import { useDesigner } from '../context/DesignerContext'; +import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; +import type { SchemaNode } from '@object-ui/core'; +import type { CanvasDesignerConfig } from '../types/designer-modes'; +import { SchemaRenderer } from '@object-ui/react'; +import { ResizeHandles } from './ResizeHandle'; +import { ComponentRegistry } from '@object-ui/core'; +import { cn } from '@object-ui/components'; + +interface CanvasDesignerProps { + initialSchema?: SchemaNode; + onSchemaChange?: (schema: SchemaNode) => void; + config?: Partial; +} + +// Canvas-specific component categories (all visual components) +const CANVAS_CATEGORIES = { + 'Basic': ['div', 'card', 'text', 'button', 'image'], + 'Form': ['input', 'textarea', 'select', 'checkbox', 'switch'], + 'Layout': ['stack', 'grid'], + 'Display': ['badge', 'avatar', 'separator'] +}; + +// Allowed components for canvas designer (all visual components) +const CANVAS_COMPONENTS = [ + 'div', 'card', 'text', 'span', 'button', 'image', + 'input', 'textarea', 'select', 'checkbox', 'switch', 'label', + 'stack', 'grid', 'separator', + 'badge', 'avatar' +]; + +const FreeFormCanvas: React.FC<{ showGrid?: boolean; gridSize?: number }> = ({ + showGrid = true, + gridSize = 20 +}) => { + const { + schema, + selectedNodeId, + setSelectedNodeId, + draggingType, + setDraggingType, + addNode, + updateNode, + resizingNode, + setResizingNode, + } = useDesigner(); + + const canvasRef = useRef(null); + const [draggedNode, setDraggedNode] = useState<{ id: string; startX: number; startY: number; offsetX: number; offsetY: number } | null>(null); + + // Handle drop from palette + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + + if (!canvasRef.current || !draggingType) return; + + const rect = canvasRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // Snap to grid if enabled + const snappedX = showGrid ? Math.round(x / gridSize) * gridSize : x; + const snappedY = showGrid ? Math.round(y / gridSize) * gridSize : y; + + // Create new node with absolute positioning + const config = ComponentRegistry.getConfig(draggingType); + const newNode: SchemaNode = { + type: draggingType, + ...(config?.defaultProps || {}), + style: { + position: 'absolute', + left: `${snappedX}px`, + top: `${snappedY}px`, + ...(config?.defaultProps?.style || {}), + }, + }; + + // Add to root + addNode(schema.id || null, newNode); + setDraggingType(null); + }, [draggingType, schema.id, addNode, setDraggingType, showGrid, gridSize]); + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; + }, []); + + // Handle canvas component drag start + const handleCanvasDragStart = useCallback((e: React.MouseEvent, nodeId: string) => { + const target = e.currentTarget as HTMLElement; + const rect = target.getBoundingClientRect(); + + setDraggedNode({ + id: nodeId, + startX: e.clientX, + startY: e.clientY, + offsetX: e.clientX - rect.left, + offsetY: e.clientY - rect.top, + }); + }, []); + + // Handle canvas component drag + const handleCanvasDrag = useCallback((e: MouseEvent) => { + if (!draggedNode || !canvasRef.current) return; + + const rect = canvasRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left - draggedNode.offsetX; + const y = e.clientY - rect.top - draggedNode.offsetY; + + // Snap to grid + const snappedX = showGrid ? Math.round(x / gridSize) * gridSize : x; + const snappedY = showGrid ? Math.round(y / gridSize) * gridSize : y; + + // Update node position + updateNode(draggedNode.id, { + style: { + position: 'absolute', + left: `${snappedX}px`, + top: `${snappedY}px`, + }, + }); + }, [draggedNode, updateNode, showGrid, gridSize]); + + // Setup mouse move/up listeners + React.useEffect(() => { + if (!draggedNode) return; + + const handleMouseMove = (e: MouseEvent) => handleCanvasDrag(e); + const handleMouseUp = () => setDraggedNode(null); + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [draggedNode, handleCanvasDrag]); + + // Render canvas children with absolute positioning + const renderCanvasChildren = (nodes: SchemaNode[]) => { + if (!Array.isArray(nodes)) return null; + + return nodes.map((node) => { + const isSelected = node.id === selectedNodeId; + const isResizable = ComponentRegistry.getConfig(node.type)?.resizable || false; + + return ( +
{ + e.stopPropagation(); + setSelectedNodeId(node.id || null); + }} + onMouseDown={(e) => { + e.preventDefault(); + if (node.id) handleCanvasDragStart(e, node.id); + }} + > + + + {/* Resize handles for selected node */} + {isSelected && isResizable && ( + { + const element = document.querySelector(`[data-obj-id="${node.id}"]`) as HTMLElement; + if (element) { + setResizingNode({ + nodeId: node.id || '', + direction, + startX: 0, + startY: 0, + startWidth: element.offsetWidth, + startHeight: element.offsetHeight, + }); + } + }} + /> + )} +
+ ); + }); + }; + + return ( +
setSelectedNodeId(null)} + > + {/* Canvas content */} + {schema.body && Array.isArray(schema.body) && renderCanvasChildren(schema.body)} + + {/* Empty state */} + {(!schema.body || (Array.isArray(schema.body) && schema.body.length === 0)) && ( +
+
+

Free-form Canvas

+

Drag components here to position them freely

+
+
+ )} +
+ ); +}; + +export const CanvasDesignerContent: React.FC<{ config?: Partial }> = ({ config }) => { + const { + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo + } = useDesigner(); + + // Use shared keyboard shortcuts hook + useKeyboardShortcuts({ + undo, + redo, + copyNode, + cutNode, + duplicateNode, + pasteNode, + removeNode, + selectedNodeId, + canUndo, + canRedo, + }); + + return ( +
+ {/* Header */} +
+

+ Canvas Designer +

+
+ Free-form design with absolute positioning +
+
+ +
+ {/* Left Sidebar - Canvas Components & Component Tree */} +
+ {/* Component Palette */} +
+ +
+ + {/* Component Tree */} +
+ +
+
+ + {/* Main Canvas Area */} +
+ +
+ + {/* Right Sidebar - Property Panel */} +
+ +
+
+
+ ); +}; + +export const CanvasDesigner: React.FC = ({ + initialSchema, + onSchemaChange, + config +}) => { + // Default initial schema for canvas with absolute positioning root + const defaultCanvasSchema: SchemaNode = { + type: 'div', + className: 'relative', + style: { + width: '100%', + height: '100%', + minHeight: '600px', + }, + id: 'canvas-root', + body: [] + }; + + return ( + + + + ); +}; diff --git a/packages/designer/src/components/Designer.tsx b/packages/designer/src/components/Designer.tsx index a15eb7162..271d863a2 100644 --- a/packages/designer/src/components/Designer.tsx +++ b/packages/designer/src/components/Designer.tsx @@ -1,9 +1,10 @@ /** * Unified Designer Component * - * This is the main designer entry point that supports three modes: + * This is the main designer entry point that supports four modes: * - 'form': Specialized form designer - * - 'layout': Page layout designer + * - 'layout': Page layout designer + * - 'canvas': Free-form canvas designer with absolute positioning * - 'general': Full-featured general designer (default) */ @@ -12,6 +13,7 @@ import type { SchemaNode } from '@object-ui/core'; import type { DesignerMode } from '../types/designer-modes'; import { FormDesigner } from './FormDesigner'; import { LayoutDesigner } from './LayoutDesigner'; +import { CanvasDesigner } from './CanvasDesigner'; import { GeneralDesigner, GeneralDesignerContent } from './GeneralDesigner'; import { DesignerProvider } from '../context/DesignerContext'; @@ -44,6 +46,9 @@ export const DesignerContent: React.FC = () => { * // Layout designer * * + * // Canvas designer + * + * * // General designer (default) * * ``` @@ -59,6 +64,8 @@ export const Designer: React.FC = ({ return ; case 'layout': return ; + case 'canvas': + return ; case 'general': default: return ; diff --git a/packages/designer/src/index.ts b/packages/designer/src/index.ts index b766a4fde..a65a5890a 100644 --- a/packages/designer/src/index.ts +++ b/packages/designer/src/index.ts @@ -4,6 +4,7 @@ export { Designer, DesignerContent } from './components/Designer'; // Specialized Designers export { FormDesigner } from './components/FormDesigner'; export { LayoutDesigner } from './components/LayoutDesigner'; +export { CanvasDesigner } from './components/CanvasDesigner'; export { GeneralDesigner, GeneralDesignerContent } from './components/GeneralDesigner'; // Context and Hooks @@ -12,7 +13,7 @@ export type { DesignerContextValue, ViewportMode, ResizingState } from './contex export { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'; // Designer Mode Types -export type { DesignerMode, DesignerConfig, FormDesignerConfig, LayoutDesignerConfig } from './types/designer-modes'; +export type { DesignerMode, DesignerConfig, FormDesignerConfig, LayoutDesignerConfig, CanvasDesignerConfig } from './types/designer-modes'; // Individual Components (for custom layouts) export { Canvas } from './components/Canvas'; diff --git a/packages/designer/src/types/designer-modes.ts b/packages/designer/src/types/designer-modes.ts index a7d82dd2a..fd900b06a 100644 --- a/packages/designer/src/types/designer-modes.ts +++ b/packages/designer/src/types/designer-modes.ts @@ -6,9 +6,10 @@ * Available designer modes * - 'form': Specialized form designer with validation and field management * - 'layout': Page layout designer with grid/flex helpers + * - 'canvas': Free-form canvas designer with absolute positioning * - 'general': Full-featured general purpose designer */ -export type DesignerMode = 'form' | 'layout' | 'general'; +export type DesignerMode = 'form' | 'layout' | 'canvas' | 'general'; /** * Configuration for specialized designers @@ -100,3 +101,34 @@ export interface LayoutDesignerConfig extends DesignerConfig { */ layoutTypes?: string[]; } + +/** + * Canvas designer specific configuration + */ +export interface CanvasDesignerConfig extends DesignerConfig { + mode: 'canvas'; + + /** + * Whether to enable free-form positioning + * @default true + */ + enableFreePositioning?: boolean; + + /** + * Whether to show grid for alignment + * @default true + */ + showGrid?: boolean; + + /** + * Grid snap size in pixels + * @default 10 + */ + gridSize?: number; + + /** + * Canvas background color + * @default '#ffffff' + */ + canvasBackground?: string; +} diff --git a/packages/plugin-editor/vite.config.ts.timestamp-1768467704562-21cdd04a7c5ed.mjs b/packages/plugin-editor/vite.config.ts.timestamp-1768467704562-21cdd04a7c5ed.mjs new file mode 100644 index 000000000..9d56204c2 --- /dev/null +++ b/packages/plugin-editor/vite.config.ts.timestamp-1768467704562-21cdd04a7c5ed.mjs @@ -0,0 +1,43 @@ +// vite.config.ts +import { defineConfig } from "file:///home/runner/work/objectui/objectui/node_modules/.pnpm/vite@5.4.21_@types+node@24.10.8/node_modules/vite/dist/node/index.js"; +import react from "file:///home/runner/work/objectui/objectui/node_modules/.pnpm/@vitejs+plugin-react@4.7.0_vite@5.4.21_@types+node@24.10.8_/node_modules/@vitejs/plugin-react/dist/index.js"; +import dts from "file:///home/runner/work/objectui/objectui/node_modules/.pnpm/vite-plugin-dts@3.9.1_@types+node@24.10.8_rollup@4.55.1_typescript@5.9.3_vite@5.4.21_@types+node@24.10.8_/node_modules/vite-plugin-dts/dist/index.mjs"; +import { resolve } from "path"; +var __vite_injected_original_dirname = "/home/runner/work/objectui/objectui/packages/plugin-editor"; +var vite_config_default = defineConfig({ + plugins: [ + react(), + dts({ + insertTypesEntry: true, + include: ["src"] + }) + ], + resolve: { + alias: { + "@": resolve(__vite_injected_original_dirname, "./src") + } + }, + build: { + lib: { + entry: resolve(__vite_injected_original_dirname, "src/index.tsx"), + name: "ObjectUIPluginEditor", + fileName: "index" + }, + rollupOptions: { + external: ["react", "react-dom", "@object-ui/components", "@object-ui/core", "@object-ui/react"], + output: { + globals: { + react: "React", + "react-dom": "ReactDOM", + "@object-ui/components": "ObjectUIComponents", + "@object-ui/core": "ObjectUICore", + "@object-ui/react": "ObjectUIReact" + } + } + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ydW5uZXIvd29yay9vYmplY3R1aS9vYmplY3R1aS9wYWNrYWdlcy9wbHVnaW4tZWRpdG9yXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvaG9tZS9ydW5uZXIvd29yay9vYmplY3R1aS9vYmplY3R1aS9wYWNrYWdlcy9wbHVnaW4tZWRpdG9yL3ZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9ob21lL3J1bm5lci93b3JrL29iamVjdHVpL29iamVjdHVpL3BhY2thZ2VzL3BsdWdpbi1lZGl0b3Ivdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCc7XG5pbXBvcnQgZHRzIGZyb20gJ3ZpdGUtcGx1Z2luLWR0cyc7XG5pbXBvcnQgeyByZXNvbHZlIH0gZnJvbSAncGF0aCc7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtcbiAgICByZWFjdCgpLFxuICAgIGR0cyh7XG4gICAgICBpbnNlcnRUeXBlc0VudHJ5OiB0cnVlLFxuICAgICAgaW5jbHVkZTogWydzcmMnXSxcbiAgICB9KSxcbiAgXSxcbiAgcmVzb2x2ZToge1xuICAgIGFsaWFzOiB7XG4gICAgICAnQCc6IHJlc29sdmUoX19kaXJuYW1lLCAnLi9zcmMnKSxcbiAgICB9LFxuICB9LFxuICBidWlsZDoge1xuICAgIGxpYjoge1xuICAgICAgZW50cnk6IHJlc29sdmUoX19kaXJuYW1lLCAnc3JjL2luZGV4LnRzeCcpLFxuICAgICAgbmFtZTogJ09iamVjdFVJUGx1Z2luRWRpdG9yJyxcbiAgICAgIGZpbGVOYW1lOiAnaW5kZXgnLFxuICAgIH0sXG4gICAgcm9sbHVwT3B0aW9uczoge1xuICAgICAgZXh0ZXJuYWw6IFsncmVhY3QnLCAncmVhY3QtZG9tJywgJ0BvYmplY3QtdWkvY29tcG9uZW50cycsICdAb2JqZWN0LXVpL2NvcmUnLCAnQG9iamVjdC11aS9yZWFjdCddLFxuICAgICAgb3V0cHV0OiB7XG4gICAgICAgIGdsb2JhbHM6IHtcbiAgICAgICAgICByZWFjdDogJ1JlYWN0JyxcbiAgICAgICAgICAncmVhY3QtZG9tJzogJ1JlYWN0RE9NJyxcbiAgICAgICAgICAnQG9iamVjdC11aS9jb21wb25lbnRzJzogJ09iamVjdFVJQ29tcG9uZW50cycsXG4gICAgICAgICAgJ0BvYmplY3QtdWkvY29yZSc6ICdPYmplY3RVSUNvcmUnLFxuICAgICAgICAgICdAb2JqZWN0LXVpL3JlYWN0JzogJ09iamVjdFVJUmVhY3QnLFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWdXLFNBQVMsb0JBQW9CO0FBQzdYLE9BQU8sV0FBVztBQUNsQixPQUFPLFNBQVM7QUFDaEIsU0FBUyxlQUFlO0FBSHhCLElBQU0sbUNBQW1DO0FBS3pDLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLE1BQU07QUFBQSxJQUNOLElBQUk7QUFBQSxNQUNGLGtCQUFrQjtBQUFBLE1BQ2xCLFNBQVMsQ0FBQyxLQUFLO0FBQUEsSUFDakIsQ0FBQztBQUFBLEVBQ0g7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQUssUUFBUSxrQ0FBVyxPQUFPO0FBQUEsSUFDakM7QUFBQSxFQUNGO0FBQUEsRUFDQSxPQUFPO0FBQUEsSUFDTCxLQUFLO0FBQUEsTUFDSCxPQUFPLFFBQVEsa0NBQVcsZUFBZTtBQUFBLE1BQ3pDLE1BQU07QUFBQSxNQUNOLFVBQVU7QUFBQSxJQUNaO0FBQUEsSUFDQSxlQUFlO0FBQUEsTUFDYixVQUFVLENBQUMsU0FBUyxhQUFhLHlCQUF5QixtQkFBbUIsa0JBQWtCO0FBQUEsTUFDL0YsUUFBUTtBQUFBLFFBQ04sU0FBUztBQUFBLFVBQ1AsT0FBTztBQUFBLFVBQ1AsYUFBYTtBQUFBLFVBQ2IseUJBQXlCO0FBQUEsVUFDekIsbUJBQW1CO0FBQUEsVUFDbkIsb0JBQW9CO0FBQUEsUUFDdEI7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6943c2908..d60ce9015 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,73 @@ importers: specifier: ^3.4.0 version: 3.5.26(typescript@5.9.3) + examples/designer-modes: + dependencies: + '@object-ui/components': + specifier: workspace:* + version: link:../../packages/components + '@object-ui/core': + specifier: workspace:* + version: link:../../packages/core + '@object-ui/designer': + specifier: workspace:* + version: link:../../packages/designer + '@object-ui/react': + specifier: workspace:* + version: link:../../packages/react + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.2 + '@types/node': + specifier: ^24.10.1 + version: 24.10.8 + '@types/react': + specifier: 18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: 18.3.1 + version: 18.3.1 + '@vitejs/plugin-react': + specifier: ^5.1.2 + version: 5.1.2(vite@5.4.21(@types/node@24.10.8)) + autoprefixer: + specifier: ^10.4.23 + version: 10.4.23(postcss@8.5.6) + eslint: + specifier: ^9.39.1 + version: 9.39.2(jiti@1.21.7) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-refresh: + specifier: ^0.4.24 + version: 0.4.26(eslint@9.39.2(jiti@1.21.7)) + globals: + specifier: ^16.5.0 + version: 16.5.0 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.46.4 + version: 8.53.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@24.10.8) + examples/prototype: dependencies: '@object-ui/components': @@ -7180,6 +7247,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@5.1.2(vite@5.4.21(@types/node@24.10.8))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 5.4.21(@types/node@24.10.8) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@24.10.8)(jiti@1.21.7))': dependencies: '@babel/core': 7.28.6 From b5ad57c3050384b57b32d7ba98e693e300970fb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:08:25 +0000 Subject: [PATCH 07/14] Update documentation for Canvas mode Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- packages/designer/SPECIALIZED_DESIGNERS.md | 88 +++++++++++++++++++--- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/packages/designer/SPECIALIZED_DESIGNERS.md b/packages/designer/SPECIALIZED_DESIGNERS.md index a75596bbf..e0a10e2a7 100644 --- a/packages/designer/SPECIALIZED_DESIGNERS.md +++ b/packages/designer/SPECIALIZED_DESIGNERS.md @@ -1,6 +1,6 @@ # Specialized Designers -Object UI Designer now supports three specialized designer modes, each optimized for specific use cases: +Object UI Designer now supports **four** specialized designer modes, each optimized for specific use cases: ## ๐ŸŽฏ Designer Modes @@ -132,6 +132,65 @@ function App() { All components from the component registry, including: - Layout, Form, Data Display, Feedback, Overlay, Navigation categories +### 4. Canvas Designer (`mode="canvas"`) ๐Ÿ†• + +A free-form canvas designer with absolute positioning, similar to design tools like Figma. + +**Features:** +- Free-form positioning - drag components anywhere +- Absolute coordinate system (x, y positioning) +- Resize components freely +- Grid snapping for alignment (configurable) +- Visual grid background +- Works like a traditional design canvas + +**Usage:** +```tsx +import { Designer } from '@object-ui/designer'; + +function App() { + return ( + + ); +} +``` + +Or use the dedicated component: +```tsx +import { CanvasDesigner } from '@object-ui/designer'; + +function App() { + return ( + + ); +} +``` + +**Available Components:** +- **Basic**: div, card, text, button, image +- **Form**: input, textarea, select, checkbox, switch +- **Layout**: stack, grid +- **Display**: badge, avatar, separator + +**Configuration Options:** +- `showGrid`: Show/hide alignment grid (default: true) +- `gridSize`: Grid snap size in pixels (default: 20) +- `canvasBackground`: Canvas background color (default: '#ffffff') + ## ๐Ÿ”„ Migration Guide ### From Previous Version @@ -149,6 +208,7 @@ import { Designer } from '@object-ui/designer'; // New - specialized modes + // ๐Ÿ†• NEW! ``` All existing code will continue to work without changes. The default mode is `'general'`, which provides the same functionality as before. @@ -189,31 +249,37 @@ function CustomDesigner() { ## ๐Ÿ“‹ Component Comparison -| Feature | Form Designer | Layout Designer | General Designer | -|---------|--------------|----------------|------------------| -| Form Components | โœ… Primary | โš ๏ธ Limited | โœ… Full | -| Layout Components | โš ๏ธ Limited | โœ… Primary | โœ… Full | -| Navigation | โŒ None | โœ… Full | โœ… Full | -| Data Display | โš ๏ธ Basic | โš ๏ธ Basic | โœ… Full | -| Feedback/Overlay | โŒ None | โŒ None | โœ… Full | -| Component Count | ~15 | ~15 | ~30+ | -| Complexity | Low | Medium | High | -| Use Case | Forms Only | Page Layouts | Everything | +| Feature | Form Designer | Layout Designer | Canvas Designer ๐Ÿ†• | General Designer | +|---------|--------------|-----------------|-------------------|------------------| +| Form Components | โœ… Primary | โš ๏ธ Limited | โœ… Full | โœ… Full | +| Layout Components | โš ๏ธ Limited | โœ… Primary | โš ๏ธ Limited | โœ… Full | +| Navigation | โŒ None | โœ… Full | โŒ None | โœ… Full | +| Data Display | โš ๏ธ Basic | โš ๏ธ Basic | โš ๏ธ Basic | โœ… Full | +| Feedback/Overlay | โŒ None | โŒ None | โŒ None | โœ… Full | +| **Positioning** | **Tree** | **Tree** | **Absolute (x,y)** | **Tree** | +| **Grid Snap** | **โŒ** | **โŒ** | **โœ… Yes** | **โŒ** | +| Component Count | ~15 | ~15 | ~15 | ~30+ | +| Complexity | Low | Medium | Medium | High | +| Use Case | Forms Only | Page Layouts | Free-form Design | Everything | ## ๐Ÿš€ Best Practices 1. **Choose the Right Mode**: Use specialized designers when you know your use case: - Building a form? Use `mode="form"` - Designing a page structure? Use `mode="layout"` + - Creating mockups or free-form designs? Use `mode="canvas"` ๐Ÿ†• - Need everything? Use `mode="general"` or omit the prop 2. **Start Specialized, Upgrade Later**: Begin with a specialized designer for focused work, then export the schema and open it in the general designer if you need additional components. 3. **Component Filtering**: The specialized designers limit available components to reduce cognitive load and improve the design experience for specific tasks. +4. **Canvas Mode for Prototyping**: Use canvas mode when you need pixel-perfect positioning or are creating mockups that don't follow typical web layout patterns. ๐Ÿ†• + ## ๐Ÿ“ Examples See the `examples/` directory for complete working examples: +- `examples/designer-modes/` - Interactive demo with all 4 modes ๐Ÿ†• - `examples/form-designer/` - Form builder example - `examples/layout-designer/` - Page layout example - `examples/general-designer/` - General purpose example From d45eaf6717237f3444e2d607076b74799949c109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:13:55 +0000 Subject: [PATCH 08/14] Initial plan From eddcee4491f18a0ff8dd244f84315ff46d7be632 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:20:09 +0000 Subject: [PATCH 09/14] Fix TypeScript error in CanvasDesigner ResizeHandles usage Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .../src/components/CanvasDesigner.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/designer/src/components/CanvasDesigner.tsx b/packages/designer/src/components/CanvasDesigner.tsx index b1796e037..ba22a6a86 100644 --- a/packages/designer/src/components/CanvasDesigner.tsx +++ b/packages/designer/src/components/CanvasDesigner.tsx @@ -18,7 +18,7 @@ import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; import type { SchemaNode } from '@object-ui/core'; import type { CanvasDesignerConfig } from '../types/designer-modes'; import { SchemaRenderer } from '@object-ui/react'; -import { ResizeHandles } from './ResizeHandle'; +import { ResizeHandles, type ResizeDirection } from './ResizeHandle'; import { ComponentRegistry } from '@object-ui/core'; import { cn } from '@object-ui/components'; @@ -158,7 +158,22 @@ const FreeFormCanvas: React.FC<{ showGrid?: boolean; gridSize?: number }> = ({ return nodes.map((node) => { const isSelected = node.id === selectedNodeId; - const isResizable = ComponentRegistry.getConfig(node.type)?.resizable || false; + const config = ComponentRegistry.getConfig(node.type); + const isResizable = config?.resizable || false; + + // Determine which directions to show based on constraints + const constraints = config?.resizeConstraints || {}; + const directions: ResizeDirection[] = []; + + if (constraints.width !== false) { + directions.push('e', 'w'); + } + if (constraints.height !== false) { + directions.push('n', 's'); + } + if (constraints.width !== false && constraints.height !== false) { + directions.push('ne', 'nw', 'se', 'sw'); + } return (
= ({ {/* Resize handles for selected node */} {isSelected && isResizable && ( { + directions={directions} + onResizeStart={(direction, e) => { const element = document.querySelector(`[data-obj-id="${node.id}"]`) as HTMLElement; if (element) { setResizingNode({ From 4194da2b739e60a2a0e55e1d90ddf921ca564ffc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:26:06 +0000 Subject: [PATCH 10/14] Initial plan From 5e17dfbd8e293d75cf02b439eee2dfe8af8b9de1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:31:37 +0000 Subject: [PATCH 11/14] Fix TypeScript errors in designer-modes example - Remove unnecessary React import from App.tsx (jsx: react-jsx handles JSX automatically) - Update main.tsx to use named imports instead of namespace import - Add vite/client types to tsconfig for CSS import support Fixes TS6133 and TS2307 errors that were causing CI build failures Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/designer-modes/src/App.tsx | 2 +- examples/designer-modes/src/main.tsx | 10 +++++----- examples/designer-modes/tsconfig.json | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/designer-modes/src/App.tsx b/examples/designer-modes/src/App.tsx index e1dae7e80..9f40800d6 100644 --- a/examples/designer-modes/src/App.tsx +++ b/examples/designer-modes/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Designer, type DesignerMode } from '@object-ui/designer'; import type { SchemaNode } from '@object-ui/core'; diff --git a/examples/designer-modes/src/main.tsx b/examples/designer-modes/src/main.tsx index 9aa52ffd1..dfacde090 100644 --- a/examples/designer-modes/src/main.tsx +++ b/examples/designer-modes/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; import App from './App'; import './index.css'; -ReactDOM.createRoot(document.getElementById('root')!).render( - +createRoot(document.getElementById('root')!).render( + - , + , ); diff --git a/examples/designer-modes/tsconfig.json b/examples/designer-modes/tsconfig.json index df2545fa1..4723c2da6 100644 --- a/examples/designer-modes/tsconfig.json +++ b/examples/designer-modes/tsconfig.json @@ -4,6 +4,7 @@ "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", + "types": ["vite/client"], "skipLibCheck": true, /* Bundler mode */ From 8fde9fea3392f92e421b6cd5c2d01b43cd225b4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:41:13 +0000 Subject: [PATCH 12/14] Initial plan From 1265ae78f93a5665c52cf77d3d3a9ac60a363332 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:55:24 +0000 Subject: [PATCH 13/14] Changes before error encountered Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- packages/components/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 64319ac1d..621ff237d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -3,14 +3,14 @@ "version": "0.2.0", "type": "module", "license": "MIT", - "main": "dist/index.umd.js", - "module": "dist/index.mjs", + "main": "dist/index.umd.cjs", + "module": "dist/index.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.umd.js" + "import": "./dist/index.js", + "require": "./dist/index.umd.cjs" }, "./dist/style.css": "./dist/style.css" }, From 2ee2655f0460af343753b58924adcd72377ee0df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:04:14 +0000 Subject: [PATCH 14/14] Changes before error encountered Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- package.json | 2 +- packages/components/package.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 55d368102..fcc106faf 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "scripts": { "dev": "pnpm --filter prototype dev", "start": "pnpm --filter prototype dev", - "build": "pnpm -r build", + "build": "pnpm --filter './packages/*' -r build && pnpm --filter './examples/*' -r build", "pretest": "pnpm --filter @object-ui/types build && pnpm --filter @object-ui/core build && pnpm --filter @object-ui/react build && pnpm --filter @object-ui/components build", "test": "vitest run", "docs:dev": "pnpm --filter object-ui-docs dev", diff --git a/packages/components/package.json b/packages/components/package.json index 64319ac1d..621ff237d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -3,14 +3,14 @@ "version": "0.2.0", "type": "module", "license": "MIT", - "main": "dist/index.umd.js", - "module": "dist/index.mjs", + "main": "dist/index.umd.cjs", + "module": "dist/index.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.umd.js" + "import": "./dist/index.js", + "require": "./dist/index.umd.cjs" }, "./dist/style.css": "./dist/style.css" },