context-menu.tsx 8.0 KB

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