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