ivt
Version:
Ivt Components Library
381 lines (378 loc) • 17.8 kB
JavaScript
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { c } from '../chunks/index.module-1-Lm1QYF.mjs';
import { c as cn } from '../chunks/utils-05LlW3Cl.mjs';
import { X } from '../chunks/x-BOMmTgZd.mjs';
import { C as ChevronsUpDown } from '../chunks/chevrons-up-down-BFiJwHit.mjs';
import { C as Check } from '../chunks/check-BBGTedl-.mjs';
import { W as WandSparkles } from '../chunks/wand-sparkles-BIbU0kjG.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 { B as Button } from '../chunks/button-Co_1yLv6.mjs';
import { B as Badge } from '../chunks/badge-rV4HbE_R.mjs';
import { c as TooltipProvider, T as Tooltip, a as TooltipTrigger, b as TooltipContent } from '../chunks/tooltip-BTEGteNb.mjs';
import { S as Separator } from '../chunks/separator-StpvupIv.mjs';
import { C as Command, b as CommandInput, c as CommandList, d as CommandEmpty, e as CommandGroup, f as CommandItem, h as CommandSeparator } from '../chunks/command-IckfUQsK.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-An4yBrAZ.mjs';
import '../chunks/index-BRYGnp2Q.mjs';
import '../chunks/dialog-BkF50Tmo.mjs';
/**
* Variants for the multi-select component to handle different styles.
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
*/ const multiSelectVariants = cva("m-1 transition ease-in-out delay-150 duration-300", {
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
positive: "border-transparent bg-positive text-positive-foreground shadow hover:bg-positive/80",
warning: "border-transparent bg-warning text-warning-foreground shadow hover:bg-warning/80",
info: "border-transparent bg-info text-info-foreground shadow hover:bg-info/80",
outline: "text-foreground bg-transparent hover:bg-transparent border"
},
subtle: {
true: "",
false: ""
}
},
defaultVariants: {
variant: "default"
}
});
const AutoCompleteMultiSelect = /*#__PURE__*/ React.forwardRef(({ selectedOptions = [], onChange, fetchOptions, variant, placeholder = "Select options", searchPlaceholder = "Buscar...", messageEmpty = "Nenhum resultado encontrado.", animation = 0, maxCount = 3, modalPopover = false, className, label, description, subtle = false, focusOnSelect = true, id, onError }, ref)=>{
const inputRef = React.useRef(null);
const lastClickedIndexRef = React.useRef(-1);
const isShiftPressedRef = React.useRef(false);
const generatedId = React.useId();
const inputId = id || generatedId;
// Estado interno para resposta imediata
const [internalSelected, setInternalSelected] = React.useState(selectedOptions);
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
const [isAnimating, setIsAnimating] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");
const [options, setOptions] = React.useState([]);
const [loading, setLoading] = React.useState(false);
// Sincroniza estado interno quando selectedOptions muda (do pai)
React.useEffect(()=>{
setInternalSelected(selectedOptions);
}, [
selectedOptions
]);
// Listener global para detectar Shift
React.useEffect(()=>{
const handleKeyDown = (e)=>{
if (e.key === "Shift") {
isShiftPressedRef.current = true;
}
};
const handleKeyUp = (e)=>{
if (e.key === "Shift") {
isShiftPressedRef.current = false;
}
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
return ()=>{
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
}, []);
// Busca assíncrona com debounce (do AutoComplete)
const debouncedFetch = c(async (term)=>{
if (!term.trim()) {
setOptions([]);
setLoading(false);
return;
}
setLoading(true);
try {
const result = await fetchOptions(term);
setOptions(result);
} catch (err) {
if (onError) {
onError(err);
}
setOptions([]);
} finally{
setLoading(false);
}
}, 500);
const handleInputChange = (value)=>{
setInputValue(value);
if (value.trim() === "") {
setOptions([]);
setLoading(false);
return;
}
setLoading(true);
debouncedFetch(value);
};
const handleInputKeyDown = (event)=>{
if (event.key === "Enter") {
setIsPopoverOpen(true);
}
};
const toggleOption = (option, index)=>{
// Normaliza para string para comparação
const optionValueStr = String(option.value);
const isSelected = internalSelected.find((o)=>String(o.value) === optionValueStr);
const newSelectedOptions = isSelected ? internalSelected.filter((value)=>String(value.value) !== optionValueStr) : [
...internalSelected,
option
];
// Atualiza estado interno IMEDIATAMENTE
setInternalSelected(newSelectedOptions);
// Notifica o pai
onChange(newSelectedOptions);
// Armazena o índice do último clique
lastClickedIndexRef.current = index;
};
const removeOption = (option)=>{
// Normaliza para string para comparação
const optionValueStr = String(option.value);
const newSelectedOptions = internalSelected.filter((value)=>String(value.value) !== optionValueStr);
setInternalSelected(newSelectedOptions);
onChange(newSelectedOptions);
};
const toggleInterval = (index)=>{
// Se não houver último clique registrado, apenas seleciona o item atual
if (lastClickedIndexRef.current === -1 || internalSelected.length < 1) {
toggleOption(options[index], index);
return;
}
const lastIndex = lastClickedIndexRef.current;
const start = Math.min(lastIndex, index);
const end = Math.max(lastIndex, index) + 1;
const rangeOptions = options.slice(start, end);
const rangeValuesStr = rangeOptions.map((o)=>String(o.value));
const areAllSelected = rangeValuesStr.every((v)=>internalSelected.some((opt)=>String(opt.value) === v));
if (areAllSelected) {
// Desselecionar todos os itens do intervalo
const newSelectedOptions = internalSelected.filter((v)=>!rangeValuesStr.includes(String(v.value)));
setInternalSelected(newSelectedOptions);
onChange(newSelectedOptions);
} else {
// Adicionar todos os itens do intervalo sem duplicados
const existingValuesStr = new Set(internalSelected.map((opt)=>String(opt.value)));
const newOptions = rangeOptions.filter((opt)=>!existingValuesStr.has(String(opt.value)));
const newSelectedOptions = [
...internalSelected,
...newOptions
];
setInternalSelected(newSelectedOptions);
onChange(newSelectedOptions);
}
// Atualiza o último índice clicado
lastClickedIndexRef.current = index;
};
const handleClear = ()=>{
setInternalSelected([]);
onChange([]);
};
const handleTogglePopover = ()=>{
setIsPopoverOpen((prev)=>!prev);
};
const clearExtraOptions = ()=>{
const newSelectedOptions = internalSelected.slice(0, maxCount);
setInternalSelected(newSelectedOptions);
onChange(newSelectedOptions);
};
const toggleAll = ()=>{
if (internalSelected.length === options.length) {
handleClear();
} else {
setInternalSelected(options);
onChange(options);
}
};
const subtleStyles = (variant)=>{
if (variant === "default") {
return "border-transparent bg-accent text-primary hover:bg-accent/80";
}
if (variant === "destructive") {
return "border-transparent bg-destructive-foreground text-destructive hover:bg-destructive-foreground/80";
}
if (variant === "positive") {
return "border-transparent bg-positive-foreground text-positive hover:bg-positive-foreground/80";
}
if (variant === "warning") {
return "border-transparent bg-warning-foreground text-warning hover:bg-warning-foreground/80";
}
if (variant === "info") {
return "border-transparent bg-info-foreground text-info hover:bg-info-foreground/80";
}
return "";
};
const finalSubtleStyles = subtleStyles(variant);
return /*#__PURE__*/ React.createElement("div", {
className: "font-lato space-y-2"
}, label && /*#__PURE__*/ React.createElement(Label, {
htmlFor: inputId,
className: "text-foreground text-sm font-medium gap-0"
}, label), /*#__PURE__*/ React.createElement(Popover, {
open: isPopoverOpen,
onOpenChange: setIsPopoverOpen,
modal: modalPopover
}, /*#__PURE__*/ React.createElement(PopoverTrigger, {
asChild: true
}, /*#__PURE__*/ React.createElement(Button, {
id: inputId,
ref: ref,
onClick: handleTogglePopover,
variant: "outline",
"aria-expanded": isPopoverOpen,
className: cn("flex h-auto min-h-9 w-full items-center justify-between rounded-md border bg-inherit px-4 py-2 hover:bg-inherit [&_svg]:pointer-events-auto", className)
}, internalSelected.length > 0 ? /*#__PURE__*/ React.createElement("div", {
className: "flex w-full items-center justify-between"
}, /*#__PURE__*/ React.createElement("div", {
className: "flex flex-wrap items-center"
}, internalSelected.slice(0, maxCount).map((value)=>{
const option = value;
const IconComponent = option?.icon;
return /*#__PURE__*/ React.createElement(Badge, {
key: value.value,
className: cn("px-1", isAnimating ? "animate-bounce" : "", multiSelectVariants({
variant
}), subtle && finalSubtleStyles),
style: {
animationDuration: `${animation}s`
}
}, IconComponent && /*#__PURE__*/ React.createElement(IconComponent, {
className: "mr-2 h-4 w-4"
}), option?.label, /*#__PURE__*/ React.createElement(X, {
className: "ml-2 size-3 cursor-pointer",
onClick: (event)=>{
event.stopPropagation();
removeOption(value);
}
}));
}), internalSelected.length > maxCount && /*#__PURE__*/ React.createElement(Badge, {
className: cn("text-foreground border-foreground/1 bg-transparent px-1 hover:bg-transparent", isAnimating ? "animate-bounce" : "", multiSelectVariants({
variant
}), subtle && finalSubtleStyles),
style: {
animationDuration: `${animation}s`
}
}, `+ ${internalSelected.length - maxCount} itens`, /*#__PURE__*/ React.createElement(X, {
className: "ml-2 size-3 cursor-pointer",
onClick: (event)=>{
event.stopPropagation();
clearExtraOptions();
}
}))), /*#__PURE__*/ React.createElement("div", {
className: "flex items-center justify-between gap-1"
}, /*#__PURE__*/ React.createElement(TooltipProvider, null, /*#__PURE__*/ React.createElement(Tooltip, null, /*#__PURE__*/ React.createElement(TooltipTrigger, null, /*#__PURE__*/ React.createElement(X, {
className: "text-muted-foreground h-4 cursor-pointer",
onClick: (event)=>{
event.stopPropagation();
handleClear();
}
})), /*#__PURE__*/ React.createElement(TooltipContent, null, /*#__PURE__*/ React.createElement("span", {
className: "text-xs"
}, "Limpar tudo")))), /*#__PURE__*/ React.createElement(Separator, {
orientation: "vertical",
className: "flex h-full min-h-6"
}), /*#__PURE__*/ React.createElement(ChevronsUpDown, {
className: "text-muted-foreground h-4 cursor-pointer"
}))) : /*#__PURE__*/ React.createElement("div", {
className: "flex w-full items-center justify-between"
}, /*#__PURE__*/ React.createElement("span", {
className: "text-muted-foreground"
}, placeholder), /*#__PURE__*/ React.createElement(ChevronsUpDown, {
className: "text-muted-foreground h-4 cursor-pointer"
})))), /*#__PURE__*/ React.createElement(PopoverContent, {
className: "w-auto p-0",
align: "start",
onEscapeKeyDown: ()=>setIsPopoverOpen(false),
onOpenAutoFocus: (e)=>{
e.preventDefault();
// Foca no input sem causar scroll
inputRef.current?.focus({
preventScroll: true
});
}
}, /*#__PURE__*/ React.createElement(Command, {
shouldFilter: false
}, /*#__PURE__*/ React.createElement(CommandInput, {
ref: inputRef,
placeholder: searchPlaceholder,
value: inputValue,
onValueChange: handleInputChange,
onKeyDown: handleInputKeyDown
}), /*#__PURE__*/ React.createElement(CommandList, null, /*#__PURE__*/ React.createElement(CommandEmpty, null, loading ? "Carregando..." : inputValue.trim() === "" ? "Digite algo para buscar..." : messageEmpty), options.length > 0 && /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(CommandGroup, null, /*#__PURE__*/ React.createElement(CommandItem, {
key: "all",
onSelect: ()=>{
toggleAll();
if (focusOnSelect) {
inputRef.current?.focus({
preventScroll: true
});
}
},
className: "flex cursor-pointer items-center justify-between"
}, /*#__PURE__*/ React.createElement("span", null, "(Selecionar tudo)"), /*#__PURE__*/ React.createElement("div", {
className: cn(internalSelected.length === options.length ? "" : "opacity-50 [&_svg]:invisible")
}, /*#__PURE__*/ React.createElement(Check, {
className: "text-foreground h-4 w-4"
}))), options.map((option, index)=>{
// Comparação convertendo AMBOS para string
const optionValueStr = String(option.value);
const isSelected = internalSelected.some((opt)=>String(opt.value) === optionValueStr);
return /*#__PURE__*/ React.createElement(CommandItem, {
key: `${option.value}-${isSelected}`,
value: option.value,
onSelect: ()=>{
// Usa o ref que é atualizado pelos event listeners globais
const isShiftPressed = isShiftPressedRef.current;
if (isShiftPressed) {
toggleInterval(index);
} else {
toggleOption(option, index);
}
if (focusOnSelect) {
// Foca sem causar scroll
inputRef.current?.focus({
preventScroll: true
});
}
},
className: "flex cursor-pointer items-center justify-between"
}, /*#__PURE__*/ React.createElement("div", {
className: "flex items-center"
}, option.icon && /*#__PURE__*/ React.createElement(option.icon, {
className: "text-muted-foreground mr-2 h-4 w-4"
}), /*#__PURE__*/ React.createElement("span", null, option.label)), /*#__PURE__*/ React.createElement("div", {
className: cn(isSelected ? "" : "opacity-50 [&_svg]:invisible")
}, /*#__PURE__*/ React.createElement(Check, {
className: "text-foreground h-4 w-4"
})));
})), /*#__PURE__*/ React.createElement(CommandSeparator, null))))), animation > 0 && internalSelected.length > 0 && /*#__PURE__*/ React.createElement(WandSparkles, {
className: cn("text-foreground bg-background my-2 h-3 w-3 cursor-pointer", isAnimating ? "" : "text-muted-foreground"),
onClick: ()=>setIsAnimating(!isAnimating)
})), description && /*#__PURE__*/ React.createElement("p", {
className: "text-muted-foreground text-sm"
}, description));
});
AutoCompleteMultiSelect.displayName = "AutoCompleteMultiSelect";
export { AutoCompleteMultiSelect };
//# sourceMappingURL=index.mjs.map