@baseplate-dev/ui-components
Version:
Shared UI component library
114 lines • 6.62 kB
JavaScript
'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