UNPKG

@codeworker.br/govbr-tw-react

Version:

Biblioteca de componentes React usando Tailwind CSS que implementa o Padrão Digital de Governo.

195 lines (194 loc) 12.8 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Children, cloneElement, useEffect, useMemo, useState } from "react"; import BASE_CLASSNAMES from "../../config/baseClassNames"; import { cn } from "../../libs/utils"; import Input from "../Input"; import { Button } from "../Button"; import Checkbox from "../Checkbox"; import Radio from "../Radio"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronLeft, faChevronRight, faTrash, faXmark, } from "@fortawesome/free-solid-svg-icons"; const getNodeText = (node) => { var _a; if (node === null || node === undefined) return ""; if (typeof node === "string" || typeof node === "number") return String(node); if (Array.isArray(node)) { return node.map(getNodeText).join(" "); } if ((_a = node === null || node === void 0 ? void 0 : node.props) === null || _a === void 0 ? void 0 : _a.children) { return getNodeText(node.props.children); } return ""; }; const GroupOptions = (_a) => { var _b, _c, _d; var { children, pageSize = 8, filterPlaceholder = "Filtrar opcoes", emptyMessage = "Nenhuma opcao encontrada.", className, hint, density = "default", searchInputProps, previousButtonProps, nextButtonProps, clearButtonProps, clearable = true, clearSelectionButtonProps, clearSelection = true } = _a, props = __rest(_a, ["children", "pageSize", "filterPlaceholder", "emptyMessage", "className", "hint", "density", "searchInputProps", "previousButtonProps", "nextButtonProps", "clearButtonProps", "clearable", "clearSelectionButtonProps", "clearSelection"]); const [selection, setSelection] = useState({}); const allItems = useMemo(() => { return Children.toArray(children) .filter(Boolean) .map((child, index) => { var _a, _b, _c, _d, _e, _f, _g; const element = child; const key = String((_a = element.key) !== null && _a !== void 0 ? _a : `group-option-${index}`); const label = getNodeText((_b = element.props) === null || _b === void 0 ? void 0 : _b.children); const optionType = element.type === Checkbox ? "checkbox" : element.type === Radio ? "radio" : "other"; const isControlled = ((_c = element.props) === null || _c === void 0 ? void 0 : _c.checked) !== undefined; const initialChecked = optionType === "checkbox" ? Boolean((_g = (_e = (_d = element.props) === null || _d === void 0 ? void 0 : _d.checked) !== null && _e !== void 0 ? _e : (_f = element.props) === null || _f === void 0 ? void 0 : _f.defaultChecked) !== null && _g !== void 0 ? _g : false) : false; return { element, key, label, optionType, isControlled, initialChecked }; }); }, [children]); const [search, setSearch] = useState(""); const [page, setPage] = useState(1); const filteredItems = useMemo(() => { const normalized = search.trim().toLowerCase(); if (!normalized) return allItems; return allItems.filter(({ label }) => { return label.toLowerCase().includes(normalized); }); }, [allItems, search]); const totalPages = Math.max(1, Math.ceil(filteredItems.length / pageSize)); const safePage = Math.min(page, totalPages); const paginatedItems = useMemo(() => { const startIndex = (safePage - 1) * pageSize; return filteredItems.slice(startIndex, startIndex + pageSize); }, [filteredItems, pageSize, safePage]); const hasItems = filteredItems.length > 0; const displayCurrentPage = hasItems ? safePage : 0; const displayTotalPages = hasItems ? totalPages : 0; const selectionCount = useMemo(() => { return allItems.reduce((count, item) => { var _a, _b; if (item.optionType === "checkbox") { if (item.isControlled) { return ((_a = item.element.props) === null || _a === void 0 ? void 0 : _a.checked) ? count + 1 : count; } return selection[item.key] ? count + 1 : count; } if (item.optionType === "radio") { if (item.isControlled) { return ((_b = item.element.props) === null || _b === void 0 ? void 0 : _b.checked) ? count + 1 : count; } } return count; }, 0); }, [allItems, selection]); useEffect(() => { setSelection((prev) => { const next = Object.assign({}, prev); let changed = false; const keys = new Set(allItems.map((item) => item.key)); allItems.forEach((item) => { if (item.optionType !== "checkbox" || item.isControlled) { return; } if (!(item.key in next)) { next[item.key] = item.initialChecked; changed = true; } }); Object.keys(next).forEach((key) => { if (!keys.has(key)) { delete next[key]; changed = true; } }); return changed ? next : prev; }); }, [allItems]); useEffect(() => { setPage(1); }, [search, pageSize]); useEffect(() => { if (page !== safePage) { setPage(safePage); } }, [page, safePage]); const createSyntheticEvent = (checked, item) => { var _a, _b; const target = { checked, value: (_a = item.element.props) === null || _a === void 0 ? void 0 : _a.value, name: (_b = item.element.props) === null || _b === void 0 ? void 0 : _b.name, }; return { target, currentTarget: target, }; }; const handlePrev = () => setPage((prev) => Math.max(1, prev - 1)); const handleNext = () => setPage((prev) => Math.min(totalPages, prev + 1)); const handleClearSelection = () => { const nextSelection = Object.assign({}, selection); let shouldUpdateState = false; allItems.forEach((item) => { var _a, _b, _c, _d; if (item.optionType === "checkbox") { if (item.isControlled) { if (((_a = item.element.props) === null || _a === void 0 ? void 0 : _a.checked) && ((_b = item.element.props) === null || _b === void 0 ? void 0 : _b.onChange)) { item.element.props.onChange(createSyntheticEvent(false, item)); } } else if (item.key in nextSelection) { if (nextSelection[item.key]) { nextSelection[item.key] = false; shouldUpdateState = true; } } } else if (item.optionType === "radio") { if (item.isControlled && ((_c = item.element.props) === null || _c === void 0 ? void 0 : _c.checked) && ((_d = item.element.props) === null || _d === void 0 ? void 0 : _d.onChange)) { item.element.props.onChange(createSyntheticEvent(false, item)); } } }); if (shouldUpdateState) { setSelection(nextSelection); } }; const _e = previousButtonProps !== null && previousButtonProps !== void 0 ? previousButtonProps : {}, { children: previousChildren } = _e, previousRest = __rest(_e, ["children"]); const _f = nextButtonProps !== null && nextButtonProps !== void 0 ? nextButtonProps : {}, { children: nextChildren } = _f, nextRest = __rest(_f, ["children"]); const _g = clearButtonProps !== null && clearButtonProps !== void 0 ? clearButtonProps : {}, { children: clearChildren } = _g, clearRest = __rest(_g, ["children"]); const _h = clearSelectionButtonProps !== null && clearSelectionButtonProps !== void 0 ? clearSelectionButtonProps : {}, { children: clearSelectionChildren } = _h, clearSelectionRest = __rest(_h, ["children"]); const buttonDensity = (density === "lowest" ? "low" : density); return (_jsxs("div", Object.assign({ className: cn("flex w-full flex-col gap-4", (_b = BASE_CLASSNAMES.groupOptions) === null || _b === void 0 ? void 0 : _b.root, className) }, props, { children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "flex-1", children: _jsx(Input, Object.assign({ value: search, onChange: (event) => setSearch(event.target.value), placeholder: filterPlaceholder, density: density, hint: hint }, searchInputProps)) }), _jsxs("div", { className: "flex items-center gap-2", children: [clearable && (_jsx(Button, Object.assign({ size: "icon", variant: "ghost", density: buttonDensity, onClick: () => setSearch(""), disabled: search.length === 0, "aria-label": "Limpar filtro" }, clearRest, { children: clearChildren !== null && clearChildren !== void 0 ? clearChildren : _jsx(FontAwesomeIcon, { icon: faXmark }) }))), clearSelection && (_jsxs("div", { className: "relative", children: [_jsx(Button, Object.assign({ size: "icon", variant: "ghost", density: buttonDensity, onClick: handleClearSelection, disabled: selectionCount === 0, "aria-label": "Limpar selecao" }, clearSelectionRest, { children: clearSelectionChildren !== null && clearSelectionChildren !== void 0 ? clearSelectionChildren : _jsx(FontAwesomeIcon, { icon: faTrash }) })), selectionCount > 0 && (_jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-[1.25rem] items-center justify-center rounded-full bg-govbr-red-vivid-50 px-1 text-[0.625rem] font-semibold text-govbr-pure-0", children: selectionCount > 99 ? "99+" : selectionCount }))] }))] })] }), _jsx("div", { className: cn("flex max-h-80 flex-col gap-3 overflow-y-auto pr-1", (_c = BASE_CLASSNAMES.groupOptions) === null || _c === void 0 ? void 0 : _c.list), children: paginatedItems.length > 0 ? (paginatedItems.map((item) => { var _a; const { element, key, optionType, isControlled } = item; const shouldControl = optionType === "checkbox" && !isControlled && key in selection; const augmentedElement = shouldControl ? cloneElement(element, { checked: (_a = selection[key]) !== null && _a !== void 0 ? _a : false, defaultChecked: undefined, onChange: (event) => { var _a, _b; setSelection((prev) => (Object.assign(Object.assign({}, prev), { [key]: event.target.checked }))); (_b = (_a = element.props) === null || _a === void 0 ? void 0 : _a.onChange) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, }) : element; return (_jsx("div", { className: "w-full", children: augmentedElement }, key)); })) : (_jsx("span", { className: "text-sm text-govbr-gray-60", children: emptyMessage })) }), _jsxs("div", { className: cn("flex items-center justify-between gap-3", (_d = BASE_CLASSNAMES.groupOptions) === null || _d === void 0 ? void 0 : _d.footer), children: [_jsxs("span", { className: "text-xs text-govbr-gray-60", children: ["Pagina ", displayCurrentPage, " de ", displayTotalPages] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, Object.assign({ variant: "ghost", size: "icon", density: buttonDensity, onClick: handlePrev, disabled: safePage === 1 || filteredItems.length === 0 }, previousRest, { children: previousChildren !== null && previousChildren !== void 0 ? previousChildren : _jsx(FontAwesomeIcon, { icon: faChevronLeft }) })), _jsx(Button, Object.assign({ variant: "ghost", size: "icon", density: buttonDensity, onClick: handleNext, disabled: safePage === totalPages || filteredItems.length === 0 }, nextRest, { children: nextChildren !== null && nextChildren !== void 0 ? nextChildren : _jsx(FontAwesomeIcon, { icon: faChevronRight }) }))] })] })] }))); }; export default GroupOptions;