ui-arreya-components 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.storybook/main.ts +18 -0
  2. package/.storybook/preview.ts +16 -0
  3. package/.storybook/vitest.setup.ts +9 -0
  4. package/README.md +54 -0
  5. package/components.json +21 -0
  6. package/dist/styles.css +3 -0
  7. package/dist/ui.cjs.js +31 -0
  8. package/dist/ui.es.js +3060 -0
  9. package/dist/ui.umd.js +31 -0
  10. package/eslint.config.js +28 -0
  11. package/package.json +120 -0
  12. package/postcss.config.js +6 -0
  13. package/scripts/template.sh +57 -0
  14. package/src/components/feature/login-form.stories.tsx +35 -0
  15. package/src/components/feature/login-form.tsx +97 -0
  16. package/src/components/index.ts +1 -0
  17. package/src/components/ui/accordion.stories.tsx.disabled +36 -0
  18. package/src/components/ui/accordion.tsx +55 -0
  19. package/src/components/ui/alert-dialog.stories.tsx +46 -0
  20. package/src/components/ui/alert-dialog.tsx +139 -0
  21. package/src/components/ui/alert.stories.tsx +45 -0
  22. package/src/components/ui/alert.tsx +59 -0
  23. package/src/components/ui/aspect-ratio.stories.tsx +24 -0
  24. package/src/components/ui/aspect-ratio.tsx +5 -0
  25. package/src/components/ui/avatar.stories.tsx +29 -0
  26. package/src/components/ui/avatar.tsx +48 -0
  27. package/src/components/ui/badge.stories.tsx +43 -0
  28. package/src/components/ui/badge.tsx +36 -0
  29. package/src/components/ui/breadcrumb.stories.tsx +146 -0
  30. package/src/components/ui/breadcrumb.tsx +115 -0
  31. package/src/components/ui/button.stories.tsx +87 -0
  32. package/src/components/ui/button.tsx +57 -0
  33. package/src/components/ui/card.stories.tsx +99 -0
  34. package/src/components/ui/card.tsx +76 -0
  35. package/src/components/ui/carousel.stories.tsx +47 -0
  36. package/src/components/ui/carousel.tsx +260 -0
  37. package/src/components/ui/chart.tsx +363 -0
  38. package/src/components/ui/checkbox.tsx +28 -0
  39. package/src/components/ui/collapsible.tsx +9 -0
  40. package/src/components/ui/context-menu.tsx +198 -0
  41. package/src/components/ui/dialog.tsx +120 -0
  42. package/src/components/ui/drawer.tsx +116 -0
  43. package/src/components/ui/dropdown-menu.stories.tsx +92 -0
  44. package/src/components/ui/dropdown-menu.tsx +199 -0
  45. package/src/components/ui/form.tsx +176 -0
  46. package/src/components/ui/hover-card.tsx +27 -0
  47. package/src/components/ui/index.ts +1 -0
  48. package/src/components/ui/input-otp.tsx +69 -0
  49. package/src/components/ui/input.tsx +22 -0
  50. package/src/components/ui/label.tsx +24 -0
  51. package/src/components/ui/menubar.tsx +254 -0
  52. package/src/components/ui/navigation-menu.tsx +128 -0
  53. package/src/components/ui/pagination.tsx +117 -0
  54. package/src/components/ui/popover.tsx +31 -0
  55. package/src/components/ui/progress.tsx +26 -0
  56. package/src/components/ui/radio-group.tsx +42 -0
  57. package/src/components/ui/resizable.tsx +43 -0
  58. package/src/components/ui/scroll-area.tsx +46 -0
  59. package/src/components/ui/select.tsx +157 -0
  60. package/src/components/ui/separator.tsx +29 -0
  61. package/src/components/ui/sheet.tsx +140 -0
  62. package/src/components/ui/sidebar.tsx +771 -0
  63. package/src/components/ui/skeleton.tsx +15 -0
  64. package/src/components/ui/slider.tsx +26 -0
  65. package/src/components/ui/sonner.tsx +29 -0
  66. package/src/components/ui/switch.tsx +27 -0
  67. package/src/components/ui/table.tsx +120 -0
  68. package/src/components/ui/tabs.tsx +53 -0
  69. package/src/components/ui/textarea.tsx +22 -0
  70. package/src/components/ui/toast.tsx +127 -0
  71. package/src/components/ui/toaster.tsx +33 -0
  72. package/src/components/ui/toggle-group.tsx +59 -0
  73. package/src/components/ui/toggle.tsx +43 -0
  74. package/src/components/ui/tooltip.tsx +30 -0
  75. package/src/hooks/use-mobile.tsx +19 -0
  76. package/src/hooks/use-toast.ts +194 -0
  77. package/src/index.css +3484 -0
  78. package/src/index.ts +1 -0
  79. package/src/lib/types.ts +5 -0
  80. package/src/lib/utils.ts +6 -0
  81. package/src/styles/tailwind.css +86 -0
  82. package/tailwind.config.js +96 -0
  83. package/tsconfig.app.json +30 -0
  84. package/tsconfig.json +13 -0
  85. package/tsconfig.node.json +24 -0
  86. package/vite.config.ts +33 -0
  87. package/vitest.workspace.ts +32 -0
