UNPKG

pagamio-frontend-commons-lib

Version:

Pagamio library for Frontend reusable components like the form engine and table container

63 lines (62 loc) 4.73 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Checkbox, Radio } from 'flowbite-react'; import { FiChevronLeft, FiChevronRight } from 'react-icons/fi'; import { RiSearchLine } from 'react-icons/ri'; import { useMemo, useState } from 'react'; /** * FilterList component for displaying filterable, searchable lists of options * Supports both single-select (radio buttons) and multi-select (checkboxes) * Generic type T extends SelectOption to allow for custom option types * * @example * ```tsx * // Single-select mode * <FilterList * options={categoryOptions} * selectedValue={filters.categoryId} * name="category" * onChange={handleCategoryChange} * idPrefix="category" * /> * * // Multi-select mode * <FilterList * options={locationOptions} * selectedValues={filters.locations} * isMultiSelect={true} * onChange={handleLocationChange} * idPrefix="location" * /> * ``` */ const FilterList = ({ options, selectedValue, selectedValues = [], name, isMultiSelect = false, onChange, idPrefix, itemsPerPage = 10, }) => { const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); const filteredOptions = useMemo(() => { return options?.filter((option) => option.label.toLowerCase().includes(searchQuery.toLowerCase())) ?? []; }, [options, searchQuery]); const totalPages = Math.ceil(filteredOptions?.length / itemsPerPage); const displayedOptions = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return filteredOptions?.slice(startIndex, startIndex + itemsPerPage) ?? []; }, [filteredOptions, currentPage, itemsPerPage]); const handleNextPage = () => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }; const handlePrevPage = () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); } }; const handleChange = (optionValue) => { onChange(optionValue); }; return (_jsxs("div", { children: [_jsxs("div", { className: "relative mb-3", children: [_jsx("div", { className: "pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3", children: _jsx(RiSearchLine, { className: "text-gray-500", "aria-hidden": "true" }) }), _jsx("input", { type: "text", className: "block w-full rounded-lg border border-gray-300 bg-white p-2 pl-10 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500", placeholder: `Search ${isMultiSelect ? 'locations' : ''}...`, value: searchQuery, onChange: (e) => { setSearchQuery(e.target.value); setCurrentPage(1); }, "aria-label": `Search ${isMultiSelect ? 'locations' : 'options'}` })] }), _jsx("fieldset", { className: "max-h-100 space-y-1 overflow-y-auto", children: displayedOptions?.length > 0 ? (displayedOptions.map((option) => (_jsxs("div", { className: "flex items-center rounded-md p-1 transition-colors hover:bg-primary-50", children: [isMultiSelect ? (_jsx(Checkbox, { id: `${idPrefix}-${option.value}`, checked: selectedValues.includes(option.value), onChange: () => handleChange(option.value), className: "text-primary-600 focus:ring-primary-500" })) : (_jsx(Radio, { id: `${idPrefix}-${option.value}`, name: name, value: option.value, checked: selectedValue === option.value, onChange: () => handleChange(option.value), className: "text-primary-600 focus:ring-primary-500" })), _jsx("label", { htmlFor: `${idPrefix}-${option.value}`, className: "ml-2 text-sm font-medium text-gray-900", children: option.label })] }, `${option.value}`)))) : (_jsx("p", { className: "text-sm text-gray-500", children: "No options found" })) }), filteredOptions?.length > itemsPerPage && (_jsxs("div", { className: "mt-3 flex items-center justify-between text-sm", children: [_jsxs("button", { onClick: handlePrevPage, disabled: currentPage === 1, className: `flex items-center ${currentPage === 1 ? 'cursor-not-allowed text-gray-300' : 'text-primary-600 hover:text-primary-700'}`, "aria-label": "Previous page", children: [_jsx(FiChevronLeft, { className: "mr-1", "aria-hidden": "true" }), "Prev"] }), _jsxs("span", { className: "text-xs text-gray-500", children: ["Page ", currentPage, " of ", totalPages] }), _jsxs("button", { onClick: handleNextPage, disabled: currentPage >= totalPages, className: `flex items-center ${currentPage >= totalPages ? 'cursor-not-allowed text-gray-300' : 'text-primary-600 hover:text-primary-700'}`, "aria-label": "Next page", children: ["Next", _jsx(FiChevronRight, { className: "ml-1", "aria-hidden": "true" })] })] }))] })); }; export default FilterList;