UNPKG

aura-glass

Version:

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

312 lines (309 loc) 11.2 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { forwardRef, useState, useCallback } from 'react'; 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 { MotionFramer } from '../../../primitives/motion/MotionFramer.js'; import { GlassGrid, GlassGridItem } from '../../layout/GlassGrid.js'; import { GlassCard } from '../../card/GlassCard.js'; import { GlassButton, IconButton } from '../../button/GlassButton.js'; import { PageHeader } from '../../layout/GlassAppShell.js'; import { HStack, VStack } from '../../layout/GlassStack.js'; import { cn } from '../../../lib/utilsComprehensive.js'; /** * GlassDashboard component * Drag-and-drop dashboard with glassmorphism widgets */ const GlassDashboard = /*#__PURE__*/forwardRef(({ title = "Dashboard", description, layout, availableWidgets = [], editMode = false, onEditModeChange, onLayoutChange, onWidgetAdd, onWidgetRemove, onWidgetUpdate, dragEnabled = true, widgetRenderers = {}, actions, loading = false, emptyState, className, ...props }, ref) => { const [draggedWidget, setDraggedWidget] = useState(null); const [dragOverPosition, setDragOverPosition] = useState(null); // Handle drag start const handleDragStart = useCallback(widgetId => { if (!editMode || !dragEnabled) return; setDraggedWidget(widgetId); }, [editMode, dragEnabled]); // Handle drag end const handleDragEnd = useCallback(() => { setDraggedWidget(null); setDragOverPosition(null); }, []); // Handle drop const handleDrop = useCallback(position => { if (!draggedWidget || !onWidgetUpdate) return; onWidgetUpdate(draggedWidget, { position }); handleDragEnd(); }, [draggedWidget, onWidgetUpdate, handleDragEnd]); // Add new widget const handleAddWidget = useCallback(widgetType => { const widgetTemplate = availableWidgets.find(w => w.type === widgetType); if (!widgetTemplate || !onWidgetAdd) return; // Find empty position const usedPositions = new Set(layout.widgets.map(w => `${w.position.x},${w.position.y}`)); let position = { x: 0, y: 0 }; for (let y = 0; y < 10; y++) { for (let x = 0; x < layout.cols; x++) { const posKey = `${x},${y}`; if (!usedPositions.has(posKey)) { position = { x, y }; break; } } if (position.x !== 0 || position.y !== 0) break; } onWidgetAdd({ title: widgetTemplate.title, type: widgetTemplate.type, size: widgetTemplate.defaultSize, position }); }, [availableWidgets, layout.widgets, layout.cols, onWidgetAdd]); // Default widget renderers const defaultWidgetRenderers = { metric: ({ widget }) => jsxs(VStack, { space: "md", children: [jsx("div", { className: 'glass-text-2xl font-bold text-primary', children: widget.data?.value || "0" }), jsx("div", { className: "glass-text-sm glass-text-secondary", children: widget.data?.label || "Metric" }), widget.data?.change && jsxs("div", { className: cn("glass-text-xs font-medium", widget.data?.change > 0 ? "text-success" : "text-destructive"), children: [widget.data?.change > 0 ? "+" : "", widget.data?.change, "%"] })] }), chart: ({ widget }) => jsxs(VStack, { space: "md", children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: widget.data?.title || "Chart" }), jsx("div", { className: 'h-32 glass-surface-subtle glass-radius-md glass-flex glass-items-center glass-justify-center', children: jsx("span", { className: "glass-text-secondary", children: widget.data?.chartType ? `${widget.data?.chartType} Chart` : "Chart Widget" }) })] }), table: ({ widget }) => jsxs(VStack, { space: "md", children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: widget.data?.title || "Table" }), jsx("div", { className: "glass-auto-gap glass-auto-gap-sm", children: (widget.data?.rows || []).slice(0, 3).map((row, index) => jsxs("div", { className: "glass-flex glass-justify-between glass-text-sm", children: [jsx("span", { className: 'text-primary', children: row.name }), jsx("span", { className: "glass-text-secondary", children: row.value })] }, index)) })] }), text: ({ widget }) => jsx("div", { className: 'glass-text-sm text-primary', children: widget.data?.content || widget.data?.title || "Text Widget" }) }; // Render widget const renderWidget = widget => { const WidgetRenderer = widgetRenderers?.[widget.type] || defaultWidgetRenderers?.[widget.type] || widget.component; if (!WidgetRenderer) { return jsxs("div", { "data-glass-component": true, className: "glass-h-full glass-flex glass-items-center glass-justify-center glass-text-secondary", children: ["Unknown widget type: ", widget.type] }); } return jsx(WidgetRenderer, { widget: widget }); }; // Render add widget menu const renderAddWidgetMenu = () => { if (!editMode || (availableWidgets?.length || 0) === 0) return null; return jsx("div", { className: "glass-grid glass-grid-cols-2 glass-gap-2 glass-p-2", children: availableWidgets.map(widgetType => jsx(GlassButton, { variant: "ghost", size: "sm", leftIcon: widgetType.icon, onClick: e => handleAddWidget(widgetType.type), className: "glass-justify-start glass-focus glass-touch-target", children: widgetType.title }, widgetType.type)) }); }; if (loading) { return jsx("div", { className: 'glass-flex glass-items-center glass-justify-center h-64', children: jsx("div", { className: 'w-8 h-8 glass-border-2 glass-border-primary glass-border-t-transparent glass-radius-full animate-spin' }) }); } if ((layout.widgets?.length || 0) === 0 && emptyState) { return emptyState; } return jsxs("div", { ref: ref, className: cn("w-full glass-auto-gap glass-auto-gap-2xl", className), ...props, children: [jsx(PageHeader, { title: title, description: description, actions: jsxs(HStack, { space: "sm", children: [actions, (availableWidgets?.length || 0) > 0 && jsx(GlassButton, { variant: editMode ? "primary" : "outline", size: "sm", leftIcon: editMode ? "✓" : "✏️", onClick: e => onEditModeChange?.(!editMode), className: "glass-focus glass-touch-target", children: editMode ? "Done" : "Edit" })] }) }), editMode && (availableWidgets?.length || 0) > 0 && jsx(MotionFramer, { preset: "slideDown", children: jsx(GlassCore, { className: "glass-p-4 glass-radius-lg", children: jsxs(VStack, { space: "sm", children: [jsx("h3", { className: 'glass-text-sm font-medium text-primary', children: "Add Widget" }), renderAddWidgetMenu()] }) }) }), jsxs(GlassGrid, { cols: layout.cols, gap: layout.gap, className: "glass-min-h-96", children: [layout.widgets.map(widget => jsx(GlassGridItem, { colSpan: widget.size.cols, rowSpan: widget.size.rows, className: cn("transition-all duration-200", draggedWidget === widget.id && "opacity-50 scale-95", editMode && dragEnabled && "cursor-move"), draggable: editMode && dragEnabled, onDragStart: () => handleDragStart(widget.id), onDragEnd: handleDragEnd, children: jsxs(GlassCard, { variant: "default", className: cn("h-full relative group", editMode && "hover:ring-2 hover:ring-primary/50"), children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-4', children: [jsx("h3", { className: 'glass-text-sm font-medium text-primary truncate', children: widget.title }), editMode && jsxs(HStack, { space: "xs", className: 'opacity-0 group-hover:opacity-100 transition-opacity', children: [widget.removable !== false && jsx(IconButton, { icon: "\uD83D\uDDD1\uFE0F", variant: "ghost", size: "xs", onClick: e => onWidgetRemove?.(widget.id), "aria-label": "Remove widget", className: "glass-focus glass-touch-target glass-contrast-guard" }), jsx(IconButton, { icon: "\u2699\uFE0F", variant: "ghost", size: "xs", onClick: e => { // Handle widget configuration }, "aria-label": "Configure widget", className: "glass-focus glass-touch-target glass-contrast-guard" })] })] }), jsx("div", { className: "glass-flex-1", children: renderWidget(widget) })] }) }, widget.id)), editMode && draggedWidget && jsx(Fragment, { children: Array.from({ length: layout.cols * 4 }, (_, index) => { const x = index % layout.cols; const y = Math.floor(index / layout.cols); const isOccupied = layout.widgets.some(w => w.position.x === x && w.position.y === y); if (isOccupied) return null; return jsx(GlassGridItem, { colSpan: 1, rowSpan: 1, className: "glass-min-h-24", style: { gridColumnStart: x + 1, gridRowStart: y + 1 }, children: jsx("div", { className: 'glass-h-full glass-border-2 glass-border-dashed glass-border-primary/30 glass-radius-lg glass-surface-primary/5 glass-flex glass-items-center glass-justify-center transition-colors hover:border-primary/50 hover:glass-surface-primary/10', onDragOver: e => { e.preventDefault(); setDragOverPosition({ x, y }); }, onDrop: e => { e.preventDefault(); handleDrop({ x, y }); }, children: jsx("span", { className: 'glass-text-xs text-primary/60', children: "Drop here" }) }) }, `drop-zone-${x}-${y}`); }) })] })] }); }); GlassDashboard.displayName = "GlassDashboard"; export { GlassDashboard }; //# sourceMappingURL=GlassDashboard.js.map