UNPKG

@baseplate-dev/ui-components

Version:

Shared UI component library

114 lines 6.62 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import React, { useId, useMemo, useState } from 'react'; import { MdCheck, MdClose, MdUnfoldMore } from 'react-icons/md'; import { useControlledState } from '#src/hooks/index.js'; import { inputVariants, selectContentVariants, selectItemVariants, } from '#src/styles/index.js'; import { cn } from '#src/utils/index.js'; import { Badge } from '../badge/badge.js'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '../command/command.js'; import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, } from '../popover/popover.js'; const MultiComboboxContext = React.createContext(null); function MultiCombobox({ children, value, onChange, disabled, }) { const [selectedValues, setSelectedValues] = useControlledState(value, onChange, []); const [searchQuery, setSearchQuery] = useState(''); const [isOpen, setIsOpen] = useState(false); const inputId = useId(); const filterId = useId(); const contextValue = useMemo(() => ({ selectedValues, onSelect: (value, label, selected) => { setSearchQuery(''); if (selected) { setSelectedValues([...selectedValues, { label, value }]); } else { setSelectedValues(selectedValues.filter((v) => v.value !== value)); } }, disabled, isOpen, setIsOpen: (open) => { setSearchQuery(''); setIsOpen(open); }, inputId, filterId, searchQuery, setSearchQuery, }), [ selectedValues, setSelectedValues, disabled, isOpen, inputId, filterId, searchQuery, setSearchQuery, ]); return (_jsx(MultiComboboxContext.Provider, { value: contextValue, children: _jsx(Popover, { open: isOpen, onOpenChange: setIsOpen, children: children }) })); } export function useMultiComboboxContext() { const value = React.useContext(MultiComboboxContext); if (!value) { throw new Error(`useMultiComboboxContext must be used inside a MultiComboboxContext provider`); } return value; } function MultiComboboxInput({ className, placeholder, }) { const { selectedValues, onSelect, disabled, setIsOpen, isOpen, inputId, filterId, } = useMultiComboboxContext(); const handleClick = () => { if (disabled) return; setIsOpen(!isOpen); }; return (_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: cn(inputVariants({ height: 'flexible', }), 'flex items-center gap-2', disabled && 'opacity-50', className), onClick: handleClick, onKeyDown: (e) => { if (e.key === 'Enter') { handleClick(); } }, role: "button", tabIndex: 0, "data-cmdk-input-id": inputId, children: [_jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-1", children: [selectedValues.length === 0 && (_jsx("div", { className: "text-muted-foreground", children: placeholder })), selectedValues.length > 0 && (_jsx(_Fragment, { children: selectedValues.map((option) => (_jsxs(Badge, { variant: "secondary", className: "flex items-center gap-1 rounded-xs px-1 font-normal", children: [_jsx("div", { children: option.label }), _jsx("button", { className: "-mr-1 rounded-full p-0.5 hover:bg-secondary-hover", onKeyDown: (e) => { if (e.key === 'Enter') { onSelect(option.value, option.label, false); e.stopPropagation(); } }, onClick: (e) => { onSelect(option.value, option.label, false); if (isOpen) { document .querySelector(`[data-cmdk-filter-id="${filterId}"]`) ?.focus(); } e.stopPropagation(); }, children: _jsx(MdClose, {}) })] }, option.value))) }))] }), _jsx(PopoverTrigger, { children: _jsx(MdUnfoldMore, { className: "size-4" }) })] }) })); } function MultiComboboxContent({ children, className, maxHeight = '320px', style, ...rest }) { const { inputId, filterId, searchQuery, setSearchQuery } = useMultiComboboxContext(); return (_jsx(PopoverContent, { align: "start", width: "none", padding: "none", sideOffset: 0, className: cn(selectContentVariants({ popper: 'active' }), className), style: { '--max-popover-height': maxHeight, width: 'var(--radix-popover-trigger-width)', ...style, }, onInteractOutside: (e) => { if (e.target && e.target instanceof Element && e.target.closest(`[data-cmdk-input-id="${inputId}"]`)) { e.preventDefault(); } }, ...rest, "data-combobox-content": "", children: _jsxs(Command, { children: [_jsx(CommandInput, { "data-cmdk-filter-id": filterId, value: searchQuery, onValueChange: setSearchQuery }), _jsx(CommandList, { children: children })] }) })); } function MultiComboboxEmpty({ className, ...props }) { return _jsx(CommandEmpty, { className: cn('p-2 text-sm', className), ...props }); } const MultiComboboxGroup = CommandGroup; function MultiComboboxItem({ value, className, label, children, ...rest }) { const { selectedValues, onSelect } = useMultiComboboxContext(); const isSelected = selectedValues.some((v) => v.value === value); const itemRef = React.useRef(null); return (_jsxs(CommandItem, { onSelect: () => { const itemLabel = label ?? itemRef.current?.textContent ?? undefined; onSelect(value, itemLabel, !isSelected); }, className: cn(selectItemVariants(), className), ...rest, ref: itemRef, children: [_jsx("div", { className: cn('mr-2 flex h-4 w-4 items-center justify-center rounded-xs border', isSelected ? 'opacity-100' : '[&_svg]:invisible'), children: _jsx(MdCheck, { className: 'size-4' }) }), children] })); } export { MultiCombobox, MultiComboboxContent, MultiComboboxEmpty, MultiComboboxGroup, MultiComboboxInput, MultiComboboxItem, }; //# sourceMappingURL=multi-combobox.js.map