UNPKG

@explita/daily-toolset-components

Version:

A lightweight and versatile collection of TypeScript utility functions and form components, inspired by ShadCN UI, designed for seamless everyday development. Enhance your Node.js, React, and Next.js projects with a well-structured suite of helpers for st

152 lines (151 loc) 9.32 kB
"use client"; "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiSelect = MultiSelect; const react_1 = __importStar(require("react")); const lu_1 = require("react-icons/lu"); const command_1 = require("../ui/command"); const popover_1 = require("../ui/popover"); const scroll_area_1 = require("../ui/scroll-area"); const Button_1 = require("./Button"); const FormLabel_1 = require("./FormLabel"); const lu_2 = require("react-icons/lu"); const InputError_1 = require("./InputError"); const useField_1 = require("../hooks/useField"); const daily_toolset_hooks_1 = require("@explita/daily-toolset-hooks"); function MultiSelect({ addEmpty = false, name = "", label, options, isDisabled = false, isRequired = true, isSearchable = false, isClearable = true, defaultValue, handleSelection, error, placeholder, maxCount, classNames, }) { const { fieldError, fieldValue, setValue } = (0, useField_1.useField)(name); const [open, setOpen] = (0, react_1.useState)(false); const [search, setSearch] = (0, react_1.useState)(""); const id = (0, react_1.useId)(); const { label: labelClass = "", trigger = "", root = "", dropdown = "", input = "", item: itemClass = "", empty = "", } = classNames || {}; // Extract formValues[name] safely to avoid unnecessary re-renders const formValue = (0, react_1.useMemo)(() => { var _a; return name && fieldValue ? (_a = fieldValue === null || fieldValue === void 0 ? void 0 : fieldValue.split(",")) !== null && _a !== void 0 ? _a : [] : []; }, [name, fieldValue]); // Use lazy initialization to avoid unnecessary re-renders const [value, setInputValue] = (0, react_1.useState)(() => defaultValue || []); // Sync state when formValues or defaultValue changes (0, react_1.useEffect)(() => { const inputValue = defaultValue ? defaultValue : formValue; if (name && inputValue.length > 0) { setInputValue(inputValue); } }, [defaultValue, formValue, name]); // Simplify error handling const errorData = error ? error : fieldError || ""; // Memoize options formatting to improve performance const formattedOptions = (0, react_1.useMemo)(() => { let optionsList = (options === null || options === void 0 ? void 0 : options.map((option) => { var _a, _b, _c; return ({ value: ((_a = option.value) === null || _a === void 0 ? void 0 : _a.toString()) || "", label: ((_b = option.label) === null || _b === void 0 ? void 0 : _b.toString()) || "", disabled: (_c = option.disabled) !== null && _c !== void 0 ? _c : false, }); })) || []; if (addEmpty && options) { optionsList = [{ value: "", label: "", disabled: false }, ...optionsList]; } return optionsList; }, [options, addEmpty]); const filteredOptions = (0, react_1.useMemo)(() => { if (!isSearchable || !search.trim()) return formattedOptions; const lowerSearch = search.toLowerCase(); return formattedOptions.filter((opt) => opt.value.toLowerCase().includes(lowerSearch) || // Ensure value is lowercase (0, daily_toolset_hooks_1.stripTags)(opt.label).toLowerCase().includes(lowerSearch)); }, [formattedOptions, isSearchable, search]); function handleSelectOptions(selected) { let newValue = value; if (!value.includes(selected)) { newValue = [...value, selected]; } else { newValue = [...value.filter((v) => v !== selected)]; } setInputValue(newValue); handleSelection === null || handleSelection === void 0 ? void 0 : handleSelection(newValue); setValue(newValue.join(",")); } const values = (0, react_1.useMemo)(() => { if (maxCount) { return value.filter(Boolean).slice(0, maxCount); } return value.filter(Boolean); }, [value, maxCount]); return (react_1.default.createElement("div", { className: "explita-input-root" }, react_1.default.createElement(FormLabel_1.Label, { id: id, label: label, isRequired: isRequired, className: labelClass }), react_1.default.createElement(popover_1.Popover, { open: open, onOpenChange: setOpen }, react_1.default.createElement(popover_1.PopoverTrigger, { asChild: true }, react_1.default.createElement(Button_1.Button, { variant: "outline", role: "combobox", "aria-expanded": open, "data-error": errorData.length > 0, "data-empty": value.filter(Boolean).length === 0, "data-clearable": isClearable && value.filter(Boolean).length > 0, className: `group multi-select-input ${trigger}`, disabled: isDisabled }, values.length > 0 ? (react_1.default.createElement("div", { className: "multi-select-items items-center" }, values.map((v) => { var _a; const label = (_a = filteredOptions.find((item) => item.value === v)) === null || _a === void 0 ? void 0 : _a.label; return (react_1.default.createElement(SelectItem, { key: v, value: v, label: label || "", handleSelectOptions: handleSelectOptions, className: itemClass })); }))) : (react_1.default.createElement("span", null, placeholder)), react_1.default.createElement(Remainder, { value: value, maxCount: maxCount }), react_1.default.createElement(lu_1.LuChevronsUpDown, { className: "chevron-icon" }))), react_1.default.createElement(popover_1.PopoverContent, { className: `explita-popover-content ${root}`, align: "start", onPointerDown: (e) => e.stopPropagation(), forceMount: true }, react_1.default.createElement(command_1.Command, { shouldFilter: false, loop: true }, isSearchable && (react_1.default.createElement(command_1.CommandInput, { placeholder: "Search...", value: search, onValueChange: setSearch, className: `${input}` })), react_1.default.createElement(command_1.CommandList, null, react_1.default.createElement(scroll_area_1.ScrollArea, { className: dropdown }, react_1.default.createElement("div", { className: `select-list` }, react_1.default.createElement(command_1.CommandEmpty, { className: `empty-list ${empty}` }, "No records found."), react_1.default.createElement(command_1.CommandGroup, { className: "flex flex-col gap-3" }, filteredOptions.map((item) => (react_1.default.createElement(command_1.CommandItem, { key: item.value, value: item.value, onSelect: (currentValue) => { handleSelectOptions(currentValue); }, className: `select-list-item ${itemClass}` }, item.label, react_1.default.createElement(lu_1.LuCheck, { "data-checked": value && value.includes(item.value), className: "check-icon" }))))))))))), react_1.default.createElement(InputError_1.InputError, { message: errorData }), react_1.default.createElement("input", { type: "hidden", name: name, value: value.join(","), id: id }))); } function SelectItem({ label, value, handleSelectOptions, className, }) { return (react_1.default.createElement("span", { className: `multi-select-item ${className}` }, label, react_1.default.createElement("span", { onClick: () => handleSelectOptions(value || "") }, react_1.default.createElement(lu_2.LuX, null)))); } function Remainder({ value, maxCount }) { if (!maxCount || value.length <= maxCount) return null; const rem = value.length - maxCount; return react_1.default.createElement("span", { className: "multi-select-remainder" }, "+", rem > 9 ? 9 : rem); }