import { useState, useCallback } from 'react'; import { VisualElement } from '../../types/blocks'; import { useEditorStore } from '../../store/editorStore'; import { Plus, Trash2, Palette, Type, Move, Square, Bold, Italic, AlignLeft, AlignCenter, AlignRight, MousePointer, EyeOff, Maximize2, Grid3X3, Play, RotateCcw, Underline, Strikethrough } from 'lucide-react'; interface PropertyPanelProps { element: VisualElement | null; } // Spacing quick buttons const SPACING_PRESETS = ['0', '4px', '8px', '12px', '16px', '24px', '32px', '48px']; const RADIUS_PRESETS = [ { label: 'None', value: '0' }, { label: 'Small', value: '4px' }, { label: 'Medium', value: '8px' }, { label: 'Large', value: '16px' }, { label: 'Round', value: '50%' }, ]; interface CatProp { key: string; label: string; type: string; options?: { label: string; value: string }[]; suffix?: string; } const CSS_CATEGORIES: { name: string; icon: any; properties: CatProp[] }[] = [ { name: 'Colors', icon: Palette, properties: [ { key: 'backgroundColor', label: 'Background', type: 'color' }, { key: 'color', label: 'Text Color', type: 'color' }, { key: 'opacity', label: 'Opacity', type: 'number', suffix: '' }, ] }, { name: 'Text', icon: Type, properties: [ { key: 'fontSize', label: 'Size', type: 'px', suffix: 'px' }, { key: 'fontWeight', label: 'Weight', type: 'select', options: [ { label: 'Normal', value: 'normal' }, { label: 'Bold', value: 'bold' }, { label: 'Light', value: '300' }, { label: 'Medium', value: '500' }, { label: 'Semi Bold', value: '600' }, { label: 'Black', value: '900' }, ]}, { key: 'fontFamily', label: 'Font', type: 'select', options: [ { label: 'Default', value: '' }, { label: 'Arial', value: 'Arial, sans-serif' }, { label: 'Georgia', value: 'Georgia, serif' }, { label: 'Verdana', value: 'Verdana, sans-serif' }, { label: 'Monospace', value: "'Courier New', monospace" }, ]}, { key: 'textAlign', label: 'Align', type: 'select', options: [ { label: 'Left', value: 'left' }, { label: 'Center', value: 'center' }, { label: 'Right', value: 'right' }, ]}, { key: 'lineHeight', label: 'Line Height', type: 'number', suffix: '' }, { key: 'letterSpacing', label: 'Letter Space', type: 'px', suffix: 'px' }, { key: 'textDecoration', label: 'Decoration', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Underline', value: 'underline' }, { label: 'Line Through', value: 'line-through' }, ]}, ] }, { name: 'Spacing & Size', icon: Move, properties: [ { key: 'padding', label: 'All Padding', type: 'text', suffix: '' }, { key: 'paddingTop', label: 'Padding Top', type: 'px', suffix: 'px' }, { key: 'paddingBottom', label: 'Padding Bottom', type: 'px', suffix: 'px' }, { key: 'paddingLeft', label: 'Padding Left', type: 'px', suffix: 'px' }, { key: 'paddingRight', label: 'Padding Right', type: 'px', suffix: 'px' }, { key: 'margin', label: 'All Margin', type: 'text', suffix: '' }, { key: 'marginTop', label: 'Margin Top', type: 'px', suffix: 'px' }, { key: 'marginBottom', label: 'Margin Bottom', type: 'px', suffix: 'px' }, { key: 'marginLeft', label: 'Margin Left', type: 'px', suffix: 'px' }, { key: 'marginRight', label: 'Margin Right', type: 'px', suffix: 'px' }, { key: 'width', label: 'Width', type: 'text', suffix: '' }, { key: 'height', label: 'Height', type: 'text', suffix: '' }, { key: 'minWidth', label: 'Min Width', type: 'text', suffix: '' }, { key: 'minHeight', label: 'Min Height', type: 'text', suffix: '' }, { key: 'maxWidth', label: 'Max Width', type: 'text', suffix: '' }, { key: 'maxHeight', label: 'Max Height', type: 'text', suffix: '' }, ] }, { name: 'Layout', icon: Grid3X3, properties: [ { key: 'display', label: 'Display', type: 'select', options: [ { label: 'Default', value: '' }, { label: 'Block', value: 'block' }, { label: 'Inline', value: 'inline' }, { label: 'Inline Block', value: 'inline-block' }, { label: 'Flex', value: 'flex' }, { label: 'Grid', value: 'grid' }, { label: 'None', value: 'none' }, ]}, { key: 'flexDirection', label: 'Flex Direction', type: 'select', options: [ { label: 'Row', value: 'row' }, { label: 'Column', value: 'column' }, ]}, { key: 'justifyContent', label: 'Justify', type: 'select', options: [ { label: 'Start', value: 'flex-start' }, { label: 'Center', value: 'center' }, { label: 'End', value: 'flex-end' }, { label: 'Space Between', value: 'space-between' }, ]}, { key: 'alignItems', label: 'Align', type: 'select', options: [ { label: 'Start', value: 'flex-start' }, { label: 'Center', value: 'center' }, { label: 'End', value: 'flex-end' }, { label: 'Stretch', value: 'stretch' }, ]}, { key: 'gap', label: 'Gap', type: 'px', suffix: 'px' }, { key: 'position', label: 'Position', type: 'select', options: [ { label: 'Static', value: 'static' }, { label: 'Relative', value: 'relative' }, { label: 'Absolute', value: 'absolute' }, { label: 'Fixed', value: 'fixed' }, { label: 'Sticky', value: 'sticky' }, ]}, { key: 'top', label: 'Top', type: 'px', suffix: 'px' }, { key: 'left', label: 'Left', type: 'px', suffix: 'px' }, { key: 'right', label: 'Right', type: 'px', suffix: 'px' }, { key: 'bottom', label: 'Bottom', type: 'px', suffix: 'px' }, { key: 'overflow', label: 'Overflow', type: 'select', options: [ { label: 'Visible', value: 'visible' }, { label: 'Hidden', value: 'hidden' }, { label: 'Auto', value: 'auto' }, { label: 'Scroll', value: 'scroll' }, ]}, ] }, { name: 'Border & Rounding', icon: Square, properties: [ { key: 'borderWidth', label: 'Border Width', type: 'px', suffix: 'px' }, { key: 'borderStyle', label: 'Border Style', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Solid', value: 'solid' }, { label: 'Dashed', value: 'dashed' }, { label: 'Dotted', value: 'dotted' }, ]}, { key: 'borderColor', label: 'Border Color', type: 'color' }, { key: 'borderRadius', label: 'Round Corners', type: 'px', suffix: 'px' }, { key: 'boxShadow', label: 'Shadow', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Small', value: '0 1px 3px rgba(0,0,0,0.12)' }, { label: 'Medium', value: '0 4px 12px rgba(0,0,0,0.15)' }, { label: 'Large', value: '0 10px 40px rgba(0,0,0,0.2)' }, { label: 'Glow', value: '0 0 20px rgba(99,102,241,0.3)' }, ]}, ] }, { name: 'Effects', icon: EyeOff, properties: [ { key: 'transition', label: 'Transition', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Fast', value: 'all 0.15s ease' }, { label: 'Normal', value: 'all 0.3s ease' }, { label: 'Slow', value: 'all 0.5s ease' }, ]}, { key: 'transform', label: 'Transform', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Scale 1.1', value: 'scale(1.1)' }, { label: 'Rotate 45°', value: 'rotate(45deg)' }, { label: 'Rotate 90°', value: 'rotate(90deg)' }, { label: 'Flip X', value: 'scaleX(-1)' }, { label: 'Flip Y', value: 'scaleY(-1)' }, ]}, { key: 'cursor', label: 'Cursor', type: 'select', options: [ { label: 'Default', value: 'default' }, { label: 'Pointer', value: 'pointer' }, { label: 'Grab', value: 'grab' }, { label: 'Move', value: 'move' }, ]}, ] }, ]; export default function PropertyPanel({ element }: PropertyPanelProps) { const { updateVisualElement, removeVisualElement, setSelectedElement } = useEditorStore(); const [expandedCat, setExpandedCat] = useState('Colors'); const [addingToCat, setAddingToCat] = useState(null); if (!element) { return (

