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