UNPKG

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,002 lines (990 loc) • 48.4 kB
"use client"; "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { ZunoBuilder: () => ZunoBuilder, useZunoBuilder: () => useZunoBuilder }); module.exports = __toCommonJS(src_exports); // src/components/ZunoBuilder.tsx var import_react4 = require("react"); // src/components/builder/BuilderProvider.tsx var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var initialState = { layout: null, selectedBlockId: null, hoveredBlockId: null, draggedBlockId: null, clipboard: null, isLoading: true, isDirty: false, mode: "design", tool: "select", viewport: { width: 1200, height: 800, scale: 1, offsetX: 0, offsetY: 0, showGrid: false, gridSize: 10, snapToGrid: false, showRulers: false }, history: { past: [], present: null, future: [], maxHistorySize: 50 } }; function builderReducer(state, action) { switch (action.type) { case "SET_LAYOUT": return { ...state, layout: action.payload, isLoading: false }; case "ADD_BLOCK": if (!state.layout) return state; return { ...state, layout: { ...state.layout, blocks: [...state.layout.blocks, action.payload] }, isDirty: true }; case "UPDATE_BLOCK": if (!state.layout) return state; return { ...state, layout: { ...state.layout, blocks: state.layout.blocks.map( (block) => block.id === action.payload.id ? { ...block, ...action.payload.updates } : block ) }, isDirty: true }; case "REMOVE_BLOCK": if (!state.layout) return state; return { ...state, layout: { ...state.layout, blocks: state.layout.blocks.filter((block) => block.id !== action.payload) }, selectedBlockId: state.selectedBlockId === action.payload ? null : state.selectedBlockId, isDirty: true }; case "SELECT_BLOCK": return { ...state, selectedBlockId: action.payload }; case "SET_LOADING": return { ...state, isLoading: action.payload }; case "SET_TOOL": return { ...state, tool: action.payload }; default: return state; } } var BuilderContext = (0, import_react.createContext)(null); function BuilderProvider({ children }) { const [state, dispatch] = (0, import_react.useReducer)(builderReducer, initialState); (0, import_react.useEffect)(() => { const loadLayout = async () => { try { const defaultLayout = { version: "1.0.0", id: "default-layout", name: "Default Layout", blocks: [], metadata: { version: "1.0.0", createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), targetPath: "" }, viewport: { width: 1200, height: 800, scale: 1, offsetX: 0, offsetY: 0 } }; dispatch({ type: "SET_LAYOUT", payload: defaultLayout }); } catch (error) { console.error("Failed to load layout:", error); dispatch({ type: "SET_LOADING", payload: false }); } }; if (process.env.NODE_ENV === "development") { loadLayout(); } else { dispatch({ type: "SET_LOADING", payload: false }); } }, []); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BuilderContext.Provider, { value: { state, dispatch }, children }); } function useBuilder() { const context = (0, import_react.useContext)(BuilderContext); if (!context) { throw new Error("useBuilder must be used within BuilderProvider"); } return context; } // src/components/blocks/BlockRenderer.tsx var import_react2 = __toESM(require("react")); // src/utils/index.ts var import_clsx = require("clsx"); var import_tailwind_merge = require("tailwind-merge"); function cn(...inputs) { return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs)); } function generateId() { return Math.random().toString(36).substr(2, 9); } // src/components/blocks/BlockRenderer.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); function BlockRenderer({ block, isSelected, isHovered }) { const { dispatch } = useBuilder(); const handleClick = (e) => { e.stopPropagation(); dispatch({ type: "SELECT_BLOCK", payload: block.id }); }; const commonProps = { onClick: handleClick, className: cn( "absolute cursor-pointer transition-all", block.style.className, isSelected && "ring-2 ring-blue-500", isHovered && "ring-1 ring-gray-400" ), style: { left: block.position.x, top: block.position.y, width: block.position.width, height: block.position.height, ...block.style.customStyles } }; switch (block.type) { case "text": return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...commonProps, children: import_react2.default.createElement( block.props.tag || "p", { className: cn( "w-full h-full", `text-${block.props.fontSize || "base"}`, `font-${block.props.fontWeight || "normal"}`, `text-${block.props.textAlign || "left"}` ) }, block.props.content || "Text" ) }); case "button": return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { ...commonProps, children: block.props.text || "Button" }); case "input": return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "input", { ...commonProps, type: block.props.type || "text", placeholder: block.props.placeholder, disabled: block.props.disabled, readOnly: block.props.readOnly } ); case "image": return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "img", { ...commonProps, src: block.props.src, alt: block.props.alt || "", style: { ...commonProps.style, objectFit: block.props.objectFit || "cover" } } ); case "section": case "card": case "div": return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...commonProps, children: block.children?.length ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xs text-gray-500 p-2", children: [ "Container with ", block.children.length, " children" ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xs text-gray-400 p-2", children: [ "Empty ", block.type ] }) }); default: return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...commonProps, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xs text-red-500 p-2", children: [ "Unknown block type: ", block.type ] }) }); } } // src/components/builder/Canvas.tsx var import_jsx_runtime3 = require("react/jsx-runtime"); function Canvas() { const { state, dispatch } = useBuilder(); const { layout, viewport } = state; const handleCanvasClick = (e) => { if (e.target === e.currentTarget) { dispatch({ type: "SELECT_BLOCK", payload: null }); } }; if (!layout) { return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-8 h-8 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }) }) }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-gray-500 text-sm", children: "Loading canvas..." }) ] }) }); } return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { className: "relative bg-white rounded-lg shadow-xl border border-gray-200 overflow-hidden", style: { width: 800, height: 600 }, children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { className: "relative w-full h-full bg-white cursor-default", onClick: handleCanvasClick, style: { backgroundImage: viewport.showGrid ? ` radial-gradient(circle, #e5e7eb 1px, transparent 1px) ` : void 0, backgroundSize: viewport.showGrid ? `${viewport.gridSize || 20}px ${viewport.gridSize || 20}px` : void 0 }, children: [ layout.blocks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-20 h-20 mx-auto mb-4 bg-gray-50 rounded-2xl flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-10 h-10 text-gray-300", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 4v16m8-8H4" }) }) }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-medium text-gray-900 mb-2", children: "Start designing" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 mb-4", children: "Drag components from the left sidebar to get started" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-center gap-2 text-xs text-gray-400", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-3 h-3 border border-gray-300 rounded" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Text" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-3 h-3 bg-blue-500 rounded" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Button" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-3 h-3 border border-gray-300 rounded" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Input" }) ] }) ] }) ] }) }), layout.blocks.map((block) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( BlockRenderer, { block, isSelected: state.selectedBlockId === block.id, isHovered: state.hoveredBlockId === block.id }, block.id )), state.selectedBlockId && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SelectionOverlay, { blockId: state.selectedBlockId }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("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", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "800 \xD7 600" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [ layout.blocks.length, " elements" ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "100%" }) }) ] }) ] } ) }); } function SelectionOverlay({ blockId }) { const { state } = useBuilder(); const block = state.layout?.blocks.find((b) => b.id === blockId); if (!block) return null; return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "div", { className: "absolute border-2 border-blue-500 pointer-events-none z-10", style: { left: block.position.x - 1, top: block.position.y - 1, width: block.position.width + 2, height: block.position.height + 2 } } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { className: "absolute pointer-events-none z-20", style: { left: block.position.x - 4, top: block.position.y - 4, width: block.position.width + 8, height: block.position.height + 8 }, children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -top-1 -left-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -top-1 -right-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -bottom-1 -left-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 bg-white border-2 border-blue-500 rounded-sm" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "div", { className: "absolute -top-6 left-0 bg-blue-500 text-white px-2 py-0.5 rounded text-xs font-medium capitalize z-30", style: { left: block.position.x }, children: block.type } ) ] }); } // src/components/builder/Sidebar.tsx var import_react3 = require("react"); var import_jsx_runtime4 = require("react/jsx-runtime"); var BLOCK_TEMPLATES = [ { type: "text", name: "Text", description: "Add headings, paragraphs, and labels", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }), category: "Content", create: () => ({ id: generateId(), type: "text", props: { id: generateId(), content: "Edit this text", tag: "p", fontSize: "base", fontWeight: "normal", textAlign: "left" }, style: { className: "text-gray-900 leading-relaxed" }, position: { x: 100, y: 100, width: 200, height: 40 } }) }, { type: "button", name: "Button", description: "Interactive button component", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }), category: "Interactive", create: () => ({ id: generateId(), type: "button", props: { id: generateId(), text: "Button", variant: "default", size: "default" }, style: { className: "bg-blue-600 hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg font-medium transition-colors" }, position: { x: 100, y: 200, width: 120, height: 44 } }) }, { type: "input", name: "Input", description: "Text input field for forms", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }), category: "Interactive", create: () => ({ id: generateId(), type: "input", props: { id: generateId(), type: "text", placeholder: "Enter text...", size: "default", variant: "default" }, style: { 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" }, position: { x: 100, y: 300, width: 240, height: 44 } }) }, { type: "section", name: "Container", description: "Layout container for grouping elements", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }), category: "Layout", create: () => ({ id: generateId(), type: "section", props: { id: generateId(), tag: "div", display: "block" }, style: { className: "bg-white border border-gray-200 rounded-lg p-6 shadow-sm" }, position: { x: 100, y: 400, width: 320, height: 200 } }) } ]; var TABS = [ { id: "components", name: "Design", icon: "\u{1F3A8}" }, { id: "layers", name: "Layers", icon: "\u{1F4CB}" } ]; function Sidebar() { const [activeTab, setActiveTab] = (0, import_react3.useState)("components"); const { dispatch, state } = useBuilder(); const handleAddBlock = (template) => { const block = template.create(); dispatch({ type: "ADD_BLOCK", payload: block }); dispatch({ type: "SELECT_BLOCK", payload: block.id }); }; const categories = Array.from(new Set(BLOCK_TEMPLATES.map((t) => t.category))); return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col h-full", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex border-b border-gray-200", children: TABS.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "button", { onClick: () => setActiveTab(tab.id), className: `flex-1 flex items-center justify-center gap-2 py-3 text-sm font-medium transition-colors ${activeTab === tab.id ? "text-blue-600 border-b-2 border-blue-600" : "text-gray-600 hover:text-gray-900"}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: tab.icon }), tab.name ] }, tab.id )) }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 overflow-auto", children: [ activeTab === "components" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4", children: categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-6", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3", children: category }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "space-y-2", children: BLOCK_TEMPLATES.filter((t) => t.category === category).map((template) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "button", { onClick: () => handleAddBlock(template), 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", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-2 bg-gray-100 rounded-lg group-hover:bg-blue-100 transition-colors", children: template.icon }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-sm font-medium text-gray-900", children: template.name }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-xs text-gray-500 mt-0.5", children: template.description }) ] }) ] }, template.type )) }) ] }, category)) }), activeTab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3", children: "Layers" }), state.layout?.blocks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "text-center py-8 text-gray-500", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "w-8 h-8 mx-auto mb-2 opacity-50", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm", children: "No layers yet" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs", children: "Add components to see them here" }) ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "space-y-1", children: state.layout?.blocks.map((block) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "button", { onClick: () => dispatch({ type: "SELECT_BLOCK", payload: block.id }), className: `w-full flex items-center gap-2 p-2 rounded text-left text-sm transition-colors ${state.selectedBlockId === block.id ? "bg-blue-100 text-blue-900" : "hover:bg-gray-100"}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-4 h-4 bg-gray-300 rounded" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "capitalize", children: block.type }) ] }, block.id )) }) ] }) ] }) ] }); } // src/components/builder/PropertyPanel.tsx var import_jsx_runtime5 = require("react/jsx-runtime"); function PropertyPanel() { const { state, dispatch } = useBuilder(); const selectedBlock = state.layout?.blocks.find((b) => b.id === state.selectedBlockId); if (!selectedBlock) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-64 bg-gray-50 border-l border-gray-200 p-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "font-semibold text-sm text-gray-900 mb-4", children: "Properties" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-gray-500", children: "Select a block to edit its properties" }) ] }); } const updateBlockProps = (updates) => { dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { props: { ...selectedBlock.props, ...updates } } } }); }; const updateBlockStyle = (updates) => { dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { style: { ...selectedBlock.style, ...updates } } } }); }; return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-64 bg-gray-50 border-l border-gray-200 p-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "font-semibold text-sm text-gray-900 mb-4", children: "Properties" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Block Type" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-gray-900 bg-gray-100 px-2 py-1 rounded", children: selectedBlock.type }) ] }), selectedBlock.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Content" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "textarea", { value: selectedBlock.props.content || "", onChange: (e) => updateBlockProps({ content: e.target.value }), className: "w-full text-sm border border-gray-300 rounded px-2 py-1", rows: 3 } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Tag" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( "select", { value: selectedBlock.props.tag || "p", onChange: (e) => updateBlockProps({ tag: e.target.value }), className: "w-full text-sm border border-gray-300 rounded px-2 py-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "p", children: "Paragraph" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "h1", children: "Heading 1" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "h2", children: "Heading 2" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "h3", children: "Heading 3" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "span", children: "Span" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "div", children: "Div" }) ] } ) ] }) ] }), selectedBlock.type === "button" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Text" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "text", value: selectedBlock.props.text || "", onChange: (e) => updateBlockProps({ text: e.target.value }), className: "w-full text-sm border border-gray-300 rounded px-2 py-1" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Variant" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( "select", { value: selectedBlock.props.variant || "default", onChange: (e) => updateBlockProps({ variant: e.target.value }), className: "w-full text-sm border border-gray-300 rounded px-2 py-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "default", children: "Default" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "destructive", children: "Destructive" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "outline", children: "Outline" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "secondary", children: "Secondary" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "ghost", children: "Ghost" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "link", children: "Link" }) ] } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-2", children: "Position & Size" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-2 text-xs", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-gray-600", children: "X" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "number", value: selectedBlock.position.x, onChange: (e) => dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { position: { ...selectedBlock.position, x: parseInt(e.target.value) || 0 } } } }), className: "w-full border border-gray-300 rounded px-1 py-1" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-gray-600", children: "Y" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "number", value: selectedBlock.position.y, onChange: (e) => dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { position: { ...selectedBlock.position, y: parseInt(e.target.value) || 0 } } } }), className: "w-full border border-gray-300 rounded px-1 py-1" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-gray-600", children: "Width" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "number", value: selectedBlock.position.width, onChange: (e) => dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { position: { ...selectedBlock.position, width: parseInt(e.target.value) || 0 } } } }), className: "w-full border border-gray-300 rounded px-1 py-1" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-gray-600", children: "Height" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "number", value: selectedBlock.position.height, onChange: (e) => dispatch({ type: "UPDATE_BLOCK", payload: { id: selectedBlock.id, updates: { position: { ...selectedBlock.position, height: parseInt(e.target.value) || 0 } } } }), className: "w-full border border-gray-300 rounded px-1 py-1" } ) ] }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "button", { onClick: () => dispatch({ type: "REMOVE_BLOCK", payload: selectedBlock.id }), className: "w-full bg-red-600 hover:bg-red-700 text-white text-sm py-2 rounded", children: "Delete Block" } ) ] }) ] }); } // src/components/builder/Toolbar.tsx var import_jsx_runtime6 = require("react/jsx-runtime"); function Toolbar() { const { state, dispatch } = useBuilder(); const tools = [ { id: "select", icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }), name: "Select", active: state.tool === "select" }, { id: "hand", icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }), name: "Hand", active: state.tool === "hand" }, { id: "text", icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }), name: "Text", active: state.tool === "text" } ]; return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-1", children: [ tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { onClick: () => dispatch({ type: "SET_TOOL", payload: tool.id }), className: `p-2 rounded transition-colors ${tool.active ? "bg-blue-100 text-blue-600" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}`, title: tool.name, children: tool.icon }, tool.id )), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "h-6 w-px bg-gray-300 mx-2" }), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }) }), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs text-gray-600 px-1", children: "100%" }), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }) }) ] }) ] }); } // src/components/ZunoBuilder.tsx var import_jsx_runtime7 = require("react/jsx-runtime"); function ZunoBuilder({ config, children }) { const [isBuilderOpen, setIsBuilderOpen] = (0, import_react4.useState)(false); const isEnabled = config?.enabled ?? process.env.NODE_ENV === "development"; if (!isEnabled) { return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children }); } if (!isBuilderOpen) { return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative", children: [ children, /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "fixed bottom-6 right-6 z-50", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( "button", { onClick: () => setIsBuilderOpen(true), 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", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" }) }), "Open Builder" ] } ) }) ] }); } return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BuilderProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "fixed inset-0 z-50 bg-gray-50 flex flex-col", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "h-12 bg-white border-b border-gray-200 flex items-center justify-between px-4 shadow-sm", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-6 h-6 bg-gradient-to-br from-blue-500 to-purple-600 rounded flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-white text-xs font-bold", children: "Z" }) }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-gray-900", children: "Zuno Builder" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-6 w-px bg-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Toolbar, {}) ] }), /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded transition-colors", children: "Preview" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "px-3 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors", children: "Publish" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-6 w-px bg-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( "button", { onClick: () => setIsBuilderOpen(false), className: "p-1.5 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-1 overflow-hidden", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-64 bg-white border-r border-gray-200 flex flex-col", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Sidebar, {}) }), /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex-1 bg-gray-50 flex flex-col overflow-hidden", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-10 bg-white border-b border-gray-200 flex items-center justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "100%" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-4 w-px bg-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Frame 1" }) ] }) }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex-1 overflow-auto p-8", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Canvas, {}) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-72 bg-white border-l border-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PropertyPanel, {}) }) ] }) ] }) }); } // src/hooks/useZunoBuilder.ts var import_react5 = require("react"); function useZunoBuilder() { const [layout, setLayout] = (0, import_react5.useState)(null); const [isLoading, setIsLoading] = (0, import_react5.useState)(true); (0, import_react5.useEffect)(() => { if (process.env.NODE_ENV === "development") { loadLayout(); } else { setIsLoading(false); } }, []); const loadLayout = async () => { try { setLayout({ version: "1.0.0", id: "default-layout", name: "Default Layout", blocks: [], metadata: { version: "1.0.0", createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), targetPath: "" }, viewport: { width: 1200, height: 800, scale: 1, offsetX: 0, offsetY: 0 } }); } catch (error) { console.warn("Failed to load Zuno layout:", error); } finally { setIsLoading(false); } }; const saveLayout = async (newLayout) => { try { setLayout(newLayout); } catch (error) { console.error("Failed to save Zuno layout:", error); } }; const addBlock = (block) => { if (!layout) return; const updatedLayout = { ...layout, blocks: [...layout.blocks, block], metadata: { ...layout.metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } }; saveLayout(updatedLayout); }; const updateBlock = (blockId, updates) => { if (!layout) return; const updatedBlocks = layout.blocks.map( (block) => block.id === blockId ? { ...block, ...updates } : block ); const updatedLayout = { ...layout, blocks: updatedBlocks, metadata: { ...layout.metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } }; saveLayout(updatedLayout); }; const removeBlock = (blockId) => { if (!layout) return; const updatedBlocks = layout.blocks.filter((block) => block.id !== blockId); const updatedLayout = { ...layout, blocks: updatedBlocks, metadata: { ...layout.metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } }; saveLayout(updatedLayout); }; return { layout, isLoading, addBlock, updateBlock, removeBlock, saveLayout }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ZunoBuilder, useZunoBuilder }); //# sourceMappingURL=index.js.map