aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
519 lines (516 loc) • 23 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { forwardRef, useRef, useState, useEffect, useMemo, 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 { IconButton } from '../button/GlassButton.js';
import { GlassInput } from '../input/GlassInput.js';
import { GlassSelect } from '../input/GlassSelect.js';
import { usePredictiveEngine, useInteractionRecorder } from '../advanced/GlassPredictiveEngine.js';
import { useAchievements } from '../advanced/GlassAchievementSystem.js';
import { useBiometricAdaptation } from '../advanced/GlassBiometricAdaptation.js';
import { useEyeTracking } from '../advanced/GlassEyeTracking.js';
import { useSpatialAudio } from '../advanced/GlassSpatialAudio.js';
/**
* GlassDataTable component
* A comprehensive data table with glassmorphism styling
*/
const GlassDataTable = props => {
// TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance
return jsx(GlassDataTableInner, {
...props
});
};
const GlassDataTableInner = /*#__PURE__*/forwardRef(({
data: incomingData = [],
columns: incomingColumns = [],
loading = false,
emptyMessage = "No data available",
variant = "default",
size = "md",
sortable = true,
filterable = false,
searchable = true,
searchPlaceholder = "Search...",
pagination = true,
pageSizeOptions: incomingPageSizeOptions = [10, 25, 50, 100],
initialPageSize = 10,
selectable = false,
selectionMode = "multiple",
selectedRows: incomingSelectedRows = [],
onSelectionChange,
onRowClick,
getRowId = (row, index) => index.toString(),
getRowProps,
stickyHeader = false,
actions,
cellRenderers,
emptyState,
className,
// Consciousness features
predictive = false,
preloadContent = false,
eyeTracking = false,
gazeResponsive = false,
adaptive = false,
biometricResponsive = false,
spatialAudio = false,
audioFeedback = false,
trackAchievements = false,
achievementId,
usageContext = "main"
}, ref) => {
const data = Array.isArray(incomingData) ? incomingData : [];
const columns = Array.isArray(incomingColumns) ? incomingColumns : [];
const pageSizeOptions = Array.isArray(incomingPageSizeOptions) && incomingPageSizeOptions.length ? incomingPageSizeOptions : [initialPageSize];
const selectedRows = Array.isArray(incomingSelectedRows) ? incomingSelectedRows : [];
const tableRef = useRef(null);
const [columnUsage, setColumnUsage] = useState({});
const [predictedSortColumn, setPredictedSortColumn] = useState(null);
const [adaptivePageSize, setAdaptivePageSize] = useState(initialPageSize);
const [interactionHeatmap, setInteractionHeatmap] = useState({});
// Consciousness feature hooks - only initialize if features are enabled
const predictiveEngine = predictive ? usePredictiveEngine() : null;
const eyeTracker = eyeTracking ? useEyeTracking() : null;
const biometricAdapter = adaptive ? useBiometricAdaptation() : null;
const spatialAudioEngine = spatialAudio ? useSpatialAudio() : null;
const achievementTracker = trackAchievements ? useAchievements() : null;
const interactionRecorder = predictive || trackAchievements ? useInteractionRecorder(`glass-datatable-${usageContext}`) : null;
const [sortState, setSortState] = useState(null);
const [filterState, setFilterState] = useState({});
const [searchQuery, setSearchQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(initialPageSize);
// Ensure size is always valid
const validSize = size && ["sm", "md", "lg"].includes(size) ? size : "md";
// Biometric adaptation for table density and pagination
useEffect(() => {
if (!biometricResponsive || !biometricAdapter) return;
const adaptTable = () => {
const stressLevel = biometricAdapter.currentStressLevel;
// Adapt page size based on stress level
if (stressLevel > 0.7) {
setAdaptivePageSize(Math.min(initialPageSize, 10)); // Smaller pages when stressed
} else if (stressLevel < 0.3) {
setAdaptivePageSize(Math.max(initialPageSize, 25)); // Larger pages when relaxed
} else {
setAdaptivePageSize(initialPageSize); // Use original page size
}
};
// Initial adaptation
adaptTable();
// Listen for biometric changes
const interval = setInterval(adaptTable, 5000);
return () => clearInterval(interval);
}, [biometricResponsive, biometricAdapter, initialPageSize]);
// Predictive sorting based on column usage patterns
useEffect(() => {
if (!predictive || !predictiveEngine) return;
const updatePredictions = () => {
const predictions = predictiveEngine.predictions;
const sortPrediction = predictions.find(p => p.type === "suggest" && p.metadata?.tableContext === usageContext);
if (sortPrediction && sortPrediction.confidence > 0.8) {
setPredictedSortColumn(sortPrediction.metadata.columnId);
} else {
setPredictedSortColumn(null);
}
};
const interval = setInterval(updatePredictions, 3000);
updatePredictions(); // Initial update
return () => clearInterval(interval);
}, [predictive, predictiveEngine, usageContext]);
// Eye tracking for data attention analysis
useEffect(() => {
if (!gazeResponsive || !eyeTracker || !tableRef.current) return;
// Note: onGazeEnter/offGazeEnter not available on current eye tracker interface
// eyeTracker.onGazeEnter?.(tableRef.current, handleCellGaze);
return () => {
// if (tableRef.current) {
// eyeTracker.offGazeEnter?.(tableRef.current, handleCellGaze);
// }
};
}, [gazeResponsive, eyeTracker, spatialAudioEngine, audioFeedback]);
const sizeClasses = {
sm: "glass-text-xs",
md: "glass-text-sm",
lg: "glass-text-base"
};
const cellPaddingClasses = {
sm: "glass-px-3 glass-py-2",
md: "glass-px-4 glass-py-3",
lg: "glass-px-6 glass-py-4"
};
// Filter and search data
const filteredData = useMemo(() => {
let result = [...(data || [])];
// Apply search
if (searchQuery) {
result = result.filter(row => columns.some(column => {
const value = column.accessorFn ? column.accessorFn(row) : column.accessorKey ? row[column.accessorKey] : "";
return String(value).toLowerCase().includes(searchQuery.toLowerCase());
}));
}
// Apply filters
Object.entries(filterState).forEach(([columnId, filterValue]) => {
if (filterValue) {
const column = columns.find(col => col.id === columnId);
if (column) {
result = result.filter(row => {
const value = column.accessorFn ? column.accessorFn(row) : column.accessorKey ? row[column.accessorKey] : "";
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
});
}
}
});
return result;
}, [data, columns, searchQuery, filterState]);
// Sort data
const sortedData = useMemo(() => {
if (!sortState || !filteredData) return filteredData || [];
const column = columns.find(col => (col.id || `col-${columns.indexOf(col)}`) === sortState.id);
if (!column) return filteredData;
return [...filteredData].sort((a, b) => {
const aValue = column.accessorFn ? column.accessorFn(a) : column.accessorKey ? a[column.accessorKey] : "";
const bValue = column.accessorFn ? column.accessorFn(b) : column.accessorKey ? b[column.accessorKey] : "";
if (aValue < bValue) return sortState.desc ? 1 : -1;
if (aValue > bValue) return sortState.desc ? -1 : 1;
return 0;
});
}, [filteredData, sortState, columns]);
// Paginate data
const paginatedData = useMemo(() => {
if (!pagination || !sortedData) return sortedData || [];
const startIndex = (currentPage - 1) * pageSize;
return sortedData.slice(startIndex, startIndex + pageSize);
}, [sortedData, currentPage, pageSize, pagination]);
const totalPages = Math.ceil(((sortedData || [])?.length || 0) / pageSize);
// Enhanced sorting with consciousness tracking
const handleSort = useCallback(columnId => {
const column = columns.find(col => (col.id || `col-${columns.indexOf(col)}`) === columnId);
if (!column?.sortable && !sortable) return;
// Track column usage for predictive sorting
if (predictive) {
setColumnUsage(prev => ({
...prev,
[columnId]: (prev[columnId] || 0) + 1
}));
}
// Record interaction for learning
if (interactionRecorder) {
interactionRecorder.recordClick({
target: {
id: `column-sort-${columnId}`
},
currentTarget: document.createElement("div"),
clientX: 0,
clientY: 0,
pageX: 0,
pageY: 0,
screenX: 0,
screenY: 0,
movementX: 0,
movementY: 0,
button: 0,
buttons: 0,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
getModifierState: () => false,
preventDefault: () => {},
stopPropagation: () => {},
isDefaultPrevented: () => false,
isPropagationStopped: () => false,
nativeEvent: new MouseEvent("click"),
persist: () => {}
});
}
// Track achievements
if (achievementTracker && trackAchievements) {
achievementTracker.recordAction("table_sort", {
columnId,
usage: (columnUsage[columnId] || 0) + 1,
context: usageContext
});
}
// Play spatial audio feedback
if (spatialAudioEngine && audioFeedback) {
spatialAudioEngine.playGlassSound("sort_column");
}
setSortState(prev => {
if (prev?.id === columnId) {
return prev.desc ? null : {
id: columnId,
desc: true
};
}
return {
id: columnId,
desc: false
};
});
}, [columns, sortable, predictive, interactionRecorder, achievementTracker, trackAchievements, spatialAudioEngine, audioFeedback, columnUsage, usageContext]);
// Handle selection
const handleRowSelection = (rowId, selected) => {
if (!onSelectionChange) return;
if (selectionMode === "single") {
onSelectionChange(selected ? [rowId] : []);
} else {
const newSelection = selected ? [...selectedRows, rowId] : selectedRows.filter(id => id !== rowId);
onSelectionChange(newSelection);
}
};
const handleSelectAll = selected => {
if (!onSelectionChange) return;
if (selected) {
const allIds = (paginatedData || []).map((row, index) => getRowId(row, index));
onSelectionChange(allIds);
} else {
onSelectionChange([]);
}
};
const isAllSelected = (paginatedData || []).length > 0 && (paginatedData || []).every((row, index) => selectedRows.includes(getRowId(row, index)));
return jsxs("div", {
"data-glass-component": true,
ref: ref,
className: cn("w-full", className),
children: [(searchable || actions || filterable) && jsxs("div", {
className: 'glass-flex glass-items-center glass-justify-between glass-gap-4 mb-4',
children: [jsx("div", {
className: "glass-flex glass-items-center glass-gap-4",
children: searchable && jsx(GlassInput, {
placeholder: searchPlaceholder,
value: searchQuery,
onChange: e => setSearchQuery(e.target.value),
leftIcon: jsx("svg", {
className: 'w-4 h-4',
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
children: jsx("path", {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
})
}),
clearable: true,
className: 'w-64'
})
}), actions && jsx("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: actions
})]
}), jsxs(OptimizedGlassCore, {
elevation: "level2",
intensity: "medium",
depth: 2,
tint: "neutral",
border: "subtle",
animation: "none",
performanceMode: "medium",
className: cn("overflow-hidden transition-all duration-500", variant === "bordered" && "border border-border/20"),
children: [jsx("div", {
className: 'overflow-x-auto',
children: jsxs("table", {
className: "glass-w-full",
children: [jsx("thead", {
className: cn("relative bg-muted/20 border-b border-border/20", stickyHeader && "sticky top-0 z-10"),
children: jsxs("tr", {
children: [selectable && jsx("th", {
className: cn("w-12", cellPaddingClasses[validSize]),
children: jsx(GlassInput, {
type: "checkbox",
checked: isAllSelected,
onChange: e => handleSelectAll(e.target.checked),
className: 'glass-radius-md glass-border-glass-border focus:ring-primary'
})
}), (columns || []).map((column, index) => {
const columnId = column.id || `col-${index}`;
const headerContent = typeof column.header === "function" ? column.header({
column
}) : column.header;
return jsx("th", {
className: cn("font-semibold text-foreground border-b border-border/10 transition-all duration-200", cellPaddingClasses[validSize], sizeClasses[validSize], {
"text-center": column.align === "center",
"text-right": column.align === "right",
"cursor-pointer hover:bg-muted/10 hover:text-foreground": column.sortable || sortable
}),
style: {
width: column.width
},
onClick: e => handleSort(columnId),
children: jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx("span", {
children: headerContent
}), (column.sortable || sortable) && jsxs("div", {
className: "glass-flex glass-flex-col",
children: [jsx("svg", {
className: cn("w-3 h-3 -glass-mb-1 transition-colors", sortState?.id === columnId && !sortState.desc ? "text-primary" : "glass-text-secondary hover:text-foreground"),
fill: "currentColor",
viewBox: "0 0 20 20",
children: jsx("path", {
d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
})
}), jsx("svg", {
className: cn("w-3 h-3 rotate-180 transition-colors", sortState?.id === columnId && sortState.desc ? "text-primary" : "glass-text-secondary hover:text-foreground"),
fill: "currentColor",
viewBox: "0 0 20 20",
children: jsx("path", {
d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
})
})]
})]
})
}, columnId);
})]
})
}), jsx("tbody", {
children: loading ? jsx("tr", {
children: jsx("td", {
colSpan: columns.length + (selectable ? 1 : 0),
className: cn("text-center", cellPaddingClasses[validSize]),
children: jsxs("div", {
className: "glass-flex glass-items-center glass-justify-center glass-gap-2 glass-py-8",
children: [jsx("div", {
className: 'w-4 h-4 glass-border-2 glass-border-primary glass-border-t-transparent glass-radius-full animate-spin'
}), jsx("span", {
className: "glass-text-secondary",
children: "Loading..."
})]
})
})
}) : paginatedData.length === 0 ? jsx("tr", {
children: jsx("td", {
colSpan: columns.length + (selectable ? 1 : 0),
className: cn("text-center glass-text-secondary py-8", cellPaddingClasses[validSize]),
children: emptyState ? jsxs("div", {
className: "glass-flex glass-flex-col glass-items-center glass-gap-2",
children: [emptyState.icon, jsx("div", {
className: 'font-medium',
children: emptyState.message || emptyMessage
}), emptyState.description && jsx("div", {
className: "glass-text-sm glass-text-secondary",
children: emptyState.description
})]
}) : emptyMessage
})
}) : (paginatedData || []).map((row, index) => {
const rowId = getRowId(row, index);
const isSelected = selectedRows.includes(rowId);
const rowProps = getRowProps?.(row) || {};
return jsxs("tr", {
className: cn("group transition-all duration-200 glass-radius-md", {
"bg-muted/5": variant === "striped" && index % 2 === 1,
"bg-primary/10 shadow-md shadow-primary/20 ring-1 ring-primary/20": isSelected,
"hover:bg-muted/10 cursor-pointer hover:shadow-lg hover:shadow-primary/10 hover:-translate-y-0.5 hover:ring-1 hover:ring-white/10": onRowClick,
// Consciousness feature styles
"ring-1 ring-blue-400/20 bg-blue-400/5": gazeResponsive && interactionHeatmap[`${index}-0`] > 5,
"animate-pulse": predictedSortColumn && columns.some(col => (col.id || `col-${columns.indexOf(col)}`) === predictedSortColumn),
"transform-gpu": gazeResponsive || biometricResponsive
}),
onClick: e => onRowClick?.(row),
...rowProps,
children: [selectable && jsx("td", {
className: cellPaddingClasses[validSize],
children: jsx(GlassInput, {
type: "checkbox",
checked: isSelected,
onChange: e => handleRowSelection(rowId, e.target.checked),
className: 'glass-radius-md glass-border-glass-border focus:ring-primary',
onClick: e => e.stopPropagation()
})
}), (columns || []).map((column, colIndex) => {
const columnId = column.id || `col-${colIndex}`;
const value = column.accessorFn ? column.accessorFn(row) : column.accessorKey ? row[column.accessorKey] : "";
return jsx("td", {
className: cn("border-b border-border/5 text-foreground/80 transition-colors group-hover:text-foreground", cellPaddingClasses[validSize], sizeClasses[validSize], {
"text-center": column.align === "center",
"text-right": column.align === "right"
}),
children: column.cell ? column.cell({
row,
value
}) : cellRenderers && (cellRenderers[column.id || column.accessorKey || ""] ? cellRenderers[column.id || column.accessorKey || ""](value, row) : String(value))
}, columnId);
})]
}, rowId);
})
})]
})
}), pagination && !loading && (paginatedData || []).length > 0 && jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between glass-px-4 glass-py-3 glass-border-t glass-border-glass-border/10 glass-surface-subtle",
children: [jsx("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: jsxs("span", {
className: "glass-text-sm glass-text-secondary",
children: ["Showing ", (currentPage - 1) * pageSize + 1, " to", " ", Math.min(currentPage * pageSize, (sortedData || []).length), " ", "of ", (sortedData || []).length, " results"]
})
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-4",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx("span", {
className: "glass-text-sm glass-text-secondary",
children: "Rows per page:"
}), jsx(GlassSelect, {
value: pageSize,
onChange: e => {
setPageSize(Number(e.target.value));
setCurrentPage(1);
},
options: (pageSizeOptions || []).map(size => ({
value: size,
label: size.toString()
})),
size: "sm"
})]
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(IconButton, {
icon: "\u2190",
variant: "ghost",
size: "sm",
disabled: currentPage === 1,
onClick: e => setCurrentPage(1),
"aria-label": "First page"
}), jsx(IconButton, {
icon: "\u2039",
variant: "ghost",
size: "sm",
disabled: currentPage === 1,
onClick: e => setCurrentPage(p => p - 1),
"aria-label": "Previous page"
}), jsxs("span", {
className: "glass-px-3 glass-py-1 glass-text-sm",
children: [currentPage, " of ", totalPages]
}), jsx(IconButton, {
icon: "\u203A",
variant: "ghost",
size: "sm",
disabled: currentPage === totalPages,
onClick: e => setCurrentPage(p => p + 1),
"aria-label": "Next page"
}), jsx(IconButton, {
icon: "\u2192",
variant: "ghost",
size: "sm",
disabled: currentPage === totalPages,
onClick: e => setCurrentPage(totalPages),
"aria-label": "Last page"
})]
})]
})]
})]
})]
});
});
GlassDataTableInner.displayName = "GlassDataTable";
export { GlassDataTable };
//# sourceMappingURL=GlassDataTable.js.map