UNPKG

wix-style-react

Version:
117 lines 6.26 kB
import React, { useContext, useCallback, useState, useRef, useEffect, } from 'react'; import { SidebarNextContext } from '../../SidebarNext/SidebarNextContext'; import SidebarItemButtonNext from '../../SidebarItemNext/SidebarItemButtonNext'; import { dataHooks, SIDEBAR_SUBMENU_ANIMATION_TYPES, getCompoundDataHook, } from '../constants'; import { st, classes } from '../SidebarSubMenuNext.st.css'; import { ContextMenuPopover, } from './ContextMenuPopover'; import { usePrevious } from './hooks'; import { getSuffixWithChevron } from './common'; export const AccordionMenu = ({ title, suffix, prefix, children, dataHook, itemKey, disabled, onClick, onExpand, onCollapse, onQuickNavOpen, ...asComponentProps }) => { const context = useContext(SidebarNextContext); const expanded = !disabled && context.isExpanded(itemKey); const selected = context.selectedPath.includes(itemKey); // We differentiate between logical and visual expanded states: // - logical state refers to collapsed/expanded state due to user interaction // (e.g. clicking on a sub menu item, or external state change on // SidebarNext `selectedKey` prop which causes the sub menu to expand) // - visual state refers to logical state + sidebar minimization state. When // the sidebar is minimized, all sub menu items are visually collapsed, but // the logical state is preserved and restored when the sidebar is no // longer minimized. const visuallyExpanded = context.minimized ? false : expanded; const expandAnimationState = useExpandAnimationState(visuallyExpanded); // TODO: we report the visual state changes, is this the desired behavior? useCallOnExpandedStateChange(visuallyExpanded, context.expandTrigger, onExpand, onCollapse); const handleClick = useCallback(event => { if (selected) { context.setExpanded(itemKey, !expanded); event.preventDefault(); } // @ts-ignore onClick?.(event, itemKey); // FIXME: this was never typed but implemented originally }, // eslint-disable-next-line react-hooks/exhaustive-deps [context.setExpanded, itemKey, expanded, selected, onClick]); const [quickNavOpen, setQuickNavOpen] = useState(false); // only for data attr const quickNavHandleRef = useRef(null); const closeQuickNav = useCallback(() => { quickNavHandleRef.current?.hide(); }, []); const handleQuickNavShow = () => { setQuickNavOpen(true); onQuickNavOpen?.(); }; const handleQuickNavHide = () => { setQuickNavOpen(false); }; let triggerElement = (React.createElement(SidebarItemButtonNext, { prefix: prefix, suffix: getSuffixWithChevron(suffix), onClick: handleClick, disabled: disabled, itemKey: itemKey, dataHook: getCompoundDataHook(dataHook, dataHooks.sidebarItem), className: st(classes.subMenu, { selected, animation: expandAnimationState, // animationWithDelay: shouldNextMenuOpenWithDelay, }), "aria-expanded": visuallyExpanded, ...asComponentProps }, title)); if (React.Children.count(children) > 0) { triggerElement = (React.createElement(ContextMenuPopover, { dataHook: getCompoundDataHook(dataHook, dataHooks.submenuPopover), ref: quickNavHandleRef, element: triggerElement, content: React.createElement(SidebarNextContext.Provider, { value: { ...context, level: context.level + 1, parent: 'inContextMenu', inert: true, // popover items are not focusable isParentWithPrefix: !!prefix, closeQuickNav, minimized: false, } }, children), disabled: disabled || visuallyExpanded, onShow: handleQuickNavShow, onHide: handleQuickNavHide })); } return (React.createElement("li", { className: st(classes.root, { skin: context.skin }), "aria-level": context.level, "data-hook": dataHook, "data-open": visuallyExpanded, "data-quick-nav-open": quickNavOpen }, triggerElement, React.createElement("ul", { className: st(classes.subMenuItems, { animation: expandAnimationState, // animationWithDelay: shouldNextMenuOpenWithDelay, }), "aria-hidden": !visuallyExpanded }, React.createElement(SidebarNextContext.Provider, { value: { ...context, level: context.level + 1, parent: 'subMenu', inert: context.inert || !visuallyExpanded, isParentWithPrefix: !!prefix, } }, children)))); }; export const useCallOnExpandedStateChange = (expanded, expandTrigger, onExpand, onCollapse) => { const stateRef = useRef(null); if (stateRef.current === null) { stateRef.current = { expanded: false }; } useEffect(() => { const state = stateRef.current; state.expandTrigger = expandTrigger; state.onExpand = onExpand; state.onCollapse = onCollapse; }); useEffect(() => { const state = stateRef.current; if (state.expanded !== expanded) { // The desired behavior is to only fire callbacks on state changes, and // not when the callbacks themselves change. Which means that we need to // exclude them from the dependency array and use a ref to access the // latest callbacks. state.expanded = expanded; if (expanded) { state.onExpand?.(state.expandTrigger); } else { state.onCollapse?.(state.expandTrigger); } } }, [expanded]); }; export const useExpandAnimationState = (expanded) => { // FIXME: this causes double render, maybe we can get rid of it somehow? const prevExpanded = usePrevious(expanded, false); if (prevExpanded !== expanded) { return expanded ? SIDEBAR_SUBMENU_ANIMATION_TYPES.OPENING : SIDEBAR_SUBMENU_ANIMATION_TYPES.CLOSING; } return expanded ? SIDEBAR_SUBMENU_ANIMATION_TYPES.OPENING : SIDEBAR_SUBMENU_ANIMATION_TYPES.CLOSED; }; //# sourceMappingURL=AccordionMenu.js.map