@coreui/react-pro
Version:
UI Components Library for React.js
140 lines (137 loc) • 6.74 kB
JavaScript
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