aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
417 lines (414 loc) • 17.6 kB
JavaScript
'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