dropdown-menu.tsx

1'use client'
2
3import * as React from 'react'
4import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
5import {
6	CheckIcon,
7	ChevronRightIcon,
8	DotFilledIcon,
9} from '@radix-ui/react-icons'
10
11import { cn } from '@/lib/utils'
12
13const DropdownMenu = DropdownMenuPrimitive.Root
14
15const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
16
17const DropdownMenuGroup = DropdownMenuPrimitive.Group
18
19const DropdownMenuPortal = DropdownMenuPrimitive.Portal
20
21const DropdownMenuSub = DropdownMenuPrimitive.Sub
22
23const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
24
25const DropdownMenuSubTrigger = React.forwardRef<
26	React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
27	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
28		inset?: boolean
29	}
30>(({ className, inset, children, ...props }, ref) => (
31	<DropdownMenuPrimitive.SubTrigger
32		ref={ref}
33		className={cn(
34			'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',
35			inset && 'pl-8',
36			className,
37		)}
38		{...props}
39	>
40		{children}
41		<ChevronRightIcon className="ml-auto" />
42	</DropdownMenuPrimitive.SubTrigger>
43))
44DropdownMenuSubTrigger.displayName =
45	DropdownMenuPrimitive.SubTrigger.displayName
46
47const DropdownMenuSubContent = React.forwardRef<
48	React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
49	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
50>(({ className, ...props }, ref) => (
51	<DropdownMenuPrimitive.SubContent
52		ref={ref}
53		className={cn(
54			'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',
55			className,
56		)}
57		{...props}
58	/>
59))
60DropdownMenuSubContent.displayName =
61	DropdownMenuPrimitive.SubContent.displayName
62
63const DropdownMenuContent = React.forwardRef<
64	React.ElementRef<typeof DropdownMenuPrimitive.Content>,
65	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
66>(({ className, sideOffset = 4, ...props }, ref) => (
67	<DropdownMenuPrimitive.Portal>
68		<DropdownMenuPrimitive.Content
69			ref={ref}
70			sideOffset={sideOffset}
71			className={cn(
72				'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
73				'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',
74				className,
75			)}
76			{...props}
77		/>
78	</DropdownMenuPrimitive.Portal>
79))
80DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
81
82const DropdownMenuItem = React.forwardRef<
83	React.ElementRef<typeof DropdownMenuPrimitive.Item>,
84	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
85		inset?: boolean
86	}
87>(({ className, inset, ...props }, ref) => (
88	<DropdownMenuPrimitive.Item
89		ref={ref}
90		className={cn(
91			'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',
92			inset && 'pl-8',
93			className,
94		)}
95		{...props}
96	/>
97))
98DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
99
100const DropdownMenuCheckboxItem = React.forwardRef<
101	React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
102	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
103>(({ className, children, checked, ...props }, ref) => (
104	<DropdownMenuPrimitive.CheckboxItem
105		ref={ref}
106		className={cn(
107			'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',
108			className,
109		)}
110		checked={checked}
111		{...props}
112	>
113		<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
114			<DropdownMenuPrimitive.ItemIndicator>
115				<CheckIcon className="h-4 w-4" />
116			</DropdownMenuPrimitive.ItemIndicator>
117		</span>
118		{children}
119	</DropdownMenuPrimitive.CheckboxItem>
120))
121DropdownMenuCheckboxItem.displayName =
122	DropdownMenuPrimitive.CheckboxItem.displayName
123
124const DropdownMenuRadioItem = React.forwardRef<
125	React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
126	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
127>(({ className, children, ...props }, ref) => (
128	<DropdownMenuPrimitive.RadioItem
129		ref={ref}
130		className={cn(
131			'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',
132			className,
133		)}
134		{...props}
135	>
136		<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
137			<DropdownMenuPrimitive.ItemIndicator>
138				<DotFilledIcon className="h-4 w-4 fill-current" />
139			</DropdownMenuPrimitive.ItemIndicator>
140		</span>
141		{children}
142	</DropdownMenuPrimitive.RadioItem>
143))
144DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
145
146const DropdownMenuLabel = React.forwardRef<
147	React.ElementRef<typeof DropdownMenuPrimitive.Label>,
148	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
149		inset?: boolean
150	}
151>(({ className, inset, ...props }, ref) => (
152	<DropdownMenuPrimitive.Label
153		ref={ref}
154		className={cn(
155			'px-2 py-1.5 text-sm font-semibold',
156			inset && 'pl-8',
157			className,
158		)}
159		{...props}
160	/>
161))
162DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
163
164const DropdownMenuSeparator = React.forwardRef<
165	React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
166	React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
167>(({ className, ...props }, ref) => (
168	<DropdownMenuPrimitive.Separator
169		ref={ref}
170		className={cn('-mx-1 my-1 h-px bg-muted', className)}
171		{...props}
172	/>
173))
174DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
175
176const DropdownMenuShortcut = ({
177	className,
178	...props
179}: React.HTMLAttributes<HTMLSpanElement>) => {
180	return (
181		<span
182			className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
183			{...props}
184		/>
185	)
186}
187DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
188
189export {
190	DropdownMenu,
191	DropdownMenuTrigger,
192	DropdownMenuContent,
193	DropdownMenuItem,
194	DropdownMenuCheckboxItem,
195	DropdownMenuRadioItem,
196	DropdownMenuLabel,
197	DropdownMenuSeparator,
198	DropdownMenuShortcut,
199	DropdownMenuGroup,
200	DropdownMenuPortal,
201	DropdownMenuSub,
202	DropdownMenuSubContent,
203	DropdownMenuSubTrigger,
204	DropdownMenuRadioGroup,
205}
206