Select an element on the canvas

or in the element tree

); } const updateProp = (key: string, value: any) => updateVisualElement(element.id, { props: { ...element.props, [key]: value } }); const updateStyle = (key: string, value: any) => updateVisualElement(element.id, { styles: { ...element.styles, [key]: value } }); const removeStyle = (key: string) => { const { [key]: _, ...rest } = element.styles; updateVisualElement(element.id, { styles: rest }); }; const updateAttribute = (key: string, value: any) => updateVisualElement(element.id, { attributes: { ...element.attributes, [key]: value } }); const activeStyles = new Set(Object.keys(element.styles || {})); const addStyle = (key: string) => { updateStyle(key, ''); setAddingToCat(null); }; const renderStyleEditor = (prop: CatProp) => { const value = (element.styles || {})[prop.key] ?? ''; return (
{prop.type === 'color' ? (
updateStyle(prop.key, e.target.value)} className="w-6 h-6 rounded cursor-pointer border border-surface-600 p-0.5 flex-shrink-0" /> updateStyle(prop.key, e.target.value)} className="flex-1 bg-transparent text-[11px] text-surface-300 outline-none min-w-0" placeholder={prop.label} />
) : prop.type === 'select' ? ( ) : prop.type === 'px' ? (
updateStyle(prop.key, e.target.value ? `${e.target.value}px` : '')} className="flex-1 bg-transparent text-[11px] text-surface-300 outline-none min-w-0" placeholder={prop.label} min="0" /> px
) : (
updateStyle(prop.key, e.target.value)} className="flex-1 bg-transparent text-[11px] text-surface-300 outline-none min-w-0" placeholder={prop.label} /> {prop.suffix && {prop.suffix}}
)}
); }; return (
{/* Element header */}
<{element.tagName}> {element.props?.id ? `#${element.props.id}` : ''}
{/* Quick element identity */}
updateProp('id', e.target.value)} className="w-full bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 text-[11px] text-surface-200 outline-none" placeholder="my-id" />
updateVisualElement(element.id, { classes: e.target.value.split(' ').filter(Boolean) })} className="w-full bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 text-[11px] text-surface-200 outline-none" placeholder="class1 class2" />
{/* Text Content */} {['p','h1','h2','h3','h4','h5','h6','span','a','button','label','li','strong','em','b','i','u','s','mark','figcaption','legend','summary','dt','dd','th','td','caption','option','blockquote','pre','code','abbr','cite','time','address'].includes(element.tagName) && (