dropdown-menu.tsx 8.1 KB

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