UNPKG

aura-glass

Version:

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

519 lines (516 loc) 23 kB
'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