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