blob: e6ac7900b4c112445590347d5057b3fb4cccce4c [file] [log] [blame]
gio818da4e2025-05-12 14:45:35 +00001import * as React from "react";
2import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3import { cn } from "@/lib/utils";
4import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons";
5
6const DropdownMenu = DropdownMenuPrimitive.Root;
7
8const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9
10const DropdownMenuGroup = DropdownMenuPrimitive.Group;
11
12const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
13
14const DropdownMenuSub = DropdownMenuPrimitive.Sub;
15
16const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
17
18const DropdownMenuSubTrigger = React.forwardRef<
19 React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
20 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
21 inset?: boolean;
22 }
23>(({ className, inset, children, ...props }, ref) => (
24 <DropdownMenuPrimitive.SubTrigger
25 ref={ref}
26 className={cn(
27 "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
28 inset && "pl-8",
29 className,
30 )}
31 {...props}
32 >
33 {children}
34 <ChevronRightIcon className="ml-auto" />
35 </DropdownMenuPrimitive.SubTrigger>
36));
37DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
38
39const DropdownMenuSubContent = React.forwardRef<
40 React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
41 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
42>(({ className, ...props }, ref) => (
43 <DropdownMenuPrimitive.SubContent
44 ref={ref}
45 className={cn(
46 "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-dropdown-menu-content-transform-origin]",
47 className,
48 )}
49 {...props}
50 />
51));
52DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
53
54const DropdownMenuContent = React.forwardRef<
55 React.ElementRef<typeof DropdownMenuPrimitive.Content>,
56 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
57>(({ className, sideOffset = 4, ...props }, ref) => (
58 <DropdownMenuPrimitive.Portal>
59 <DropdownMenuPrimitive.Content
60 ref={ref}
61 sideOffset={sideOffset}
62 className={cn(
63 "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
64 "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 origin-[--radix-dropdown-menu-content-transform-origin]",
65 className,
66 )}
67 {...props}
68 />
69 </DropdownMenuPrimitive.Portal>
70));
71DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
72
73const DropdownMenuItem = React.forwardRef<
74 React.ElementRef<typeof DropdownMenuPrimitive.Item>,
75 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
76 inset?: boolean;
77 }
78>(({ className, inset, ...props }, ref) => (
79 <DropdownMenuPrimitive.Item
80 ref={ref}
81 className={cn(
82 "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
83 inset && "pl-8",
84 className,
85 )}
86 {...props}
87 />
88));
89DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
90
91const DropdownMenuCheckboxItem = React.forwardRef<
92 React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
93 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
94>(({ className, children, checked, ...props }, ref) => (
95 <DropdownMenuPrimitive.CheckboxItem
96 ref={ref}
97 className={cn(
98 "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
99 className,
100 )}
101 checked={checked}
102 {...props}
103 >
104 <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
105 <DropdownMenuPrimitive.ItemIndicator>
106 <CheckIcon className="h-4 w-4" />
107 </DropdownMenuPrimitive.ItemIndicator>
108 </span>
109 {children}
110 </DropdownMenuPrimitive.CheckboxItem>
111));
112DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
113
114const DropdownMenuRadioItem = React.forwardRef<
115 React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
116 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
117>(({ className, children, ...props }, ref) => (
118 <DropdownMenuPrimitive.RadioItem
119 ref={ref}
120 className={cn(
121 "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122 className,
123 )}
124 {...props}
125 >
126 <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127 <DropdownMenuPrimitive.ItemIndicator>
128 <DotFilledIcon className="h-2 w-2 fill-current" />
129 </DropdownMenuPrimitive.ItemIndicator>
130 </span>
131 {children}
132 </DropdownMenuPrimitive.RadioItem>
133));
134DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
135
136const DropdownMenuLabel = React.forwardRef<
137 React.ElementRef<typeof DropdownMenuPrimitive.Label>,
138 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
139 inset?: boolean;
140 }
141>(({ className, inset, ...props }, ref) => (
142 <DropdownMenuPrimitive.Label
143 ref={ref}
144 className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
145 {...props}
146 />
147));
148DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
149
150const DropdownMenuSeparator = React.forwardRef<
151 React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
152 React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
153>(({ className, ...props }, ref) => (
154 <DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
155));
156DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
157
158const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
159 return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />;
160};
161DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
162
163export {
164 DropdownMenu,
165 DropdownMenuTrigger,
166 DropdownMenuContent,
167 DropdownMenuItem,
168 DropdownMenuCheckboxItem,
169 DropdownMenuRadioItem,
170 DropdownMenuLabel,
171 DropdownMenuSeparator,
172 DropdownMenuShortcut,
173 DropdownMenuGroup,
174 DropdownMenuPortal,
175 DropdownMenuSub,
176 DropdownMenuSubContent,
177 DropdownMenuSubTrigger,
178 DropdownMenuRadioGroup,
179};