UNPKG

aura-glass

Version:

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

232 lines (229 loc) 9.46 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { forwardRef, useState, 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'; const GlassTransferList = /*#__PURE__*/forwardRef(({ items, value = [], onChange, leftTitle = "Available", rightTitle = "Selected", searchable = true, leftSearchPlaceholder = "Search available...", rightSearchPlaceholder = "Search selected...", showCount = true, minHeight = "300px", maxHeight = "400px", elevation = "level2", disabled = false, className, ...props }, ref) => { const [leftSearch, setLeftSearch] = useState(""); const [rightSearch, setRightSearch] = useState(""); const [leftChecked, setLeftChecked] = useState(new Set()); const [rightChecked, setRightChecked] = useState(new Set()); const selectedItems = useMemo(() => items.filter(item => value.includes(item.id)), [items, value]); const availableItems = useMemo(() => items.filter(item => !value.includes(item.id)), [items, value]); const filteredAvailable = useMemo(() => { if (!leftSearch) return availableItems; return availableItems.filter(item => item.label.toLowerCase().includes(leftSearch.toLowerCase()) || item.description?.toLowerCase().includes(leftSearch.toLowerCase())); }, [availableItems, leftSearch]); const filteredSelected = useMemo(() => { if (!rightSearch) return selectedItems; return selectedItems.filter(item => item.label.toLowerCase().includes(rightSearch.toLowerCase()) || item.description?.toLowerCase().includes(rightSearch.toLowerCase())); }, [selectedItems, rightSearch]); const handleToggle = useCallback((id, isLeft) => { if (disabled) return; const checked = isLeft ? leftChecked : rightChecked; const setChecked = isLeft ? setLeftChecked : setRightChecked; const newChecked = new Set(checked); if (newChecked.has(id)) { newChecked.delete(id); } else { newChecked.add(id); } setChecked(newChecked); }, [disabled, leftChecked, rightChecked]); const handleMoveRight = useCallback(() => { if (disabled || leftChecked.size === 0) return; const newValue = [...value, ...Array.from(leftChecked)]; onChange?.(newValue); setLeftChecked(new Set()); }, [disabled, leftChecked, value, onChange]); const handleMoveLeft = useCallback(() => { if (disabled || rightChecked.size === 0) return; const newValue = value.filter(id => !rightChecked.has(id)); onChange?.(newValue); setRightChecked(new Set()); }, [disabled, rightChecked, value, onChange]); const handleMoveAllRight = useCallback(() => { if (disabled) return; const allIds = availableItems.filter(item => !item.disabled).map(item => item.id); onChange?.([...value, ...allIds]); setLeftChecked(new Set()); }, [disabled, availableItems, value, onChange]); const handleMoveAllLeft = useCallback(() => { if (disabled) return; onChange?.([]); setRightChecked(new Set()); }, [disabled, onChange]); const renderList = (listItems, checked, onToggle, search, setSearch, searchPlaceholder, title, isLeft) => jsxs("div", { className: "glass-flex-1 glass-flex glass-flex-col", children: [jsxs("div", { className: "glass-p-3 glass-border-b glass-border-subtle", children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-2', children: [jsx("h3", { className: 'glass-text-base font-semibold glass-text-primary', children: title }), showCount && jsxs("span", { className: "glass-text-sm glass-text-secondary", children: [listItems.length, " item", listItems.length !== 1 ? "s" : ""] })] }), searchable && jsx("input", { type: "text", value: search, onChange: e => setSearch(e.target.value), placeholder: searchPlaceholder, disabled: disabled, className: cn("w-full glass-p-2 glass-radius-md", "glass-text-sm glass-text-primary", "bg-white/5 border glass-border-subtle", "focus:outline-none glass-focus", "disabled:opacity-50 disabled:cursor-not-allowed") })] }), jsx("div", { className: 'glass-flex-1 overflow-y-auto glass-p-2', style: { minHeight, maxHeight }, children: listItems.length === 0 ? jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-h-full glass-text-secondary glass-text-sm", children: "No items" }) : jsx("div", { className: 'space-y-1', children: listItems.map(item => jsxs("label", { className: cn("flex items-start gap-3 glass-p-2 glass-radius-md", "cursor-pointer transition-all duration-200", "hover:bg-white/5", checked.has(item.id) && "bg-white/10", item.disabled && "opacity-50 cursor-not-allowed"), children: [jsx("input", { type: "checkbox", checked: checked.has(item.id), onChange: () => onToggle(item.id), disabled: disabled || item.disabled, className: 'mt-0.5 glass-focus' }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [item.icon && jsx("span", { className: "glass-flex-shrink-0", children: item.icon }), jsx("span", { className: 'glass-text-sm glass-text-primary font-medium truncate', children: item.label })] }), item.description && jsx("p", { className: 'glass-text-xs glass-text-secondary mt-1 truncate', children: item.description })] })] }, item.id)) }) })] }); const TransferButton = ({ onClick, icon, disabled: btnDisabled, label }) => jsx("button", { type: "button", onClick: onClick, disabled: disabled || btnDisabled, className: cn("glass-p-2 glass-radius-md", "transition-all duration-200", "hover:bg-white/10 active:bg-white/20", "disabled:opacity-50 disabled:cursor-not-allowed", "glass-text-primary", "glass-focus glass-touch-target glass-contrast-guard"), "aria-label": label, title: label, children: icon }); return jsxs(OptimizedGlassCore, { "data-glass-component": true, ref: ref, elevation: elevation, className: cn("flex gap-4 glass-p-4 glass-radius-lg", className), ...props, children: [renderList(filteredAvailable, leftChecked, id => handleToggle(id, true), leftSearch, setLeftSearch, leftSearchPlaceholder, leftTitle), jsxs("div", { className: "glass-flex glass-flex-col glass-justify-center glass-gap-2 glass-p-2", children: [jsx(TransferButton, { onClick: handleMoveAllRight, disabled: availableItems.length === 0, label: "Move all to selected", icon: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: 'w-4 h-4', children: jsx("path", { d: "M13 5l7 7-7 7M5 5l7 7-7 7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), jsx(TransferButton, { onClick: handleMoveRight, disabled: leftChecked.size === 0, label: "Move selected to right", icon: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: 'w-4 h-4', children: jsx("path", { d: "M5 12h14m-7-7l7 7-7 7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), jsx(TransferButton, { onClick: handleMoveLeft, disabled: rightChecked.size === 0, label: "Move selected to left", icon: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: 'w-4 h-4', children: jsx("path", { d: "M19 12H5m7 7l-7-7 7-7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), jsx(TransferButton, { onClick: handleMoveAllLeft, disabled: selectedItems.length === 0, label: "Move all to available", icon: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: 'w-4 h-4', children: jsx("path", { d: "M11 5l-7 7 7 7M19 5l-7 7 7 7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), renderList(filteredSelected, rightChecked, id => handleToggle(id, false), rightSearch, setRightSearch, rightSearchPlaceholder, rightTitle)] }); }); GlassTransferList.displayName = "GlassTransferList"; export { GlassTransferList, GlassTransferList as default }; //# sourceMappingURL=GlassTransferList.js.map