/ src / components / ui / dropdown-menu.tsx
dropdown-menu.tsx
  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 flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
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  }