@@ -0,0 +1,363 @@
1
+ import * as React from "react"
2
+ import * as RechartsPrimitive from "recharts"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ // Format: { THEME_NAME: CSS_SELECTOR }
7
+ const THEMES = { light: "", dark: ".dark" } as const
8
+
9
+ export type ChartConfig = {
10
+ [k in string]: {
11
+ label?: React.ReactNode
12
+ icon?: React.ComponentType
13
+ } & (
14
+ | { color?: string; theme?: never }
15
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
16
+ )
17
+ }
18
+
19
+ type ChartContextProps = {
20
+ config: ChartConfig
21
+ }
22
+
23
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
24
+
25
+ function useChart() {
26
+ const context = React.useContext(ChartContext)
27
+
28
+ if (!context) {
29
+ throw new Error("useChart must be used within a <ChartContainer />")
30
+ }
31
+
32
+ return context
33
+ }
34
+
35
+ const ChartContainer = React.forwardRef<
36
+ HTMLDivElement,
37
+ React.ComponentProps<"div"> & {
38
+ config: ChartConfig
39
+ children: React.ComponentProps<
40
+ typeof RechartsPrimitive.ResponsiveContainer
41
+ >["children"]
42
+ }
43
+ >(({ id, className, children, config, ...props }, ref) => {
44
+ const uniqueId = React.useId()
45
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46
+
47
+ return (
48
+ <ChartContext.Provider value={{ config }}>
49
+ <div
50
+ data-chart={chartId}
51
+ ref={ref}
52
+ className={cn(
53
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
54
+ className
55
+ )}
56
+ {...props}
57
+ >
58
+ <ChartStyle id={chartId} config={config} />
59
+ <RechartsPrimitive.ResponsiveContainer>
60
+ {children}
61
+ </RechartsPrimitive.ResponsiveContainer>
62
+ </div>
63
+ </ChartContext.Provider>
64
+ )
65
+ })
66
+ ChartContainer.displayName = "Chart"
67
+
68
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69
+ const colorConfig = Object.entries(config).filter(
70
+ ([, config]) => config.theme || config.color
71
+ )
72
+
73
+ if (!colorConfig.length) {
74
+ return null
75
+ }
76
+
77
+ return (
78
+ <style
79
+ dangerouslySetInnerHTML={{
80
+ __html: Object.entries(THEMES)
81
+ .map(
82
+ ([theme, prefix]) => `
83
+ ${prefix} [data-chart=${id}] {
84
+ ${colorConfig
85
+ .map(([key, itemConfig]) => {
86
+ const color =
87
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
88
+ itemConfig.color
89
+ return color ? ` --color-${key}: ${color};` : null
90
+ })
91
+ .join("\n")}
92
+ }
93
+ `
94
+ )
95
+ .join("\n"),
96
+ }}
97
+ />
98
+ )
99
+ }
100
+
101
+ const ChartTooltip = RechartsPrimitive.Tooltip
102
+
103
+ const ChartTooltipContent = React.forwardRef<
104
+ HTMLDivElement,
105
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
106
+ React.ComponentProps<"div"> & {
107
+ hideLabel?: boolean
108
+ hideIndicator?: boolean
109
+ indicator?: "line" | "dot" | "dashed"
110
+ nameKey?: string
111
+ labelKey?: string
112
+ }
113
+ >(
114
+ (
115
+ {
116
+ active,
117
+ payload,
118
+ className,
119
+ indicator = "dot",
120
+ hideLabel = false,
121
+ hideIndicator = false,
122
+ label,
123
+ labelFormatter,
124
+ labelClassName,
125
+ formatter,
126
+ color,
127
+ nameKey,
128
+ labelKey,
129
+ },
130
+ ref
131
+ ) => {
132
+ const { config } = useChart()
133
+
134
+ const tooltipLabel = React.useMemo(() => {
135
+ if (hideLabel || !payload?.length) {
136
+ return null
137
+ }
138
+
139
+ const [item] = payload
140
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`
141
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
142
+ const value =
143
+ !labelKey && typeof label === "string"
144
+ ? config[label as keyof typeof config]?.label || label
145
+ : itemConfig?.label
146
+
147
+ if (labelFormatter) {
148
+ return (
149
+ <div className={cn("font-medium", labelClassName)}>
150
+ {labelFormatter(value, payload)}
151
+ </div>
152
+ )
153
+ }
154
+
155
+ if (!value) {
156
+ return null
157
+ }
158
+
159
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
160
+ }, [
161
+ label,
162
+ labelFormatter,
163
+ payload,
164
+ hideLabel,
165
+ labelClassName,
166
+ config,
167
+ labelKey,
168
+ ])
169
+
170
+ if (!active || !payload?.length) {
171
+ return null
172
+ }
173
+
174
+ const nestLabel = payload.length === 1 && indicator !== "dot"
175
+
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ className={cn(
180
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
181
+ className
182
+ )}
183
+ >
184
+ {!nestLabel ? tooltipLabel : null}
185
+ <div className="grid gap-1.5">
186
+ {payload.map((item, index) => {
187
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
188
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
189
+ const indicatorColor = color || item.payload.fill || item.color
190
+
191
+ return (
192
+ <div
193
+ key={item.dataKey}
194
+ className={cn(
195
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
196
+ indicator === "dot" && "items-center"
197
+ )}
198
+ >
199
+ {formatter && item?.value !== undefined && item.name ? (
200
+ formatter(item.value, item.name, item, index, item.payload)
201
+ ) : (
202
+ <>
203
+ {itemConfig?.icon ? (
204
+ <itemConfig.icon />
205
+ ) : (
206
+ !hideIndicator && (
207
+ <div
208
+ className={cn(
209
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
210
+ {
211
+ "h-2.5 w-2.5": indicator === "dot",
212
+ "w-1": indicator === "line",
213
+ "w-0 border-[1.5px] border-dashed bg-transparent":
214
+ indicator === "dashed",
215
+ "my-0.5": nestLabel && indicator === "dashed",
216
+ }
217
+ )}
218
+ style={
219
+ {
220
+ "--color-bg": indicatorColor,
221
+ "--color-border": indicatorColor,
222
+ } as React.CSSProperties
223
+ }
224
+ />
225
+ )
226
+ )}
227
+ <div
228
+ className={cn(
229
+ "flex flex-1 justify-between leading-none",
230
+ nestLabel ? "items-end" : "items-center"
231
+ )}
232
+ >
233
+ <div className="grid gap-1.5">
234
+ {nestLabel ? tooltipLabel : null}
235
+ <span className="text-muted-foreground">
236
+ {itemConfig?.label || item.name}
237
+ </span>
238
+ </div>
239
+ {item.value && (
240
+ <span className="font-mono font-medium tabular-nums text-foreground">
241
+ {item.value.toLocaleString()}
242
+ </span>
243
+ )}
244
+ </div>
245
+ </>
246
+ )}
247
+ </div>
248
+ )
249
+ })}
250
+ </div>
251
+ </div>
252
+ )
253
+ }
254
+ )
255
+ ChartTooltipContent.displayName = "ChartTooltip"
256
+
257
+ const ChartLegend = RechartsPrimitive.Legend
258
+
259
+ const ChartLegendContent = React.forwardRef<
260
+ HTMLDivElement,
261
+ React.ComponentProps<"div"> &
262
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
263
+ hideIcon?: boolean
264
+ nameKey?: string
265
+ }
266
+ >(
267
+ (
268
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
269
+ ref
270
+ ) => {
271
+ const { config } = useChart()
272
+
273
+ if (!payload?.length) {
274
+ return null
275
+ }
276
+
277
+ return (
278
+ <div
279
+ ref={ref}
280
+ className={cn(
281
+ "flex items-center justify-center gap-4",
282
+ verticalAlign === "top" ? "pb-3" : "pt-3",
283
+ className
284
+ )}
285
+ >
286
+ {payload.map((item) => {
287
+ const key = `${nameKey || item.dataKey || "value"}`
288
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
289
+
290
+ return (
291
+ <div
292
+ key={item.value}
293
+ className={cn(
294
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
295
+ )}
296
+ >
297
+ {itemConfig?.icon && !hideIcon ? (
298
+ <itemConfig.icon />
299
+ ) : (
300
+ <div
301
+ className="h-2 w-2 shrink-0 rounded-[2px]"
302
+ style={{
303
+ backgroundColor: item.color,
304
+ }}
305
+ />
306
+ )}
307
+ {itemConfig?.label}
308
+ </div>
309
+ )
310
+ })}
311
+ </div>
312
+ )
313
+ }
314
+ )
315
+ ChartLegendContent.displayName = "ChartLegend"
316
+
317
+ // Helper to extract item config from a payload.
318
+ function getPayloadConfigFromPayload(
319
+ config: ChartConfig,
320
+ payload: unknown,
321
+ key: string
322
+ ) {
323
+ if (typeof payload !== "object" || payload === null) {
324
+ return undefined
325
+ }
326
+
327
+ const payloadPayload =
328
+ "payload" in payload &&
329
+ typeof payload.payload === "object" &&
330
+ payload.payload !== null
331
+ ? payload.payload
332
+ : undefined
333
+
334
+ let configLabelKey: string = key
335
+
336
+ if (
337
+ key in payload &&
338
+ typeof payload[key as keyof typeof payload] === "string"
339
+ ) {
340
+ configLabelKey = payload[key as keyof typeof payload] as string
341
+ } else if (
342
+ payloadPayload &&
343
+ key in payloadPayload &&
344
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
345
+ ) {
346
+ configLabelKey = payloadPayload[
347
+ key as keyof typeof payloadPayload
348
+ ] as string
349
+ }
350
+
351
+ return configLabelKey in config
352
+ ? config[configLabelKey]
353
+ : config[key as keyof typeof config]
354
+ }
355
+
356
+ export {
357
+ ChartContainer,
358
+ ChartTooltip,
359
+ ChartTooltipContent,
360
+ ChartLegend,
361
+ ChartLegendContent,
362
+ ChartStyle,
363
+ }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator
20
+ className={cn("flex items-center justify-center text-current")}
21
+ >
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ))
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
+
28
+ export { Checkbox }
@@ -0,0 +1,9 @@
1
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
@@ -0,0 +1,198 @@
1
+ import * as React from "react"
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <ContextMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </ContextMenuPrimitive.SubTrigger>
37
+ ))
38
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
39
+
40
+ const ContextMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <ContextMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 origin-[--radix-context-menu-content-transform-origin]",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
54
+
55
+ const ContextMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58
+ >(({ className, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Portal>
60
+ <ContextMenuPrimitive.Content
61
+ ref={ref}
62
+ className={cn(
63
+ "z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 origin-[--radix-context-menu-content-transform-origin]",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ </ContextMenuPrimitive.Portal>
69
+ ))
70
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
71
+
72
+ const ContextMenuItem = React.forwardRef<
73
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
74
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75
+ inset?: boolean
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <ContextMenuPrimitive.Item
79
+ ref={ref}
80
+ className={cn(
81
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ inset && "pl-8",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ ))
88
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
89
+
90
+ const ContextMenuCheckboxItem = React.forwardRef<
91
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93
+ >(({ className, children, checked, ...props }, ref) => (
94
+ <ContextMenuPrimitive.CheckboxItem
95
+ ref={ref}
96
+ className={cn(
97
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
98
+ className
99
+ )}
100
+ checked={checked}
101
+ {...props}
102
+ >
103
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104
+ <ContextMenuPrimitive.ItemIndicator>
105
+ <Check className="h-4 w-4" />
106
+ </ContextMenuPrimitive.ItemIndicator>
107
+ </span>
108
+ {children}
109
+ </ContextMenuPrimitive.CheckboxItem>
110
+ ))
111
+ ContextMenuCheckboxItem.displayName =
112
+ ContextMenuPrimitive.CheckboxItem.displayName
113
+
114
+ const ContextMenuRadioItem = React.forwardRef<
115
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
116
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <ContextMenuPrimitive.RadioItem
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <ContextMenuPrimitive.ItemIndicator>
128
+ <Circle className="h-4 w-4 fill-current" />
129
+ </ContextMenuPrimitive.ItemIndicator>
130
+ </span>
131
+ {children}
132
+ </ContextMenuPrimitive.RadioItem>
133
+ ))
134
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
135
+
136
+ const ContextMenuLabel = React.forwardRef<
137
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
138
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
139
+ inset?: boolean
140
+ }
141
+ >(({ className, inset, ...props }, ref) => (
142
+ <ContextMenuPrimitive.Label
143
+ ref={ref}
144
+ className={cn(
145
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
146
+ inset && "pl-8",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ ))
152
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
153
+
154
+ const ContextMenuSeparator = React.forwardRef<
155
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
156
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
157
+ >(({ className, ...props }, ref) => (
158
+ <ContextMenuPrimitive.Separator
159
+ ref={ref}
160
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
161
+ {...props}
162
+ />
163
+ ))
164
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
165
+
166
+ const ContextMenuShortcut = ({
167
+ className,
168
+ ...props
169
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
170
+ return (
171
+ <span
172
+ className={cn(
173
+ "ml-auto text-xs tracking-widest text-muted-foreground",
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
181
+
182
+ export {
183
+ ContextMenu,
184
+ ContextMenuTrigger,
185
+ ContextMenuContent,
186
+ ContextMenuItem,
187
+ ContextMenuCheckboxItem,
188
+ ContextMenuRadioItem,
189
+ ContextMenuLabel,
190
+ ContextMenuSeparator,
191
+ ContextMenuShortcut,
192
+ ContextMenuGroup,
193
+ ContextMenuPortal,
194
+ ContextMenuSub,
195
+ ContextMenuSubContent,
196
+ ContextMenuSubTrigger,
197
+ ContextMenuRadioGroup,
198
+ }