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