UNPKG

phx-react

Version:

PHX REACT

157 lines 11.8 kB
"use strict"; 'use client'; Object.defineProperty(exports, "__esModule", { value: true }); exports.PHXSelectWithAction = PHXSelectWithAction; const tslib_1 = require("tslib"); const image_1 = tslib_1.__importDefault(require("next/image")); const react_1 = tslib_1.__importStar(require("react")); const chevron_down_icon_svg_1 = tslib_1.__importDefault(require("../../assets/icons/chevron-down-icon.svg")); const pencil_icon_svg_1 = tslib_1.__importDefault(require("../../assets/icons/pencil-icon.svg")); const plus_circle_icon_svg_1 = tslib_1.__importDefault(require("../../assets/icons/plus-circle-icon.svg")); const search_icon_svg_1 = tslib_1.__importDefault(require("../../assets/icons/search-icon.svg")); const IconLoading_1 = require("../../commons/IconLoading"); const Badge_1 = require("../Badge"); const useDebounce_1 = tslib_1.__importDefault(require("../Func/useDebounce")); const Text_1 = require("../Text/Text"); const types_1 = require("../types"); const HelpText_1 = require("./HelpText"); const remove_vn_tones_1 = require("./remove-vn-tones"); const ADD_NEW_VALUE = 'ADD_NEW'; function PHXSelectWithAction({ addNew, className = '', emptySearchState, emptyState, error, helpText, label, loading, enableSearch = true, maxHeight = '300px', onChange, options, placeholder = 'Lựa chọn', search, searchPlaceholder = 'Tìm kiếm', suffix, value: defaultValue, disabled, }) { const [searchQuery, setSearchQuery] = (0, react_1.useState)(''); const [isOpen, setIsOpen] = (0, react_1.useState)(false); const [isLeaving, setIsLeaving] = (0, react_1.useState)(false); const deferredQuery = (0, useDebounce_1.default)(searchQuery, 300); const [selectedCache, setSelectedCache] = (0, react_1.useState)(null); const query = (0, remove_vn_tones_1.normalizeVietnameseText)(deferredQuery).trim(); const filteredOptions = !query ? options : options.filter((option) => { var _a; return (0, remove_vn_tones_1.normalizeVietnameseText)((_a = option.label) !== null && _a !== void 0 ? _a : '') .trim() .includes(query); }); const containerRef = (0, react_1.useRef)(null); const inputRef = (0, react_1.useRef)(null); (0, react_1.useEffect)(() => { search === null || search === void 0 ? void 0 : search.handleFilter(deferredQuery); }, [deferredQuery]); (0, react_1.useEffect)(() => { if (!defaultValue) return; const found = options.find((o) => o.value === defaultValue); if (found) setSelectedCache(found); }, [JSON.stringify(options), defaultValue]); const handleClose = () => { setIsLeaving(true); setTimeout(() => { setIsOpen(false); setIsLeaving(false); setSearchQuery(''); }, 100); }; (0, react_1.useEffect)(() => { const handleClickOutside = (event) => { if (containerRef.current && !containerRef.current.contains(event.target)) { handleClose(); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isOpen]); (0, react_1.useEffect)(() => { if (isOpen && search && inputRef.current) { inputRef.current.focus(); } }, [isOpen, search]); const selectedOption = selectedCache; const handleChange = (option) => { if (option === ADD_NEW_VALUE) { addNew === null || addNew === void 0 ? void 0 : addNew.onClick(); handleClose(); return; } if (option && typeof option !== 'string') { setSelectedCache(option); onChange === null || onChange === void 0 ? void 0 : onChange(option.value); handleClose(); } }; const handleSearch = (val) => { setSearchQuery(val || ''); }; const getEmptyState = () => { if (loading) { return (react_1.default.createElement("div", { className: 'flex items-center justify-center' }, react_1.default.createElement(IconLoading_1.IconLoading, { color: 'bg-gray-50' }))); } if (options.length === 0 && deferredQuery) { return emptySearchState || `Không tìm thấy ${label.toLowerCase()} nào`; } return emptyState || `Chưa có ${label.toLowerCase()} nào`; }; const getIconItem = (type) => { if (type === 'edit') { return pencil_icon_svg_1.default; } return pencil_icon_svg_1.default; }; const isOptionSelected = (option) => option.value === defaultValue; return (react_1.default.createElement("div", { ref: containerRef, className: (0, types_1.classNames)('relative w-full', className) }, react_1.default.createElement("div", { className: 'mb-1 flex items-center justify-between' }, react_1.default.createElement(Text_1.PHXText, null, label), suffix && react_1.default.createElement("div", { className: 'text-sm font-medium' }, suffix)), react_1.default.createElement("div", { className: 'relative' }, react_1.default.createElement("button", { className: (0, types_1.classNames)('border-input bg-background shadow-sm mt-1 flex w-full items-center justify-between rounded-lg border-[0.5px] border-gray-500 py-1.5 pl-3 pr-2 text-xs hover:bg-gray-50', defaultValue ? '' : 'text-muted-foreground', error ? 'border-red-800 bg-red-50 hover:bg-red-50 focus:border-red-800 focus:bg-red-50' : '', disabled ? 'bg-neutral-200 border-[0px] hover:bg-neutral-200' : ''), onClick: !disabled ? () => setIsOpen(!isOpen) : undefined, type: 'button' }, react_1.default.createElement("span", { className: 'truncate' }, (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) || placeholder), !disabled && (react_1.default.createElement(image_1.default, { alt: '', className: (0, types_1.classNames)(' shrink-0 transition-transform', isOpen ? 'rotate-180' : ''), height: 20, // @ts-ignore src: chevron_down_icon_svg_1.default, width: 20 }))), isOpen && (react_1.default.createElement("div", { className: (0, types_1.classNames)('border-border shadow-lg absolute z-50 mt-1 w-full rounded-lg border bg-white transition-opacity duration-100', isLeaving ? 'opacity-0' : 'opacity-100') }, enableSearch && (react_1.default.createElement("div", { className: 'border-border border-b p-3' }, react_1.default.createElement("div", { className: 'relative' }, react_1.default.createElement(image_1.default, { alt: '', className: 'text-muted-foreground pointer-events-none absolute left-3 top-1/2 -translate-y-1/2', height: 13, // @ts-ignore src: search_icon_svg_1.default, width: 13 }), react_1.default.createElement("input", { ref: inputRef, className: 'shadow-sm w-full rounded-lg border-[0.5px] border-gray-500 py-1.5 pr-4 text-xs font-normal outline-none transition-colors hover:bg-gray-50 focus:border-gray-500 focus:bg-gray-50 focus:outline-none focus:outline-offset-1 focus:outline-indigo-500 focus:ring-transparent', onChange: (e) => handleSearch(e.target.value), placeholder: searchPlaceholder, style: { paddingLeft: '30px' }, type: 'text', value: searchQuery })))), react_1.default.createElement("div", { className: 'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300 hover:scrollbar-thumb-gray-400 overflow-y-auto', style: { maxHeight } }, addNew && (react_1.default.createElement("button", { className: (0, types_1.classNames)('border-border flex w-full cursor-pointer items-center gap-2 border-b px-4 py-3 text-sm transition-colors hover:bg-zinc-100'), onClick: () => handleChange(ADD_NEW_VALUE), type: 'button' }, react_1.default.createElement(image_1.default, { alt: '', height: 13, src: plus_circle_icon_svg_1.default, width: 13 }), react_1.default.createElement(Text_1.PHXText, null, addNew.title))), react_1.default.createElement("div", null, filteredOptions.length > 0 ? (filteredOptions.map((option, index) => { const selected = isOptionSelected(option); return (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement("button", { key: option.value, className: (0, types_1.classNames)('group w-full cursor-pointer px-2', selected ? 'bg-zinc-200 ' : ' hover:bg-zinc-100'), onClick: () => handleChange(option), type: 'button' }, react_1.default.createElement("div", { className: 'flex items-center justify-between' }, react_1.default.createElement("div", { className: 'max-w-[90%] px-2 py-2.5 text-left space-y-1' }, react_1.default.createElement(Text_1.PHXText, { className: `truncate ${selected ? 'font-semibold text-gray-900' : 'text-gray-700 font-medium'}` }, option.label), option.helpText && (react_1.default.createElement(HelpText_1.PHXHelpText, { helpText: option.helpText, isHelpTextArray: option.isHelpTextArray })), option.badge && react_1.default.createElement(Badge_1.PHXBadge, { ...option.badge })), !!option.action && (react_1.default.createElement("div", { className: `mr-2 flex h-8 w-8 items-center justify-center rounded-md ${selected ? 'hover:bg-gray-300' : 'hover:bg-gray-200'}`, onClick: (e) => { var _a; e.stopPropagation(); (_a = option.action) === null || _a === void 0 ? void 0 : _a.onClick(option.value); }, onKeyDown: (e) => { var _a; if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); (_a = option.action) === null || _a === void 0 ? void 0 : _a.onClick(option.value); } }, role: 'button', tabIndex: 0 }, react_1.default.createElement("div", { className: `flex items-center opacity-0 transition-opacity group-hover:opacity-100 ` }, react_1.default.createElement(image_1.default, { alt: '', height: 13, src: getIconItem(option.action.type), width: 13 })))))), index === filteredOptions.length - 1 ? null : react_1.default.createElement("div", { className: 'border-t ' }))); })) : (react_1.default.createElement("div", { className: ' px-3 py-8 text-center' }, react_1.default.createElement(Text_1.PHXText, { as: 'div' }, getEmptyState())))))))), react_1.default.createElement("div", { className: `${error ? 'mt-1 block' : 'hidden'}` }, react_1.default.createElement(Text_1.PHXText, { as: 'div', tone: 'error' }, error)), react_1.default.createElement("div", { className: `${helpText ? 'mt-1 block' : 'hidden'}` }, react_1.default.createElement(HelpText_1.PHXHelpText, { helpText: helpText })))); } //# sourceMappingURL=SelectWithAction.js.map