Spaces:
Running
Running
| import { useCallback, useState } from 'react'; | |
| import { | |
| Layout, Type, Square, Image, Link, List, PanelTop, PanelBottom, | |
| Menu, ChevronRight, Edit3, Hash, Grid3X3, Music, Video, | |
| Code, Table, CheckSquare, Star, Layers, Plus | |
| } from 'lucide-react'; | |
| interface ElementPreset { | |
| tagName: string; | |
| label: string; | |
| icon: any; | |
| defaultProps: any; | |
| } | |
| const ELEMENT_CATEGORIES: { name: string; icon: any; items: ElementPreset[] }[] = [ | |
| { | |
| name: 'Layout', icon: Layout, items: [ | |
| { tagName: 'div', label: 'Container', icon: Layout, defaultProps: { style: { padding: '16px', border: '1px dashed #94a3b8', minHeight: '50px', borderRadius: '4px' } } }, | |
| { tagName: 'span', label: 'Inline Box', icon: Layout, defaultProps: { style: { padding: '4px 8px', border: '1px dashed #94a3b8', borderRadius: '4px', display: 'inline-block' } } }, | |
| { tagName: 'section', label: 'Section', icon: Layout, defaultProps: { style: { padding: '24px 16px', margin: '8px 0', border: '1px dashed #cbd5e1', borderRadius: '8px' } } }, | |
| { tagName: 'article', label: 'Article', icon: Layout, defaultProps: { style: { padding: '24px', margin: '8px 0', border: '1px dashed #cbd5e1', borderRadius: '8px' } } }, | |
| { tagName: 'main', label: 'Main Content', icon: Layout, defaultProps: { style: { padding: '16px', minHeight: '200px' } } }, | |
| { tagName: 'header', label: 'Header', icon: PanelTop, defaultProps: { style: { padding: '16px', background: '#f1f5f9', borderRadius: '4px' } } }, | |
| { tagName: 'footer', label: 'Footer', icon: PanelBottom, defaultProps: { style: { padding: '16px', background: '#f1f5f9', borderRadius: '4px', marginTop: '16px' } } }, | |
| { tagName: 'nav', label: 'Nav Bar', icon: Menu, defaultProps: { style: { padding: '12px 16px', background: '#1e293b', color: '#fff', display: 'flex', gap: '20px', borderRadius: '4px' } } }, | |
| { tagName: 'aside', label: 'Sidebar', icon: Layout, defaultProps: { style: { padding: '16px', background: '#f8fafc', borderRadius: '4px', width: '250px' } } }, | |
| { tagName: 'details', label: 'Details', icon: ChevronRight, defaultProps: { style: { padding: '8px', border: '1px solid #e2e8f0', borderRadius: '4px' } } }, | |
| { tagName: 'dialog', label: 'Dialog/Modal', icon: Layers, defaultProps: { style: { padding: '24px', background: '#fff', border: '1px solid #cbd5e1', borderRadius: '8px', boxShadow: '0 20px 60px rgba(0,0,0,0.3)', maxWidth: '400px', margin: '40px auto' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Typography', icon: Type, items: [ | |
| { tagName: 'h1', label: 'Heading 1', icon: Type, defaultProps: { textContent: 'Heading 1', style: { fontSize: '32px', fontWeight: 'bold', margin: '0 0 8px 0' } } }, | |
| { tagName: 'h2', label: 'Heading 2', icon: Type, defaultProps: { textContent: 'Heading 2', style: { fontSize: '28px', fontWeight: 'bold', margin: '0 0 8px 0' } } }, | |
| { tagName: 'h3', label: 'Heading 3', icon: Type, defaultProps: { textContent: 'Heading 3', style: { fontSize: '24px', fontWeight: '600', margin: '0 0 6px 0' } } }, | |
| { tagName: 'h4', label: 'Heading 4', icon: Type, defaultProps: { textContent: 'Heading 4', style: { fontSize: '20px', fontWeight: '600', margin: '0 0 6px 0' } } }, | |
| { tagName: 'h5', label: 'Heading 5', icon: Type, defaultProps: { textContent: 'Heading 5', style: { fontSize: '18px', fontWeight: '600', margin: '0 0 4px 0' } } }, | |
| { tagName: 'h6', label: 'Heading 6', icon: Type, defaultProps: { textContent: 'Heading 6', style: { fontSize: '16px', fontWeight: '600', margin: '0 0 4px 0' } } }, | |
| { tagName: 'p', label: 'Paragraph', icon: Type, defaultProps: { textContent: 'This is a paragraph of text that can be edited. You can change its color, size, and more in the properties panel.', style: { fontSize: '16px', lineHeight: '1.6', margin: '0 0 12px 0' } } }, | |
| { tagName: 'blockquote', label: 'Quote', icon: Type, defaultProps: { textContent: 'A famous quote goes right here.', style: { padding: '12px 20px', borderLeft: '4px solid #6366f1', background: '#f8fafc', fontStyle: 'italic', margin: '12px 0' } } }, | |
| { tagName: 'pre', label: 'Preformatted', icon: Code, defaultProps: { textContent: 'code block\n with formatting', style: { padding: '12px', background: '#1e293b', color: '#e2e8f0', borderRadius: '4px', fontFamily: 'monospace', fontSize: '14px' } } }, | |
| { tagName: 'code', label: 'Inline Code', icon: Code, defaultProps: { textContent: 'code()', style: { padding: '2px 6px', background: '#f1f5f9', borderRadius: '3px', fontFamily: 'monospace', fontSize: '14px' } } }, | |
| { tagName: 'mark', label: 'Highlight', icon: Type, defaultProps: { textContent: 'highlighted text', style: { background: '#fef08a', padding: '2px 4px', borderRadius: '2px' } } }, | |
| { tagName: 'small', label: 'Small Text', icon: Type, defaultProps: { textContent: 'small print', style: { fontSize: '12px', color: '#64748b' } } }, | |
| { tagName: 'abbr', label: 'Abbreviation', icon: Type, defaultProps: { textContent: 'HTML', style: { textDecoration: 'underline dotted', cursor: 'help' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Forms & Inputs', icon: Edit3, items: [ | |
| { tagName: 'form', label: 'Form', icon: Layout, defaultProps: { style: { padding: '20px', border: '1px solid #e2e8f0', borderRadius: '8px', background: '#f8fafc', display: 'flex', flexDirection: 'column', gap: '12px' } } }, | |
| { tagName: 'input', label: 'Text Input', icon: Edit3, defaultProps: { type: 'text', placeholder: 'Enter text...', style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%' } } }, | |
| { tagName: 'input', label: 'Number Input', icon: Hash, defaultProps: { type: 'number', placeholder: '0', style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%' } } }, | |
| { tagName: 'input', label: 'Email Input', icon: Edit3, defaultProps: { type: 'email', placeholder: 'email@example.com', style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%' } } }, | |
| { tagName: 'input', label: 'Password Input', icon: Edit3, defaultProps: { type: 'password', placeholder: '••••••••', style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%' } } }, | |
| { tagName: 'input', label: 'Checkbox', icon: CheckSquare, defaultProps: { type: 'checkbox', style: { width: '18px', height: '18px', cursor: 'pointer' } } }, | |
| { tagName: 'input', label: 'Radio', icon: Star, defaultProps: { type: 'radio', style: { width: '18px', height: '18px', cursor: 'pointer' } } }, | |
| { tagName: 'input', label: 'Color Picker', icon: Layout, defaultProps: { type: 'color', value: '#6366f1', style: { width: '40px', height: '40px', cursor: 'pointer', border: 'none', padding: '0' } } }, | |
| { tagName: 'input', label: 'Range Slider', icon: Layout, defaultProps: { type: 'range', min: '0', max: '100', value: '50', style: { width: '100%', cursor: 'pointer' } } }, | |
| { tagName: 'input', label: 'Date Picker', icon: Layout, defaultProps: { type: 'date', style: { padding: '8px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px' } } }, | |
| { tagName: 'input', label: 'File Upload', icon: Image, defaultProps: { type: 'file', style: { fontSize: '14px' } } }, | |
| { tagName: 'textarea', label: 'Text Area', icon: Edit3, defaultProps: { placeholder: 'Enter long text...', style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%', minHeight: '80px', resize: 'vertical' } } }, | |
| { tagName: 'select', label: 'Dropdown', icon: ChevronRight, defaultProps: { style: { padding: '8px 12px', border: '1px solid #cbd5e1', borderRadius: '4px', fontSize: '14px', width: '100%', background: '#fff' } } }, | |
| { tagName: 'option', label: 'Option', icon: ChevronRight, defaultProps: { textContent: 'Option 1', value: '1', style: { padding: '4px' } } }, | |
| { tagName: 'label', label: 'Label', icon: Type, defaultProps: { textContent: 'Label:', style: { fontSize: '14px', fontWeight: '500', marginBottom: '4px', display: 'block' } } }, | |
| { tagName: 'fieldset', label: 'Field Set', icon: Layout, defaultProps: { style: { padding: '16px', border: '1px solid #cbd5e1', borderRadius: '6px' } } }, | |
| { tagName: 'legend', label: 'Legend', icon: Type, defaultProps: { textContent: 'Section Title', style: { fontSize: '14px', fontWeight: '600', padding: '0 6px' } } }, | |
| { tagName: 'progress', label: 'Progress Bar', icon: Layout, defaultProps: { value: '50', max: '100', style: { width: '100%', height: '12px', borderRadius: '6px' } } }, | |
| { tagName: 'meter', label: 'Meter', icon: Layout, defaultProps: { value: '0.6', min: '0', max: '1', style: { width: '100%', height: '16px' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Buttons', icon: Square, items: [ | |
| { tagName: 'button', label: 'Button', icon: Square, defaultProps: { textContent: 'Click Me', type: 'button', style: { padding: '10px 20px', background: '#6366f1', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', fontWeight: '500' } } }, | |
| { tagName: 'button', label: 'Success Button', icon: Square, defaultProps: { textContent: 'Save', type: 'button', style: { padding: '10px 20px', background: '#22c55e', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', fontWeight: '500' } } }, | |
| { tagName: 'button', label: 'Danger Button', icon: Square, defaultProps: { textContent: 'Delete', type: 'button', style: { padding: '10px 20px', background: '#ef4444', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', fontWeight: '500' } } }, | |
| { tagName: 'button', label: 'Outline Button', icon: Square, defaultProps: { textContent: 'Cancel', type: 'button', style: { padding: '10px 20px', background: 'transparent', color: '#64748b', border: '1px solid #cbd5e1', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', fontWeight: '500' } } }, | |
| { tagName: 'button', label: 'Icon Button', icon: Square, defaultProps: { textContent: '★', type: 'button', style: { padding: '8px 12px', background: '#f1f5f9', color: '#64748b', border: '1px solid #e2e8f0', borderRadius: '6px', cursor: 'pointer', fontSize: '16px', lineHeight: '1' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Media', icon: Image, items: [ | |
| { tagName: 'img', label: 'Image', icon: Image, defaultProps: { src: 'https://via.placeholder.com/300x200', alt: 'Placeholder image', style: { maxWidth: '100%', borderRadius: '4px', height: 'auto' } } }, | |
| { tagName: 'img', label: 'Avatar', icon: Image, defaultProps: { src: 'https://via.placeholder.com/48', alt: 'Avatar', style: { borderRadius: '50%', width: '48px', height: '48px' } } }, | |
| { tagName: 'figure', label: 'Figure', icon: Image, defaultProps: { style: { margin: '12px 0', textAlign: 'center' } } }, | |
| { tagName: 'figcaption', label: 'Caption', icon: Type, defaultProps: { textContent: 'Image caption here', style: { fontSize: '13px', color: '#64748b', marginTop: '4px', fontStyle: 'italic' } } }, | |
| { tagName: 'picture', label: 'Picture', icon: Image, defaultProps: { style: { display: 'block' } } }, | |
| { tagName: 'video', label: 'Video', icon: Video, defaultProps: { src: 'https://www.w3schools.com/html/mov_bbb.mp4', controls: true, style: { maxWidth: '100%', borderRadius: '4px' } } }, | |
| { tagName: 'audio', label: 'Audio', icon: Music, defaultProps: { src: 'https://www.w3schools.com/html/horse.mp3', controls: true, style: { width: '100%' } } }, | |
| { tagName: 'canvas', label: 'Canvas', icon: Layout, defaultProps: { width: '300', height: '150', style: { border: '1px solid #cbd5e1', borderRadius: '4px', width: '100%' } } }, | |
| { tagName: 'svg', label: 'SVG Icon', icon: Layout, defaultProps: { width: '24', height: '24', viewBox: '0 0 24 24', style: { display: 'inline-block' } } }, | |
| { tagName: 'iframe', label: 'IFrame', icon: Layout, defaultProps: { src: 'https://example.com', style: { width: '100%', height: '200px', border: '1px solid #e2e8f0', borderRadius: '4px' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Links & Navigation', icon: Link, items: [ | |
| { tagName: 'a', label: 'Link', icon: Link, defaultProps: { href: '#', textContent: 'Click here', style: { color: '#6366f1', textDecoration: 'underline', cursor: 'pointer' } } }, | |
| { tagName: 'a', label: 'Button Link', icon: Link, defaultProps: { href: '#', textContent: 'Get Started', style: { display: 'inline-block', padding: '10px 24px', background: '#6366f1', color: '#fff', borderRadius: '6px', textDecoration: 'none', fontWeight: '500' } } }, | |
| { tagName: 'nav', label: 'Nav Bar', icon: Menu, defaultProps: { style: { padding: '12px 16px', background: '#1e293b', color: '#fff', display: 'flex', gap: '20px', borderRadius: '4px' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Lists', icon: List, items: [ | |
| { tagName: 'ul', label: 'Unordered List', icon: List, defaultProps: { style: { paddingLeft: '24px', margin: '8px 0' } } }, | |
| { tagName: 'ol', label: 'Ordered List', icon: List, defaultProps: { style: { paddingLeft: '24px', margin: '8px 0' } } }, | |
| { tagName: 'li', label: 'List Item', icon: List, defaultProps: { textContent: 'List item', style: { margin: '4px 0' } } }, | |
| { tagName: 'dl', label: 'Definition List', icon: List, defaultProps: { style: { margin: '8px 0' } } }, | |
| { tagName: 'dt', label: 'Term', icon: List, defaultProps: { textContent: 'Term', style: { fontWeight: '600', marginTop: '8px' } } }, | |
| { tagName: 'dd', label: 'Definition', icon: List, defaultProps: { textContent: 'Definition description', style: { marginLeft: '20px', color: '#475569' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Tables', icon: Table, items: [ | |
| { tagName: 'table', label: 'Table', icon: Table, defaultProps: { style: { width: '100%', borderCollapse: 'collapse', border: '1px solid #e2e8f0' } } }, | |
| { tagName: 'thead', label: 'Table Head', icon: Table, defaultProps: { style: { background: '#f8fafc' } } }, | |
| { tagName: 'tbody', label: 'Table Body', icon: Table, defaultProps: {} }, | |
| { tagName: 'tfoot', label: 'Table Foot', icon: Table, defaultProps: { style: { background: '#f8fafc', fontWeight: '600' } } }, | |
| { tagName: 'tr', label: 'Table Row', icon: Table, defaultProps: { style: { borderBottom: '1px solid #e2e8f0' } } }, | |
| { tagName: 'th', label: 'Table Header', icon: Table, defaultProps: { textContent: 'Header', style: { padding: '8px 12px', textAlign: 'left', fontWeight: '600', borderBottom: '2px solid #e2e8f0' } } }, | |
| { tagName: 'td', label: 'Table Cell', icon: Table, defaultProps: { textContent: 'Cell data', style: { padding: '8px 12px', borderBottom: '1px solid #e2e8f0' } } }, | |
| { tagName: 'caption', label: 'Table Caption', icon: Type, defaultProps: { textContent: 'Table Title', style: { captionSide: 'bottom', padding: '8px', fontSize: '13px', color: '#64748b', fontStyle: 'italic' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Interactive', icon: Layers, items: [ | |
| { tagName: 'details', label: 'Collapsible', icon: ChevronRight, defaultProps: { textContent: 'Click to expand', style: { padding: '8px 12px', border: '1px solid #e2e8f0', borderRadius: '6px' } } }, | |
| { tagName: 'summary', label: 'Summary', icon: ChevronRight, defaultProps: { textContent: 'Click me', style: { fontWeight: '600', cursor: 'pointer', padding: '4px 0' } } }, | |
| { tagName: 'dialog', label: 'Modal', icon: Layers, defaultProps: { textContent: 'Modal content', style: { padding: '24px', background: '#fff', border: 'none', borderRadius: '12px', boxShadow: '0 20px 60px rgba(0,0,0,0.3)', maxWidth: '400px', margin: '40px auto' } } }, | |
| { tagName: 'menu', label: 'Menu', icon: Menu, defaultProps: { style: { padding: '4px', border: '1px solid #e2e8f0', borderRadius: '6px', background: '#fff', display: 'flex', flexDirection: 'column', gap: '2px' } } }, | |
| ] | |
| }, | |
| { | |
| name: 'Semantic', icon: Type, items: [ | |
| { tagName: 'strong', label: 'Strong', icon: Type, defaultProps: { textContent: 'important text', style: { fontWeight: 'bold' } } }, | |
| { tagName: 'em', label: 'Emphasis', icon: Type, defaultProps: { textContent: 'emphasized text', style: { fontStyle: 'italic' } } }, | |
| { tagName: 'b', label: 'Bold', icon: Type, defaultProps: { textContent: 'bold text', style: { fontWeight: 'bold' } } }, | |
| { tagName: 'i', label: 'Italic', icon: Type, defaultProps: { textContent: 'italic text', style: { fontStyle: 'italic' } } }, | |
| { tagName: 'u', label: 'Underline', icon: Type, defaultProps: { textContent: 'underlined text', style: { textDecoration: 'underline' } } }, | |
| { tagName: 's', label: 'Strikethrough', icon: Type, defaultProps: { textContent: 'strikethrough', style: { textDecoration: 'line-through' } } }, | |
| { tagName: 'sub', label: 'Subscript', icon: Type, defaultProps: { textContent: '2', style: { fontSize: '75%', verticalAlign: 'sub' } } }, | |
| { tagName: 'sup', label: 'Superscript', icon: Type, defaultProps: { textContent: 'th', style: { fontSize: '75%', verticalAlign: 'super' } } }, | |
| { tagName: 'cite', label: 'Citation', icon: Type, defaultProps: { textContent: 'Source Title', style: { fontStyle: 'italic' } } }, | |
| { tagName: 'time', label: 'Date/Time', icon: Type, defaultProps: { textContent: 'June 9, 2026', style: { fontWeight: '500' } } }, | |
| { tagName: 'address', label: 'Address', icon: Type, defaultProps: { textContent: '123 Main St, City', style: { fontStyle: 'normal', padding: '8px', background: '#f8fafc', borderRadius: '4px' } } }, | |
| ] | |
| }, | |
| ]; | |
| export default function Toolbox() { | |
| const [expandedCategories, setExpandedCategories] = useState<Set<string>>(() => new Set(ELEMENT_CATEGORIES.slice(0, 3).map(c => c.name))); | |
| const handleDragStart = useCallback((e: React.DragEvent, element: ElementPreset) => { | |
| e.dataTransfer.setData('application/visual-element', JSON.stringify(element)); | |
| e.dataTransfer.effectAllowed = 'copy'; | |
| }, []); | |
| const toggleCategory = useCallback((name: string) => { | |
| setExpandedCategories(prev => { | |
| const next = new Set(prev); | |
| if (next.has(name)) next.delete(name); | |
| else next.add(name); | |
| return next; | |
| }); | |
| }, []); | |
| return ( | |
| <div className="space-y-2 w-full"> | |
| {ELEMENT_CATEGORIES.map((cat) => { | |
| const isExpanded = expandedCategories.has(cat.name); | |
| const CatIcon = cat.icon; | |
| return ( | |
| <div key={cat.name} className="w-full"> | |
| <button | |
| onClick={() => toggleCategory(cat.name)} | |
| className="w-full flex items-center gap-1.5 px-2 py-1.5 text-xs font-medium text-surface-300 hover:text-white hover:bg-surface-800 rounded-md transition-colors" | |
| > | |
| <CatIcon className="w-3.5 h-3.5" /> | |
| <span className="truncate">{cat.name}</span> | |
| <span className="ml-auto text-surface-500 text-[10px]">{cat.items.length}</span> | |
| </button> | |
| {isExpanded && ( | |
| <div className="ml-1 space-y-0.5 mt-0.5"> | |
| {cat.items.map((el) => { | |
| const Icon = el.icon; | |
| return ( | |
| <div | |
| key={`${el.tagName}-${el.label}`} | |
| draggable | |
| onDragStart={(e) => handleDragStart(e, el)} | |
| className="w-full flex items-center gap-1.5 px-2 py-1 rounded-md cursor-grab active:cursor-grabbing | |
| hover:bg-surface-700 text-surface-400 hover:text-white transition-colors text-xs" | |
| title={el.label} | |
| > | |
| <Icon className="w-3.5 h-3.5 flex-shrink-0" /> | |
| <span className="truncate">{el.label}</span> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| } | |