'use client' import * as React from 'react' import { NavigationMenu as BaseNavigationMenu } from '@base-ui/react/navigation-menu' import { useDirection } from '../../hooks/use-direction' import { cn } from '../../internal/utils' import { navigationLinkVariants } from '../navigation/navigation-link-variants' type NavigationMenuVariant = 'pill' | 'line' type NavigationMenuSize = 'sm' | 'md' | 'lg' type NavigationMenuIconKind = 'chevron' | 'caret' | 'plus' | false type NavigationMenuOrientation = 'horizontal' | 'vertical' interface NavigationMenuContextValue { variant: NavigationMenuVariant size: NavigationMenuSize icon: NavigationMenuIconKind orientation: NavigationMenuOrientation backdrop: boolean morph: boolean } const NavigationMenuContext = React.createContext(null) function useNavigationMenuContext() { const ctx = React.useContext(NavigationMenuContext) if (!ctx) { throw new Error('NavigationMenu sub-components must be rendered inside ') } return ctx } const NavigationMenuContentContext = React.createContext(false) type BaseRootProps = React.ComponentProps interface NavigationMenuProps extends BaseRootProps { /** * Trigger appearance - hover/active pill, or an animated underline. * @default 'pill' */ variant?: NavigationMenuVariant /** * Scales triggers, popup radius, and link padding. * @default 'md' */ size?: NavigationMenuSize /** * The open/close indicator shown by each `NavigationMenuIcon`. * @default 'chevron' */ icon?: NavigationMenuIconKind /** * Render a dimmed, blurred backdrop behind the open panel. * @default false */ backdrop?: boolean /** * Auto-render the portalled positioner + popup. Set `false` to render your own. * @default true */ viewport?: boolean /** * Animate the popup's size and position between items. * @default true */ morph?: boolean /** * Gap between the trigger and the auto-rendered popup. * @default 6 */ sideOffset?: number } function NavigationMenu({ className, variant = 'pill', size = 'md', icon = 'chevron', orientation = 'horizontal', backdrop = false, viewport = true, morph = true, sideOffset, children, ...rest }: NavigationMenuProps) { const nested = React.useContext(NavigationMenuContentContext) const resolvedSideOffset = sideOffset ?? (nested ? 12 : 6) const ctx = React.useMemo( () => ({ variant, size, icon, orientation, backdrop, morph }), [variant, size, icon, orientation, backdrop, morph], ) return ( {children} {viewport && } ) } type NavigationMenuListProps = React.ComponentProps const HORIZONTAL_GAP: Partial> = { pill: 'gap-0.5', line: 'gap-7', } const VERTICAL_GAP: Partial> = { pill: 'gap-0.5', } function NavigationMenuList({ className, ...props }: NavigationMenuListProps) { const { orientation, variant } = useNavigationMenuContext() const vertical = orientation === 'vertical' const gap = (vertical ? VERTICAL_GAP : HORIZONTAL_GAP)[variant] return ( ) } type NavigationMenuItemProps = React.ComponentProps function NavigationMenuItem(props: NavigationMenuItemProps) { return } type NavigationMenuTriggerProps = React.ComponentProps function NavigationMenuTrigger({ className, ...props }: NavigationMenuTriggerProps) { const { variant, size, orientation, backdrop } = useNavigationMenuContext() const inContent = React.useContext(NavigationMenuContentContext) const vertical = inContent || orientation === 'vertical' return ( ) } const ICON_SIZE: Record = { sm: 'size-3.5', md: 'size-4', lg: 'size-4.5', } type NavigationMenuIconProps = Omit, 'children'> & { /** * Override the indicator for this trigger. * @default root */ icon?: NavigationMenuIconKind } function NavigationMenuIcon({ icon, className, ...props }: NavigationMenuIconProps) { const ctx = useNavigationMenuContext() const resolved = icon === undefined ? ctx.icon : icon if (resolved === false) { return null } const sizeClass = ICON_SIZE[ctx.size] const vertical = ctx.orientation === 'vertical' return ( {resolved === 'chevron' && } {resolved === 'caret' && } {resolved === 'plus' && } ) } const CONTENT_PADDING: Record = { sm: 'p-1.5', md: 'p-2', lg: 'p-2.5', } type NavigationMenuContentProps = React.ComponentProps function NavigationMenuContent({ className, children, ...props }: NavigationMenuContentProps) { const { morph, size } = useNavigationMenuContext() return ( {children} ) } type NavigationMenuLinkProps = React.ComponentProps function NavigationMenuLink({ className, ...props }: NavigationMenuLinkProps) { const { size, orientation, variant } = useNavigationMenuContext() const inContent = React.useContext(NavigationMenuContentContext) const vertical = inContent || orientation === 'vertical' return ( ) } const POPUP_RADIUS: Record = { sm: 'rounded-md', md: 'rounded-lg', lg: 'rounded-xl', } type PositionerPicks = Pick< React.ComponentProps, 'side' | 'sideOffset' | 'align' | 'alignOffset' | 'collisionPadding' | 'collisionAvoidance' > type PortalPicks = Pick, 'container'> interface NavigationMenuPositionerProps extends PositionerPicks, PortalPicks { /** Extra classes, merged via `tailwind-merge`. */ className?: string } function NavigationMenuPositioner({ className, side, sideOffset = 6, align = 'start', alignOffset, collisionPadding, collisionAvoidance = { side: 'none' }, container, }: NavigationMenuPositionerProps) { const { size, orientation, backdrop, morph } = useNavigationMenuContext() const direction = useDirection() const vertical = orientation === 'vertical' return ( {backdrop && ( )} ) } interface IconProps extends React.SVGProps {} function ChevronIconSvg({ className, ...props }: IconProps) { return ( ) } function CaretIconSvg({ className, ...props }: IconProps) { return ( ) } function PlusIconSvg({ className, ...props }: IconProps) { return ( ) } type NavigationMenuViewportProps = React.ComponentProps function NavigationMenuViewport({ className, ...props }: NavigationMenuViewportProps) { return ( ) } export { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuIcon, NavigationMenuContent, NavigationMenuLink, NavigationMenuPositioner, NavigationMenuViewport, } export type { NavigationMenuProps, NavigationMenuListProps, NavigationMenuItemProps, NavigationMenuTriggerProps, NavigationMenuIconProps, NavigationMenuContentProps, NavigationMenuLinkProps, NavigationMenuPositionerProps, NavigationMenuViewportProps, }