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
JavaScript
"use client";
;
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