phx-react
Version:
PHX REACT
157 lines • 11.8 kB
JavaScript
"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