UNPKG

phx-react

Version:

PHX REACT

153 lines 10.6 kB
'use client'; import Image from 'next/image'; import React, { useEffect, useRef, useState } from 'react'; import ChevronDownIcon from '../../assets/icons/chevron-down-icon.svg'; import PencilIcon from '../../assets/icons/pencil-icon.svg'; import PlusIcon from '../../assets/icons/plus-circle-icon.svg'; import SearchIcon from '../../assets/icons/search-icon.svg'; import { IconLoading } from '../../commons/IconLoading'; import { PHXBadge } from '../Badge'; import PHXUseDebounce from '../Func/useDebounce'; import { PHXText } from '../Text/Text'; import { classNames } from '../types'; import { PHXHelpText } from './HelpText'; import { normalizeVietnameseText } from './remove-vn-tones'; const ADD_NEW_VALUE = 'ADD_NEW'; export 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] = useState(''); const [isOpen, setIsOpen] = useState(false); const [isLeaving, setIsLeaving] = useState(false); const deferredQuery = PHXUseDebounce(searchQuery, 300); const [selectedCache, setSelectedCache] = useState(null); const query = normalizeVietnameseText(deferredQuery).trim(); const filteredOptions = !query ? options : options.filter((option) => { var _a; return normalizeVietnameseText((_a = option.label) !== null && _a !== void 0 ? _a : '') .trim() .includes(query); }); const containerRef = useRef(null); const inputRef = useRef(null); useEffect(() => { search === null || search === void 0 ? void 0 : search.handleFilter(deferredQuery); }, [deferredQuery]); 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); }; 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]); 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.createElement("div", { className: 'flex items-center justify-center' }, React.createElement(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 PencilIcon; } return PencilIcon; }; const isOptionSelected = (option) => option.value === defaultValue; return (React.createElement("div", { ref: containerRef, className: classNames('relative w-full', className) }, React.createElement("div", { className: 'mb-1 flex items-center justify-between' }, React.createElement(PHXText, null, label), suffix && React.createElement("div", { className: 'text-sm font-medium' }, suffix)), React.createElement("div", { className: 'relative' }, React.createElement("button", { className: 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.createElement("span", { className: 'truncate' }, (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) || placeholder), !disabled && (React.createElement(Image, { alt: '', className: classNames(' shrink-0 transition-transform', isOpen ? 'rotate-180' : ''), height: 20, // @ts-ignore src: ChevronDownIcon, width: 20 }))), isOpen && (React.createElement("div", { className: 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.createElement("div", { className: 'border-border border-b p-3' }, React.createElement("div", { className: 'relative' }, React.createElement(Image, { alt: '', className: 'text-muted-foreground pointer-events-none absolute left-3 top-1/2 -translate-y-1/2', height: 13, // @ts-ignore src: SearchIcon, width: 13 }), React.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.createElement("div", { className: 'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300 hover:scrollbar-thumb-gray-400 overflow-y-auto', style: { maxHeight } }, addNew && (React.createElement("button", { className: 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.createElement(Image, { alt: '', height: 13, src: PlusIcon, width: 13 }), React.createElement(PHXText, null, addNew.title))), React.createElement("div", null, filteredOptions.length > 0 ? (filteredOptions.map((option, index) => { const selected = isOptionSelected(option); return (React.createElement(React.Fragment, null, React.createElement("button", { key: option.value, className: classNames('group w-full cursor-pointer px-2', selected ? 'bg-zinc-200 ' : ' hover:bg-zinc-100'), onClick: () => handleChange(option), type: 'button' }, React.createElement("div", { className: 'flex items-center justify-between' }, React.createElement("div", { className: 'max-w-[90%] px-2 py-2.5 text-left space-y-1' }, React.createElement(PHXText, { className: `truncate ${selected ? 'font-semibold text-gray-900' : 'text-gray-700 font-medium'}` }, option.label), option.helpText && (React.createElement(PHXHelpText, { helpText: option.helpText, isHelpTextArray: option.isHelpTextArray })), option.badge && React.createElement(PHXBadge, { ...option.badge })), !!option.action && (React.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.createElement("div", { className: `flex items-center opacity-0 transition-opacity group-hover:opacity-100 ` }, React.createElement(Image, { alt: '', height: 13, src: getIconItem(option.action.type), width: 13 })))))), index === filteredOptions.length - 1 ? null : React.createElement("div", { className: 'border-t ' }))); })) : (React.createElement("div", { className: ' px-3 py-8 text-center' }, React.createElement(PHXText, { as: 'div' }, getEmptyState())))))))), React.createElement("div", { className: `${error ? 'mt-1 block' : 'hidden'}` }, React.createElement(PHXText, { as: 'div', tone: 'error' }, error)), React.createElement("div", { className: `${helpText ? 'mt-1 block' : 'hidden'}` }, React.createElement(PHXHelpText, { helpText: helpText })))); } //# sourceMappingURL=SelectWithAction.js.map