UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

140 lines (137 loc) 6.74 kB
import { __rest } from '../../node_modules/tslib/tslib.es6.js'; import React, { forwardRef, useId, useContext, useRef, useState, useEffect, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import classNames from '../../_virtual/index.js'; import { CNavGroupContext } from './CNavGroupContext.js'; import Transition from '../../node_modules/react-transition-group/esm/Transition.js'; const CNavGroup = forwardRef((_a, ref) => { var { children, as: Component = 'li', className, compact, onVisibleChange, toggler, visible } = _a, rest = __rest(_a, ["children", "as", "className", "compact", "onVisibleChange", "toggler", "visible"]); const id = useId(); // Accordion of the parent level: lets this group read whether it is the open one and request // its own open/close. `null` when rendered outside a `CSidebarNav`. const parentContext = useContext(CNavGroupContext); const parentContextRef = useRef(parentContext); parentContextRef.current = parentContext; const [height, setHeight] = useState(0); // eslint-disable-next-line @typescript-eslint/no-explicit-any const navItemsRef = useRef(null); const [uncontrolledVisible, setUncontrolledVisible] = useState(Boolean(visible)); // This group's own accordion state for its children. It survives the group being collapsed, // so reopening the group restores whichever child was previously open. const [activeChildId, setActiveChildId] = useState(); const controlled = visible !== undefined && onVisibleChange !== undefined; const _visible = controlled ? Boolean(visible) : parentContext ? parentContext.activeId === id : uncontrolledVisible; // Sync the accordion with the `visible` prop: seeds default-open groups and follows later // changes (and keeps controlled groups in sync). Watching only `visible` keeps this from // re-running on unrelated accordion updates—so a default-open group can be collapsed manually. useEffect(() => { var _a; if (visible === undefined) { return; } const parentSetActiveId = (_a = parentContextRef.current) === null || _a === void 0 ? void 0 : _a.setActiveId; if (parentSetActiveId) { if (visible) { parentSetActiveId(id); } else { parentSetActiveId((prev) => (prev === id ? undefined : prev)); } } else { setUncontrolledVisible(visible); } }, [visible, id]); // Accordion: when another branch opens, a controlled group must close too. As its visibility // is owned by the parent, request the change through `onVisibleChange`. useEffect(() => { if (!controlled || !visible) { return; } const activeId = parentContext === null || parentContext === void 0 ? void 0 : parentContext.activeId; if (activeId !== undefined && activeId !== id) { onVisibleChange === null || onVisibleChange === void 0 ? void 0 : onVisibleChange(false); } }, [controlled, visible, parentContext === null || parentContext === void 0 ? void 0 : parentContext.activeId, id, onVisibleChange]); // Open this group within its parent level and cascade up to the root, revealing the whole // branch. Used by an active nav link and by nested groups to bubble the request upwards. const openBranch = useCallback(() => { const parent = parentContextRef.current; if (!parent) { return; } parent.setActiveId(id); parent.openBranch(); }, [id]); const childContextValue = useMemo(() => ({ activeId: activeChildId, setActiveId: setActiveChildId, openBranch }), [activeChildId, openBranch]); const handleTogglerOnClick = (event) => { event.preventDefault(); const next = !_visible; if (!controlled) { if (parentContext) { parentContext.setActiveId(next ? id : undefined); } else { setUncontrolledVisible(next); } } onVisibleChange === null || onVisibleChange === void 0 ? void 0 : onVisibleChange(next); }; const style = { height: 0, }; const onEntering = () => { if (navItemsRef.current) { setHeight(navItemsRef.current.scrollHeight); } }; const onEntered = () => { setHeight('auto'); }; const onExit = () => { if (navItemsRef.current) { setHeight(navItemsRef.current.scrollHeight); } }; const onExiting = () => { var _a; // @ts-expect-error reflow is necessary to get correct height of the element // eslint-disable-next-line @typescript-eslint/no-unused-vars (_a = navItemsRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight; setHeight(0); }; const onExited = () => { setHeight(0); }; const transitionStyles = { entering: { display: 'block', height: height }, entered: { display: 'block', height: height }, exiting: { display: 'block', height: height }, exited: { height: height }, unmounted: {}, }; const NavGroupItemsComponent = Component === 'li' ? 'ul' : 'div'; const togglerContent = typeof toggler === 'function' ? toggler({ visible: _visible }) : toggler; return (React.createElement(CNavGroupContext.Provider, { value: childContextValue }, React.createElement(Component, Object.assign({ className: classNames('nav-group', { show: _visible }, className) }, rest, { ref: ref }), toggler && (React.createElement("a", { "aria-expanded": _visible, className: "nav-link nav-group-toggle", href: "#", onClick: handleTogglerOnClick }, togglerContent)), React.createElement(Transition, { appear: true, in: _visible, nodeRef: navItemsRef, onEntering: onEntering, onEntered: onEntered, onExit: onExit, onExiting: onExiting, onExited: onExited, timeout: 300 }, (state) => (React.createElement(NavGroupItemsComponent, { className: classNames('nav-group-items', { compact: compact, }), style: Object.assign(Object.assign({}, style), transitionStyles[state]), ref: navItemsRef }, children)))))); }); CNavGroup.propTypes = { as: PropTypes.elementType, children: PropTypes.node, className: PropTypes.string, compact: PropTypes.bool, onVisibleChange: PropTypes.func, toggler: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), visible: PropTypes.bool, }; CNavGroup.displayName = 'CNavGroup'; export { CNavGroup }; //# sourceMappingURL=CNavGroup.js.map