UNPKG

@veracity/vui

Version:

Veracity UI is a React component library crafted for use within Veracity applications and pages. Based on Styled Components and @xstyled.

236 lines (234 loc) 8.46 kB
import { generateMenuItemId } from "../utils/string.js"; import { cs } from "../utils/styles.js"; import { isActivePath } from "../utils/window.js"; import { useStyleConfig } from "../core/theme.js"; import vui from "../core/vui.js"; import { Box } from "../box/box.js"; import { Icon } from "../icon/icon.js"; import { Button } from "../button/button.js"; import { SidemenuStateProvider, useSidemenuContext } from "./context.js"; import { SidemenuItemV2 } from "./sidemenuItemV2.js"; import { SidemenuTopV2 } from "./sidemenuTopV2.js"; import { useCallback, useMemo } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; //#region src/sidemenuV2/sidemenuV2.tsx /** A collapsible side menu for navigation. */ const SidemenuV2 = vui((props, ref) => { const { isExpandedInitial = true, onExpandedChange, size, variant = "light", children, className, items, shouldExpand, isSticky, width, top, onNavigate, ...restProps } = props; return /* @__PURE__ */ jsx(SidemenuStateProvider, { isExpandedInitial, onExpandedChange, variant, size, isDark: variant === "dark", children: /* @__PURE__ */ jsx(SidemenuInner, { ref, className, items, shouldExpand, isSticky, size, variant, width, top, onNavigate, ...restProps, children }) }); }); const SidemenuInner = vui((props, ref) => { const { children, className, items, shouldExpand, isSticky = false, size, variant = "light", width = 240, top, onNavigate, ...rest } = props; const { isMenuExpanded = false, setIsExpanded } = useSidemenuContext() || {}; const styles = useStyleConfig("SidemenuV2", props); const conditions = useMemo(() => ({ isDark: variant === "dark", hasItems: !!items, shouldShowItems: !!items && items.length > 0, isMenuSticky: isSticky, isMenuExpanded }), [ variant, items, isSticky, isMenuExpanded ]); const computedValues = useMemo(() => { const toggleButtonTheme = styles.toggleButton || {}; const currentSize = size || "md"; const toggleButtonSizeTheme = (styles.sizes?.[currentSize] || {}).toggleButton || {}; const toggleConfig = { baseHeight: toggleButtonSizeTheme.baseHeight || toggleButtonTheme.baseHeight || 52, basePadding: toggleButtonSizeTheme.basePadding || toggleButtonTheme.basePadding || 2, expandedPaddingMultiplier: toggleButtonTheme.expandedPaddingMultiplier || 5, expandedWidth: toggleButtonTheme.expandedWidth || "52px", collapsedWidth: toggleButtonTheme.collapsedWidth || "100%" }; const getMenuTopPosition = (isSticky, topValue) => { if (!isSticky) return "auto"; if (typeof topValue === "string") return topValue; if (typeof topValue === "number") return `${topValue}px`; return "0px"; }; return { menuWidth: conditions.isMenuExpanded ? width : 52, toggleArrowDirection: conditions.isMenuExpanded ? "Left" : "Right", toggleButtonJustification: conditions.isMenuExpanded ? "flex-end" : "center", toggleButtonVariant: styles.toggleButton?.variant || (conditions.isDark ? "tertiaryLight" : "tertiaryDark"), toggleAriaLabel: conditions.isMenuExpanded ? "Collapse menu" : "Expand menu", toggleButtonPadding: toggleConfig.basePadding, toggleButtonVerticalPadding: conditions.isMenuExpanded ? toggleConfig.basePadding * toggleConfig.expandedPaddingMultiplier : toggleConfig.basePadding, toggleButtonHeight: toggleConfig.baseHeight, toggleButtonWidth: conditions.isMenuExpanded ? toggleConfig.expandedWidth : toggleConfig.collapsedWidth, menuPosition: conditions.isMenuSticky ? "sticky" : "static", menuTopPosition: getMenuTopPosition(conditions.isMenuSticky, top) }; }, [ conditions, width, top, styles, size ]); const toggleIcon = useMemo(() => `falArrow${computedValues.toggleArrowDirection}`, [computedValues.toggleArrowDirection]); const handleToggle = useCallback(() => { const newExpandedState = !conditions.isMenuExpanded; if (shouldExpand && !shouldExpand("toggle", conditions.isMenuExpanded)) return; setIsExpanded?.(newExpandedState); }, [ conditions.isMenuExpanded, shouldExpand, setIsExpanded ]); const handleItemClick = useCallback((item) => { if (shouldExpand && item?.path) { const shouldExpandOnNav = shouldExpand("navigation", conditions.isMenuExpanded); if (typeof shouldExpandOnNav === "boolean" && shouldExpandOnNav !== conditions.isMenuExpanded) setIsExpanded?.(shouldExpandOnNav); } item?.onClick?.(); if (item?.path) onNavigate?.(item.path); }, [ onNavigate, shouldExpand, conditions.isMenuExpanded, setIsExpanded ]); const itemClickHandlers = useMemo(() => { return (items ?? []).reduce((handlers, item, index) => { const itemId = item.itemId || item.path || generateMenuItemId(item.title, item.level || 0) || `item-${index}`; handlers[itemId] = () => handleItemClick(item); return handlers; }, {}); }, [items, handleItemClick]); const renderedItems = useMemo(() => { if (!conditions.shouldShowItems) return children; return items?.map?.((item, index) => { const itemId = item.itemId || item.path || generateMenuItemId(item.title, item.level || 0) || `item-${index}`; const calculatedIsActive = item.isActive !== void 0 ? item.isActive : isActivePath(item?.path || ""); return /* @__PURE__ */ jsx(SidemenuItemV2, { isActive: calculatedIsActive, onBottom: item.onBottom, itemId, level: item.level || 0, ...item, onClick: itemClickHandlers[itemId] }, itemId); }); }, [ conditions.shouldShowItems, conditions.isMenuExpanded, items, children, itemClickHandlers ]); const cssClasses = useMemo(() => cs("vui-sidemenu", `vui-sidemenu--${variant}`, conditions.isMenuExpanded && "vui-sidemenu--expanded", !conditions.isMenuExpanded && "vui-sidemenu--collapsed", conditions.isDark && "vui-sidemenu--dark", conditions.isMenuSticky && "vui-sidemenu--sticky", className), [ variant, conditions, className ]); const containerProps = useMemo(() => ({ className: cssClasses, flexDirection: "column", overflowX: "hidden", position: computedValues.menuPosition, ref, top: computedValues.menuTopPosition, transitionDuration: "normal", transitionProperty: "width", w: computedValues.menuWidth, ...styles.container, ...rest }), [ cssClasses, computedValues.menuPosition, computedValues.menuTopPosition, computedValues.menuWidth, ref, styles.container, rest ]); const toggleButtonProps = useMemo(() => ({ "aria-expanded": conditions.isMenuExpanded, "aria-label": computedValues.toggleAriaLabel, border: 0, borderRadius: 0, justifyContent: computedValues.toggleButtonJustification, minHeight: `${computedValues.toggleButtonHeight}px`, minWidth: 0, onClick: handleToggle, px: computedValues.toggleButtonPadding, py: `${computedValues.toggleButtonVerticalPadding}px`, size: "lg", variant: computedValues.toggleButtonVariant, w: computedValues.toggleButtonWidth, flexShrink: 0, transition: styles.toggleButton?.transition || "padding 0.2s ease-in-out, width 0.2s ease-in-out" }), [ computedValues.toggleAriaLabel, computedValues.toggleButtonJustification, computedValues.toggleButtonVariant, computedValues.toggleButtonPadding, computedValues.toggleButtonVerticalPadding, computedValues.toggleButtonHeight, computedValues.toggleButtonWidth, conditions.isMenuExpanded, handleToggle, styles.toggleButton ]); return /* @__PURE__ */ jsxs(Box, { elevation: "1", ...containerProps, children: [/* @__PURE__ */ jsx(Box, { className: "vui-sidemenu-content", flexDirection: "column", flexGrow: 1, justifyContent: "flex-start", minW: 240, overflowX: "hidden", overflowY: "auto", w: "100%", role: "navigation", "aria-label": "Main navigation", children: renderedItems }), /* @__PURE__ */ jsx(Box, { className: "vui-sidemenu-bottom", justifyContent: conditions.isMenuExpanded ? "flex-end" : "flex-start", borderTop: "1px solid sandstone.main", children: /* @__PURE__ */ jsx(Button, { ...toggleButtonProps, children: /* @__PURE__ */ jsx(Icon, { name: toggleIcon, size: "sm", "aria-hidden": "true" }) }, "sidemenu-toggle-button") })] }); }); SidemenuV2.Item = SidemenuItemV2; SidemenuV2.Top = SidemenuTopV2; SidemenuV2.displayName = "SidemenuV2"; //#endregion export { SidemenuStateProvider, SidemenuV2, SidemenuV2 as default, useSidemenuContext }; globalThis.__vuiVersion__ = "5.3.1" //# sourceMappingURL=sidemenuV2.js.map