@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
118 lines (116 loc) • 3.83 kB
JavaScript
'use client';
import * as React from 'react';
import { useScrollLock } from '@base-ui-components/utils/useScrollLock';
import { FloatingNode, FloatingTree, useFloatingNodeId, useFloatingTree } from "../floating-ui-react/index.js";
import { MenubarContext, useMenubarContext } from "./MenubarContext.js";
import { useOpenInteractionType } from "../utils/useOpenInteractionType.js";
import { CompositeRoot } from "../composite/root/CompositeRoot.js";
import { useBaseUiId } from "../utils/useBaseUiId.js";
import { jsx as _jsx } from "react/jsx-runtime";
const menubarStateAttributesMapping = {
hasSubmenuOpen(value) {
return {
'data-has-submenu-open': value ? 'true' : 'false'
};
}
};
/**
* The container for menus.
*
* Documentation: [Base UI Menubar](https://base-ui.com/react/components/menubar)
*/
export const Menubar = /*#__PURE__*/React.forwardRef(function Menubar(props, forwardedRef) {
const {
orientation = 'horizontal',
loopFocus = true,
render,
className,
modal = true,
disabled = false,
id: idProp,
...elementProps
} = props;
const [contentElement, setContentElement] = React.useState(null);
const [hasSubmenuOpen, setHasSubmenuOpen] = React.useState(false);
const {
openMethod,
triggerProps: interactionTypeProps,
reset: resetOpenInteractionType
} = useOpenInteractionType(hasSubmenuOpen);
React.useEffect(() => {
if (!hasSubmenuOpen) {
resetOpenInteractionType();
}
}, [hasSubmenuOpen, resetOpenInteractionType]);
useScrollLock(modal && hasSubmenuOpen && openMethod !== 'touch', contentElement);
const id = useBaseUiId(idProp);
const state = React.useMemo(() => ({
orientation,
modal,
hasSubmenuOpen
}), [orientation, modal, hasSubmenuOpen]);
const contentRef = React.useRef(null);
const allowMouseUpTriggerRef = React.useRef(false);
const context = React.useMemo(() => ({
contentElement,
setContentElement,
setHasSubmenuOpen,
hasSubmenuOpen,
modal,
disabled,
orientation,
allowMouseUpTriggerRef,
rootId: id
}), [contentElement, hasSubmenuOpen, modal, disabled, orientation, id]);
return /*#__PURE__*/_jsx(MenubarContext.Provider, {
value: context,
children: /*#__PURE__*/_jsx(FloatingTree, {
children: /*#__PURE__*/_jsx(MenubarContent, {
children: /*#__PURE__*/_jsx(CompositeRoot, {
render: render,
className: className,
state: state,
stateAttributesMapping: menubarStateAttributesMapping,
refs: [forwardedRef, setContentElement, contentRef],
props: [{
role: 'menubar',
id
}, interactionTypeProps, elementProps],
orientation: orientation,
loopFocus: loopFocus,
highlightItemOnHover: hasSubmenuOpen
})
})
})
});
});
if (process.env.NODE_ENV !== "production") Menubar.displayName = "Menubar";
function MenubarContent(props) {
const nodeId = useFloatingNodeId();
const {
events: menuEvents
} = useFloatingTree();
const rootContext = useMenubarContext();
React.useEffect(() => {
function onSubmenuOpenChange(details) {
if (!details.nodeId || details.parentNodeId !== nodeId) {
return;
}
if (details.open) {
if (!rootContext.hasSubmenuOpen) {
rootContext.setHasSubmenuOpen(true);
}
} else if (details.reason !== 'sibling-open' && details.reason !== 'list-navigation') {
rootContext.setHasSubmenuOpen(false);
}
}
menuEvents.on('menuopenchange', onSubmenuOpenChange);
return () => {
menuEvents.off('menuopenchange', onSubmenuOpenChange);
};
}, [menuEvents, nodeId, rootContext]);
return /*#__PURE__*/_jsx(FloatingNode, {
id: nodeId,
children: props.children
});
}