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
JavaScript
'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;