ivt
Version:
Ivt Components Library
369 lines (366 loc) • 16.6 kB
JavaScript
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