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