UNPKG

wix-style-react

Version:
159 lines 7.85 kB
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import isEqual from 'lodash/isEqual'; import { ToggleButton } from './components/ToggleButton'; import { SidebarNextContext, } from './SidebarNextContext'; import { st, classes } from './SidebarNext.st.css'; import { dataHooks, sidebarSkins } from './constants'; import SidebarSkeleton from './components/SidebarSkeleton'; import { extractItemNodes, getItemPath, getMaxDepth, getMenuPath, pick, } from './utils'; import { useMemoizedIdentity } from './hooks/useMemoizedIdentity'; const DEFAULT_SKIN = sidebarSkins.dark; const DEFAULT_WIDTH = 228; const MINIMIZED_WIDTH = 54; export const SidebarNext = memo(({ skin = DEFAULT_SKIN, width: orgWidth = DEFAULT_WIDTH, isLoading = true, dataHook, className, ariaLabel, selectedKey, header, footer, hidden = false, minimized = false, enableMinimizeToggle, minimizeToggleTooltip, onMinimizeToggleClick, zIndex, children, }) => { const [width, setWidth] = useState(orgWidth); const [contentWidth, setContentWidth] = useState(orgWidth); const scrollAreaRef = useRef(null); const structure = useMemoizedIdentity(extractItemNodes(children)); const widthForCalc = typeof width === 'number' ? `${width}px` : width; const hiddenMarginLeft = `calc(-1 * ${widthForCalc})`; const { selectedPath, expandTrigger, isExpanded, setExpanded } = useSidebarSelectionState(selectedKey, structure); const maxDepth = useMemo(() => getMaxDepth(structure.items), [structure.items]); // Sidebar with maximum depth of 1 or 2 is styled differently (legacy mode) const legacy = maxDepth < 3; useEffect(() => { let timer; if (minimized) { setWidth(MINIMIZED_WIDTH); timer = setTimeout(() => { setContentWidth(MINIMIZED_WIDTH); }, 500); } else { setWidth(orgWidth); setContentWidth(orgWidth); } return () => { if (timer) clearTimeout(timer); }; }, [minimized, orgWidth]); const sidebarContextValue = useMemoizedIdentity({ skin, legacy, hidden, minimized, inert: false, level: 1, parent: 'sidebar', scrollAreaRef, selectedPath, expandTrigger, isExpanded, setExpanded, }); return (React.createElement(SidebarNextContext.Provider, { value: sidebarContextValue }, React.createElement("div", { className: st(classes.root, { hidden }, className), style: { width, zIndex, ...(hidden ? { marginLeft: hiddenMarginLeft } : {}), } }, React.createElement("section", { className: st(classes.section, { skin, hidden }), "aria-label": "Sidebar" // FIXME: translations? , "data-hook": dataHook, "data-skin": skin, "data-hidden": hidden, "data-selected": selectedKey, "data-width": width, "data-minimized": minimized, "data-selected-key": selectedKey, "data-is-selected-expanded": isExpanded(selectedKey), "data-margin-left": hiddenMarginLeft, style: { width } }, enableMinimizeToggle && (React.createElement(ToggleButton, { minimized: minimized, minimizeToggleTooltip: minimizeToggleTooltip, onMinimizeToggleClick: onMinimizeToggleClick, skin: skin })), React.createElement("header", { "data-hook": dataHooks.header }, header), React.createElement("div", { className: classes.content }, React.createElement("nav", { ref: scrollAreaRef, "data-hook": dataHooks.scrollArea, className: st(classes.scrollArea, { skin }), "aria-label": ariaLabel, style: { width: contentWidth } }, isLoading ? (React.createElement("div", { // ref={scrollAreaContentRef} style: { overflow: 'hidden' } }, React.createElement(SidebarSkeleton, null))) : (React.createElement("ul", { // ref={scrollAreaContentRef} className: classes.listItems }, children)))), React.createElement("footer", { "data-hook": dataHooks.footer }, footer))))); }); const useSidebarSelectionState = (selectedKey, structure) => { const selectedItemPath = getItemPath(structure.keyToParentKey, selectedKey); const selectedMenuPath = getMenuPath(structure.keyToParentKey, selectedKey); const [expandedState, setExpandedState] = useState(() => ({ expandedPath: selectedMenuPath, userCollapsedKeys: {}, trigger: 'select', })); const isExpanded = useCallback((itemKey) => { if (itemKey === undefined) { return false; } const withinExpandedPath = expandedState.expandedPath.includes(itemKey); const collapsedByUser = expandedState.userCollapsedKeys[itemKey]; return withinExpandedPath && !collapsedByUser; }, [expandedState.expandedPath, expandedState.userCollapsedKeys]); // We only allow toggling menus that are within the selected path. Menus // outside of the selected path can only be expanded by changing `selectedKey` // prop. This replicates previous two-level sidebar behavior. const setExpanded = useCallback((itemKey, expanded) => { setExpandedState(prev => { if (!prev.expandedPath.includes(itemKey)) { return prev; } return { ...prev, userCollapsedKeys: { ...prev.userCollapsedKeys, [itemKey]: !expanded, }, trigger: 'click', }; }); }, []); // Handle `selectedKey` change useEffect(() => { if (!isEqual(expandedState.expandedPath, selectedMenuPath)) { setExpandedState(prev => ({ expandedPath: selectedMenuPath, userCollapsedKeys: pick(prev.userCollapsedKeys, selectedMenuPath), trigger: 'select', })); } }, [expandedState.expandedPath, selectedMenuPath]); return { selectedPath: selectedItemPath, expandTrigger: expandedState.trigger, isExpanded, setExpanded, }; }; SidebarNext.displayName = 'SidebarNext'; // FIXME: prop types on memoized component // SidebarNext.propTypes = { // /** The dataHook of the Sidebar */ // dataHook: PropTypes.string, // /** The classes of the Sidebar */ // className: PropTypes.string, // /** Sidebar menu children */ // children: PropTypes.node, // /** Sets the skin of the Sidebar */ // skin: PropTypes.oneOf(['dark', 'light', 'neutral']), // /** Renders header element */ // header: PropTypes.node, // /** Renders footer element */ // footer: PropTypes.node, // /** Controls if the sidebar is shown or hidden, using an animation to switch between the two */ // hidden: PropTypes.bool, // /** The selected item key */ // selectedKey: PropTypes.string, // /** Control whether the sidebar displays a loading animation, true by default */ // isLoading: PropTypes.bool, // /** Define a string that labels the current element in case where a text label is not visible on the screen */ // ariaLabel: PropTypes.string, // /** Defines the width */ // width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // /** Enables minimized mode */ // minimized: PropTypes.bool, // /** Enables toggle button */ // enableMinimizeToggle: PropTypes.bool, // /** Defines the tooltip content of minimization toggle. */ // minimizeToggleTooltip: PropTypes.string, // /** Defines a callback function which is called every time a minimize toggle button is clicked. */ // onMinimizeToggleClick: PropTypes.func, // }; //# sourceMappingURL=SidebarNextNext.js.map