UNPKG

ivt

Version:

Ivt Components Library

369 lines (366 loc) 16.6 kB
import React__default from 'react'; import { c } from '../chunks/index.module-1-Lm1QYF.mjs'; import { c as cn } from '../chunks/utils-05LlW3Cl.mjs'; import { C as ChevronsUpDown } from '../chunks/chevrons-up-down-BFiJwHit.mjs'; import { C as Check } from '../chunks/check-BBGTedl-.mjs'; import { T as Trash2 } from '../chunks/trash-2-D6lkozey.mjs'; import { L as Label } from '../chunks/label-atj6gghV.mjs'; import { P as Popover, a as PopoverTrigger, b as PopoverContent } from '../chunks/popover-CsYW0nDm.mjs'; import { I as Input } from '../chunks/input-BEkvMaQp.mjs'; import { C as Command, b as CommandInput, c as CommandList, f as CommandItem, d as CommandEmpty, e as CommandGroup } from '../chunks/command-IckfUQsK.mjs'; import { B as Button } from '../chunks/button-Co_1yLv6.mjs'; import '../chunks/bundle-mjs-BYcyWisL.mjs'; import '../chunks/createLucideIcon-DLrNgMqk.mjs'; import '../chunks/index-DgKlJYZP.mjs'; import 'react-dom'; import '@radix-ui/react-slot'; import 'react/jsx-runtime'; import '../chunks/index-Bl-WJHvp.mjs'; import '../chunks/index-1tQVI0Jh.mjs'; import '../chunks/index-DT8WgpCS.mjs'; import '../chunks/index-DUpRrJTH.mjs'; import '../chunks/index-Cbm3--wc.mjs'; import '../chunks/index-DvCZGX3H.mjs'; import '../chunks/tslib.es6-DXUeYCTx.mjs'; import '../chunks/index-tkRL9Tft.mjs'; import '../chunks/index-DKOlG3mh.mjs'; import '../chunks/index-aLIsJMgt.mjs'; import '../chunks/index-DmY774z-.mjs'; import '../chunks/index-BTe1rv5Z.mjs'; import '../chunks/index-C6s8KI_8.mjs'; import '../chunks/index-D4FMFHi9.mjs'; import '../chunks/index-BRYGnp2Q.mjs'; import '../chunks/dialog-BkF50Tmo.mjs'; import '../chunks/x-BOMmTgZd.mjs'; import 'class-variance-authority'; const MultiInputList = /*#__PURE__*/ React__default.forwardRef(({ options: initialOptions, value: controlledValue, defaultValue = [], onValueChange, placeholder = "Digite...", searchPlaceholder = "Buscar...", label, description, listTitle = "Itens adicionados", listUnit = "itens", className, disabled, selectAllLabel = "(Selecionar todos)", validationRegex, onInvalidInput, id, showAddButton, addButtonLabel = "Adicionar", fetchOptions, maxHeight = "200px", fetchDependencies = [], ...props }, ref)=>{ const generatedId = React__default.useId(); const inputId = id || generatedId; const [selectedValues, setSelectedValues] = React__default.useState(controlledValue || defaultValue); const [inputValue, setInputValue] = React__default.useState(""); const [searchTerm, setSearchTerm] = React__default.useState(""); const [isPopoverOpen, setIsPopoverOpen] = React__default.useState(false); const [isShiftOn, setIsShiftOn] = React__default.useState(false); const [knownOptions, setKnownOptions] = React__default.useState(initialOptions); const [fetchedOptions, setFetchedOptions] = React__default.useState([]); const [loading, setLoading] = React__default.useState(false); React__default.useEffect(()=>{ if (controlledValue) { setSelectedValues(controlledValue); } }, [ controlledValue ]); React__default.useEffect(()=>{ if (initialOptions.length > 0) { setKnownOptions((prev)=>{ const prevMap = new Map(prev.map((o)=>[ o.value, o ])); initialOptions.forEach((o)=>{ prevMap.set(o.value, o); }); return Array.from(prevMap.values()); }); } }, [ initialOptions ]); const displayOptions = React__default.useMemo(()=>{ if (fetchOptions) { return fetchedOptions; } if (!searchTerm) return initialOptions; return initialOptions.filter((opt)=>opt.label.toLowerCase().includes(searchTerm.toLowerCase())); }, [ fetchOptions, fetchedOptions, initialOptions, searchTerm ]); const debouncedFetch = c(async (term)=>{ if (!fetchOptions) return; try { const result = await fetchOptions(term); setFetchedOptions(result); setKnownOptions((prev)=>{ const prevMap = new Map(prev.map((o)=>[ o.value, o ])); result.forEach((o)=>{ prevMap.set(o.value, o); }); return Array.from(prevMap.values()); }); } catch (err) { console.error("Erro ao buscar opções:", err); setFetchedOptions([]); } finally{ setLoading(false); } }, 500); const dependenciesSerialized = React__default.useMemo(()=>JSON.stringify(fetchDependencies), [ fetchDependencies ]); // biome-ignore lint/correctness/useExhaustiveDependencies: <useEffect> React__default.useEffect(()=>{ if (fetchOptions && (searchTerm || inputValue)) { setLoading(true); debouncedFetch(searchTerm || inputValue); } }, [ dependenciesSerialized ]); const addFromInput = (text)=>{ if (!text) return; const parts = text.split(",").map((p)=>p.trim()).filter((p)=>p.length > 0); if (parts.length === 0) return; const validParts = validationRegex ? parts.filter((p)=>validationRegex.test(p)) : parts; const invalidParts = validationRegex ? parts.filter((p)=>!validationRegex.test(p)) : []; if (invalidParts.length > 0) { if (onInvalidInput) onInvalidInput(invalidParts); setInputValue(invalidParts.join(", ")); } else { setInputValue(""); } if (validParts.length > 0) { const newSet = new Set(selectedValues); validParts.forEach((part)=>{ newSet.add(part); setKnownOptions((prev)=>{ if (prev.some((o)=>o.value === part)) return prev; return [ ...prev, { label: part, value: part } ]; }); }); const newValues = Array.from(newSet); setSelectedValues(newValues); onValueChange(newValues); } }; const handleKeyDown = (e)=>{ if (e.key === "ArrowDown") { e.preventDefault(); if (!isPopoverOpen) { setIsPopoverOpen(true); } requestAnimationFrame(()=>{ const firstItem = document.querySelector('[role="option"]'); firstItem?.focus(); }); } else if (e.key === "Enter") { e.preventDefault(); addFromInput(inputValue); setIsPopoverOpen(false); } else if (e.key === ",") { e.preventDefault(); addFromInput(inputValue); } else if (e.key === "Tab") { if (inputValue) { addFromInput(inputValue); } } }; const toggleOption = (optionValue)=>{ const newValues = selectedValues.includes(optionValue) ? selectedValues.filter((v)=>v !== optionValue) : [ ...selectedValues, optionValue ]; setSelectedValues(newValues); onValueChange(newValues); setInputValue(""); }; const toggleInterval = (index)=>{ if (selectedValues.length < 1) return; const currentOptions = displayOptions; const lastValue = selectedValues.at(-1); const lastIndex = currentOptions.findIndex((o)=>o.value === lastValue); if (lastIndex === -1) return; const start = Math.min(lastIndex, index); const end = Math.max(lastIndex, index) + 1; const rangeValues = currentOptions.slice(start, end).map((o)=>o.value); const areAllSelected = rangeValues.every((v)=>selectedValues.includes(v)); const newSelectedValues = areAllSelected ? selectedValues.filter((v)=>!rangeValues.includes(v)) : [ ...new Set([ ...selectedValues, ...rangeValues ]) ]; setSelectedValues(newSelectedValues); onValueChange(newSelectedValues); }; const removeItem = (val)=>{ const newValues = selectedValues.filter((v)=>v !== val); setSelectedValues(newValues); onValueChange(newValues); }; const handleSelectAll = ()=>{ const optionsToSelect = displayOptions; const allSelected = optionsToSelect.every((o)=>selectedValues.includes(o.value)); if (allSelected) { const newValues = selectedValues.filter((val)=>!optionsToSelect.some((opt)=>opt.value === val)); setSelectedValues(newValues); onValueChange(newValues); } else { const newSet = new Set(selectedValues); optionsToSelect.forEach((opt)=>{ newSet.add(opt.value); }); const newValues = Array.from(newSet); setSelectedValues(newValues); onValueChange(newValues); } }; const isAllFilteredSelected = displayOptions.length > 0 && displayOptions.every((opt)=>selectedValues.includes(opt.value)); return /*#__PURE__*/ React__default.createElement("div", { className: cn("space-y-2", className) }, label && /*#__PURE__*/ React__default.createElement(Label, { htmlFor: inputId, className: "text-foreground gap-0 text-sm font-medium" }, label), /*#__PURE__*/ React__default.createElement("div", { className: "flex w-full items-center gap-2" }, /*#__PURE__*/ React__default.createElement("div", { className: "relative flex-1" }, /*#__PURE__*/ React__default.createElement(Popover, { open: isPopoverOpen && (displayOptions.length > 0 || loading || !!fetchOptions), onOpenChange: (open)=>{ setIsPopoverOpen(open); if (!open) setSearchTerm(""); } }, /*#__PURE__*/ React__default.createElement(PopoverTrigger, { asChild: true }, /*#__PURE__*/ React__default.createElement("div", { className: "relative" }, /*#__PURE__*/ React__default.createElement(Input, { ref: ref, id: inputId, placeholder: placeholder, value: inputValue, onClick: (e)=>{ e.stopPropagation(); setIsPopoverOpen(true); }, onChange: (e)=>{ const val = e.target.value; setInputValue(val); if (fetchOptions) { setSearchTerm(val); setLoading(true); debouncedFetch(val); } if (!isPopoverOpen) setIsPopoverOpen(true); }, onKeyDown: handleKeyDown, onFocus: ()=>setIsPopoverOpen(true), disabled: disabled, className: "w-full pr-8", autoComplete: "off", ...props }), (initialOptions.length > 0 || fetchOptions) && /*#__PURE__*/ React__default.createElement("div", { className: "pointer-events-none absolute top-2.5 right-4 opacity-50" }, /*#__PURE__*/ React__default.createElement(ChevronsUpDown, { className: "size-4" })))), /*#__PURE__*/ React__default.createElement(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", onOpenAutoFocus: (e)=>e.preventDefault(), onKeyDown: (event)=>{ event.key === "Shift" ? setIsShiftOn(true) : null; }, onKeyUp: (event)=>{ event.key === "Shift" ? setIsShiftOn(false) : null; } }, /*#__PURE__*/ React__default.createElement(Command, { shouldFilter: false, className: "" }, !fetchOptions && /*#__PURE__*/ React__default.createElement(CommandInput, { placeholder: searchPlaceholder, value: searchTerm, onValueChange: (val)=>{ setSearchTerm(val); if (fetchOptions) { setLoading(true); debouncedFetch(val); } } }), /*#__PURE__*/ React__default.createElement(CommandList, null, displayOptions.length > 0 && !fetchOptions && /*#__PURE__*/ React__default.createElement(CommandItem, { onSelect: handleSelectAll, className: "mx-1 flex cursor-pointer items-center justify-between" }, /*#__PURE__*/ React__default.createElement("span", null, selectAllLabel), isAllFilteredSelected && /*#__PURE__*/ React__default.createElement(Check, { className: "h-4 w-4" })), loading && /*#__PURE__*/ React__default.createElement("div", { className: cn("text-muted-foreground py-6 text-center text-sm", fetchOptions && "p-6") }, "Carregando..."), !loading && displayOptions.length === 0 && /*#__PURE__*/ React__default.createElement(CommandEmpty, { className: cn("text-sm", fetchOptions && "p-6") }, "Nenhuma sugestão."), !loading && displayOptions.length > 0 && /*#__PURE__*/ React__default.createElement(CommandGroup, null, displayOptions.map((option)=>{ const isSelected = selectedValues.includes(option.value); return /*#__PURE__*/ React__default.createElement(CommandItem, { key: option.value, value: option.label, onSelect: ()=>{ if (isShiftOn) { const originalIndex = displayOptions.findIndex((o)=>o.value === option.value); toggleInterval(originalIndex); } else { toggleOption(option.value); } }, className: "flex cursor-pointer justify-between" }, /*#__PURE__*/ React__default.createElement("span", null, option.label), isSelected && /*#__PURE__*/ React__default.createElement(Check, { className: "h-4 w-4" })); }))))))), showAddButton && /*#__PURE__*/ React__default.createElement(Button, { type: "button", onClick: ()=>addFromInput(inputValue), disabled: disabled || !inputValue, isAction: true }, addButtonLabel)), description && /*#__PURE__*/ React__default.createElement("p", { className: "text-muted-foreground text-sm" }, description), selectedValues.length > 0 && /*#__PURE__*/ React__default.createElement("div", { className: "mt-4 pt-4" }, /*#__PURE__*/ React__default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /*#__PURE__*/ React__default.createElement("h4", { className: "text-muted-foreground px-1 text-sm font-medium" }, listTitle)), /*#__PURE__*/ React__default.createElement("div", { className: "rounded-md rounded-b-none border-b" }, /*#__PURE__*/ React__default.createElement("div", { style: { maxHeight }, className: "w-full overflow-y-auto rounded-md" }, /*#__PURE__*/ React__default.createElement("div", { className: "divide-y" }, selectedValues.map((val)=>{ const option = knownOptions.find((o)=>o.value === val); const displayLabel = option ? option.label : val; return /*#__PURE__*/ React__default.createElement("div", { key: val, className: "bg-background hover:bg-muted/50 flex items-center justify-between px-1 py-2 text-sm transition-colors" }, /*#__PURE__*/ React__default.createElement("span", { className: "truncate" }, displayLabel), /*#__PURE__*/ React__default.createElement(Button, { variant: "ghost", size: "icon", onClick: ()=>removeItem(val), className: "text-destructive hover:text-destructive hover:bg-destructive/10 h-8 w-8", type: "button" }, /*#__PURE__*/ React__default.createElement(Trash2, { className: "h-4 w-4" }), /*#__PURE__*/ React__default.createElement("span", { className: "sr-only" }, "Remover ", displayLabel))); })))), /*#__PURE__*/ React__default.createElement("div", { className: "text-muted-foreground bg-muted relative z-10 -mt-px flex items-center justify-between border-t px-1 py-2 text-sm" }, /*#__PURE__*/ React__default.createElement("span", null, "Total"), /*#__PURE__*/ React__default.createElement("span", null, selectedValues.length, " ", listUnit)))); }); MultiInputList.displayName = "MultiInputList"; export { MultiInputList }; //# sourceMappingURL=index.mjs.map