zunokit-builder
Version:
🎨 Figma-style drag-and-drop UI builder for React developers. Create UI visually, export clean Tailwind CSS code. Perfect for rapid prototyping and component creation.
1 lines • 63.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/ZunoBuilder.tsx","../src/components/builder/BuilderProvider.tsx","../src/components/blocks/BlockRenderer.tsx","../src/utils/index.ts","../src/components/builder/Canvas.tsx","../src/components/builder/Sidebar.tsx","../src/components/builder/PropertyPanel.tsx","../src/components/builder/Toolbar.tsx","../src/hooks/useZunoBuilder.ts"],"sourcesContent":["import React, { useState } from 'react'\nimport { BuilderProvider } from './builder/BuilderProvider'\nimport { Canvas } from './builder/Canvas'\nimport { Sidebar } from './builder/Sidebar'\nimport { PropertyPanel } from './builder/PropertyPanel'\nimport { Toolbar } from './builder/Toolbar'\nimport type { ZunoBuilderConfig } from '../types'\n\ninterface ZunoBuilderProps {\n config?: ZunoBuilderConfig\n children: React.ReactNode\n}\n\nexport function ZunoBuilder({ config, children }: ZunoBuilderProps) {\n const [isBuilderOpen, setIsBuilderOpen] = useState(false)\n const isEnabled = config?.enabled ?? process.env.NODE_ENV === 'development'\n \n if (!isEnabled) {\n return <>{children}</>\n }\n\n if (!isBuilderOpen) {\n return (\n <div className=\"relative\">\n {children}\n {/* Floating Builder Button */}\n <div className=\"fixed bottom-6 right-6 z-50\">\n <button\n onClick={() => setIsBuilderOpen(true)}\n className=\"group bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-6 py-3 rounded-full shadow-lg hover:shadow-xl transition-all duration-200 flex items-center gap-2 font-medium\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\" />\n </svg>\n Open Builder\n </button>\n </div>\n </div>\n )\n }\n\n return (\n <BuilderProvider>\n {/* Figma-style Builder Interface */}\n <div className=\"fixed inset-0 z-50 bg-gray-50 flex flex-col\">\n {/* Top Toolbar */}\n <div className=\"h-12 bg-white border-b border-gray-200 flex items-center justify-between px-4 shadow-sm\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <div className=\"w-6 h-6 bg-gradient-to-br from-blue-500 to-purple-600 rounded flex items-center justify-center\">\n <span className=\"text-white text-xs font-bold\">Z</span>\n </div>\n <span className=\"font-semibold text-gray-900\">Zuno Builder</span>\n </div>\n <div className=\"h-6 w-px bg-gray-300\" />\n <Toolbar />\n </div>\n \n <div className=\"flex items-center gap-3\">\n <button className=\"px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded transition-colors\">\n Preview\n </button>\n <button className=\"px-3 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors\">\n Publish\n </button>\n <div className=\"h-6 w-px bg-gray-300\" />\n <button\n onClick={() => setIsBuilderOpen(false)}\n className=\"p-1.5 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Main Content Area */}\n <div className=\"flex flex-1 overflow-hidden\">\n {/* Left Sidebar - Components & Layers */}\n <div className=\"w-64 bg-white border-r border-gray-200 flex flex-col\">\n <Sidebar />\n </div>\n \n {/* Center Canvas Area */}\n <div className=\"flex-1 bg-gray-50 flex flex-col overflow-hidden\">\n {/* Canvas Toolbar */}\n <div className=\"h-10 bg-white border-b border-gray-200 flex items-center justify-center px-4\">\n <div className=\"flex items-center gap-2 text-sm text-gray-600\">\n <span>100%</span>\n <div className=\"h-4 w-px bg-gray-300\" />\n <span>Frame 1</span>\n </div>\n </div>\n \n {/* Canvas */}\n <div className=\"flex-1 overflow-auto p-8\">\n <Canvas />\n </div>\n </div>\n \n {/* Right Sidebar - Properties */}\n <div className=\"w-72 bg-white border-l border-gray-200\">\n <PropertyPanel />\n </div>\n </div>\n </div>\n </BuilderProvider>\n )\n}","import React, { createContext, useContext, useReducer, useEffect } from 'react'\nimport type { BuilderState, ZunoBlock, ZunoLayout } from '../../types'\n\ninterface BuilderAction {\n type: 'SET_LAYOUT' | 'ADD_BLOCK' | 'UPDATE_BLOCK' | 'REMOVE_BLOCK' | 'SELECT_BLOCK' | 'SET_LOADING' | 'SET_TOOL'\n payload?: any\n}\n\nconst initialState: BuilderState = {\n layout: null,\n selectedBlockId: null,\n hoveredBlockId: null,\n draggedBlockId: null,\n clipboard: null,\n isLoading: true,\n isDirty: false,\n mode: 'design',\n tool: 'select',\n viewport: {\n width: 1200,\n height: 800,\n scale: 1,\n offsetX: 0,\n offsetY: 0,\n showGrid: false,\n gridSize: 10,\n snapToGrid: false,\n showRulers: false,\n },\n history: {\n past: [],\n present: null,\n future: [],\n maxHistorySize: 50,\n }\n}\n\nfunction builderReducer(state: BuilderState, action: BuilderAction): BuilderState {\n switch (action.type) {\n case 'SET_LAYOUT':\n return {\n ...state,\n layout: action.payload,\n isLoading: false,\n }\n \n case 'ADD_BLOCK':\n if (!state.layout) return state\n return {\n ...state,\n layout: {\n ...state.layout,\n blocks: [...state.layout.blocks, action.payload],\n },\n isDirty: true,\n }\n \n case 'UPDATE_BLOCK':\n if (!state.layout) return state\n return {\n ...state,\n layout: {\n ...state.layout,\n blocks: state.layout.blocks.map(block =>\n block.id === action.payload.id\n ? { ...block, ...action.payload.updates } as ZunoBlock\n : block\n ),\n },\n isDirty: true,\n }\n \n case 'REMOVE_BLOCK':\n if (!state.layout) return state\n return {\n ...state,\n layout: {\n ...state.layout,\n blocks: state.layout.blocks.filter(block => block.id !== action.payload),\n },\n selectedBlockId: state.selectedBlockId === action.payload ? null : state.selectedBlockId,\n isDirty: true,\n }\n \n case 'SELECT_BLOCK':\n return {\n ...state,\n selectedBlockId: action.payload,\n }\n \n case 'SET_LOADING':\n return {\n ...state,\n isLoading: action.payload,\n }\n \n case 'SET_TOOL':\n return {\n ...state,\n tool: action.payload,\n }\n \n default:\n return state\n }\n}\n\nconst BuilderContext = createContext<{\n state: BuilderState\n dispatch: React.Dispatch<BuilderAction>\n} | null>(null)\n\ninterface BuilderProviderProps {\n children: React.ReactNode\n}\n\nexport function BuilderProvider({ children }: BuilderProviderProps) {\n const [state, dispatch] = useReducer(builderReducer, initialState)\n\n useEffect(() => {\n // Load layout on mount\n const loadLayout = async () => {\n try {\n // In real implementation, load from .zuno/layout.json\n const defaultLayout: ZunoLayout = {\n version: \"1.0.0\",\n id: \"default-layout\",\n name: \"Default Layout\",\n blocks: [],\n metadata: {\n version: \"1.0.0\",\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n targetPath: \"\",\n },\n viewport: {\n width: 1200,\n height: 800,\n scale: 1,\n offsetX: 0,\n offsetY: 0,\n },\n }\n \n dispatch({ type: 'SET_LAYOUT', payload: defaultLayout })\n } catch (error) {\n console.error('Failed to load layout:', error)\n dispatch({ type: 'SET_LOADING', payload: false })\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n loadLayout()\n } else {\n dispatch({ type: 'SET_LOADING', payload: false })\n }\n }, [])\n\n return (\n <BuilderContext.Provider value={{ state, dispatch }}>\n {children}\n </BuilderContext.Provider>\n )\n}\n\nexport function useBuilder() {\n const context = useContext(BuilderContext)\n if (!context) {\n throw new Error('useBuilder must be used within BuilderProvider')\n }\n return context\n}","import React from 'react'\nimport { useBuilder } from '../builder/BuilderProvider'\nimport { cn } from '../../utils'\nimport type { ZunoBlock } from '../../types'\n\ninterface BlockRendererProps {\n block: ZunoBlock\n isSelected?: boolean\n isHovered?: boolean\n}\n\nexport function BlockRenderer({ block, isSelected, isHovered }: BlockRendererProps) {\n const { dispatch } = useBuilder()\n\n const handleClick = (e: React.MouseEvent) => {\n e.stopPropagation()\n dispatch({ type: 'SELECT_BLOCK', payload: block.id })\n }\n\n const commonProps = {\n onClick: handleClick,\n className: cn(\n 'absolute cursor-pointer transition-all',\n block.style.className,\n isSelected && 'ring-2 ring-blue-500',\n isHovered && 'ring-1 ring-gray-400'\n ),\n style: {\n left: block.position.x,\n top: block.position.y,\n width: block.position.width,\n height: block.position.height,\n ...block.style.customStyles,\n },\n }\n\n switch (block.type) {\n case 'text':\n return (\n <div {...commonProps}>\n {React.createElement(\n block.props.tag || 'p',\n {\n className: cn(\n 'w-full h-full',\n `text-${block.props.fontSize || 'base'}`,\n `font-${block.props.fontWeight || 'normal'}`,\n `text-${block.props.textAlign || 'left'}`\n ),\n },\n block.props.content || 'Text'\n )}\n </div>\n )\n\n case 'button':\n return (\n <button {...commonProps}>\n {block.props.text || 'Button'}\n </button>\n )\n\n case 'input':\n return (\n <input\n {...commonProps}\n type={block.props.type || 'text'}\n placeholder={block.props.placeholder}\n disabled={block.props.disabled}\n readOnly={block.props.readOnly}\n />\n )\n\n case 'image':\n return (\n <img\n {...commonProps}\n src={block.props.src}\n alt={block.props.alt || ''}\n style={{\n ...commonProps.style,\n objectFit: block.props.objectFit || 'cover',\n }}\n />\n )\n\n case 'section':\n case 'card':\n case 'div':\n return (\n <div {...commonProps}>\n {/* Render children if any */}\n {block.children?.length ? (\n <div className=\"text-xs text-gray-500 p-2\">\n Container with {block.children.length} children\n </div>\n ) : (\n <div className=\"text-xs text-gray-400 p-2\">\n Empty {block.type}\n </div>\n )}\n </div>\n )\n\n default:\n return (\n <div {...commonProps}>\n <div className=\"text-xs text-red-500 p-2\">\n Unknown block type: {(block as any).type}\n </div>\n </div>\n )\n }\n}","import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport function generateId(): string {\n return Math.random().toString(36).substr(2, 9)\n}","import React from 'react'\nimport { useBuilder } from './BuilderProvider'\nimport { BlockRenderer } from '../blocks/BlockRenderer'\n\nexport function Canvas() {\n const { state, dispatch } = useBuilder()\n const { layout, viewport } = state\n\n const handleCanvasClick = (e: React.MouseEvent) => {\n // Only deselect if clicking on canvas background\n if (e.target === e.currentTarget) {\n dispatch({ type: 'SELECT_BLOCK', payload: null })\n }\n }\n\n if (!layout) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <div className=\"w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-lg flex items-center justify-center\">\n <svg className=\"w-8 h-8 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\" />\n </svg>\n </div>\n <p className=\"text-gray-500 text-sm\">Loading canvas...</p>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex items-center justify-center h-full\">\n {/* Canvas Frame */}\n <div className=\"relative bg-white rounded-lg shadow-xl border border-gray-200 overflow-hidden\"\n style={{ width: 800, height: 600 }}>\n \n {/* Canvas Background */}\n <div \n className=\"relative w-full h-full bg-white cursor-default\"\n onClick={handleCanvasClick}\n style={{\n backgroundImage: viewport.showGrid ? `\n radial-gradient(circle, #e5e7eb 1px, transparent 1px)\n ` : undefined,\n backgroundSize: viewport.showGrid ? `${viewport.gridSize || 20}px ${viewport.gridSize || 20}px` : undefined,\n }}\n >\n {/* Empty state */}\n {layout.blocks.length === 0 && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"w-20 h-20 mx-auto mb-4 bg-gray-50 rounded-2xl flex items-center justify-center\">\n <svg className=\"w-10 h-10 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M12 4v16m8-8H4\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-gray-900 mb-2\">Start designing</h3>\n <p className=\"text-sm text-gray-500 mb-4\">Drag components from the left sidebar to get started</p>\n <div className=\"flex items-center justify-center gap-2 text-xs text-gray-400\">\n <div className=\"flex items-center gap-1\">\n <div className=\"w-3 h-3 border border-gray-300 rounded\"></div>\n <span>Text</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <div className=\"w-3 h-3 bg-blue-500 rounded\"></div>\n <span>Button</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <div className=\"w-3 h-3 border border-gray-300 rounded\"></div>\n <span>Input</span>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Render blocks */}\n {layout.blocks.map((block) => (\n <BlockRenderer \n key={block.id} \n block={block}\n isSelected={state.selectedBlockId === block.id}\n isHovered={state.hoveredBlockId === block.id}\n />\n ))}\n\n {/* Selection overlay */}\n {state.selectedBlockId && (\n <SelectionOverlay blockId={state.selectedBlockId} />\n )}\n </div>\n\n {/* Canvas Info Bar */}\n <div className=\"absolute bottom-0 left-0 right-0 h-8 bg-gray-50 border-t border-gray-200 flex items-center justify-between px-4 text-xs text-gray-600\">\n <div className=\"flex items-center gap-4\">\n <span>800 × 600</span>\n <span>{layout.blocks.length} elements</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span>100%</span>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nfunction SelectionOverlay({ blockId }: { blockId: string }) {\n const { state } = useBuilder()\n const block = state.layout?.blocks.find(b => b.id === blockId)\n\n if (!block) return null\n\n return (\n <>\n {/* Selection Border */}\n <div\n className=\"absolute border-2 border-blue-500 pointer-events-none z-10\"\n style={{\n left: block.position.x - 1,\n top: block.position.y - 1,\n width: block.position.width + 2,\n height: block.position.height + 2,\n }}\n />\n \n {/* Resize Handles */}\n <div\n className=\"absolute pointer-events-none z-20\"\n style={{\n left: block.position.x - 4,\n top: block.position.y - 4,\n width: block.position.width + 8,\n height: block.position.height + 8,\n }}\n >\n {/* Corner handles */}\n <div className=\"absolute -top-1 -left-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -top-1 -right-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -bottom-1 -left-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -bottom-1 -right-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n \n {/* Edge handles */}\n <div className=\"absolute -top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -bottom-1 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -left-1 top-1/2 transform -translate-y-1/2 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n <div className=\"absolute -right-1 top-1/2 transform -translate-y-1/2 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm\" />\n </div>\n\n {/* Element Label */}\n <div\n className=\"absolute -top-6 left-0 bg-blue-500 text-white px-2 py-0.5 rounded text-xs font-medium capitalize z-30\"\n style={{ left: block.position.x }}\n >\n {block.type}\n </div>\n </>\n )\n}","import React, { useState } from 'react'\nimport { useBuilder } from './BuilderProvider'\nimport { generateId } from '../../utils'\nimport type { ZunoBlock } from '../../types'\n\nconst BLOCK_TEMPLATES = [\n {\n type: 'text',\n name: 'Text',\n description: 'Add headings, paragraphs, and labels',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\" />\n </svg>\n ),\n category: 'Content',\n create: (): ZunoBlock => ({\n id: generateId(),\n type: 'text',\n props: {\n id: generateId(),\n content: 'Edit this text',\n tag: 'p',\n fontSize: 'base',\n fontWeight: 'normal',\n textAlign: 'left',\n },\n style: {\n className: 'text-gray-900 leading-relaxed',\n },\n position: {\n x: 100,\n y: 100,\n width: 200,\n height: 40,\n },\n } as ZunoBlock),\n },\n {\n type: 'button',\n name: 'Button',\n description: 'Interactive button component',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122\" />\n </svg>\n ),\n category: 'Interactive',\n create: (): ZunoBlock => ({\n id: generateId(),\n type: 'button',\n props: {\n id: generateId(),\n text: 'Button',\n variant: 'default',\n size: 'default',\n },\n style: {\n className: 'bg-blue-600 hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg font-medium transition-colors',\n },\n position: {\n x: 100,\n y: 200,\n width: 120,\n height: 44,\n },\n } as ZunoBlock),\n },\n {\n type: 'input',\n name: 'Input',\n description: 'Text input field for forms',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n ),\n category: 'Interactive',\n create: (): ZunoBlock => ({\n id: generateId(),\n type: 'input',\n props: {\n id: generateId(),\n type: 'text',\n placeholder: 'Enter text...',\n size: 'default',\n variant: 'default',\n },\n style: {\n className: 'border border-gray-300 rounded-lg px-3 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent',\n },\n position: {\n x: 100,\n y: 300,\n width: 240,\n height: 44,\n },\n } as ZunoBlock),\n },\n {\n type: 'section',\n name: 'Container',\n description: 'Layout container for grouping elements',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\" />\n </svg>\n ),\n category: 'Layout',\n create: (): ZunoBlock => ({\n id: generateId(),\n type: 'section',\n props: {\n id: generateId(),\n tag: 'div',\n display: 'block',\n },\n style: {\n className: 'bg-white border border-gray-200 rounded-lg p-6 shadow-sm',\n },\n position: {\n x: 100,\n y: 400,\n width: 320,\n height: 200,\n },\n } as ZunoBlock),\n },\n]\n\nconst TABS = [\n { id: 'components', name: 'Design', icon: '🎨' },\n { id: 'layers', name: 'Layers', icon: '📋' }\n]\n\nexport function Sidebar() {\n const [activeTab, setActiveTab] = useState('components')\n const { dispatch, state } = useBuilder()\n\n const handleAddBlock = (template: typeof BLOCK_TEMPLATES[0]) => {\n const block = template.create()\n dispatch({ type: 'ADD_BLOCK', payload: block })\n dispatch({ type: 'SELECT_BLOCK', payload: block.id })\n }\n\n const categories = Array.from(new Set(BLOCK_TEMPLATES.map(t => t.category)))\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Tabs */}\n <div className=\"flex border-b border-gray-200\">\n {TABS.map((tab) => (\n <button\n key={tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={`flex-1 flex items-center justify-center gap-2 py-3 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'text-blue-600 border-b-2 border-blue-600'\n : 'text-gray-600 hover:text-gray-900'\n }`}\n >\n <span>{tab.icon}</span>\n {tab.name}\n </button>\n ))}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-auto\">\n {activeTab === 'components' && (\n <div className=\"p-4\">\n {categories.map((category) => (\n <div key={category} className=\"mb-6\">\n <h3 className=\"text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3\">\n {category}\n </h3>\n <div className=\"space-y-2\">\n {BLOCK_TEMPLATES.filter(t => t.category === category).map((template) => (\n <button\n key={template.type}\n onClick={() => handleAddBlock(template)}\n className=\"w-full flex items-start gap-3 p-3 bg-white border border-gray-200 rounded-lg hover:border-blue-200 hover:bg-blue-50 transition-all text-left group\"\n >\n <div className=\"p-2 bg-gray-100 rounded-lg group-hover:bg-blue-100 transition-colors\">\n {template.icon}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-gray-900\">\n {template.name}\n </div>\n <div className=\"text-xs text-gray-500 mt-0.5\">\n {template.description}\n </div>\n </div>\n </button>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {activeTab === 'layers' && (\n <div className=\"p-4\">\n <h3 className=\"text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3\">\n Layers\n </h3>\n {state.layout?.blocks.length === 0 ? (\n <div className=\"text-center py-8 text-gray-500\">\n <svg className=\"w-8 h-8 mx-auto mb-2 opacity-50\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\" />\n </svg>\n <p className=\"text-sm\">No layers yet</p>\n <p className=\"text-xs\">Add components to see them here</p>\n </div>\n ) : (\n <div className=\"space-y-1\">\n {state.layout?.blocks.map((block) => (\n <button\n key={block.id}\n onClick={() => dispatch({ type: 'SELECT_BLOCK', payload: block.id })}\n className={`w-full flex items-center gap-2 p-2 rounded text-left text-sm transition-colors ${\n state.selectedBlockId === block.id\n ? 'bg-blue-100 text-blue-900'\n : 'hover:bg-gray-100'\n }`}\n >\n <div className=\"w-4 h-4 bg-gray-300 rounded\" />\n <span className=\"capitalize\">{block.type}</span>\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}","import React from 'react'\nimport { useBuilder } from './BuilderProvider'\n\nexport function PropertyPanel() {\n const { state, dispatch } = useBuilder()\n const selectedBlock = state.layout?.blocks.find(b => b.id === state.selectedBlockId)\n\n if (!selectedBlock) {\n return (\n <div className=\"w-64 bg-gray-50 border-l border-gray-200 p-4\">\n <h2 className=\"font-semibold text-sm text-gray-900 mb-4\">\n Properties\n </h2>\n <div className=\"text-sm text-gray-500\">\n Select a block to edit its properties\n </div>\n </div>\n )\n }\n\n const updateBlockProps = (updates: any) => {\n dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n props: { ...selectedBlock.props, ...updates }\n }\n }\n })\n }\n\n const updateBlockStyle = (updates: any) => {\n dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n style: { ...selectedBlock.style, ...updates }\n }\n }\n })\n }\n\n return (\n <div className=\"w-64 bg-gray-50 border-l border-gray-200 p-4\">\n <h2 className=\"font-semibold text-sm text-gray-900 mb-4\">\n Properties\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-1\">\n Block Type\n </label>\n <div className=\"text-sm text-gray-900 bg-gray-100 px-2 py-1 rounded\">\n {selectedBlock.type}\n </div>\n </div>\n\n {/* Text Block Properties */}\n {selectedBlock.type === 'text' && (\n <>\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-1\">\n Content\n </label>\n <textarea\n value={selectedBlock.props.content || ''}\n onChange={(e) => updateBlockProps({ content: e.target.value })}\n className=\"w-full text-sm border border-gray-300 rounded px-2 py-1\"\n rows={3}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-1\">\n Tag\n </label>\n <select\n value={selectedBlock.props.tag || 'p'}\n onChange={(e) => updateBlockProps({ tag: e.target.value })}\n className=\"w-full text-sm border border-gray-300 rounded px-2 py-1\"\n >\n <option value=\"p\">Paragraph</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"span\">Span</option>\n <option value=\"div\">Div</option>\n </select>\n </div>\n </>\n )}\n\n {/* Button Block Properties */}\n {selectedBlock.type === 'button' && (\n <>\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-1\">\n Text\n </label>\n <input\n type=\"text\"\n value={selectedBlock.props.text || ''}\n onChange={(e) => updateBlockProps({ text: e.target.value })}\n className=\"w-full text-sm border border-gray-300 rounded px-2 py-1\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-1\">\n Variant\n </label>\n <select\n value={selectedBlock.props.variant || 'default'}\n onChange={(e) => updateBlockProps({ variant: e.target.value })}\n className=\"w-full text-sm border border-gray-300 rounded px-2 py-1\"\n >\n <option value=\"default\">Default</option>\n <option value=\"destructive\">Destructive</option>\n <option value=\"outline\">Outline</option>\n <option value=\"secondary\">Secondary</option>\n <option value=\"ghost\">Ghost</option>\n <option value=\"link\">Link</option>\n </select>\n </div>\n </>\n )}\n\n {/* Position & Size */}\n <div>\n <label className=\"block text-xs font-medium text-gray-700 mb-2\">\n Position & Size\n </label>\n <div className=\"grid grid-cols-2 gap-2 text-xs\">\n <div>\n <label className=\"block text-gray-600\">X</label>\n <input\n type=\"number\"\n value={selectedBlock.position.x}\n onChange={(e) => dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n position: { ...selectedBlock.position, x: parseInt(e.target.value) || 0 }\n }\n }\n })}\n className=\"w-full border border-gray-300 rounded px-1 py-1\"\n />\n </div>\n <div>\n <label className=\"block text-gray-600\">Y</label>\n <input\n type=\"number\"\n value={selectedBlock.position.y}\n onChange={(e) => dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n position: { ...selectedBlock.position, y: parseInt(e.target.value) || 0 }\n }\n }\n })}\n className=\"w-full border border-gray-300 rounded px-1 py-1\"\n />\n </div>\n <div>\n <label className=\"block text-gray-600\">Width</label>\n <input\n type=\"number\"\n value={selectedBlock.position.width}\n onChange={(e) => dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n position: { ...selectedBlock.position, width: parseInt(e.target.value) || 0 }\n }\n }\n })}\n className=\"w-full border border-gray-300 rounded px-1 py-1\"\n />\n </div>\n <div>\n <label className=\"block text-gray-600\">Height</label>\n <input\n type=\"number\"\n value={selectedBlock.position.height}\n onChange={(e) => dispatch({\n type: 'UPDATE_BLOCK',\n payload: {\n id: selectedBlock.id,\n updates: {\n position: { ...selectedBlock.position, height: parseInt(e.target.value) || 0 }\n }\n }\n })}\n className=\"w-full border border-gray-300 rounded px-1 py-1\"\n />\n </div>\n </div>\n </div>\n\n {/* Delete Button */}\n <button\n onClick={() => dispatch({ type: 'REMOVE_BLOCK', payload: selectedBlock.id })}\n className=\"w-full bg-red-600 hover:bg-red-700 text-white text-sm py-2 rounded\"\n >\n Delete Block\n </button>\n </div>\n </div>\n )\n}","import React from 'react'\nimport { useBuilder } from './BuilderProvider'\n\nexport function Toolbar() {\n const { state, dispatch } = useBuilder()\n\n const tools = [\n {\n id: 'select',\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122\" />\n </svg>\n ),\n name: 'Select',\n active: state.tool === 'select'\n },\n {\n id: 'hand',\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M7 11.5V14m0-2.5v-6a1.5 1.5 0 113 0m-3 6a1.5 1.5 0 00-3 0v2a7.5 7.5 0 0015 0v-5a1.5 1.5 0 00-3 0m-6-3V11m0-5.5v-1a1.5 1.5 0 013 0v1m0 0V11m0-5.5a1.5 1.5 0 013 0v3m0 0V11\" />\n </svg>\n ),\n name: 'Hand',\n active: state.tool === 'hand'\n },\n {\n id: 'text',\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\" />\n </svg>\n ),\n name: 'Text',\n active: state.tool === 'text'\n }\n ]\n\n return (\n <div className=\"flex items-center gap-1\">\n {tools.map((tool) => (\n <button\n key={tool.id}\n onClick={() => dispatch({ type: 'SET_TOOL', payload: tool.id })}\n className={`p-2 rounded transition-colors ${\n tool.active\n ? 'bg-blue-100 text-blue-600'\n : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'\n }`}\n title={tool.name}\n >\n {tool.icon}\n </button>\n ))}\n \n <div className=\"h-6 w-px bg-gray-300 mx-2\" />\n \n {/* Zoom Controls */}\n <div className=\"flex items-center gap-1\">\n <button className=\"p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10h-3m-3 0h3m0 0V7m0 3v3\" />\n </svg>\n </button>\n <span className=\"text-xs text-gray-600 px-1\">100%</span>\n <button className=\"p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM16 10h-6\" />\n </svg>\n </button>\n </div>\n </div>\n )\n}","import { useState, useEffect } from \"react\";\nimport type { ZunoLayout, ZunoBlock } from \"../types\";\n\nexport function useZunoBuilder() {\n const [layout, setLayout] = useState<ZunoLayout | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n // Load layout from storage in development mode\n if (process.env.NODE_ENV === \"development\") {\n loadLayout();\n } else {\n setIsLoading(false);\n }\n }, []);\n\n const loadLayout = async () => {\n try {\n // In a real implementation, this would load from .zuno/layout.json\n setLayout({\n version: \"1.0.0\",\n id: \"default-layout\",\n name: \"Default Layout\",\n blocks: [],\n metadata: {\n version: \"1.0.0\",\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n targetPath: \"\",\n },\n viewport: {\n width: 1200,\n height: 800,\n scale: 1,\n offsetX: 0,\n offsetY: 0,\n },\n });\n } catch (error) {\n console.warn(\"Failed to load Zuno layout:\", error);\n } finally {\n setIsLoading(false);\n }\n };\n\n const saveLayout = async (newLayout: ZunoLayout) => {\n try {\n // In a real implementation, this would save to .zuno/layout.json\n setLayout(newLayout);\n } catch (error) {\n console.error(\"Failed to save Zuno layout:\", error);\n }\n };\n\n const addBlock = (block: ZunoBlock) => {\n if (!layout) return;\n\n const updatedLayout: ZunoLayout = {\n ...layout,\n blocks: [...layout.blocks, block],\n metadata: {\n ...layout.metadata,\n updatedAt: new Date().toISOString(),\n },\n };\n\n saveLayout(updatedLayout);\n };\n\n const updateBlock = (blockId: string, updates: Partial<ZunoBlock>) => {\n if (!layout) return;\n\n const updatedBlocks = layout.blocks.map((block) =>\n block.id === blockId ? { ...block, ...updates } as ZunoBlock : block\n );\n\n const updatedLayout: ZunoLayout = {\n ...layout,\n blocks: updatedBlocks,\n metadata: {\n ...layout.metadata,\n updatedAt: new Date().toISOString(),\n },\n };\n\n saveLayout(updatedLayout);\n };\n\n const removeBlock = (blockId: string) => {\n if (!layout) return;\n\n const updatedBlocks = layout.blocks.filter((block) => block.id !== blockId);\n\n const updatedLayout: ZunoLayout = {\n ...layout,\n blocks: updatedBlocks,\n metadata: {\n ...layout.metadata,\n updatedAt: new Date().toISOString(),\n },\n };\n\n saveLayout(updatedLayout);\n };\n\n return {\n layout,\n isLoading,\n addBlock,\n updateBlock,\n removeBlock,\n saveLayout,\n };\n}\n"],"mappings":"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