UNPKG

aura-glass

Version:

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

395 lines (392 loc) 14.8 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { forwardRef, useState, useMemo } 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 { GlassSearchInterface } from '../../interactive/GlassSearchInterface.js'; import { GlassDataTable } from '../../data-display/GlassDataTable.js'; import { GlassButton, IconButton } from '../../button/GlassButton.js'; import { GlassBadge } from '../../data-display/GlassBadge.js'; import { PageHeader } from '../../layout/GlassAppShell.js'; import { VStack, HStack } from '../../layout/GlassStack.js'; import { GlassGrid, GlassGridItem } from '../../layout/GlassGrid.js'; import { GlassCard } from '../../card/GlassCard.js'; import { GlassInput } from '../../input/GlassInput.js'; import { cn } from '../../../lib/utilsComprehensive.js'; /** * GlassListView component * Comprehensive list view with filtering, search, and multiple view modes */ const GlassListView = /*#__PURE__*/forwardRef(({ title, description, data = [], columns = [], filters = [], activeFilters = {}, onFiltersChange, searchQuery = "", onSearchChange, sort, onSortChange, currentPage = 1, pageSize = 20, totalItems, onPaginationChange, viewMode = "table", onViewModeChange, actions = [], primaryAction, selectedItems = [], onSelectionChange, onRowClick, loading = false, emptyState, renderCard, selectable = true, className, ...props }, ref) => { const [showFilters, setShowFilters] = useState(false); // Filter and search data locally if no external handlers const filteredData = useMemo(() => { let result = [...data]; // Apply search if (searchQuery && !onSearchChange) { result = result.filter(item => columns.some(column => { const value = column.accessorFn ? column.accessorFn(item) : column.accessorKey ? item?.[column.accessorKey] : ""; return String(value).toLowerCase().includes(searchQuery.toLowerCase()); })); } // Apply filters if (Object.keys(activeFilters).length > 0 && !onFiltersChange) { result = result.filter(item => { return Object.entries(activeFilters).every(([filterId, filterValue]) => { const filter = filters.find(f => f.id === filterId); if (!filter || !filterValue) return true; const itemValue = item?.[filterId]; switch (filter.type) { case "select": return itemValue === filterValue; case "multiselect": return Array.isArray(filterValue) ? filterValue.includes(itemValue) : true; case "text": return String(itemValue).toLowerCase().includes(String(filterValue).toLowerCase()); case "range": if (Array.isArray(filterValue) && filterValue.length === 2) { return itemValue >= filterValue?.[0] && itemValue <= filterValue?.[1]; } return true; default: return true; } }); }); } return result; }, [data, searchQuery, activeFilters, filters, columns, onSearchChange, onFiltersChange]); // Calculate pagination const totalFilteredItems = totalItems || filteredData?.length || 0; const totalPages = Math.ceil(totalFilteredItems / pageSize); const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; const currentPageData = onPaginationChange ? filteredData : filteredData.slice(startIndex, endIndex); // Handle selection const handleSelectionChange = newSelection => { onSelectionChange?.(newSelection); }; // Handle bulk actions const handleBulkAction = action => { const selectedData = data?.filter(item => selectedItems.includes(item?.id)); action.onClick(selectedData); }; // Render view mode toggle const renderViewModeToggle = () => { if (!onViewModeChange) return null; const modes = [{ key: "table", icon: "📊", label: "Table" }, { key: "grid", icon: "▦", label: "Grid" }, { key: "list", icon: "☰", label: "List" }]; return jsx(HStack, { "data-glass-component": true, space: "none", children: modes.map(mode => jsx(IconButton, { icon: mode.icon, variant: viewMode === mode.key ? "primary" : "ghost", size: "sm", onClick: e => onViewModeChange(mode.key), "aria-label": mode.label }, mode.key)) }); }; // Render filters const renderFilters = () => { if (!showFilters || (filters?.length || 0) === 0) return null; return jsx(MotionFramer, { preset: "slideDown", children: jsx(GlassCore, { className: "glass-p-4 glass-radius-lg", children: jsxs(VStack, { space: "md", children: [jsxs(HStack, { space: "sm", align: "center", justify: "between", children: [jsx("h3", { className: 'glass-text-sm font-medium text-primary', children: "Filters" }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => onFiltersChange?.({}), disabled: Object.keys(activeFilters).length === 0, children: "Clear All" })] }), jsx("div", { className: 'glass-grid glass-grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 glass-gap-4', children: filters.map(filter => jsxs("div", { children: [jsx("label", { className: 'block glass-text-sm font-medium text-primary mb-2', children: filter.label }), filter.type === "select" && jsxs("select", { value: activeFilters?.[filter.id] || "", onChange: e => onFiltersChange?.({ ...activeFilters, [filter.id]: e.target.value }), className: 'glass-w-full glass-px-3 glass-py-2 bg-background glass-border glass-border-glass-border glass-radius-md focus:outline-none focus:ring-2 focus:ring-primary', children: [jsx("option", { value: "", children: filter.placeholder || "Select..." }), filter.options?.map(option => jsxs("option", { value: option.value, children: [option.label, " ", option.count && `(${option.count})`] }, option.value))] }), filter.type === "text" && jsx(GlassInput, { type: "text", value: activeFilters?.[filter.id] || "", onChange: e => onFiltersChange?.({ ...activeFilters, [filter.id]: e.target.value }), placeholder: filter.placeholder, className: 'glass-w-full glass-px-3 glass-py-2 bg-background glass-border glass-border-glass-border glass-radius-md focus:outline-none focus:ring-2 focus:ring-primary' })] }, filter.id)) })] }) }) }); }; // Render actions bar const renderActionsBar = () => { const hasSelectedItems = (selectedItems?.length || 0) > 0; const availableActions = actions.filter(action => !action.requiresSelection || hasSelectedItems); if ((availableActions?.length || 0) === 0 && !primaryAction) return null; return jsxs(HStack, { space: "sm", align: "center", justify: "between", children: [jsxs(HStack, { space: "sm", align: "center", children: [hasSelectedItems && jsxs("span", { className: "glass-text-sm glass-text-secondary", children: [selectedItems?.length || 0, " item", (selectedItems?.length || 0) !== 1 ? "s" : "", " selected"] }), availableActions.map(action => jsx(GlassButton, { variant: action.variant || "outline", size: "sm", leftIcon: action.icon, onClick: e => handleBulkAction(action), disabled: action.requiresSelection && (selectedItems?.length || 0) === 0, children: action.label }, action.id))] }), jsxs(HStack, { space: "sm", align: "center", children: [renderViewModeToggle(), (filters?.length || 0) > 0 && jsx(IconButton, { icon: "\uD83D\uDD0D", variant: showFilters ? "primary" : "ghost", size: "sm", onClick: e => setShowFilters(!showFilters), "aria-label": "Toggle filters" }), primaryAction && jsx(GlassButton, { variant: "default", size: "sm", leftIcon: primaryAction.icon, onClick: primaryAction.onClick, children: primaryAction.label })] })] }); }; // Render grid view const renderGridView = () => jsx(GlassGrid, { cols: 12, gap: "md", children: currentPageData.map((item, index) => jsx(GlassGridItem, { colSpan: 4, children: renderCard ? renderCard(item, index) : jsx(GlassCard, { variant: "default", className: 'glass-h-full cursor-pointer hover:shadow-lg transition-shadow', onClick: e => onRowClick?.(item), children: jsxs(VStack, { space: "sm", children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: item?.name || item?.title || item?.id }), jsx("div", { className: "glass-text-xs glass-text-secondary", children: item?.description || "No description" })] }) }) }, item?.id)) }); // Render list view const renderListView = () => jsx(VStack, { space: "sm", children: currentPageData.map((item, index) => jsx("div", { className: 'animate-fade-in-up', style: { animationDelay: `${Math.min(index, 10) * 50}ms`, animationFillMode: "both" }, children: renderCard ? renderCard(item, index) : jsx(GlassCard, { variant: "outlined", className: 'glass-foundation-complete glass-glass-glass-backdrop-blur-md bg-transparent glass-border-white/40 glass-shadow-2xl cursor-pointer hover:shadow-2xl hover:scale-[1.01] transition-all glass-focus glass-touch-target glass-contrast-guard', onClick: e => onRowClick?.(item), children: jsxs(HStack, { space: "md", align: "center", children: [selectable && jsx(GlassInput, { type: "checkbox", checked: selectedItems.includes(item?.id), onChange: e => { const newSelection = e.target.checked ? [...selectedItems, item?.id] : selectedItems.filter(id => id !== item?.id); handleSelectionChange(newSelection); }, onClick: e => e.stopPropagation(), className: 'glass-radius-md glass-border-glass-border focus:ring-primary' }), jsxs(VStack, { space: "xs", className: "glass-flex-1", children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: item?.name || item?.title || item?.id }), jsx("div", { className: 'glass-text-xs text-primary/70', children: item?.description || "No description" })] }), item?.status && jsx(GlassBadge, { variant: "outline", size: "xs", children: item?.status })] }) }) }, item?.id)) }); // Render main content const renderContent = () => { 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 ((currentPageData?.length || 0) === 0) { return emptyState || jsxs("div", { className: 'glass-flex glass-flex-col glass-items-center glass-justify-center h-64 text-center', children: [jsx("div", { className: 'glass-text-lg font-medium glass-text-secondary mb-2', children: "No items found" }), jsx("div", { className: "glass-text-sm glass-text-secondary", children: "Try adjusting your search or filters" })] }); } switch (viewMode) { case "grid": return renderGridView(); case "list": return renderListView(); default: return jsx(GlassDataTable, { data: currentPageData, columns: columns, selectable: selectable, selectedRows: selectedItems, onSelectionChange: handleSelectionChange, onRowClick: onRowClick, getRowId: row => row.id, loading: loading, pagination: !!onPaginationChange, pageSizeOptions: [10, 20, 50, 100], initialPageSize: pageSize }); } }; return jsxs("div", { ref: ref, className: cn("w-full space-y-6", className), ...props, children: [jsx(PageHeader, { title: title, description: description, actions: jsx(GlassSearchInterface, { variant: "compact", placeholder: "Search...", value: searchQuery, onChange: onSearchChange, showFilters: false, showCategories: false, className: 'w-64' }) }), jsxs(VStack, { space: "md", children: [renderActionsBar(), renderFilters()] }), jsx("div", { children: renderContent() }), onPaginationChange && totalPages > 1 && jsxs(HStack, { space: "sm", align: "center", justify: "center", children: [jsx(GlassButton, { variant: "ghost", size: "sm", disabled: currentPage === 1, onClick: e => onPaginationChange(currentPage - 1, pageSize), children: "Previous" }), jsxs("span", { className: "glass-text-sm glass-text-secondary", children: ["Page ", currentPage, " of ", totalPages] }), jsx(GlassButton, { variant: "ghost", size: "sm", disabled: currentPage === totalPages, onClick: e => onPaginationChange(currentPage + 1, pageSize), children: "Next" })] })] }); }); GlassListView.displayName = "GlassListView"; export { GlassListView }; //# sourceMappingURL=GlassListView.js.map