UNPKG

@codeworker.br/govbr-tw-react

Version:

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

237 lines (236 loc) 13.3 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 React, { cloneElement, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { faChevronDown, faChevronRight, faFolder, faFolderOpen, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import BASE_CLASSNAMES from "../../config/baseClassNames"; import { cn } from "../../libs/utils"; import { treeIconWrapperVariants, treeItemVariants, treeLabelVariants, treeToggleVariants, treeVariants, } from "./variants"; const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; const TreeConfigContext = React.createContext(undefined); const TreeLevelContext = React.createContext(0); const useTreeConfig = () => { const context = useContext(TreeConfigContext); if (!context) { throw new Error("Tree components must be used within <Tree>."); } return context; }; const useTreeLevel = () => useContext(TreeLevelContext); const defaultToggleIcon = ({ expanded, iconClassName }) => (_jsx(FontAwesomeIcon, { icon: expanded ? faChevronDown : faChevronRight, className: iconClassName })); const Tree = (_a) => { var { density = "default", variant = "default", iconClassName = "text-govbr-blue-warm-vivid-70 text-lg", indent = 16, renderToggleIcon = defaultToggleIcon, renderItem, branchCollapsedIcon = faFolder, branchExpandedIcon = faFolderOpen, className, children } = _a, props = __rest(_a, ["density", "variant", "iconClassName", "indent", "renderToggleIcon", "renderItem", "branchCollapsedIcon", "branchExpandedIcon", "className", "children"]); const resolvedDensity = density !== null && density !== void 0 ? density : "default"; const resolvedVariant = variant !== null && variant !== void 0 ? variant : "default"; const contextValue = useMemo(() => ({ density: resolvedDensity, variant: resolvedVariant, iconClassName, indent, renderToggleIcon, renderItem, branchCollapsedIcon, branchExpandedIcon, }), [ resolvedDensity, resolvedVariant, iconClassName, indent, renderToggleIcon, renderItem, branchCollapsedIcon, branchExpandedIcon, ]); return (_jsx(TreeConfigContext.Provider, { value: contextValue, children: _jsx(TreeLevelContext.Provider, { value: 0, children: _jsx("ul", Object.assign({ role: "tree", className: cn(treeVariants({ density: resolvedDensity, variant: resolvedVariant }), BASE_CLASSNAMES.tree.root, className) }, props, { children: children })) }) })); }; const renderIcon = (icon, iconClassName) => { if (!icon) { return null; } if (typeof icon === "function") { return icon(iconClassName); } if (isValidElement(icon)) { return cloneElement(icon, { className: cn(icon.props.className, iconClassName), }); } if (typeof icon === "object" && icon !== null && "iconName" in icon && "prefix" in icon) { return _jsx(FontAwesomeIcon, { icon: icon, className: iconClassName }); } return icon; }; const TreeBranch = (_a) => { var _b, _c, _d, _e; var { label, children, defaultExpanded = false, expanded, onExpandedChange, onToggle, toggleIconClassName, icon, iconCollapsed, iconExpanded, iconClassName, labelClassName, toggleIcon, onSelect, disabled = false, expandOnLabelClick = true, renderItem, className } = _a, props = __rest(_a, ["label", "children", "defaultExpanded", "expanded", "onExpandedChange", "onToggle", "toggleIconClassName", "icon", "iconCollapsed", "iconExpanded", "iconClassName", "labelClassName", "toggleIcon", "onSelect", "disabled", "expandOnLabelClick", "renderItem", "className"]); const { density, variant, iconClassName: contextIconClass, indent, renderToggleIcon, renderItem: contextRenderItem, branchCollapsedIcon: contextBranchCollapsedIcon, branchExpandedIcon: contextBranchExpandedIcon, } = useTreeConfig(); const level = useTreeLevel(); const isControlled = expanded !== undefined; const [internalExpanded, setInternalExpanded] = useState(defaultExpanded); const isExpanded = isControlled ? !!expanded : internalExpanded; const hasChildren = React.Children.count(children) > 0; const contentRef = useRef(null); const [contentHeight, setContentHeight] = useState(0); const handleToggle = useCallback((event) => { if (disabled) { return; } const next = !isExpanded; if (!isControlled) { setInternalExpanded(next); } onExpandedChange === null || onExpandedChange === void 0 ? void 0 : onExpandedChange(next); onToggle === null || onToggle === void 0 ? void 0 : onToggle(next); if (expandOnLabelClick && event) { event.stopPropagation(); } }, [ disabled, isExpanded, isControlled, onExpandedChange, onToggle, expandOnLabelClick, ]); const measureHeight = useCallback(() => { const node = contentRef.current; if (!node) { return; } const previous = node.style.maxHeight; node.style.maxHeight = "none"; const height = node.scrollHeight; node.style.maxHeight = previous; setContentHeight(height); }, []); useIsomorphicLayoutEffect(() => { if (!hasChildren) { setContentHeight(0); return; } measureHeight(); }, [measureHeight, hasChildren, children, isExpanded, density]); useEffect(() => { if (!hasChildren || typeof window === "undefined") { return; } const handleResize = () => measureHeight(); window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, [hasChildren, measureHeight]); const paddingStyle = level > 0 ? { marginLeft: level * indent } : {}; const mergedToggleIconClass = cn(contextIconClass, toggleIconClassName); const mergedItemIconClass = cn(contextIconClass, iconClassName); const toggleRenderer = toggleIcon !== null && toggleIcon !== void 0 ? toggleIcon : renderToggleIcon; const labelContent = (_jsx("span", { className: cn(treeLabelVariants({ variant }), labelClassName, disabled ? "opacity-60" : undefined), children: label })); const collapsedIcon = (_b = iconCollapsed !== null && iconCollapsed !== void 0 ? iconCollapsed : icon) !== null && _b !== void 0 ? _b : (hasChildren ? contextBranchCollapsedIcon : undefined); const expandedIcon = (_c = iconExpanded !== null && iconExpanded !== void 0 ? iconExpanded : icon) !== null && _c !== void 0 ? _c : (hasChildren ? contextBranchExpandedIcon : undefined); const collapsedIconNode = renderIcon(collapsedIcon, mergedItemIconClass); const expandedIconNode = renderIcon(expandedIcon, mergedItemIconClass); const iconNode = isExpanded ? expandedIconNode !== null && expandedIconNode !== void 0 ? expandedIconNode : collapsedIconNode : collapsedIconNode !== null && collapsedIconNode !== void 0 ? collapsedIconNode : expandedIconNode; const toggleIconNode = toggleRenderer({ expanded: isExpanded, iconClassName: mergedToggleIconClass, }); const content = (_jsxs("div", { className: cn(treeItemVariants({ variant, density, disabled }), BASE_CLASSNAMES.tree.item, className), style: paddingStyle, children: [_jsx("button", { type: "button", className: cn(treeToggleVariants({ variant, density }), BASE_CLASSNAMES.tree.toggle), onClick: handleToggle, disabled: disabled, "aria-label": isExpanded ? "Recolher" : "Expandir", children: toggleIconNode }), _jsx("span", { className: cn(treeIconWrapperVariants({ density }), BASE_CLASSNAMES.tree.icon), children: iconNode }), onSelect ? (_jsx("button", { type: "button", className: "flex flex-1 items-center gap-2 text-left", onClick: (event) => { if (expandOnLabelClick) { handleToggle(event); } onSelect(event); }, disabled: disabled, children: labelContent })) : (_jsx("span", { className: "flex flex-1 items-center gap-2", onClick: (event) => { if (expandOnLabelClick) { handleToggle(event); } }, children: labelContent }))] })); const group = hasChildren ? (_jsx(TreeLevelContext.Provider, { value: level + 1, children: _jsx("ul", { ref: contentRef, role: "group", "data-expanded": isExpanded, className: cn("flex flex-col overflow-hidden transition-all duration-250 ease-in-out", isExpanded ? "opacity-100" : "pointer-events-none opacity-0", BASE_CLASSNAMES.tree.group), style: { maxHeight: isExpanded ? contentHeight ? `${contentHeight}px` : "999px" : "0px", opacity: isExpanded ? 1 : 0, willChange: "max-height, opacity", }, "aria-hidden": !isExpanded, children: children }) })) : null; const containerProps = Object.assign({ role: "treeitem", "aria-expanded": isExpanded, className: "list-none" }, props); const defaultNode = (_jsxs("li", Object.assign({}, containerProps, { children: [content, group] }))); const renderItemFn = (_d = renderItem) !== null && _d !== void 0 ? _d : contextRenderItem; const rendered = (_e = renderItemFn === null || renderItemFn === void 0 ? void 0 : renderItemFn({ type: "branch", level, label, icon, collapsedIcon, expandedIcon, iconClassName: mergedItemIconClass, iconNode, collapsedIconNode, expandedIconNode, toggleIconNode, defaultNode, expanded: isExpanded, hasChildren, disabled, toggle: handleToggle, onSelect, density, variant, content, group, containerProps, })) !== null && _e !== void 0 ? _e : null; return rendered !== null && rendered !== void 0 ? rendered : defaultNode; }; const TreeLeaf = (_a) => { var _b, _c; var { label, icon, iconClassName, labelClassName, onSelect, disabled = false, renderItem, className } = _a, props = __rest(_a, ["label", "icon", "iconClassName", "labelClassName", "onSelect", "disabled", "renderItem", "className"]); const { density, variant, iconClassName: contextIconClass, indent, renderItem: contextRenderItem, } = useTreeConfig(); const level = useTreeLevel(); const paddingStyle = level > 0 ? { marginLeft: level * indent } : {}; const mergedIconClass = cn(contextIconClass, iconClassName); const labelContent = (_jsx("span", { className: cn(treeLabelVariants({ variant }), labelClassName, disabled ? "opacity-60" : undefined), children: label })); const iconNode = renderIcon(icon, mergedIconClass); const content = (_jsxs("div", { className: cn(treeItemVariants({ variant, density, disabled }), BASE_CLASSNAMES.tree.item, className), style: paddingStyle, children: [_jsx("span", { className: cn(treeToggleVariants({ variant, density }), "pointer-events-none opacity-0", BASE_CLASSNAMES.tree.toggle), "aria-hidden": "true" }), _jsx("span", { className: cn(treeIconWrapperVariants({ density }), BASE_CLASSNAMES.tree.icon), children: iconNode }), onSelect ? (_jsx("button", { type: "button", className: "flex flex-1 items-center gap-2 text-left", onClick: onSelect, disabled: disabled, children: labelContent })) : (_jsx("span", { className: "flex flex-1 items-center gap-2", children: labelContent }))] })); const containerProps = Object.assign({ role: "treeitem", "aria-expanded": false, className: "list-none" }, props); const defaultNode = (_jsx("li", Object.assign({}, containerProps, { children: content }))); const renderItemFn = (_b = renderItem) !== null && _b !== void 0 ? _b : contextRenderItem; const rendered = (_c = renderItemFn === null || renderItemFn === void 0 ? void 0 : renderItemFn({ type: "leaf", level, label, icon, iconClassName: mergedIconClass, defaultNode, disabled, onSelect, density, variant, iconNode, content, containerProps, })) !== null && _c !== void 0 ? _c : null; return rendered !== null && rendered !== void 0 ? rendered : defaultNode; }; TreeBranch.displayName = "TreeBranch"; TreeLeaf.displayName = "TreeLeaf"; const TreeNamespace = Object.assign(Tree, { Branch: TreeBranch, Leaf: TreeLeaf, }); export { TreeNamespace as Tree };