UNPKG

aura-glass

Version:

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

344 lines (341 loc) 13.1 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { AnimatePresence, motion } from 'framer-motion'; import { Filter, RotateCcw, Search, Save, ChevronDown, ChevronRight } from 'lucide-react'; import React, { useState, useCallback, useMemo } from 'react'; import { GlassButton } from '../button/GlassButton.js'; import { GlassCheckbox } from '../input/GlassCheckbox.js'; import { GlassDateRangePicker } from '../input/GlassDateRangePicker.js'; import { GlassInput } from '../input/GlassInput.js'; import { GlassSelect, GlassSelectTrigger, GlassSelectValue, GlassSelectContent, GlassSelectItem } from '../input/GlassSelectCompound.js'; import { GlassSlider } from '../input/GlassSlider.js'; const GlassFilterPanel = /*#__PURE__*/React.forwardRef(({ filters, values, onChange, onApply, onClear, presets = [], onSavePreset, className, title = "Filters", collapsible = true, showSearch = true, showPresets = true, showApplyButton = true, showClearButton = true, variant = "default", size = "md", elevation = "medium", ...props }, ref) => { const prefersReducedMotion = useReducedMotion(); const [expandedGroups, setExpandedGroups] = useState(Object.fromEntries(filters.map(f => [f.id, !f.collapsed]))); const [searchQuery, setSearchQuery] = useState(""); const [showPresetDialog, setShowPresetDialog] = useState(false); const [newPresetName, setNewPresetName] = useState(""); const toggleGroup = useCallback(groupId => { setExpandedGroups(prev => ({ ...prev, [groupId]: !prev[groupId] })); }, []); const handleFilterChange = useCallback((groupId, value) => { onChange({ ...values, [groupId]: value }); }, [values, onChange]); const handleClearAll = useCallback(() => { const clearedValues = Object.fromEntries(filters.map(f => [f.id, f.type === "checkbox" ? [] : null])); onChange(clearedValues); onClear?.(); }, [filters, onChange, onClear]); const handleApplyFilters = useCallback(() => { onApply?.(); }, [onApply]); const handlePresetSelect = useCallback(preset => { onChange(preset.filters); }, [onChange]); const handleSavePreset = useCallback(() => { if (newPresetName.trim()) { onSavePreset?.(newPresetName.trim()); setNewPresetName(""); setShowPresetDialog(false); } }, [newPresetName, onSavePreset]); const filteredGroups = useMemo(() => { if (!searchQuery) return filters; return filters.filter(group => group.label.toLowerCase().includes(searchQuery.toLowerCase()) || group.options?.some(option => option.label.toLowerCase().includes(searchQuery.toLowerCase()))); }, [filters, searchQuery]); const hasActiveFilters = useMemo(() => { return Object.values(values).some(value => Array.isArray(value) ? (value?.length || 0) > 0 : value != null); }, [values]); const sizeClasses = { sm: "glass-text-sm", md: "glass-text-base", lg: "glass-text-lg" }; const variantClasses = { default: "glass-p-6", compact: "glass-p-4", minimal: "glass-p-2" }; const elevationClasses = { low: "glass-glass-backdrop-blur-md bg-white/10 border border-white/20 glass-contrast-guard", medium: "glass-glass-backdrop-blur-md bg-white/20 border border-white/30 shadow-lg glass-contrast-guard", high: "glass-glass-backdrop-blur-md bg-white/30 border border-white/40 shadow-2xl glass-contrast-guard" }; return jsxs("div", { ref: ref, className: cn("glass-radius-xl", elevationClasses[elevation], variantClasses[variant], sizeClasses[size], className), ...props, children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-6', children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx(Filter, { className: 'w-5 h-5 text-primary/70' }), jsx("h3", { className: 'font-semibold text-primary', children: title }), hasActiveFilters && jsx("span", { className: "glass-px-2 glass-py-1 glass-text-xs glass-surface-blue/20 glass-text-secondary glass-radius-full", children: "Active" })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [showClearButton && hasActiveFilters && jsxs(GlassButton, { variant: "ghost", size: "sm", onClick: handleClearAll, className: 'text-primary/70 hover:text-primary glass-focus glass-touch-target', children: [jsx(RotateCcw, { className: 'w-4 h-4 glass-mr-1' }), "Clear"] }), showApplyButton && jsx(GlassButton, { variant: "primary", size: "sm", onClick: handleApplyFilters, disabled: !hasActiveFilters, className: "glass-focus glass-touch-target", children: "Apply Filters" })] })] }), showSearch && jsx("div", { className: 'mb-4', children: jsx(GlassInput, { placeholder: "Search filters...", value: searchQuery, onChange: e => setSearchQuery(e.target.value), className: "glass-w-full", leftIcon: jsx(Search, { className: 'w-4 h-4' }) }) }), showPresets && presets.length > 0 && jsxs("div", { className: 'mb-6', children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-2', children: [jsx("span", { className: 'glass-text-sm font-medium text-primary/70', children: "Presets" }), onSavePreset && jsxs(GlassButton, { variant: "ghost", size: "sm", onClick: e => setShowPresetDialog(true), className: 'text-primary/70 hover:text-primary glass-focus glass-touch-target', children: [jsx(Save, { className: 'w-4 h-4 glass-mr-1' }), "Save"] })] }), jsx("div", { className: "glass-flex glass-flex-wrap glass-gap-2", children: presets.map(preset => jsx(GlassButton, { variant: "outline", size: "sm", onClick: e => handlePresetSelect(preset), className: "glass-text-xs glass-focus glass-touch-target", children: preset.name }, preset.id)) })] }), jsx("div", { className: "glass-gap-4", children: jsx(AnimatePresence, { children: filteredGroups.map(group => jsxs(motion.div, { initial: { opacity: 0, height: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, className: 'glass-radius-lg overflow-hidden ring-1 ring-white/10', children: [collapsible && jsxs("button", { onClick: e => toggleGroup(group.id), className: 'glass-w-full glass-flex glass-items-center glass-justify-between glass-p-3 text-left hover:glass-surface-subtle/5 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("span", { className: 'font-medium text-primary', children: group.label }), group.required && jsx("span", { className: 'glass-text-xs text-primary', children: "*" })] }), expandedGroups[group.id] ? jsx(ChevronDown, { className: 'w-4 h-4 text-primary/70' }) : jsx(ChevronRight, { className: 'w-4 h-4 text-primary/70' })] }), jsx(AnimatePresence, { children: (!collapsible || expandedGroups[group.id]) && jsx(motion.div, { initial: { opacity: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1 }, exit: { opacity: 0 }, className: "glass-p-3 glass-border-t glass-border-white/10", children: jsx(FilterGroupContent, { group: group, value: values[group.id], onChange: value => handleFilterChange(group.id, value) }) }) })] }, group.id)) }) }), showPresetDialog && jsx("div", { className: 'fixed inset-0 glass-surface-dark/50 glass-glass-glass-backdrop-blur-md z-50 glass-flex glass-items-center glass-justify-center glass-contrast-guard', children: jsxs("div", { className: 'glass-radius-lg glass-p-6 max-w-md glass-w-full glass-mx-4 glass-surface-subtle/5 ring-1 ring-white/10 glass-contrast-guard', children: [jsx("h3", { className: 'glass-text-lg font-semibold text-primary mb-4', children: "Save Filter Preset" }), jsx(GlassInput, { placeholder: "Preset name...", value: newPresetName, onChange: e => setNewPresetName(e.target.value), className: 'mb-4' }), jsxs("div", { className: "glass-flex glass-gap-2", children: [jsx(GlassButton, { variant: "ghost", onClick: e => setShowPresetDialog(false), className: "glass-flex-1 glass-focus glass-touch-target", children: "Cancel" }), jsx(GlassButton, { variant: "primary", onClick: handleSavePreset, disabled: !newPresetName.trim(), className: "glass-flex-1 glass-focus glass-touch-target", children: "Save" })] })] }) })] }); }); GlassFilterPanel.displayName = "GlassFilterPanel"; const FilterGroupContent = ({ group, value, onChange }) => { switch (group.type) { case "checkbox": return jsx("div", { className: "glass-gap-2", children: group.options?.map(option => jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsx(GlassCheckbox, { id: option.id, checked: Array.isArray(value) && value.includes(option.value), onCheckedChange: checked => { const currentValues = Array.isArray(value) ? value : []; const newValues = checked ? [...currentValues, option.value] : currentValues.filter(v => v !== option.value); onChange(newValues); }, disabled: option.disabled, label: option.label }), option.count != null && jsxs("span", { className: 'glass-text-xs text-primary/50', children: ["(", option.count, ")"] })] }, option.id)) }); case "select": return jsxs(GlassSelect, { value: value || "", onValueChange: onChange, children: [jsx(GlassSelectTrigger, { children: jsx(GlassSelectValue, { placeholder: group.placeholder || "Select..." }) }), jsx(GlassSelectContent, { children: group.options?.map(option => jsx(GlassSelectItem, { value: option.value, disabled: option.disabled, children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between glass-w-full", children: [jsx("span", { children: option.label }), option.count != null && jsxs("span", { className: 'glass-text-xs text-primary/50 glass-ml-2', children: ["(", option.count, ")"] })] }) }, option.id)) })] }); case "slider": return jsxs("div", { className: "glass-gap-3", children: [jsx(GlassSlider, { min: group.min || 0, max: group.max || 100, step: group.step || 1, value: Array.isArray(value) ? value : [group.min || 0], onValueChange: newValue => onChange(newValue), className: "glass-w-full" }), jsxs("div", { className: 'glass-flex glass-justify-between glass-text-sm text-primary/70', children: [jsx("span", { children: group.min || 0 }), jsx("span", { children: group.max || 100 })] })] }); case "daterange": return jsx(GlassDateRangePicker, { value: value, onChange: onChange, placeholder: group.placeholder }); case "search": return jsx(GlassInput, { placeholder: group.placeholder || "Search...", value: value || "", onChange: e => onChange(e.target.value), leftIcon: jsx(Search, { className: 'w-4 h-4' }) }); default: return null; } }; export { GlassFilterPanel }; //# sourceMappingURL=GlassFilterPanel.js.map