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