context-menu.tsx 8.0 KB

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