src/components/ui/dropdown-menu.tsx 8.2 K raw
1
import * as React from "react"
2
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
4
5
import { cn } from "@/lib/utils"
6
7
function DropdownMenu({
8
  ...props
9
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
10
  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
11
}
12
13
function DropdownMenuPortal({
14
  ...props
15
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
16
  return (
17
    <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
18
  )
19
}
20
21
function DropdownMenuTrigger({
22
  ...props
23
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
24
  return (
25
    <DropdownMenuPrimitive.Trigger
26
      data-slot="dropdown-menu-trigger"
27
      {...props}
28
    />
29
  )
30
}
31
32
function DropdownMenuContent({
33
  className,
34
  sideOffset = 4,
35
  ...props
36
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
37
  return (
38
    <DropdownMenuPrimitive.Portal>
39
      <DropdownMenuPrimitive.Content
40
        data-slot="dropdown-menu-content"
41
        sideOffset={sideOffset}
42
        className={cn(
43
          "bg-popover text-popover-foreground 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
44
          className
45
        )}
46
        {...props}
47
      />
48
    </DropdownMenuPrimitive.Portal>
49
  )
50
}
51
52
function DropdownMenuGroup({
53
  ...props
54
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
55
  return (
56
    <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
57
  )
58
}
59
60
function DropdownMenuItem({
61
  className,
62
  inset,
63
  variant = "default",
64
  ...props
65
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
66
  inset?: boolean
67
  variant?: "default" | "destructive"
68
}) {
69
  return (
70
    <DropdownMenuPrimitive.Item
71
      data-slot="dropdown-menu-item"
72
      data-inset={inset}
73
      data-variant={variant}
74
      className={cn(
75
        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
76
        className
77
      )}
78
      {...props}
79
    />
80
  )
81
}
82
83
function DropdownMenuCheckboxItem({
84
  className,
85
  children,
86
  checked,
87
  ...props
88
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
89
  return (
90
    <DropdownMenuPrimitive.CheckboxItem
91
      data-slot="dropdown-menu-checkbox-item"
92
      className={cn(
93
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
94
        className
95
      )}
96
      checked={checked}
97
      {...props}
98
    >
99
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
100
        <DropdownMenuPrimitive.ItemIndicator>
101
          <CheckIcon className="size-4" />
102
        </DropdownMenuPrimitive.ItemIndicator>
103
      </span>
104
      {children}
105
    </DropdownMenuPrimitive.CheckboxItem>
106
  )
107
}
108
109
function DropdownMenuRadioGroup({
110
  ...props
111
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
112
  return (
113
    <DropdownMenuPrimitive.RadioGroup
114
      data-slot="dropdown-menu-radio-group"
115
      {...props}
116
    />
117
  )
118
}
119
120
function DropdownMenuRadioItem({
121
  className,
122
  children,
123
  ...props
124
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
125
  return (
126
    <DropdownMenuPrimitive.RadioItem
127
      data-slot="dropdown-menu-radio-item"
128
      className={cn(
129
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
130
        className
131
      )}
132
      {...props}
133
    >
134
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
135
        <DropdownMenuPrimitive.ItemIndicator>
136
          <CircleIcon className="size-2 fill-current" />
137
        </DropdownMenuPrimitive.ItemIndicator>
138
      </span>
139
      {children}
140
    </DropdownMenuPrimitive.RadioItem>
141
  )
142
}
143
144
function DropdownMenuLabel({
145
  className,
146
  inset,
147
  ...props
148
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
149
  inset?: boolean
150
}) {
151
  return (
152
    <DropdownMenuPrimitive.Label
153
      data-slot="dropdown-menu-label"
154
      data-inset={inset}
155
      className={cn(
156
        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
157
        className
158
      )}
159
      {...props}
160
    />
161
  )
162
}
163
164
function DropdownMenuSeparator({
165
  className,
166
  ...props
167
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
168
  return (
169
    <DropdownMenuPrimitive.Separator
170
      data-slot="dropdown-menu-separator"
171
      className={cn("bg-border -mx-1 my-1 h-px", className)}
172
      {...props}
173
    />
174
  )
175
}
176
177
function DropdownMenuShortcut({
178
  className,
179
  ...props
180
}: React.ComponentProps<"span">) {
181
  return (
182
    <span
183
      data-slot="dropdown-menu-shortcut"
184
      className={cn(
185
        "text-muted-foreground ml-auto text-xs tracking-widest",
186
        className
187
      )}
188
      {...props}
189
    />
190
  )
191
}
192
193
function DropdownMenuSub({
194
  ...props
195
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
196
  return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
197
}
198
199
function DropdownMenuSubTrigger({
200
  className,
201
  inset,
202
  children,
203
  ...props
204
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
205
  inset?: boolean
206
}) {
207
  return (
208
    <DropdownMenuPrimitive.SubTrigger
209
      data-slot="dropdown-menu-sub-trigger"
210
      data-inset={inset}
211
      className={cn(
212
        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
213
        className
214
      )}
215
      {...props}
216
    >
217
      {children}
218
      <ChevronRightIcon className="ml-auto size-4" />
219
    </DropdownMenuPrimitive.SubTrigger>
220
  )
221
}
222
223
function DropdownMenuSubContent({
224
  className,
225
  ...props
226
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
227
  return (
228
    <DropdownMenuPrimitive.SubContent
229
      data-slot="dropdown-menu-sub-content"
230
      className={cn(
231
        "bg-popover text-popover-foreground 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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
232
        className
233
      )}
234
      {...props}
235
    />
236
  )
237
}
238
239
export {
240
  DropdownMenu,
241
  DropdownMenuPortal,
242
  DropdownMenuTrigger,
243
  DropdownMenuContent,
244
  DropdownMenuGroup,
245
  DropdownMenuLabel,
246
  DropdownMenuItem,
247
  DropdownMenuCheckboxItem,
248
  DropdownMenuRadioGroup,
249
  DropdownMenuRadioItem,
250
  DropdownMenuSeparator,
251
  DropdownMenuShortcut,
252
  DropdownMenuSub,
253
  DropdownMenuSubTrigger,
254
  DropdownMenuSubContent,
255
}