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