@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
JavaScript
"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);
}