UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

417 lines (414 loc) 17.6 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useRef, useState } from 'react'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { GlassCore } from '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { useDragDrop } from './GlassDragDropProvider.js'; const DropZone = ({ targetId, position, isActive, onDrop }) => { const handleDragOver = e => { e.preventDefault(); e.dataTransfer.dropEffect = "copy"; }; const handleDrop = e => { e.preventDefault(); onDrop(targetId, position); }; return jsx("div", { "data-glass-component": true, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("transition-all duration-200", position === "inside" ? "absolute inset-0 rounded-lg" : "h-2 rounded-full my-1", isActive ? "bg-blue-500 bg-opacity-20 border-2 border-blue-500 border-dashed" : "bg-transparent hover:bg-blue-100 border-2 border-transparent") }); }; const ComponentRenderer = ({ component, isSelected, onSelect }) => { const { updateComponent, dragDropState, onDragStart } = useDragDrop(); const [isEditing, setIsEditing] = useState(null); const handleClick = e => { e.stopPropagation(); onSelect?.(component.id); }; const handleDoubleClick = e => { e.stopPropagation(); if (component.type === "text" || component.type === "heading") { setIsEditing("content"); } }; const handleInlineEdit = (prop, value) => { updateComponent(component.id, { [prop]: value }); setIsEditing(null); }; const handleDragStart = e => { e.dataTransfer.effectAllowed = "move"; onDragStart(component, "element"); }; const renderComponent = () => { const baseStyle = { position: "relative", ...component.props }; switch (component.type) { case "container": return jsx("div", { style: { ...baseStyle, display: component.props.display || "block", padding: component.props.padding, margin: component.props.margin, backgroundColor: component.props.backgroundColor, borderRadius: component.props.borderRadius, maxWidth: component.props.maxWidth, minHeight: component.children.length === 0 ? "100px" : "auto" }, className: cn("glass-relative", component.children.length === 0 && "glass-border glass-border-2 glass-border-dashed glass-flex glass-items-center glass-justify-center"), children: component.children.length === 0 ? jsx("span", { className: "glass-text-secondary glass-text-sm", children: "Drop components here" }) : component.children.map(child => jsx(ComponentRenderer, { component: child, isSelected: isSelected, onSelect: onSelect }, child.id)) }); case "row": return jsx("div", { style: { ...baseStyle, display: "flex", gap: component.props.gap, justifyContent: component.props.justifyContent, alignItems: component.props.alignItems, flexWrap: component.props.wrap, padding: component.props.padding, minHeight: component.children.length === 0 ? "80px" : "auto" }, className: cn("glass-relative", component.children.length === 0 && "glass-border glass-border-2 glass-border-dashed glass-flex glass-items-center glass-justify-center"), children: component.children.length === 0 ? jsx("span", { className: "glass-text-secondary glass-text-sm", children: "Add columns here" }) : component.children.map(child => jsx(ComponentRenderer, { component: child, isSelected: isSelected, onSelect: onSelect }, child.id)) }); case "column": return jsx("div", { style: { ...baseStyle, display: "flex", flexDirection: "column", flex: component.props.flex, padding: component.props.padding, gap: component.props.gap, alignItems: component.props.alignItems, minHeight: component.children.length === 0 ? "120px" : "auto" }, className: cn("glass-relative", component.children.length === 0 && "glass-border glass-border-2 glass-border-dashed glass-flex glass-items-center glass-justify-center"), children: component.children.length === 0 ? jsx("span", { className: "glass-text-secondary glass-text-sm", children: "Column content" }) : component.children.map(child => jsx(ComponentRenderer, { component: child, isSelected: isSelected, onSelect: onSelect }, child.id)) }); case "text": return jsx("div", { style: { ...baseStyle, fontSize: component.props.fontSize, fontWeight: component.props.fontWeight, color: component.props.color, textAlign: component.props.textAlign, lineHeight: component.props.lineHeight, fontFamily: component.props.fontFamily }, children: isEditing === "content" ? jsx("input", { type: "text", value: component.props.content, onChange: e => handleInlineEdit("content", e.target.value), onBlur: () => setIsEditing(null), onKeyDown: e => { if (e.key === "Enter") setIsEditing(null); if (e.key === "Escape") setIsEditing(null); }, autoFocus: true, className: 'bg-transparent glass-border-none outline-none glass-w-full' }) : component.props.content }); case "heading": const HeadingTag = component.props.level; const Tag = HeadingTag; return jsx(Tag, { style: { ...baseStyle, fontSize: component.props.fontSize, fontWeight: component.props.fontWeight, color: component.props.color, textAlign: component.props.textAlign, margin: component.props.margin }, children: isEditing === "content" ? jsx("input", { type: "text", value: component.props.content, onChange: e => handleInlineEdit("content", e.target.value), onBlur: () => setIsEditing(null), onKeyDown: e => { if (e.key === "Enter") setIsEditing(null); if (e.key === "Escape") setIsEditing(null); }, autoFocus: true, className: 'bg-transparent glass-border-none outline-none glass-w-full' }) : component.props.content }); case "image": return jsx("img", { src: component.props.src, alt: component.props.alt, style: { ...baseStyle, width: component.props.width, height: component.props.height, borderRadius: component.props.borderRadius, objectFit: component.props.objectFit } }); case "button": const ButtonTag = component.props.href ? "a" : "button"; return jsx(ButtonTag, { href: component.props.href || undefined, disabled: component.props.disabled, onClick: component.props.href ? undefined : () => { if (component.props.onClick) { try { // eslint-disable-next-line no-new-func new Function(component.props.onClick)(); } catch (e) { console.warn("Button onClick error:", e); } } }, className: cn("glass-inline-flex glass-items-center glass-justify-center glass-font-medium glass-radius-lg", // Size variants component.props.size === "small" && "glass-px-3 glass-py-1-5 glass-text-sm", component.props.size === "medium" && "glass-px-4 glass-py-2 glass-text-base", component.props.size === "large" && "glass-px-6 glass-py-3 glass-text-lg", // Variant styles component.props.variant === "primary" && "glass-surface-primary glass-text-primary", component.props.variant === "secondary" && "glass-surface-secondary glass-text-primary", component.props.variant === "outline" && "glass-border glass-text-secondary", component.props.variant === "ghost" && "glass-text-secondary", // Disabled state component.props.disabled && "glass-opacity-90 glass-cursor-pointer"), style: baseStyle, children: component.props.text }); case "card": return jsx("div", { style: { ...baseStyle, padding: component.props.padding, borderRadius: component.props.borderRadius, backgroundColor: component.props.backgroundColor, boxShadow: component.props.boxShadow, border: component.props.border, minHeight: component.children.length === 0 ? "150px" : "auto" }, className: cn("glass-relative", component.children.length === 0 && "glass-border glass-border-2 glass-border-dashed glass-flex glass-items-center glass-justify-center"), children: component.children.length === 0 ? jsx("span", { className: "glass-text-secondary glass-text-sm glass-focus glass-touch-target glass-contrast-guard", children: "Card content goes here" }) : component.children.map(child => jsx(ComponentRenderer, { component: child, isSelected: isSelected, onSelect: onSelect }, child.id)) }); default: return jsxs("div", { style: baseStyle, className: 'glass-p-4 glass-border glass-border-subtle glass-surface-subtle text-primary glass-contrast-guard', children: ["Unknown component type: ", component.type] }); } }; return jsxs("div", { draggable: true, onDragStart: handleDragStart, onClick: handleClick, onDoubleClick: handleDoubleClick, className: cn("relative group transition-all duration-200 glass-focus glass-touch-target", isSelected && "ring-2 ring-blue-500 ring-opacity-50", dragDropState.draggedItem?.id === component.id && "opacity-50"), children: [renderComponent(), isSelected && jsx("div", { className: 'absolute inset-0 pointer-events-none glass-border-2 glass-radius glass-surface-overlay glass-focus glass-touch-target glass-contrast-guard', children: jsx("div", { className: 'absolute glass-top-2 left-0 glass-surface-overlay text-primary glass-px-2 glass-py-1 glass-text-xs glass-radius-t font-medium glass-contrast-guard', children: component.type }) }), !isSelected && jsx("div", { className: 'absolute inset-0 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity glass-border-2 glass-border-blue glass-radius glass-surface-subtle bg-opacity-5 glass-contrast-guard', children: jsx("div", { className: 'absolute -top-6 left-0 glass-surface-subtle text-primary glass-px-2 glass-py-1 glass-text-xs glass-radius-t font-medium glass-contrast-guard', children: component.type }) })] }); }; const GlassCanvas = ({ className }) => { useReducedMotion(); const { pageState, dragDropState, onDrop, selectComponent, getSelectedComponent } = useDragDrop(); const canvasRef = useRef(null); const [dragOverTarget, setDragOverTarget] = useState({}); const handleCanvasClick = () => { selectComponent(undefined); }; const handleDragOver = e => { e.preventDefault(); e.dataTransfer.dropEffect = dragDropState.draggedType === "component" ? "copy" : "move"; }; const handleDrop = e => { e.preventDefault(); onDrop(); setDragOverTarget({}); }; const breakpointStyles = { desktop: { maxWidth: "none", width: "100%" }, tablet: { maxWidth: "768px", width: "768px" }, mobile: { maxWidth: "375px", width: "375px" } }; getSelectedComponent(); return jsx("div", { className: cn("flex-1 h-full flex flex-col overflow-hidden", className), children: jsxs(GlassCore, { className: "glass-flex-1 glass-flex glass-flex-col glass-contrast-guard", children: [jsxs("div", { className: "glass-flex glass-items-center glass-justify-between glass-p-4 glass-border-b glass-border-subtle", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("span", { className: 'glass-text-sm font-medium glass-text-secondary', children: "Canvas" }), jsxs("span", { className: 'glass-text-xs glass-text-secondary capitalize', children: ["(", pageState.activeBreakpoint, ")"] }), pageState.previewMode && jsx("span", { className: 'glass-px-2 glass-py-1 glass-text-xs glass-surface-subtle text-primary glass-radius glass-contrast-guard', children: "Preview Mode" })] }), jsxs("div", { className: "glass-text-xs glass-text-secondary", children: [pageState.components.length, " components"] })] }), jsx("div", { className: 'glass-flex-1 overflow-auto glass-p-6 glass-surface-subtle glass-contrast-guard', style: { backgroundImage: pageState.showGrid ? "radial-gradient(circle, var(--glass-gray-200) 1px, transparent 1px)" : "none", backgroundSize: pageState.showGrid ? "20px 20px" : "auto" }, children: jsx("div", { className: 'glass-mx-auto glass-surface-subtle glass-min-h-full glass-shadow-sm transition-all duration-300 glass-contrast-guard', style: breakpointStyles[pageState.activeBreakpoint], children: jsxs("div", { ref: canvasRef, onClick: handleCanvasClick, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("min-h-full relative transition-all duration-200 glass-focus glass-touch-target", dragDropState.isDragging && "bg-blue-50 bg-opacity-50"), children: [pageState.components.length === 0 ? // Empty State jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-min-glass-h-96 glass-p-12 glass-focus glass-touch-target glass-contrast-guard", children: jsxs("div", { className: 'text-center', children: [jsx("div", { className: 'text-6xl mb-4', children: "\uD83C\uDFA8" }), jsx("h3", { className: 'glass-text-lg font-medium glass-text-secondary mb-2', children: "Start Building Your Page" }), jsx("p", { className: 'glass-text-secondary mb-6 max-w-sm', children: "Drag components from the palette on the left to start building your page." }), jsxs("div", { className: 'space-y-2 glass-text-sm glass-text-secondary', children: [jsx("p", { children: "\uD83D\uDCA1 Try dragging a Container or Row component first" }), jsx("p", { children: "\uD83D\uDDB1\uFE0F Double-click text elements to edit them inline" }), jsx("p", { children: "\u2328\uFE0F Use Ctrl+Z to undo changes" })] })] }) }) : // Render Components jsxs("div", { className: 'glass-p-4 space-y-4', children: [pageState.components.filter(component => !component.parent) // Only root components .map(component => jsxs("div", { className: 'relative', children: [jsx(DropZone, { position: "before", isActive: dragOverTarget.id === component.id && dragOverTarget.position === "before", onDrop: onDrop }), jsx(ComponentRenderer, { component: component, isSelected: pageState.selectedComponent === component.id, onSelect: selectComponent }), jsx(DropZone, { position: "after", isActive: dragOverTarget.id === component.id && dragOverTarget.position === "after", onDrop: onDrop })] }, component.id)), jsx(DropZone, { position: "inside", isActive: dragDropState.isDragging && !dragOverTarget.id, onDrop: onDrop })] }), dragDropState.isDragging && jsx("div", { className: 'absolute inset-0 pointer-events-none', children: jsx("div", { className: 'glass-h-full glass-w-full glass-border-4 glass-border-dashed glass-border-blue opacity-50 glass-radius-lg' }) })] }) }) })] }) }); }; export { GlassCanvas }; //# sourceMappingURL=GlassCanvas.js.map