wix-style-react
Version:
wix-style-react
117 lines • 6.26 kB
JavaScript
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