Dropdown Menu
Displays a menu to the user with a list of actions.
Installation
bash npx @moe/cli add dropdown-menu Install the following dependency:
npx expo install @rn-primitives/dropdown-menuCopy/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, StyleSheet, View } from "react-native";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { Check } from "@/lib/icons/Check";
import { ChevronRight } from "@/lib/icons/ChevronRight";
import { cn } from "@/lib/utils";
import { TextClassContext } from "@/components/ui/text";
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => {
const { open } = DropdownMenuPrimitive.useSubContext();
return (
<TextClassContext.Provider
value={cn("select-none text-sm native:text-lg text-primary")}
>
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex flex-row web:cursor-default web:select-none items-center gap-2 px-2 py-1.5 native:py-2 web:outline-none web:focus:bg-accent active:bg-accent web:hover:bg-accent rounded-sm",
open && "bg-accent",
inset && "pl-8",
className,
)}
{...props}
>
<>{children}</>
<ChevronRight size={18} className="ml-auto text-foreground" />
</DropdownMenuPrimitive.SubTrigger>
</TextClassContext.Provider>
);
});
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => {
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",
className,
)}
{...props}
/>
);
});
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, ...props }, ref) => {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Overlay
style={Platform.OS !== "web" ? StyleSheet.absoluteFill : undefined}
>
<Animated.View
entering={FadeIn.duration(200)}
exiting={FadeOut.duration(200)}
>
<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",
className,
)}
{...props}
/>
</Animated.View>
</DropdownMenuPrimitive.Overlay>
</DropdownMenuPrimitive.Portal>
);
});
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
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 items-center gap-2 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<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex flex-row web:cursor-default items-center rounded-sm py-1.5 native:py-2 pl-8 pr-2 web:outline-none web:focus:bg-accent active:bg-accent group",
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<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex flex-row web:cursor-default items-center rounded-sm py-1.5 native:py-2 pl-8 pr-2 web:outline-none web:focus:bg-accent active:bg-accent group",
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="h-2 w-2 rounded-full bg-foreground" />
</DropdownMenuPrimitive.ItemIndicator>
</View>
<>{children}</>
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
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<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ 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
}: React.ComponentPropsWithoutRef<typeof View>) => {
return (
<View
className={cn("ml-auto flex flex-row items-center gap-1", className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
};Usage