Navigation Menu Primitive
A collection of navigation links.
Installation
Install the component via your command line.
npx expo install @rn-primitives/navigation-menu
Install @radix-ui/react-navigation-menu
npx expo install @radix-ui/react-navigation-menu
Copy/paste the following code for web to ~/components/primitives/navigation-menu/navigation-menu.web.tsx
import * as NavigationMenu from '@radix-ui/react-navigation-menu';import { useAugmentedRef, useIsomorphicLayoutEffect } from '~/components/primitives/hooks';import * as Slot from '~/components/primitives/slot';import { EmptyGestureResponderEvent } from '~/components/primitives/utils';import * as React from 'react';import { GestureResponderEvent, Pressable, View } from 'react-native';import type { ContentProps, ContentRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, LinkProps, LinkRef, ListProps, ListRef, PortalProps, RootProps, RootRef, TriggerProps, TriggerRef, ViewportProps, ViewportRef,} from './types';
const NavigationMenuContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ( { asChild, value, onValueChange, delayDuration, skipDelayDuration, dir, orientation, ...viewProps }, ref ) => { const Component = asChild ? Slot.View : View; return ( <NavigationMenuContext.Provider value={{ value, onValueChange, orientation }}> <NavigationMenu.Root value={value} onValueChange={onValueChange} delayDuration={delayDuration} skipDelayDuration={skipDelayDuration} dir={dir} orientation={orientation} > <Component ref={ref} {...viewProps} /> </NavigationMenu.Root> </NavigationMenuContext.Provider> ); });
Root.displayName = 'RootWebNavigationMenu';
function useRootContext() { const context = React.useContext(NavigationMenuContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const List = React.forwardRef<ListRef, ListProps>(({ asChild, ...viewProps }, ref) => { const augmentedRef = useAugmentedRef({ ref }); const { orientation } = useRootContext();
useIsomorphicLayoutEffect(() => { if (augmentedRef.current) { const augRef = augmentedRef.current as unknown as HTMLDivElement; augRef.dataset.orientation = orientation; } }, [orientation]);
const Component = asChild ? Slot.View : View; return ( <NavigationMenu.List asChild> <Component ref={ref} {...viewProps} /> </NavigationMenu.List> );});
List.displayName = 'ListWebNavigationMenu';
const ItemContext = React.createContext<ItemProps | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>(({ asChild, value, ...props }, ref) => { const Component = asChild ? Slot.View : View; return ( <ItemContext.Provider value={{ value }}> <NavigationMenu.Item value={value} asChild> <Component ref={ref} {...props} /> </NavigationMenu.Item> </ItemContext.Provider> );});
Item.displayName = 'ItemWebNavigationMenu';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const Trigger = React.forwardRef<TriggerRef, TriggerProps>( ( { asChild, onPress: onPressProp, disabled = false, onKeyDown: onKeyDownProp, ...props }, ref ) => { const { value: rootValue, onValueChange } = useRootContext(); const { value } = useItemContext(); function onKeyDown(ev: React.KeyboardEvent) { onKeyDownProp?.(ev); if (ev.key === ' ') { onPressProp?.(EmptyGestureResponderEvent); onValueChange(value === rootValue ? '' : value); } }
function onPress(ev: GestureResponderEvent) { onPressProp?.(ev); onValueChange(value === rootValue ? '' : value); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <NavigationMenu.Trigger disabled={disabled ?? undefined} asChild> <Component ref={ref} // @ts-expect-error web only onKeyDown={onKeyDown} onPress={onPress} {...props} /> </NavigationMenu.Trigger> ); });
Trigger.displayName = 'TriggerWebNavigationMenu';
function Portal({ children }: PortalProps) { return <>{children}</>;}
const Content = React.forwardRef<ContentRef, ContentProps>( ( { asChild = false, forceMount, align: _align, side: _side, sideOffset: _sideOffset, alignOffset: _alignOffset, avoidCollisions: _avoidCollisions, onLayout: onLayoutProp, insets: _insets, disablePositioningStyle: _disablePositioningStyle, onEscapeKeyDown, onPointerDownOutside, onFocusOutside, onInteractOutside, ...props }, ref ) => { const Component = asChild ? Slot.View : View; return ( <NavigationMenu.Content forceMount={forceMount} onEscapeKeyDown={onEscapeKeyDown} onPointerDownOutside={onPointerDownOutside} onFocusOutside={onFocusOutside} onInteractOutside={onInteractOutside} > <Component ref={ref} {...props} /> </NavigationMenu.Content> ); });
Content.displayName = 'ContentWebNavigationMenu';
const Link = React.forwardRef<LinkRef, LinkProps>( ({ asChild, active, onPress: onPressProp, onKeyDown: onKeyDownProp, ...props }, ref) => { const { onValueChange } = useRootContext(); function onKeyDown(ev: React.KeyboardEvent) { onKeyDownProp?.(ev); if (ev.key === 'Enter' || ev.key === ' ') { onPressProp?.(EmptyGestureResponderEvent); onValueChange(''); } }
function onPress(ev: GestureResponderEvent) { onPressProp?.(ev); onValueChange(''); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <NavigationMenu.Link active={active} asChild> <Component ref={ref} role='link' // @ts-expect-error web only onKeyDown={onKeyDown} onPress={onPress} {...props} /> </NavigationMenu.Link> ); });
Link.displayName = 'LinkWebNavigationMenu';
const Viewport = React.forwardRef<ViewportRef, ViewportProps>((props, ref) => { return ( <Slot.View ref={ref} {...props}> <NavigationMenu.Viewport /> </Slot.View> );});
Viewport.displayName = 'ViewportWebNavigationMenu';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>(({ asChild, ...props }, ref) => { const Component = asChild ? Slot.View : View; return ( <NavigationMenu.Indicator asChild> <Component ref={ref} {...props} /> </NavigationMenu.Indicator> );});
Indicator.displayName = 'IndicatorWebNavigationMenu';
export { Content, Indicator, Item, Link, List, Portal, Root, Trigger, useItemContext, useRootContext, Viewport,};
Copy/paste the following code for native to ~/components/primitives/navigation-menu/navigation-menu.tsx
import { useRelativePosition, type LayoutPosition } from '~/components/primitives/hooks';import { Portal as RNPPortal } from '~/components/primitives/portal';import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { BackHandler, Pressable, View, type GestureResponderEvent, type LayoutChangeEvent, type LayoutRectangle,} from 'react-native';import type { ContentProps, ContentRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, LinkProps, LinkRef, ListProps, ListRef, PortalProps, RootProps, RootRef, TriggerProps, TriggerRef, ViewportProps, ViewportRef,} from './types';
interface INavigationMenuRootContext extends RootProps { triggerPosition: LayoutPosition | null; setTriggerPosition: (triggerPosition: LayoutPosition | null) => void; contentLayout: LayoutRectangle | null; setContentLayout: (contentLayout: LayoutRectangle | null) => void; nativeID: string;}
const RootContext = React.createContext<INavigationMenuRootContext | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, value, onValueChange, ...viewProps }, ref) => { const nativeID = React.useId(); const [triggerPosition, setTriggerPosition] = React.useState<LayoutPosition | null>(null); const [contentLayout, setContentLayout] = React.useState<LayoutRectangle | null>(null);
const Component = asChild ? Slot.View : View; return ( <RootContext.Provider value={{ value, onValueChange, nativeID, contentLayout, setContentLayout, setTriggerPosition, triggerPosition, }} > <Component ref={ref} role='navigation' {...viewProps} /> </RootContext.Provider> ); });
Root.displayName = 'RootNativeNavigationMenu';
function useRootContext() { const context = React.useContext(RootContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const List = React.forwardRef<ListRef, ListProps>(({ asChild, ...viewProps }, ref) => { const Component = asChild ? Slot.View : View; return <Component ref={ref} role='menubar' {...viewProps} />;});
List.displayName = 'ListNativeNavigationMenu';
const ItemContext = React.createContext<(ItemProps & { nativeID: string }) | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>(({ asChild, value, ...viewProps }, ref) => { const nativeID = React.useId();
const Component = asChild ? Slot.View : View; return ( <ItemContext.Provider value={{ value, nativeID, }} > <Component ref={ref} role='menuitem' {...viewProps} /> </ItemContext.Provider> );});
Item.displayName = 'ItemNativeNavigationMenu';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const Trigger = React.forwardRef<TriggerRef, TriggerProps>( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const triggerRef = React.useRef<View>(null); const { value, onValueChange, setTriggerPosition } = useRootContext(); const { value: menuValue } = useItemContext();
React.useImperativeHandle( ref, () => { if (!triggerRef.current) { return new View({}); } return triggerRef.current; }, [triggerRef.current] );
function onPress(ev: GestureResponderEvent) { if (disabled) return; triggerRef.current?.measure((_x, _y, width, height, pageX, pageY) => { setTriggerPosition({ width, pageX, pageY: pageY, height }); });
onValueChange(menuValue === value ? '' : menuValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={triggerRef} aria-disabled={disabled ?? undefined} role='button' onPress={onPress} disabled={disabled ?? undefined} aria-expanded={value === menuValue} {...props} /> ); });
Trigger.displayName = 'TriggerNativeNavigationMenu';
/** * @warning when using a custom `<PortalHost />`, you will have to adjust the Content's sideOffset to account for nav elements like headers. */function Portal({ forceMount, hostName, children }: PortalProps) { const navigationMenu = useRootContext(); const item = useItemContext();
if (!navigationMenu.triggerPosition) { return null; }
if (!forceMount) { if (navigationMenu.value !== item.value) { return null; } }
return ( <RNPPortal hostName={hostName} name={`${navigationMenu.nativeID}_portal_provider`}> <RootContext.Provider value={navigationMenu} key={`RootContext_${navigationMenu.nativeID}_portal_provider`} > <ItemContext.Provider value={item}>{children}</ItemContext.Provider> </RootContext.Provider> </RNPPortal> );}
/** * @info `position`, `top`, `left`, and `maxWidth` style properties are controlled internally. Opt out of this behavior by setting `disablePositioningStyle` to `true`. */const Content = React.forwardRef<ContentRef, ContentProps>( ( { asChild = false, forceMount, align = 'center', side = 'bottom', sideOffset = 0, alignOffset = 0, avoidCollisions = true, onLayout: onLayoutProp, insets, style, disablePositioningStyle, ...props }, ref ) => { const { value, onValueChange, triggerPosition, setTriggerPosition, contentLayout, setContentLayout, } = useRootContext(); const { value: menuValue, nativeID } = useItemContext();
React.useEffect(() => { const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { setTriggerPosition(null); setContentLayout(null); onValueChange(''); return true; });
return () => { setContentLayout(null); backHandler.remove(); }; }, []);
const positionStyle = useRelativePosition({ align, avoidCollisions, triggerPosition, contentLayout, alignOffset, insets, sideOffset, side, disablePositioningStyle, });
function onLayout(event: LayoutChangeEvent) { setContentLayout(event.nativeEvent.layout); onLayoutProp?.(event); }
if (!forceMount) { if (value !== menuValue) { return null; } }
const Component = asChild ? Slot.View : View; return ( <Component ref={ref} role='menu' nativeID={nativeID} aria-modal={true} style={[positionStyle, style]} onLayout={onLayout} onStartShouldSetResponder={onStartShouldSetResponder} {...props} /> ); });
Content.displayName = 'ContentNativeNavigationMenu';
const Link = React.forwardRef<LinkRef, LinkProps>(({ asChild, ...props }, ref) => { const Component = asChild ? Slot.Pressable : Pressable; return <Component ref={ref} role='link' {...props} />;});
Link.displayName = 'LinkNativeNavigationMenu';
const Viewport = React.forwardRef<ViewportRef, ViewportProps>((props, ref) => { return <View ref={ref} {...props} />;});
Viewport.displayName = 'ViewportNativeNavigationMenu';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>(({ asChild, ...props }, ref) => { const Component = asChild ? Slot.View : View; return <Component ref={ref} {...props} />;});
Indicator.displayName = 'IndicatorNativeNavigationMenu';
export { Content, Indicator, Item, Link, List, Portal, Root, Trigger, useItemContext, useRootContext, Viewport,};
function onStartShouldSetResponder() { return true;}
Copy/paste the following code for types to ~/components/primitives/navigation-menu/types.ts
import type { ForceMountable, PositionedContentProps, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';import type { ViewProps } from 'react-native';
type RootProps = SlottableViewProps & { value: string | undefined; onValueChange: (value: string | undefined) => void; /** * Platform: WEB ONLY */ delayDuration?: number; /** * Platform: WEB ONLY */ skipDelayDuration?: number; /** * Platform: WEB ONLY */ dir?: 'ltr' | 'rtl'; /** * Platform: WEB ONLY */ orientation?: 'horizontal' | 'vertical';};
type ItemProps = SlottableViewProps & { value: string | undefined;};
interface PortalProps extends ForceMountable { children: React.ReactNode; /** * Platform: NATIVE ONLY */ hostName?: string; /** * Platform: WEB ONLY */ container?: HTMLElement | null | undefined;}
type LinkProps = SlottablePressableProps & { active?: boolean;};
type ListProps = SlottableViewProps;type TriggerProps = SlottablePressableProps;type ContentProps = SlottableViewProps & PositionedContentProps;type IndicatorProps = SlottableViewProps;type ViewportProps = Omit<ViewProps, 'children'>;
type ContentRef = ViewRef;type IndicatorRef = ViewRef;type ItemRef = ViewRef;type LinkRef = PressableRef;type ListRef = ViewRef;type RootRef = ViewRef;type ViewportRef = ViewRef;type TriggerRef = PressableRef;
export type { ContentProps, ContentRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, LinkProps, LinkRef, ListProps, ListRef, PortalProps, RootProps, RootRef, TriggerProps, TriggerRef, ViewportProps, ViewportRef,};
Copy/paste the following code for exporting to ~/components/primitives/navigation-menu/index.ts
export * from './navigation-menu';export * from './types';
Copy/paste the following code for native to ~/components/primitives/navigation-menu/index.tsx
import { useRelativePosition, type LayoutPosition } from '~/components/primitives/hooks';import { Portal as RNPPortal } from '~/components/primitives/portal';import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { BackHandler, Pressable, View, type GestureResponderEvent, type LayoutChangeEvent, type LayoutRectangle,} from 'react-native';import type { ContentProps, ContentRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, LinkProps, LinkRef, ListProps, ListRef, PortalProps, RootProps, RootRef, TriggerProps, TriggerRef, ViewportProps, ViewportRef,} from './types';
interface INavigationMenuRootContext extends RootProps { triggerPosition: LayoutPosition | null; setTriggerPosition: (triggerPosition: LayoutPosition | null) => void; contentLayout: LayoutRectangle | null; setContentLayout: (contentLayout: LayoutRectangle | null) => void; nativeID: string;}
const RootContext = React.createContext<INavigationMenuRootContext | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, value, onValueChange, ...viewProps }, ref) => { const nativeID = React.useId(); const [triggerPosition, setTriggerPosition] = React.useState<LayoutPosition | null>(null); const [contentLayout, setContentLayout] = React.useState<LayoutRectangle | null>(null);
const Component = asChild ? Slot.View : View; return ( <RootContext.Provider value={{ value, onValueChange, nativeID, contentLayout, setContentLayout, setTriggerPosition, triggerPosition, }} > <Component ref={ref} role='navigation' {...viewProps} /> </RootContext.Provider> ); });
Root.displayName = 'RootNativeNavigationMenu';
function useRootContext() { const context = React.useContext(RootContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const List = React.forwardRef<ListRef, ListProps>(({ asChild, ...viewProps }, ref) => { const Component = asChild ? Slot.View : View; return <Component ref={ref} role='menubar' {...viewProps} />;});
List.displayName = 'ListNativeNavigationMenu';
const ItemContext = React.createContext<(ItemProps & { nativeID: string }) | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>(({ asChild, value, ...viewProps }, ref) => { const nativeID = React.useId();
const Component = asChild ? Slot.View : View; return ( <ItemContext.Provider value={{ value, nativeID, }} > <Component ref={ref} role='menuitem' {...viewProps} /> </ItemContext.Provider> );});
Item.displayName = 'ItemNativeNavigationMenu';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'NavigationMenu compound components cannot be rendered outside the NavigationMenu component' ); } return context;}
const Trigger = React.forwardRef<TriggerRef, TriggerProps>( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const triggerRef = React.useRef<View>(null); const { value, onValueChange, setTriggerPosition } = useRootContext(); const { value: menuValue } = useItemContext();
React.useImperativeHandle( ref, () => { if (!triggerRef.current) { return new View({}); } return triggerRef.current; }, [triggerRef.current] );
function onPress(ev: GestureResponderEvent) { if (disabled) return; triggerRef.current?.measure((_x, _y, width, height, pageX, pageY) => { setTriggerPosition({ width, pageX, pageY: pageY, height }); });
onValueChange(menuValue === value ? '' : menuValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={triggerRef} aria-disabled={disabled ?? undefined} role='button' onPress={onPress} disabled={disabled ?? undefined} aria-expanded={value === menuValue} {...props} /> ); });
Trigger.displayName = 'TriggerNativeNavigationMenu';
/** * @warning when using a custom `<PortalHost />`, you will have to adjust the Content's sideOffset to account for nav elements like headers. */function Portal({ forceMount, hostName, children }: PortalProps) { const navigationMenu = useRootContext(); const item = useItemContext();
if (!navigationMenu.triggerPosition) { return null; }
if (!forceMount) { if (navigationMenu.value !== item.value) { return null; } }
return ( <RNPPortal hostName={hostName} name={`${navigationMenu.nativeID}_portal_provider`}> <RootContext.Provider value={navigationMenu} key={`RootContext_${navigationMenu.nativeID}_portal_provider`} > <ItemContext.Provider value={item}>{children}</ItemContext.Provider> </RootContext.Provider> </RNPPortal> );}
/** * @info `position`, `top`, `left`, and `maxWidth` style properties are controlled internally. Opt out of this behavior by setting `disablePositioningStyle` to `true`. */const Content = React.forwardRef<ContentRef, ContentProps>( ( { asChild = false, forceMount, align = 'center', side = 'bottom', sideOffset = 0, alignOffset = 0, avoidCollisions = true, onLayout: onLayoutProp, insets, style, disablePositioningStyle, ...props }, ref ) => { const { value, onValueChange, triggerPosition, setTriggerPosition, contentLayout, setContentLayout, } = useRootContext(); const { value: menuValue, nativeID } = useItemContext();
React.useEffect(() => { const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { setTriggerPosition(null); setContentLayout(null); onValueChange(''); return true; });
return () => { setContentLayout(null); backHandler.remove(); }; }, []);
const positionStyle = useRelativePosition({ align, avoidCollisions, triggerPosition, contentLayout, alignOffset, insets, sideOffset, side, disablePositioningStyle, });
function onLayout(event: LayoutChangeEvent) { setContentLayout(event.nativeEvent.layout); onLayoutProp?.(event); }
if (!forceMount) { if (value !== menuValue) { return null; } }
const Component = asChild ? Slot.View : View; return ( <Component ref={ref} role='menu' nativeID={nativeID} aria-modal={true} style={[positionStyle, style]} onLayout={onLayout} onStartShouldSetResponder={onStartShouldSetResponder} {...props} /> ); });
Content.displayName = 'ContentNativeNavigationMenu';
const Link = React.forwardRef<LinkRef, LinkProps>(({ asChild, ...props }, ref) => { const Component = asChild ? Slot.Pressable : Pressable; return <Component ref={ref} role='link' {...props} />;});
Link.displayName = 'LinkNativeNavigationMenu';
const Viewport = React.forwardRef<ViewportRef, ViewportProps>((props, ref) => { return <View ref={ref} {...props} />;});
Viewport.displayName = 'ViewportNativeNavigationMenu';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>(({ asChild, ...props }, ref) => { const Component = asChild ? Slot.View : View; return <Component ref={ref} {...props} />;});
Indicator.displayName = 'IndicatorNativeNavigationMenu';
export { Content, Indicator, Item, Link, List, Portal, Root, Trigger, useItemContext, useRootContext, Viewport,};
function onStartShouldSetResponder() { return true;}
Copy/paste the following code for types to ~/components/primitives/navigation-menu/types.ts
import type { ForceMountable, PositionedContentProps, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';import type { ViewProps } from 'react-native';
type RootProps = SlottableViewProps & { value: string | undefined; onValueChange: (value: string | undefined) => void; /** * Platform: WEB ONLY */ delayDuration?: number; /** * Platform: WEB ONLY */ skipDelayDuration?: number; /** * Platform: WEB ONLY */ dir?: 'ltr' | 'rtl'; /** * Platform: WEB ONLY */ orientation?: 'horizontal' | 'vertical';};
type ItemProps = SlottableViewProps & { value: string | undefined;};
interface PortalProps extends ForceMountable { children: React.ReactNode; /** * Platform: NATIVE ONLY */ hostName?: string; /** * Platform: WEB ONLY */ container?: HTMLElement | null | undefined;}
type LinkProps = SlottablePressableProps & { active?: boolean;};
type ListProps = SlottableViewProps;type TriggerProps = SlottablePressableProps;type ContentProps = SlottableViewProps & PositionedContentProps;type IndicatorProps = SlottableViewProps;type ViewportProps = Omit<ViewProps, 'children'>;
type ContentRef = ViewRef;type IndicatorRef = ViewRef;type ItemRef = ViewRef;type LinkRef = PressableRef;type ListRef = ViewRef;type RootRef = ViewRef;type ViewportRef = ViewRef;type TriggerRef = PressableRef;
export type { ContentProps, ContentRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, LinkProps, LinkRef, ListProps, ListRef, PortalProps, RootProps, RootRef, TriggerProps, TriggerRef, ViewportProps, ViewportRef,};
Usage
import * as React from 'react';import { Platform, StyleSheet, Pressable, Text } from 'react-native';import * as NavigationMenuPrimitive from '@rn-primitives/navigation-menu';
function Example() { const [value, setValue] = React.useState<string>();
function closeAll() { setValue(''); }
// TODO: handle closing menus when pressing/clicking outside of this Example component. Ex: when navigating to another screen
return ( <> {Platform.OS !== 'web' && !!value && ( <Pressable onPress={closeAll} style={StyleSheet.absoluteFillObject} /> )} <NavigationMenuPrimitive.Root value={value} onValueChange={setValue}> <NavigationMenuPrimitive.List> <NavigationMenuPrimitive.Item value='getting-started'> <NavigationMenuPrimitive.Trigger> <Text>Getting started</Text> </NavigationMenuPrimitive.Trigger> <NavigationMenuPrimitive.Portal> <NavigationMenuPrimitive.Content> <NavigationMenuPrimitive.Link > <Text> react-native-reusables </Text> </NavigationMenuPrimitive.Link> </NavigationMenuPrimitive.Content> </ NavigationMenuPrimitive.Portal> </NavigationMenuPrimitive.Item> <NavigationMenuPrimitive.Item value='components'> <NavigationMenuPrimitive.Trigger> <Text>Components</Text> </NavigationMenuPrimitive.Trigger> <NavigationMenuPrimitive.Content> <NavigationMenuPrimitive.Link> Navigation Menu </NavigationMenuPrimitive.Link> </View> </NavigationMenuPrimitive.Content> </NavigationMenuPrimitive.Item> <NavigationMenuPrimitive.Item value='documentation'> <NavigationMenuPrimitive.Link onPress={closeAll}> <Text>Documentation</Text> </NavigationMenuPrimitive.Link> </NavigationMenuPrimitive.Item> </NavigationMenuPrimitive.List> </NavigationMenuPrimitive.Root> </> );}
Props
Root
Extends View
props
Prop | Type | Note |
---|---|---|
value* | boolean | |
onValueChange* | (value: boolean) => void | |
asChild | boolean | (optional) |
delayDuration | number | Web only (optional) |
skipDelayDuration | number | Web only (optional) |
dir | ’ltr’ | ‘rtl’ | Web only (optional) |
orientation | ’horizontal’ | ‘vertical’ | Web only (optional) |
List
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
Item
Extends Pressable
props
Prop | Type | Note |
---|---|---|
value* | string | |
asChild | boolean | (optional) |
Trigger
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
Portal
Prop | Type | Note |
---|---|---|
children* | React.ReactNode | |
forceMount | true | undefined | (optional) |
hostName | string | Web Only (optional) |
container | HTMLElement | null | undefined | Web Only (optional) |
Content
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
forceMount | true | undefined | (optional) |
alignOffset | number | Native Only (optional) |
insets | Insets | Native Only (optional) |
avoidCollisions | boolean | Native Only (optional) |
align | ’start’ | ‘center’ | ‘end’ | Native Only (optional) |
side | ’top’ | ‘bottom’ | Native Only (optional) |
sideOffset | number | Native Only (optional) |
disablePositioningStyle | boolean | Native Only (optional) |
loop | boolean | Web Only (optional) |
onEscapeKeyDown | (event: KeyboardEvent) => void | Web Only (optional) |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void | Web Only (optional) |
onFocusOutside | (event: FocusOutsideEvent) => void | Web Only (optional) |
onInteractOutside | PointerDownOutsideEvent | FocusOutsideEvent | Web Only (optional) |
Link
Extends Pressable
props
Prop | Type | Note |
---|---|---|
active | boolean | (optional) |
asChild | boolean | (optional) |
Viewport
Should only be used for web
Extends View
props except children
Indicator
Extends View
props
useRootContext
Must be used within a Root
component. It provides the following values from the navigation menu: value
, and onValueChange
.
useItemContext
Must be used within a Item
component. It provides the following values from the navigation menu: value
.