UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

1 lines 12 kB
{"version":3,"file":"Menubar.cjs","names":["factory","useProps","useStyles","MenubarContextProvider","Box","classes","MenubarMenu","MenubarTarget","MenubarDropdown"],"sources":["../../../src/components/Menubar/Menubar.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { useId, useIsomorphicEffect, useMergedRef, useUncontrolled } from '@mantine/hooks';\nimport {\n Box,\n BoxProps,\n ElementProps,\n factory,\n Factory,\n StylesApiProps,\n useProps,\n useStyles,\n} from '../../core';\nimport { FloatingPosition } from '../../utils/Floating';\nimport { MenubarContextProvider, type MenubarContextValue } from './Menubar.context';\nimport { MenubarDropdown, type MenubarDropdownProps } from './MenubarDropdown/MenubarDropdown';\nimport { MenubarMenu, type MenubarMenuProps } from './MenubarMenu/MenubarMenu';\nimport { MenubarTarget, type MenubarTargetProps } from './MenubarTarget/MenubarTarget';\nimport classes from './Menubar.module.css';\n\nexport type MenubarStylesNames = 'root' | 'target';\n\nexport type MenubarFactory = Factory<{\n props: MenubarProps;\n ref: HTMLDivElement;\n stylesNames: MenubarStylesNames;\n staticComponents: {\n Menu: typeof MenubarMenu;\n Target: typeof MenubarTarget;\n Dropdown: typeof MenubarDropdown;\n };\n}>;\n\nexport interface MenubarProps\n extends BoxProps, StylesApiProps<MenubarFactory>, ElementProps<'div'> {\n /** `Menubar.Menu` components */\n children?: React.ReactNode;\n\n /** Index of the controlled opened menu, `null` closes all menus */\n openIndex?: number | null;\n\n /** Index of the opened menu for uncontrolled component @default `null` */\n defaultOpenIndex?: number | null;\n\n /** Called when the opened menu changes with its index or `null` when all menus are closed */\n onOpenChange?: (index: number | null) => void;\n\n /** Event that opens a menu when none of the menus is opened. `'click'` opens a menu on target click and then switches menus on hover (desktop application pattern), `'hover'` opens a menu when the target is hovered @default `'click'` */\n trigger?: 'click' | 'hover';\n\n /** If set, arrow key navigation wraps from last to first menu and vice versa @default `true` */\n loop?: boolean;\n\n /** Dropdown position relative to the target element @default `'bottom-start'` */\n position?: FloatingPosition;\n}\n\nconst defaultProps = {\n trigger: 'click',\n loop: true,\n position: 'bottom-start',\n} satisfies Partial<MenubarProps>;\n\nexport const Menubar = factory<MenubarFactory>((_props) => {\n const props = useProps('Menubar', defaultProps, _props);\n const {\n classNames,\n className,\n style,\n styles,\n unstyled,\n vars,\n children,\n openIndex,\n defaultOpenIndex,\n onOpenChange,\n trigger,\n loop,\n position,\n attributes,\n mod,\n ref,\n ...others\n } = props;\n\n const getStyles = useStyles<MenubarFactory>({\n name: 'Menubar',\n classes,\n props,\n className,\n style,\n classNames,\n styles,\n unstyled,\n attributes,\n });\n\n const rootRef = useRef<HTMLDivElement>(null);\n const menubarId = useId();\n\n const [_openIndex, setOpenIndex] = useUncontrolled<number | null>({\n value: openIndex,\n defaultValue: defaultOpenIndex,\n finalValue: null,\n onChange: onOpenChange,\n });\n\n const [activeIndex, setActiveIndex] = useState(0);\n const openSourceRef = useRef<'click' | 'hover' | null>(null);\n\n const openMenu = useCallback(\n (index: number, source: 'click' | 'hover') => {\n openSourceRef.current = source;\n setOpenIndex(index);\n },\n [setOpenIndex]\n );\n\n const closeMenu = useCallback(() => {\n openSourceRef.current = null;\n setOpenIndex(null);\n }, [setOpenIndex]);\n\n const closeTimeoutRef = useRef(-1);\n\n const cancelClose = useCallback(() => {\n window.clearTimeout(closeTimeoutRef.current);\n }, []);\n\n const scheduleClose = useCallback(() => {\n window.clearTimeout(closeTimeoutRef.current);\n closeTimeoutRef.current = window.setTimeout(closeMenu, 120);\n }, [closeMenu]);\n\n useIsomorphicEffect(() => () => window.clearTimeout(closeTimeoutRef.current), []);\n\n const getOpenSource = useCallback(() => openSourceRef.current, []);\n\n const previousOpenIndexRef = useRef<number | null>(_openIndex);\n const getPreviousOpenIndex = useCallback(() => previousOpenIndexRef.current, []);\n\n useIsomorphicEffect(() => {\n previousOpenIndexRef.current = _openIndex;\n });\n\n const getTargets = useCallback(\n () =>\n Array.from(\n rootRef.current?.querySelectorAll<HTMLButtonElement>('[data-menubar-target]') ?? []\n ),\n []\n );\n\n const getMenuIndex = useCallback(\n (id: string) => getTargets().findIndex((node) => node.getAttribute('data-menubar-id') === id),\n [getTargets]\n );\n\n const getEnabledIndexes = useCallback(\n () =>\n getTargets().reduce<number[]>((acc, node, index) => {\n if (!node.disabled && !node.hasAttribute('data-disabled')) {\n acc.push(index);\n }\n return acc;\n }, []),\n [getTargets]\n );\n\n const focusTarget = useCallback(\n (index: number) => {\n getTargets()[index]?.focus();\n },\n [getTargets]\n );\n\n const focusMenuItem = useCallback(\n (index: number, itemPosition: 'first' | 'last') => {\n window.setTimeout(() => {\n const target = getTargets()[index];\n const controls = target?.getAttribute('aria-controls');\n const dropdown = controls\n ? document.getElementById(controls)\n : document.querySelector<HTMLElement>(`[data-menubar-dropdown=\"${menubarId}\"]`);\n const items = dropdown?.querySelectorAll<HTMLElement>(\n '[data-menu-item]:not([data-disabled])'\n );\n\n if (items && items.length > 0) {\n const item = itemPosition === 'first' ? items[0] : items[items.length - 1];\n item?.focus();\n }\n }, 40);\n },\n [getTargets]\n );\n\n const getAdjacentIndex = useCallback(\n (current: number, direction: 1 | -1) => {\n const enabled = getEnabledIndexes();\n\n if (enabled.length === 0) {\n return current;\n }\n\n const currentPosition = enabled.indexOf(current);\n let nextPosition = currentPosition === -1 ? 0 : currentPosition + direction;\n\n if (loop) {\n nextPosition = (nextPosition + enabled.length) % enabled.length;\n } else {\n nextPosition = Math.max(0, Math.min(enabled.length - 1, nextPosition));\n }\n\n return enabled[nextPosition] ?? current;\n },\n [getEnabledIndexes, loop]\n );\n\n useIsomorphicEffect(() => {\n const enabled = getEnabledIndexes();\n if (enabled.length === 0) {\n return;\n }\n\n // While a menu is open, the single tab stop must stay on the opened menu's target,\n // including when it was opened programmatically via openIndex/defaultOpenIndex.\n if (_openIndex !== null && enabled.includes(_openIndex)) {\n if (activeIndex !== _openIndex) {\n setActiveIndex(_openIndex);\n }\n return;\n }\n\n if (!enabled.includes(activeIndex)) {\n setActiveIndex(enabled[0]);\n }\n });\n\n const contextValue: MenubarContextValue = {\n getStyles,\n id: menubarId,\n openIndex: _openIndex,\n setOpenIndex,\n openMenu,\n closeMenu,\n scheduleClose,\n cancelClose,\n getOpenSource,\n getPreviousOpenIndex,\n activeIndex,\n setActiveIndex,\n trigger: trigger!,\n loop: loop!,\n position: position!,\n unstyled,\n getMenuIndex,\n getTargets,\n getEnabledIndexes,\n getAdjacentIndex,\n focusTarget,\n focusMenuItem,\n };\n\n return (\n <MenubarContextProvider value={contextValue}>\n <Box\n ref={useMergedRef(ref, rootRef)}\n role=\"menubar\"\n aria-orientation=\"horizontal\"\n mod={mod}\n {...getStyles('root')}\n {...others}\n data-menubar\n >\n {children}\n </Box>\n </MenubarContextProvider>\n );\n});\n\nMenubar.classes = classes;\nMenubar.displayName = '@mantine/core/Menubar';\nMenubar.Menu = MenubarMenu;\nMenubar.Target = MenubarTarget;\nMenubar.Dropdown = MenubarDropdown;\n\nexport namespace Menubar {\n export type Props = MenubarProps;\n export type StylesNames = MenubarStylesNames;\n export type Factory = MenubarFactory;\n\n export namespace Menu {\n export type Props = MenubarMenuProps;\n }\n\n export namespace Target {\n export type Props = MenubarTargetProps;\n }\n\n export namespace Dropdown {\n export type Props = MenubarDropdownProps;\n }\n}\n"],"mappings":"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"}