@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
JavaScript
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