aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
248 lines (245 loc) • 9.84 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { forwardRef, useMemo, createRef, useState, useEffect, useCallback } from 'react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import '../../primitives/motion/MotionFramer.js';
import { cn } from '../../lib/utilsComprehensive.js';
import styles from './GlassDataGrid.module.css.js';
// Stub implementations for missing hooks
const useSortableData = (data, sortConfig) => ({
sortedData: data,
sortConfig,
handleSort: () => {}
});
const useDraggableListPhysics = options => ({
styles: {},
getHandlers: () => ({}),
isDragging: false,
draggedIndex: -1
});
const useVectorSpring = options => ({
start: () => {},
value: options?.initialValue || {
x: 0,
y: 0,
z: 0
},
setValue: () => {}
});
// Define the component using forwardRef
const GlassDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
// TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance
const {
data: initialData,
columns,
className,
style,
height,
initialSort,
enableRowDragging = false,
onRowOrderChange
} = props;
const safeInitialData = Array.isArray(initialData) ? initialData : [];
const safeColumns = Array.isArray(columns) ? columns : [];
const {
sortedData: unsortedData = safeInitialData,
sortConfig,
handleSort
} = useSortableData(safeInitialData, initialSort);
const normalizedSortedData = Array.isArray(unsortedData) ? unsortedData : safeInitialData;
// Create refs for each row element for the physics hook
const rowRefs = useMemo(() => Array.from({
length: normalizedSortedData?.length || 0
}, () => /*#__PURE__*/createRef()), [normalizedSortedData?.length]);
// Need state for the order controlled by the hook
const [renderOrder, setRenderOrder] = useState(() => Array.from({
length: normalizedSortedData.length
}, (_, i) => i));
useEffect(() => {
setRenderOrder(previous => {
if (previous.length === normalizedSortedData.length) {
return previous;
}
return Array.from({
length: normalizedSortedData.length
}, (_, i) => i);
});
}, [normalizedSortedData.length]);
// Display data based on renderOrder
const displayData = useMemo(() => (renderOrder || []).map(index => normalizedSortedData?.[index]).filter(row => row !== undefined), [normalizedSortedData, renderOrder]);
// Callback for the hook to update our renderOrder
useCallback(newOrderIndices => {
setRenderOrder(newOrderIndices);
if (onRowOrderChange) {
// Map original data based on the new order of *original* indices
const originalDataInNewOrder = newOrderIndices.map(originalIndex => safeInitialData?.[originalIndex]).filter(row => row !== undefined);
onRowOrderChange(originalDataInNewOrder);
}
}, [onRowOrderChange, safeInitialData]);
const {
styles: rowStyles,
getHandlers,
isDragging: isAnyItemDragging,
draggedIndex: draggedOriginalItemIndex
} = useDraggableListPhysics();
// --- Sort Indicator Animation using useVectorSpring ---
const sortIndicatorSpring = useVectorSpring({
config: {
tension: 350,
// Use tension/friction for config
friction: 25
}
// initialValue: { x: 0, y: 0, z: 0 } // Optional initial value
});
// Map sorting state to target value for animation hook
const sortTargetValue = useMemo(() => {
if (!sortConfig) return 0; // Target 0 when not sorted
return sortConfig.direction === 'asc' ? 1 : -1; // Target 1 for asc -1 for desc
}, [sortConfig]);
// Trigger animation when target changes
useEffect(() => {
// Set the target Y value of the vector spring
sortIndicatorSpring.start();
}, [sortTargetValue, sortIndicatorSpring]);
// --- End Sort Indicator Animation ---
const handleHeaderKeyDown = event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
// handleSort would need to be updated to work without parameters
// For now, we'll skip this functionality
}
};
const totalColumns = safeColumns.length + (enableRowDragging ? 1 : 0);
const hasData = displayData.length > 0;
if (!safeColumns.length) {
return jsx(OptimizedGlassCore, {
"data-glass-component": true,
ref: ref,
intent: "neutral",
elevation: "level2",
intensity: "medium",
depth: 2,
tint: "neutral",
border: "subtle",
animation: "none",
performanceMode: "medium",
className: cn('glass-w-full glass-p-6 glass-text-center', className),
style: style,
children: jsx("p", {
className: "glass-text-sm glass-text-secondary",
children: "No columns configured for this data grid."
})
});
}
return jsx(OptimizedGlassCore, {
"data-glass-component": true,
ref: ref,
intent: "neutral",
elevation: "level2",
intensity: "medium",
depth: 2,
tint: "neutral",
border: "subtle",
animation: "none",
performanceMode: "medium",
className: cn('glass-w-full glass-overflow-hidden', height && 'glass-overflow-y-auto', className),
style: {
...style,
...(height && {
height: typeof height === 'number' ? `${height}px` : height
}),
perspective: '1000px'
},
children: jsxs("table", {
className: styles.table,
children: [jsx("thead", {
children: jsxs("tr", {
className: styles.headerRow,
children: [enableRowDragging && jsx("th", {
className: cn(styles.headerCell, styles.dragHandleCell),
"aria-hidden": "true"
}), safeColumns.map(col => {
// Determine if this column is the one being sorted
const isSortingThisColumn = sortConfig?.key === col.key;
const currentSortDirection = sortConfig && isSortingThisColumn ? sortConfig.direction : null;
const isSortable = col.sortable;
// Calculate indicator style based on animation value (from spring.value.y)
const animValue = sortIndicatorSpring.value.y; // Get the animated value from the Y dimension
const indicatorOpacity = Math.min(1, Math.abs(animValue) * 1.5); // Fade in/out
const indicatorTranslateYPercent = -50 + animValue * -10; // Vertical movement
const indicatorScale = 0.8 + Math.abs(animValue) * 0.2; // Scale effect
const headerClassName = cn(styles.headerCell, isSortable && styles.headerSortable);
return jsxs("th", {
className: headerClassName,
onClick: () => isSortable && handleSort(),
tabIndex: isSortable ? 0 : -1,
onKeyDown: e => handleHeaderKeyDown(e),
role: "columnheader",
"aria-sort": isSortable ? currentSortDirection === 'asc' ? 'ascending' : currentSortDirection === 'desc' ? 'descending' : 'none' : undefined,
children: [col.header, jsx("span", {
className: styles.sortIndicator,
style: {
opacity: indicatorOpacity,
transform: `translateY(${indicatorTranslateYPercent}%) scale(${indicatorScale})`
},
"aria-hidden": !isSortingThisColumn,
children: sortTargetValue > 0 ? '▲' : '▼'
})]
}, col.id);
})]
})
}), jsx("tbody", {
className: styles.body,
children: hasData ? displayData.map((row, displayIndex) => {
const originalIndex = renderOrder?.[displayIndex];
if (originalIndex === undefined) return null;
const rowStyle = rowStyles?.[originalIndex] || {};
const handlers = enableRowDragging ? getHandlers() : {
onPointerDown: () => {},
onKeyDown: () => {}
};
const isDraggingThisRow = isAnyItemDragging && draggedOriginalItemIndex === originalIndex;
const rowClassName = cn(styles.row, isDraggingThisRow && styles.rowDragging);
return jsxs("tr", {
ref: rowRefs?.[originalIndex],
style: rowStyle,
className: rowClassName,
children: [enableRowDragging && jsx("td", {
className: cn(styles.cell, styles.dragHandleCell),
children: jsx("span", {
...handlers,
tabIndex: 0,
role: "button",
"aria-label": `Drag row ${displayIndex + 1}`,
"aria-grabbed": isDraggingThisRow,
"data-drag-handle": "true",
className: cn(styles.dragHandle, isDraggingThisRow && styles.dragHandleActive),
children: "\u283F"
})
}), safeColumns.map(col => jsx("td", {
className: styles.cell,
children: col.cellRenderer ? col.cellRenderer(row?.[col.accessorKey], row) : row?.[col.accessorKey] ?? col.placeholder ?? '—'
}, `${col.id}-${originalIndex}`))]
}, `row-${row?.id ?? originalIndex}`);
}) : jsx("tr", {
children: jsx("td", {
className: styles.cell,
colSpan: Math.max(1, totalColumns),
children: jsx("div", {
className: "glass-text-sm glass-text-secondary text-center",
children: "No data available."
})
})
})
})]
})
});
});
// Add display name for better debugging
GlassDataGrid.displayName = 'GlassDataGrid';
export { GlassDataGrid };
//# sourceMappingURL=GlassDataGrid.js.map