@srcroot/ui 0.0.54 → 0.0.56
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.
- package/README.md +151 -151
- package/dist/index.d.ts +0 -0
- package/dist/index.js +55 -1
- package/package.json +7 -2
- package/src/registry/analytics/google-analytics.tsx +36 -39
- package/src/registry/analytics/google-tag-manager.tsx +62 -65
- package/src/registry/analytics/meta-pixel.tsx +44 -47
- package/src/registry/analytics/microsoft-clarity.tsx +31 -34
- package/src/registry/analytics/tiktok-pixel.tsx +34 -37
- package/src/registry/lib/utils.ts +0 -0
- package/src/registry/themes/v3/blue.css +157 -157
- package/src/registry/themes/v3/glass.css +153 -153
- package/src/registry/themes/v3/gray.css +157 -157
- package/src/registry/themes/v3/green.css +157 -157
- package/src/registry/themes/v3/neutral.css +157 -157
- package/src/registry/themes/v3/orange.css +157 -157
- package/src/registry/themes/v3/rose.css +157 -157
- package/src/registry/themes/v3/slate.css +157 -157
- package/src/registry/themes/v3/stone.css +157 -157
- package/src/registry/themes/v3/violet.css +186 -186
- package/src/registry/themes/v3/zinc.css +157 -157
- package/src/registry/themes/v4/blue.css +184 -184
- package/src/registry/themes/v4/glass.css +180 -180
- package/src/registry/themes/v4/gray.css +184 -184
- package/src/registry/themes/v4/green.css +184 -184
- package/src/registry/themes/v4/neutral.css +184 -184
- package/src/registry/themes/v4/orange.css +184 -184
- package/src/registry/themes/v4/rose.css +184 -184
- package/src/registry/themes/v4/slate.css +184 -184
- package/src/registry/themes/v4/stone.css +184 -184
- package/src/registry/themes/v4/violet.css +184 -184
- package/src/registry/themes/v4/zinc.css +184 -184
- package/src/registry/ui/accordion.tsx +164 -165
- package/src/registry/ui/alert-dialog.tsx +213 -214
- package/src/registry/ui/alert.tsx +73 -76
- package/src/registry/ui/aspect-ratio.tsx +44 -47
- package/src/registry/ui/avatar.tsx +96 -97
- package/src/registry/ui/badge.tsx +52 -55
- package/src/registry/ui/breadcrumb.tsx +147 -150
- package/src/registry/ui/button-group.tsx +64 -67
- package/src/registry/ui/button.tsx +71 -72
- package/src/registry/ui/calendar.tsx +514 -515
- package/src/registry/ui/card.tsx +88 -91
- package/src/registry/ui/carousel.tsx +214 -214
- package/src/registry/ui/chart.tsx +373 -373
- package/src/registry/ui/chatbot.tsx +86 -13
- package/src/registry/ui/checkbox.tsx +93 -94
- package/src/registry/ui/collapsible.tsx +107 -108
- package/src/registry/ui/combobox.tsx +171 -171
- package/src/registry/ui/command.tsx +300 -300
- package/src/registry/ui/container.tsx +44 -47
- package/src/registry/ui/context-menu.tsx +221 -221
- package/src/registry/ui/date-picker.tsx +228 -228
- package/src/registry/ui/dialog.tsx +269 -270
- package/src/registry/ui/drawer.tsx +10 -4
- package/src/registry/ui/dropdown-menu.tsx +529 -530
- package/src/registry/ui/empty-state.tsx +0 -2
- package/src/registry/ui/file-upload.tsx +0 -0
- package/src/registry/ui/floating-dock.tsx +0 -0
- package/src/registry/ui/form-field.tsx +91 -94
- package/src/registry/ui/google-analytics.tsx +38 -0
- package/src/registry/ui/google-tag-manager.tsx +64 -0
- package/src/registry/ui/hover-card.tsx +223 -223
- package/src/registry/ui/image.tsx +144 -147
- package/src/registry/ui/input-group.tsx +82 -85
- package/src/registry/ui/input.tsx +125 -125
- package/src/registry/ui/kbd.tsx +60 -63
- package/src/registry/ui/label.tsx +36 -37
- package/src/registry/ui/loading-spinner.tsx +108 -111
- package/src/registry/ui/map.tsx +0 -0
- package/src/registry/ui/marquee.tsx +2 -0
- package/src/registry/ui/menubar.tsx +246 -246
- package/src/registry/ui/meta-pixel.tsx +46 -0
- package/src/registry/ui/microsoft-clarity.tsx +33 -0
- package/src/registry/ui/native-select.tsx +49 -52
- package/src/registry/ui/otp-input.tsx +152 -155
- package/src/registry/ui/pagination.tsx +149 -152
- package/src/registry/ui/patterns.tsx +28 -0
- package/src/registry/ui/popover.tsx +226 -227
- package/src/registry/ui/progress.tsx +51 -52
- package/src/registry/ui/radio.tsx +99 -102
- package/src/registry/ui/resizable.tsx +314 -314
- package/src/registry/ui/scroll-animation.tsx +45 -0
- package/src/registry/ui/scroll-area.tsx +121 -122
- package/src/registry/ui/scroll-to-top.tsx +0 -0
- package/src/registry/ui/search.tsx +147 -150
- package/src/registry/ui/select.tsx +292 -293
- package/src/registry/ui/separator.tsx +46 -47
- package/src/registry/ui/sheet.tsx +6 -3
- package/src/registry/ui/sidebar.tsx +628 -628
- package/src/registry/ui/skeleton.tsx +26 -29
- package/src/registry/ui/slider.tsx +196 -197
- package/src/registry/ui/slot.tsx +69 -72
- package/src/registry/ui/star-rating.tsx +131 -134
- package/src/registry/ui/switch.tsx +72 -73
- package/src/registry/ui/table-of-contents.tsx +96 -96
- package/src/registry/ui/table.tsx +138 -139
- package/src/registry/ui/tabs.tsx +124 -125
- package/src/registry/ui/text.tsx +61 -64
- package/src/registry/ui/textarea.tsx +41 -42
- package/src/registry/ui/theme-switcher.tsx +66 -66
- package/src/registry/ui/tiktok-pixel.tsx +36 -0
- package/src/registry/ui/toast.tsx +97 -98
- package/src/registry/ui/toggle-group.tsx +129 -129
- package/src/registry/ui/toggle.tsx +72 -72
- package/src/registry/ui/tooltip.tsx +143 -144
- package/src/registry/ui/whatsapp.tsx +0 -0
|
@@ -1,47 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Container
|
|
45
|
-
|
|
46
|
-
export { Container, containerVariants }
|
|
47
|
-
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const containerVariants = cva("mx-auto w-full px-4", {
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: "max-w-screen-sm",
|
|
9
|
+
md: "max-w-screen-md",
|
|
10
|
+
lg: "max-w-screen-lg",
|
|
11
|
+
xl: "max-w-screen-xl",
|
|
12
|
+
"2xl": "max-w-screen-2xl",
|
|
13
|
+
full: "max-w-full",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: "xl",
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
interface ContainerProps
|
|
22
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
23
|
+
VariantProps<typeof containerVariants> { }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Container for max-width layouts
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* <Container size="lg">Content</Container>
|
|
30
|
+
*/
|
|
31
|
+
const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
32
|
+
({ className, size, ...props }, ref) => {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(containerVariants({ size }), className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
Container.displayName = "Container"
|
|
43
|
+
|
|
44
|
+
export { Container, containerVariants }
|
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
|
|
6
|
-
// Context Menu Context
|
|
7
|
-
interface ContextMenuContextValue {
|
|
8
|
-
open: boolean
|
|
9
|
-
position: { x: number; y: number }
|
|
10
|
-
onOpenChange: (open: boolean) => void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const ContextMenuContext = React.createContext<ContextMenuContextValue | null>(null)
|
|
14
|
-
|
|
15
|
-
function useContextMenu() {
|
|
16
|
-
const context = React.useContext(ContextMenuContext)
|
|
17
|
-
if (!context) {
|
|
18
|
-
throw new Error("useContextMenu must be used within a ContextMenu")
|
|
19
|
-
}
|
|
20
|
-
return context
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ContextMenu Root
|
|
24
|
-
interface ContextMenuProps {
|
|
25
|
-
children: React.ReactNode
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ContextMenu = ({ children }: ContextMenuProps) => {
|
|
29
|
-
const [open, setOpen] = React.useState(false)
|
|
30
|
-
const [position, setPosition] = React.useState({ x: 0, y: 0 })
|
|
31
|
-
|
|
32
|
-
const handleContextMenu = React.useCallback((e: React.MouseEvent) => {
|
|
33
|
-
e.preventDefault()
|
|
34
|
-
setPosition({ x: e.clientX, y: e.clientY })
|
|
35
|
-
setOpen(true)
|
|
36
|
-
}, [])
|
|
37
|
-
|
|
38
|
-
// Close on outside click
|
|
39
|
-
React.useEffect(() => {
|
|
40
|
-
if (!open) return
|
|
41
|
-
|
|
42
|
-
const handleClick = () => setOpen(false)
|
|
43
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
44
|
-
if (e.key === "Escape") setOpen(false)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
document.addEventListener("click", handleClick)
|
|
48
|
-
document.addEventListener("keydown", handleEscape)
|
|
49
|
-
return () => {
|
|
50
|
-
document.removeEventListener("click", handleClick)
|
|
51
|
-
document.removeEventListener("keydown", handleEscape)
|
|
52
|
-
}
|
|
53
|
-
}, [open])
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<ContextMenuContext.Provider value={{ open, position, onOpenChange: setOpen }}>
|
|
57
|
-
<div onContextMenu={handleContextMenu}>
|
|
58
|
-
{children}
|
|
59
|
-
</div>
|
|
60
|
-
</ContextMenuContext.Provider>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ContextMenu Trigger (the area that can be right-clicked)
|
|
65
|
-
interface ContextMenuTriggerProps {
|
|
66
|
-
children: React.ReactNode
|
|
67
|
-
className?: string
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const ContextMenuTrigger = React.forwardRef<HTMLDivElement, ContextMenuTriggerProps>(
|
|
71
|
-
({ children, className, ...props }, ref) => {
|
|
72
|
-
return (
|
|
73
|
-
<div ref={ref} className={className} {...props}>
|
|
74
|
-
{children}
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
ContextMenuTrigger.displayName = "ContextMenuTrigger"
|
|
80
|
-
|
|
81
|
-
// ContextMenu Content
|
|
82
|
-
interface ContextMenuContentProps {
|
|
83
|
-
children: React.ReactNode
|
|
84
|
-
className?: string
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const ContextMenuContent = React.forwardRef<HTMLDivElement, ContextMenuContentProps>(
|
|
88
|
-
({ children, className, ...props }, ref) => {
|
|
89
|
-
const { open, position } = useContextMenu()
|
|
90
|
-
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
91
|
-
const [adjustedPosition, setAdjustedPosition] = React.useState(position)
|
|
92
|
-
|
|
93
|
-
// Adjust position to keep menu in viewport
|
|
94
|
-
React.useEffect(() => {
|
|
95
|
-
if (!open || !contentRef.current) return
|
|
96
|
-
|
|
97
|
-
const rect = contentRef.current.getBoundingClientRect()
|
|
98
|
-
const viewportWidth = window.innerWidth
|
|
99
|
-
const viewportHeight = window.innerHeight
|
|
100
|
-
|
|
101
|
-
let x = position.x
|
|
102
|
-
let y = position.y
|
|
103
|
-
|
|
104
|
-
// Adjust if overflowing right
|
|
105
|
-
if (x + rect.width > viewportWidth) {
|
|
106
|
-
x = viewportWidth - rect.width - 8
|
|
107
|
-
}
|
|
108
|
-
// Adjust if overflowing bottom
|
|
109
|
-
if (y + rect.height > viewportHeight) {
|
|
110
|
-
y = viewportHeight - rect.height - 8
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
setAdjustedPosition({ x: Math.max(8, x), y: Math.max(8, y) })
|
|
114
|
-
}, [open, position])
|
|
115
|
-
|
|
116
|
-
if (!open) return null
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div
|
|
120
|
-
ref={contentRef}
|
|
121
|
-
className={cn(
|
|
122
|
-
"fixed z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
|
123
|
-
"animate-in fade-in-0 zoom-in-95",
|
|
124
|
-
className
|
|
125
|
-
)}
|
|
126
|
-
style={{
|
|
127
|
-
left: adjustedPosition.x,
|
|
128
|
-
top: adjustedPosition.y,
|
|
129
|
-
}}
|
|
130
|
-
onClick={(e) => e.stopPropagation()}
|
|
131
|
-
{...props}
|
|
132
|
-
>
|
|
133
|
-
{children}
|
|
134
|
-
</div>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
)
|
|
138
|
-
ContextMenuContent.displayName = "ContextMenuContent"
|
|
139
|
-
|
|
140
|
-
// ContextMenu Item
|
|
141
|
-
interface ContextMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
142
|
-
inset?: boolean
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const ContextMenuItem = React.forwardRef<HTMLButtonElement, ContextMenuItemProps>(
|
|
146
|
-
({ className, inset, children, ...props }, ref) => {
|
|
147
|
-
const { onOpenChange } = useContextMenu()
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<button
|
|
151
|
-
ref={ref}
|
|
152
|
-
className={cn(
|
|
153
|
-
"relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors",
|
|
154
|
-
"focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground",
|
|
155
|
-
"disabled:pointer-events-none disabled:opacity-50",
|
|
156
|
-
inset && "pl-8",
|
|
157
|
-
className
|
|
158
|
-
)}
|
|
159
|
-
onClick={() => onOpenChange(false)}
|
|
160
|
-
{...props}
|
|
161
|
-
>
|
|
162
|
-
{children}
|
|
163
|
-
</button>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
)
|
|
167
|
-
ContextMenuItem.displayName = "ContextMenuItem"
|
|
168
|
-
|
|
169
|
-
// ContextMenu Separator
|
|
170
|
-
const ContextMenuSeparator = React.forwardRef<
|
|
171
|
-
HTMLDivElement,
|
|
172
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
173
|
-
>(({ className, ...props }, ref) => (
|
|
174
|
-
<div
|
|
175
|
-
ref={ref}
|
|
176
|
-
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
177
|
-
{...props}
|
|
178
|
-
/>
|
|
179
|
-
))
|
|
180
|
-
ContextMenuSeparator.displayName = "ContextMenuSeparator"
|
|
181
|
-
|
|
182
|
-
// ContextMenu Label
|
|
183
|
-
const ContextMenuLabel = React.forwardRef<
|
|
184
|
-
HTMLDivElement,
|
|
185
|
-
React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }
|
|
186
|
-
>(({ className, inset, ...props }, ref) => (
|
|
187
|
-
<div
|
|
188
|
-
ref={ref}
|
|
189
|
-
className={cn(
|
|
190
|
-
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
|
191
|
-
inset && "pl-8",
|
|
192
|
-
className
|
|
193
|
-
)}
|
|
194
|
-
{...props}
|
|
195
|
-
/>
|
|
196
|
-
))
|
|
197
|
-
ContextMenuLabel.displayName = "ContextMenuLabel"
|
|
198
|
-
|
|
199
|
-
// ContextMenu Shortcut
|
|
200
|
-
const ContextMenuShortcut = ({
|
|
201
|
-
className,
|
|
202
|
-
...props
|
|
203
|
-
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
204
|
-
return (
|
|
205
|
-
<span
|
|
206
|
-
className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
|
207
|
-
{...props}
|
|
208
|
-
/>
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
|
212
|
-
|
|
213
|
-
export {
|
|
214
|
-
ContextMenu,
|
|
215
|
-
ContextMenuTrigger,
|
|
216
|
-
ContextMenuContent,
|
|
217
|
-
ContextMenuItem,
|
|
218
|
-
ContextMenuSeparator,
|
|
219
|
-
ContextMenuLabel,
|
|
220
|
-
ContextMenuShortcut,
|
|
221
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
// Context Menu Context
|
|
7
|
+
interface ContextMenuContextValue {
|
|
8
|
+
open: boolean
|
|
9
|
+
position: { x: number; y: number }
|
|
10
|
+
onOpenChange: (open: boolean) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ContextMenuContext = React.createContext<ContextMenuContextValue | null>(null)
|
|
14
|
+
|
|
15
|
+
function useContextMenu() {
|
|
16
|
+
const context = React.useContext(ContextMenuContext)
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error("useContextMenu must be used within a ContextMenu")
|
|
19
|
+
}
|
|
20
|
+
return context
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ContextMenu Root
|
|
24
|
+
interface ContextMenuProps {
|
|
25
|
+
children: React.ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ContextMenu = ({ children }: ContextMenuProps) => {
|
|
29
|
+
const [open, setOpen] = React.useState(false)
|
|
30
|
+
const [position, setPosition] = React.useState({ x: 0, y: 0 })
|
|
31
|
+
|
|
32
|
+
const handleContextMenu = React.useCallback((e: React.MouseEvent) => {
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
setPosition({ x: e.clientX, y: e.clientY })
|
|
35
|
+
setOpen(true)
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
// Close on outside click
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (!open) return
|
|
41
|
+
|
|
42
|
+
const handleClick = () => setOpen(false)
|
|
43
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
44
|
+
if (e.key === "Escape") setOpen(false)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
document.addEventListener("click", handleClick)
|
|
48
|
+
document.addEventListener("keydown", handleEscape)
|
|
49
|
+
return () => {
|
|
50
|
+
document.removeEventListener("click", handleClick)
|
|
51
|
+
document.removeEventListener("keydown", handleEscape)
|
|
52
|
+
}
|
|
53
|
+
}, [open])
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ContextMenuContext.Provider value={{ open, position, onOpenChange: setOpen }}>
|
|
57
|
+
<div onContextMenu={handleContextMenu}>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</ContextMenuContext.Provider>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ContextMenu Trigger (the area that can be right-clicked)
|
|
65
|
+
interface ContextMenuTriggerProps {
|
|
66
|
+
children: React.ReactNode
|
|
67
|
+
className?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const ContextMenuTrigger = React.forwardRef<HTMLDivElement, ContextMenuTriggerProps>(
|
|
71
|
+
({ children, className, ...props }, ref) => {
|
|
72
|
+
return (
|
|
73
|
+
<div ref={ref} className={className} {...props}>
|
|
74
|
+
{children}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
ContextMenuTrigger.displayName = "ContextMenuTrigger"
|
|
80
|
+
|
|
81
|
+
// ContextMenu Content
|
|
82
|
+
interface ContextMenuContentProps {
|
|
83
|
+
children: React.ReactNode
|
|
84
|
+
className?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ContextMenuContent = React.forwardRef<HTMLDivElement, ContextMenuContentProps>(
|
|
88
|
+
({ children, className, ...props }, ref) => {
|
|
89
|
+
const { open, position } = useContextMenu()
|
|
90
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
91
|
+
const [adjustedPosition, setAdjustedPosition] = React.useState(position)
|
|
92
|
+
|
|
93
|
+
// Adjust position to keep menu in viewport
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (!open || !contentRef.current) return
|
|
96
|
+
|
|
97
|
+
const rect = contentRef.current.getBoundingClientRect()
|
|
98
|
+
const viewportWidth = window.innerWidth
|
|
99
|
+
const viewportHeight = window.innerHeight
|
|
100
|
+
|
|
101
|
+
let x = position.x
|
|
102
|
+
let y = position.y
|
|
103
|
+
|
|
104
|
+
// Adjust if overflowing right
|
|
105
|
+
if (x + rect.width > viewportWidth) {
|
|
106
|
+
x = viewportWidth - rect.width - 8
|
|
107
|
+
}
|
|
108
|
+
// Adjust if overflowing bottom
|
|
109
|
+
if (y + rect.height > viewportHeight) {
|
|
110
|
+
y = viewportHeight - rect.height - 8
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setAdjustedPosition({ x: Math.max(8, x), y: Math.max(8, y) })
|
|
114
|
+
}, [open, position])
|
|
115
|
+
|
|
116
|
+
if (!open) return null
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
ref={contentRef}
|
|
121
|
+
className={cn(
|
|
122
|
+
"fixed z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
|
123
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
style={{
|
|
127
|
+
left: adjustedPosition.x,
|
|
128
|
+
top: adjustedPosition.y,
|
|
129
|
+
}}
|
|
130
|
+
onClick={(e) => e.stopPropagation()}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
ContextMenuContent.displayName = "ContextMenuContent"
|
|
139
|
+
|
|
140
|
+
// ContextMenu Item
|
|
141
|
+
interface ContextMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
142
|
+
inset?: boolean
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ContextMenuItem = React.forwardRef<HTMLButtonElement, ContextMenuItemProps>(
|
|
146
|
+
({ className, inset, children, ...props }, ref) => {
|
|
147
|
+
const { onOpenChange } = useContextMenu()
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<button
|
|
151
|
+
ref={ref}
|
|
152
|
+
className={cn(
|
|
153
|
+
"relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors",
|
|
154
|
+
"focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground",
|
|
155
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
156
|
+
inset && "pl-8",
|
|
157
|
+
className
|
|
158
|
+
)}
|
|
159
|
+
onClick={() => onOpenChange(false)}
|
|
160
|
+
{...props}
|
|
161
|
+
>
|
|
162
|
+
{children}
|
|
163
|
+
</button>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
ContextMenuItem.displayName = "ContextMenuItem"
|
|
168
|
+
|
|
169
|
+
// ContextMenu Separator
|
|
170
|
+
const ContextMenuSeparator = React.forwardRef<
|
|
171
|
+
HTMLDivElement,
|
|
172
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
173
|
+
>(({ className, ...props }, ref) => (
|
|
174
|
+
<div
|
|
175
|
+
ref={ref}
|
|
176
|
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
177
|
+
{...props}
|
|
178
|
+
/>
|
|
179
|
+
))
|
|
180
|
+
ContextMenuSeparator.displayName = "ContextMenuSeparator"
|
|
181
|
+
|
|
182
|
+
// ContextMenu Label
|
|
183
|
+
const ContextMenuLabel = React.forwardRef<
|
|
184
|
+
HTMLDivElement,
|
|
185
|
+
React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }
|
|
186
|
+
>(({ className, inset, ...props }, ref) => (
|
|
187
|
+
<div
|
|
188
|
+
ref={ref}
|
|
189
|
+
className={cn(
|
|
190
|
+
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
|
191
|
+
inset && "pl-8",
|
|
192
|
+
className
|
|
193
|
+
)}
|
|
194
|
+
{...props}
|
|
195
|
+
/>
|
|
196
|
+
))
|
|
197
|
+
ContextMenuLabel.displayName = "ContextMenuLabel"
|
|
198
|
+
|
|
199
|
+
// ContextMenu Shortcut
|
|
200
|
+
const ContextMenuShortcut = ({
|
|
201
|
+
className,
|
|
202
|
+
...props
|
|
203
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
204
|
+
return (
|
|
205
|
+
<span
|
|
206
|
+
className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
|
207
|
+
{...props}
|
|
208
|
+
/>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
ContextMenu,
|
|
215
|
+
ContextMenuTrigger,
|
|
216
|
+
ContextMenuContent,
|
|
217
|
+
ContextMenuItem,
|
|
218
|
+
ContextMenuSeparator,
|
|
219
|
+
ContextMenuLabel,
|
|
220
|
+
ContextMenuShortcut,
|
|
221
|
+
}
|