Dropdown Menu
Dropdown Menu Primitive
Text Component
Terminal window
~/components/ui/dropdown-menu.tsx
Demo
Shows a menu with options or actions when a user clicks the trigger button.
Installation
npx @react-native-reusables/cli@latest add dropdown-menu
Copy/paste the following code to ~/components/ui/dropdown-menu.tsx
:
import * as DropdownMenuPrimitive from '@rn-primitives/dropdown-menu';import * as React from 'react';import { Platform, type StyleProp, StyleSheet, Text, type TextProps, View, type ViewStyle,} from 'react-native';import { Check } from '~/lib/icons/Check';import { ChevronDown } from '~/lib/icons/ChevronDown';import { ChevronRight } from '~/lib/icons/ChevronRight';import { ChevronUp } from '~/lib/icons/ChevronUp';import { cn } from '~/lib/utils';import { TextClassContext } from '~/components/ui/text';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< DropdownMenuPrimitive.SubTriggerRef, DropdownMenuPrimitive.SubTriggerProps & { inset?: boolean; }>(({ className, inset, children, ...props }, ref) => { const { open } = DropdownMenuPrimitive.useSubContext(); const Icon = Platform.OS === 'web' ? ChevronRight : open ? ChevronUp : ChevronDown; return ( <TextClassContext.Provider value={cn( 'select-none text-sm native:text-lg text-primary', open && 'native:text-accent-foreground' )} > <DropdownMenuPrimitive.SubTrigger ref={ref} className={cn( 'flex flex-row web:cursor-default web:select-none gap-2 items-center web:focus:bg-accent web:hover:bg-accent active:bg-accent rounded-sm px-2 py-1.5 native:py-2 web:outline-none', open && 'bg-accent', inset && 'pl-8', className )} {...props} > <>{children}</> <Icon size={18} className='ml-auto text-foreground' /> </DropdownMenuPrimitive.SubTrigger> </TextClassContext.Provider> );});DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< DropdownMenuPrimitive.SubContentRef, DropdownMenuPrimitive.SubContentProps>(({ className, ...props }, ref) => { const { open } = DropdownMenuPrimitive.useSubContext(); return ( <DropdownMenuPrimitive.SubContent ref={ref} className={cn( 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border mt-1 bg-popover p-1 shadow-md shadow-foreground/5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', open ? 'web:animate-in web:fade-in-0 web:zoom-in-95' : 'web:animate-out web:fade-out-0 web:zoom-out', className )} {...props} /> );});DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< DropdownMenuPrimitive.ContentRef, DropdownMenuPrimitive.ContentProps & { overlayStyle?: StyleProp<ViewStyle>; overlayClassName?: string; portalHost?: string; }>(({ className, overlayClassName, overlayStyle, portalHost, ...props }, ref) => { const { open } = DropdownMenuPrimitive.useRootContext(); return ( <DropdownMenuPrimitive.Portal hostName={portalHost}> <DropdownMenuPrimitive.Overlay style={ overlayStyle ? StyleSheet.flatten([ Platform.OS !== 'web' ? StyleSheet.absoluteFill : undefined, overlayStyle, ]) : Platform.OS !== 'web' ? StyleSheet.absoluteFill : undefined } className={overlayClassName} > <DropdownMenuPrimitive.Content ref={ref} className={cn( 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md shadow-foreground/5 web:data-[side=bottom]:slide-in-from-top-2 web:data-[side=left]:slide-in-from-right-2 web:data-[side=right]:slide-in-from-left-2 web:data-[side=top]:slide-in-from-bottom-2', open ? 'web:animate-in web:fade-in-0 web:zoom-in-95' : 'web:animate-out web:fade-out-0 web:zoom-out-95', className )} {...props} /> </DropdownMenuPrimitive.Overlay> </DropdownMenuPrimitive.Portal> );});DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< DropdownMenuPrimitive.ItemRef, DropdownMenuPrimitive.ItemProps & { inset?: boolean; }>(({ className, inset, ...props }, ref) => ( <TextClassContext.Provider value='select-none text-sm native:text-lg text-popover-foreground web:group-focus:text-accent-foreground'> <DropdownMenuPrimitive.Item ref={ref} className={cn( 'relative flex flex-row web:cursor-default gap-2 items-center rounded-sm px-2 py-1.5 native:py-2 web:outline-none web:focus:bg-accent active:bg-accent web:hover:bg-accent group', inset && 'pl-8', props.disabled && 'opacity-50 web:pointer-events-none', className )} {...props} /> </TextClassContext.Provider>));DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< DropdownMenuPrimitive.CheckboxItemRef, DropdownMenuPrimitive.CheckboxItemProps>(({ className, children, checked, ...props }, ref) => ( <DropdownMenuPrimitive.CheckboxItem ref={ref} className={cn( 'relative flex flex-row web:cursor-default items-center web:group rounded-sm py-1.5 native:py-2 pl-8 pr-2 web:outline-none web:focus:bg-accent active:bg-accent', props.disabled && 'web:pointer-events-none opacity-50', className )} checked={checked} {...props} > <View className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'> <DropdownMenuPrimitive.ItemIndicator> <Check size={14} strokeWidth={3} className='text-foreground' /> </DropdownMenuPrimitive.ItemIndicator> </View> <>{children}</> </DropdownMenuPrimitive.CheckboxItem>));DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< DropdownMenuPrimitive.RadioItemRef, DropdownMenuPrimitive.RadioItemProps>(({ className, children, ...props }, ref) => ( <DropdownMenuPrimitive.RadioItem ref={ref} className={cn( 'relative flex flex-row web:cursor-default web:group items-center rounded-sm py-1.5 native:py-2 pl-8 pr-2 web:outline-none web:focus:bg-accent active:bg-accent', props.disabled && 'web:pointer-events-none opacity-50', className )} {...props} > <View className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'> <DropdownMenuPrimitive.ItemIndicator> <View className='bg-foreground h-2 w-2 rounded-full' /> </DropdownMenuPrimitive.ItemIndicator> </View> <>{children}</> </DropdownMenuPrimitive.RadioItem>));DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< DropdownMenuPrimitive.LabelRef, DropdownMenuPrimitive.LabelProps & { inset?: boolean; }>(({ className, inset, ...props }, ref) => ( <DropdownMenuPrimitive.Label ref={ref} className={cn( 'px-2 py-1.5 text-sm native:text-base font-semibold text-foreground web:cursor-default', inset && 'pl-8', className )} {...props} />));DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< DropdownMenuPrimitive.SeparatorRef, DropdownMenuPrimitive.SeparatorProps>(({ className, ...props }, ref) => ( <DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />));DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: TextProps) => { return ( <Text className={cn( 'ml-auto text-xs native:text-sm tracking-widest text-muted-foreground', className )} {...props} /> );};DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,};
Usage
import * as React from 'react';import Animated, { FadeIn } from 'react-native-reanimated';import { Button } from '~/components/ui/button';import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,} from '~/components/ui/dropdown-menu';import { Text } from '~/components/ui/text';
function Example() {
return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant='outline'> <Text>Open</Text> </Button> </DropdownMenuTrigger> <DropdownMenuContent insets={contentInsets} className='w-64 native:w-72'> <DropdownMenuLabel>My Account</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <Text>Team</Text> </DropdownMenuItem> <DropdownMenuSub> <DropdownMenuSubTrigger> <Text>Invite users</Text> </DropdownMenuSubTrigger> <DropdownMenuSubContent> <Animated.View entering={FadeIn.duration(200)}> <DropdownMenuItem> <Text>Email</Text> </DropdownMenuItem> <DropdownMenuItem> <Text>Message</Text> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Text>More...</Text> </DropdownMenuItem> </Animated.View> </DropdownMenuSubContent> </DropdownMenuSub> <DropdownMenuItem> <Text>New Team</Text> <DropdownMenuShortcut>⌘+T</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem> <Text>GitHub</Text> </DropdownMenuItem> <DropdownMenuItem> <Text>Support</Text> </DropdownMenuItem> <DropdownMenuItem disabled> <Text>API</Text> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Text>Log out</Text> <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> );}
Props
DropdownMenu
Extends View
props
Prop | Type | Note |
---|---|---|
onOpenChange | (value: boolean) => void | |
asChild | boolean | (optional) |
DropdownMenuTrigger
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
inset | boolean | (optional) |
DropdownMenuPortal
Prop | Type | Note |
---|---|---|
children* | React.ReactNode | |
forceMount | true | undefined | (optional) |
hostName | string | Web Only (optional) |
container | HTMLElement | null | undefined | Web Only (optional) |
DropdownMenuContent
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
overlayStyle | StyleProp<ViewStyle> | (optional) |
overlayClassName | string | (optional) |
forceMount | true | undefined | (optional) |
alignOffset | number | (optional) |
insets | Insets | (optional) |
avoidCollisions | boolean | (optional) |
align | ’start’ | ‘center’ | ‘end’ | (optional) |
side | ’top’ | ‘bottom’ | (optional) |
sideOffset | number | (optional) |
disablePositioningStyle | boolean | Native Only (optional) |
loop | boolean | Web Only (optional) |
onCloseAutoFocus | (event: Event) => void | 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) |
collisionBoundary | Element | null | Array<Element | null> | Web Only (optional) |
sticky | ’partial’ | ‘always’ | Web Only (optional) |
hideWhenDetached | boolean | Web Only (optional) |
DropdownMenuGroup
Extends Text
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
DropdownMenuLabel
Extends Text
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
DropdownMenuItem
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
textValue | boolean | (optional) |
closeOnPress | boolean | (optional) |
DropdownMenuCheckboxItem
Extends Pressable
props
Prop | Type | Note |
---|---|---|
checked* | boolean | |
onCheckedChange* | (checked: boolean) => void | |
textValue* | string | |
asChild | boolean | (optional) |
closeOnPress | boolean | Native Only (optional) |
DropdownMenuRadioGroup
Extends View
props
Prop | Type | Note |
---|---|---|
value* | boolean | |
onValueChange* | (value: string) => void | |
asChild | boolean | (optional) |
DropdownMenuRadioItem
Extends Pressable
props
Prop | Type | Note |
---|---|---|
value* | boolean | |
onCheckedChange* | (value: boolean) => void | |
asChild | boolean | (optional) |
closeOnPress | boolean | Native Only (optional) |
DropdownMenuSeparator
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
decorative | boolean | (optional) |
DropdownMenuSub
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
defaultOpen | boolean | (optional) |
open | boolean | (optional) |
onOpenChange | (value: boolean) => void | (optional) |
DropdownMenuSubTrigger
Extends Pressable
props
Prop | Type | Note |
---|---|---|
textValue | string | (optional) |
asChild | boolean | (optional) |
DropdownMenuSubContent
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
forceMount | true / | undefined |
DropdownMenuShortcut
Extends Text
props