@srcroot/ui 0.0.55 → 0.0.58
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 +120 -93
- 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 +163 -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 +162 -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 +146 -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,223 +1,223 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { createPortal } from "react-dom"
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
import { Slot } from "@/components/ui/slot"
|
|
7
|
-
|
|
8
|
-
// HoverCard Context
|
|
9
|
-
interface HoverCardContextValue {
|
|
10
|
-
open: boolean
|
|
11
|
-
triggerRef: React.RefObject<HTMLDivElement | null>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const HoverCardContext = React.createContext<HoverCardContextValue | null>(null)
|
|
15
|
-
|
|
16
|
-
function useHoverCard() {
|
|
17
|
-
const context = React.useContext(HoverCardContext)
|
|
18
|
-
if (!context) {
|
|
19
|
-
throw new Error("useHoverCard must be used within a HoverCard")
|
|
20
|
-
}
|
|
21
|
-
return context
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// HoverCard Root
|
|
25
|
-
interface HoverCardProps {
|
|
26
|
-
children: React.ReactNode
|
|
27
|
-
openDelay?: number
|
|
28
|
-
closeDelay?: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* HoverCard component for displaying content on hover
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* <HoverCard>
|
|
36
|
-
* <HoverCardTrigger asChild>
|
|
37
|
-
* <Button variant="link">@nextjs</Button>
|
|
38
|
-
* </HoverCardTrigger>
|
|
39
|
-
* <HoverCardContent>
|
|
40
|
-
* The React Framework – created and maintained by @vercel.
|
|
41
|
-
* </HoverCardContent>
|
|
42
|
-
* </HoverCard>
|
|
43
|
-
*/
|
|
44
|
-
const HoverCard = ({ children, openDelay = 200, closeDelay = 300 }: HoverCardProps) => {
|
|
45
|
-
const [open, setOpen] = React.useState(false)
|
|
46
|
-
const triggerRef = React.useRef<HTMLDivElement>(null)
|
|
47
|
-
const openTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
48
|
-
const closeTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
49
|
-
|
|
50
|
-
const handleMouseEnter = React.useCallback(() => {
|
|
51
|
-
if (closeTimeoutRef.current) {
|
|
52
|
-
clearTimeout(closeTimeoutRef.current)
|
|
53
|
-
closeTimeoutRef.current = null
|
|
54
|
-
}
|
|
55
|
-
openTimeoutRef.current = setTimeout(() => {
|
|
56
|
-
setOpen(true)
|
|
57
|
-
}, openDelay)
|
|
58
|
-
}, [openDelay])
|
|
59
|
-
|
|
60
|
-
const handleMouseLeave = React.useCallback(() => {
|
|
61
|
-
if (openTimeoutRef.current) {
|
|
62
|
-
clearTimeout(openTimeoutRef.current)
|
|
63
|
-
openTimeoutRef.current = null
|
|
64
|
-
}
|
|
65
|
-
closeTimeoutRef.current = setTimeout(() => {
|
|
66
|
-
setOpen(false)
|
|
67
|
-
}, closeDelay)
|
|
68
|
-
}, [closeDelay])
|
|
69
|
-
|
|
70
|
-
React.useEffect(() => {
|
|
71
|
-
return () => {
|
|
72
|
-
if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current)
|
|
73
|
-
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current)
|
|
74
|
-
}
|
|
75
|
-
}, [])
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<HoverCardContext.Provider value={{ open, triggerRef }}>
|
|
79
|
-
<div
|
|
80
|
-
onMouseEnter={handleMouseEnter}
|
|
81
|
-
onMouseLeave={handleMouseLeave}
|
|
82
|
-
className="inline-block"
|
|
83
|
-
>
|
|
84
|
-
{children}
|
|
85
|
-
</div>
|
|
86
|
-
</HoverCardContext.Provider>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// HoverCard Trigger
|
|
91
|
-
interface HoverCardTriggerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
92
|
-
asChild?: boolean
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const HoverCardTrigger = React.forwardRef<HTMLDivElement, HoverCardTriggerProps>(
|
|
96
|
-
({ children, asChild, className, ...props }, ref) => {
|
|
97
|
-
const { triggerRef } = useHoverCard()
|
|
98
|
-
|
|
99
|
-
// Merge refs manually since we have two refs to attach (triggerRef and ref)
|
|
100
|
-
const combinedRef = React.useCallback((node: HTMLDivElement | null) => {
|
|
101
|
-
if (triggerRef) (triggerRef as any).current = node
|
|
102
|
-
|
|
103
|
-
if (typeof ref === "function") ref(node)
|
|
104
|
-
else if (ref) (ref as any).current = node
|
|
105
|
-
}, [triggerRef, ref])
|
|
106
|
-
|
|
107
|
-
const Comp = asChild ? Slot : "div"
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<Comp
|
|
111
|
-
ref={combinedRef}
|
|
112
|
-
className={cn("inline-block cursor-pointer", className)}
|
|
113
|
-
{...props}
|
|
114
|
-
>
|
|
115
|
-
{children}
|
|
116
|
-
</Comp>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
)
|
|
120
|
-
HoverCardTrigger.displayName = "HoverCardTrigger"
|
|
121
|
-
|
|
122
|
-
// HoverCard Content
|
|
123
|
-
interface HoverCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
124
|
-
align?: "start" | "center" | "end"
|
|
125
|
-
side?: "top" | "bottom"
|
|
126
|
-
sideOffset?: number
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const HoverCardContent = React.forwardRef<
|
|
130
|
-
HTMLDivElement,
|
|
131
|
-
HoverCardContentProps & { portal?: boolean }
|
|
132
|
-
>(
|
|
133
|
-
({ children, className, align = "center", side = "bottom", sideOffset = 4, portal = true, ...props }, ref) => {
|
|
134
|
-
const { open, triggerRef } = useHoverCard()
|
|
135
|
-
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
136
|
-
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
137
|
-
const [mounted, setMounted] = React.useState(false)
|
|
138
|
-
|
|
139
|
-
React.useEffect(() => {
|
|
140
|
-
setMounted(true)
|
|
141
|
-
}, [])
|
|
142
|
-
|
|
143
|
-
React.useEffect(() => {
|
|
144
|
-
if (!open || !triggerRef.current || !contentRef.current) return
|
|
145
|
-
|
|
146
|
-
const checkPosition = () => {
|
|
147
|
-
if (!triggerRef.current || !contentRef.current) return
|
|
148
|
-
const triggerRect = triggerRef.current.getBoundingClientRect()
|
|
149
|
-
const contentRect = contentRef.current.getBoundingClientRect()
|
|
150
|
-
|
|
151
|
-
let top = 0
|
|
152
|
-
let left = 0
|
|
153
|
-
|
|
154
|
-
// Calculate vertical position
|
|
155
|
-
if (side === "bottom") {
|
|
156
|
-
top = triggerRect.bottom + sideOffset
|
|
157
|
-
} else {
|
|
158
|
-
top = triggerRect.top - contentRect.height - sideOffset
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Calculate horizontal position
|
|
162
|
-
if (align === "start") {
|
|
163
|
-
left = triggerRect.left
|
|
164
|
-
} else if (align === "end") {
|
|
165
|
-
left = triggerRect.right - contentRect.width
|
|
166
|
-
} else {
|
|
167
|
-
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Clamp to viewport
|
|
171
|
-
left = Math.max(8, Math.min(left, window.innerWidth - contentRect.width - 8))
|
|
172
|
-
top = Math.max(8, Math.min(top, window.innerHeight - contentRect.height - 8))
|
|
173
|
-
|
|
174
|
-
setPosition({ top, left })
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
checkPosition()
|
|
178
|
-
window.addEventListener('resize', checkPosition)
|
|
179
|
-
window.addEventListener('scroll', checkPosition, true)
|
|
180
|
-
|
|
181
|
-
return () => {
|
|
182
|
-
window.removeEventListener('resize', checkPosition)
|
|
183
|
-
window.removeEventListener('scroll', checkPosition, true)
|
|
184
|
-
}
|
|
185
|
-
}, [open, triggerRef, align, side, sideOffset])
|
|
186
|
-
|
|
187
|
-
if (!open) return null
|
|
188
|
-
|
|
189
|
-
const content = (
|
|
190
|
-
<div
|
|
191
|
-
ref={(node) => {
|
|
192
|
-
(contentRef as any).current = node
|
|
193
|
-
if (typeof ref === 'function') ref(node)
|
|
194
|
-
else if (ref) (ref as any).current = node
|
|
195
|
-
}}
|
|
196
|
-
className={cn(
|
|
197
|
-
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
198
|
-
"animate-in fade-in-0 zoom-in-95",
|
|
199
|
-
!portal && "absolute",
|
|
200
|
-
portal && "fixed",
|
|
201
|
-
className
|
|
202
|
-
)}
|
|
203
|
-
style={{
|
|
204
|
-
top: portal ? position.top : undefined,
|
|
205
|
-
left: portal ? position.left : undefined,
|
|
206
|
-
...props.style
|
|
207
|
-
}}
|
|
208
|
-
{...props}
|
|
209
|
-
>
|
|
210
|
-
{children}
|
|
211
|
-
</div>
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
if (portal && mounted) {
|
|
215
|
-
return createPortal(content, document.body)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return content
|
|
219
|
-
}
|
|
220
|
-
)
|
|
221
|
-
HoverCardContent.displayName = "HoverCardContent"
|
|
222
|
-
|
|
223
|
-
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { createPortal } from "react-dom"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import { Slot } from "@/components/ui/slot"
|
|
7
|
+
|
|
8
|
+
// HoverCard Context
|
|
9
|
+
interface HoverCardContextValue {
|
|
10
|
+
open: boolean
|
|
11
|
+
triggerRef: React.RefObject<HTMLDivElement | null>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const HoverCardContext = React.createContext<HoverCardContextValue | null>(null)
|
|
15
|
+
|
|
16
|
+
function useHoverCard() {
|
|
17
|
+
const context = React.useContext(HoverCardContext)
|
|
18
|
+
if (!context) {
|
|
19
|
+
throw new Error("useHoverCard must be used within a HoverCard")
|
|
20
|
+
}
|
|
21
|
+
return context
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// HoverCard Root
|
|
25
|
+
interface HoverCardProps {
|
|
26
|
+
children: React.ReactNode
|
|
27
|
+
openDelay?: number
|
|
28
|
+
closeDelay?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HoverCard component for displaying content on hover
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <HoverCard>
|
|
36
|
+
* <HoverCardTrigger asChild>
|
|
37
|
+
* <Button variant="link">@nextjs</Button>
|
|
38
|
+
* </HoverCardTrigger>
|
|
39
|
+
* <HoverCardContent>
|
|
40
|
+
* The React Framework – created and maintained by @vercel.
|
|
41
|
+
* </HoverCardContent>
|
|
42
|
+
* </HoverCard>
|
|
43
|
+
*/
|
|
44
|
+
const HoverCard = ({ children, openDelay = 200, closeDelay = 300 }: HoverCardProps) => {
|
|
45
|
+
const [open, setOpen] = React.useState(false)
|
|
46
|
+
const triggerRef = React.useRef<HTMLDivElement>(null)
|
|
47
|
+
const openTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
48
|
+
const closeTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
49
|
+
|
|
50
|
+
const handleMouseEnter = React.useCallback(() => {
|
|
51
|
+
if (closeTimeoutRef.current) {
|
|
52
|
+
clearTimeout(closeTimeoutRef.current)
|
|
53
|
+
closeTimeoutRef.current = null
|
|
54
|
+
}
|
|
55
|
+
openTimeoutRef.current = setTimeout(() => {
|
|
56
|
+
setOpen(true)
|
|
57
|
+
}, openDelay)
|
|
58
|
+
}, [openDelay])
|
|
59
|
+
|
|
60
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
61
|
+
if (openTimeoutRef.current) {
|
|
62
|
+
clearTimeout(openTimeoutRef.current)
|
|
63
|
+
openTimeoutRef.current = null
|
|
64
|
+
}
|
|
65
|
+
closeTimeoutRef.current = setTimeout(() => {
|
|
66
|
+
setOpen(false)
|
|
67
|
+
}, closeDelay)
|
|
68
|
+
}, [closeDelay])
|
|
69
|
+
|
|
70
|
+
React.useEffect(() => {
|
|
71
|
+
return () => {
|
|
72
|
+
if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current)
|
|
73
|
+
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current)
|
|
74
|
+
}
|
|
75
|
+
}, [])
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<HoverCardContext.Provider value={{ open, triggerRef }}>
|
|
79
|
+
<div
|
|
80
|
+
onMouseEnter={handleMouseEnter}
|
|
81
|
+
onMouseLeave={handleMouseLeave}
|
|
82
|
+
className="inline-block"
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
</div>
|
|
86
|
+
</HoverCardContext.Provider>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// HoverCard Trigger
|
|
91
|
+
interface HoverCardTriggerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
92
|
+
asChild?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const HoverCardTrigger = React.forwardRef<HTMLDivElement, HoverCardTriggerProps>(
|
|
96
|
+
({ children, asChild, className, ...props }, ref) => {
|
|
97
|
+
const { triggerRef } = useHoverCard()
|
|
98
|
+
|
|
99
|
+
// Merge refs manually since we have two refs to attach (triggerRef and ref)
|
|
100
|
+
const combinedRef = React.useCallback((node: HTMLDivElement | null) => {
|
|
101
|
+
if (triggerRef) (triggerRef as any).current = node
|
|
102
|
+
|
|
103
|
+
if (typeof ref === "function") ref(node)
|
|
104
|
+
else if (ref) (ref as any).current = node
|
|
105
|
+
}, [triggerRef, ref])
|
|
106
|
+
|
|
107
|
+
const Comp = asChild ? Slot : "div"
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Comp
|
|
111
|
+
ref={combinedRef}
|
|
112
|
+
className={cn("inline-block cursor-pointer", className)}
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</Comp>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
HoverCardTrigger.displayName = "HoverCardTrigger"
|
|
121
|
+
|
|
122
|
+
// HoverCard Content
|
|
123
|
+
interface HoverCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
124
|
+
align?: "start" | "center" | "end"
|
|
125
|
+
side?: "top" | "bottom"
|
|
126
|
+
sideOffset?: number
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const HoverCardContent = React.forwardRef<
|
|
130
|
+
HTMLDivElement,
|
|
131
|
+
HoverCardContentProps & { portal?: boolean }
|
|
132
|
+
>(
|
|
133
|
+
({ children, className, align = "center", side = "bottom", sideOffset = 4, portal = true, ...props }, ref) => {
|
|
134
|
+
const { open, triggerRef } = useHoverCard()
|
|
135
|
+
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
136
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
137
|
+
const [mounted, setMounted] = React.useState(false)
|
|
138
|
+
|
|
139
|
+
React.useEffect(() => {
|
|
140
|
+
setMounted(true)
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
React.useEffect(() => {
|
|
144
|
+
if (!open || !triggerRef.current || !contentRef.current) return
|
|
145
|
+
|
|
146
|
+
const checkPosition = () => {
|
|
147
|
+
if (!triggerRef.current || !contentRef.current) return
|
|
148
|
+
const triggerRect = triggerRef.current.getBoundingClientRect()
|
|
149
|
+
const contentRect = contentRef.current.getBoundingClientRect()
|
|
150
|
+
|
|
151
|
+
let top = 0
|
|
152
|
+
let left = 0
|
|
153
|
+
|
|
154
|
+
// Calculate vertical position
|
|
155
|
+
if (side === "bottom") {
|
|
156
|
+
top = triggerRect.bottom + sideOffset
|
|
157
|
+
} else {
|
|
158
|
+
top = triggerRect.top - contentRect.height - sideOffset
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Calculate horizontal position
|
|
162
|
+
if (align === "start") {
|
|
163
|
+
left = triggerRect.left
|
|
164
|
+
} else if (align === "end") {
|
|
165
|
+
left = triggerRect.right - contentRect.width
|
|
166
|
+
} else {
|
|
167
|
+
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Clamp to viewport
|
|
171
|
+
left = Math.max(8, Math.min(left, window.innerWidth - contentRect.width - 8))
|
|
172
|
+
top = Math.max(8, Math.min(top, window.innerHeight - contentRect.height - 8))
|
|
173
|
+
|
|
174
|
+
setPosition({ top, left })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
checkPosition()
|
|
178
|
+
window.addEventListener('resize', checkPosition)
|
|
179
|
+
window.addEventListener('scroll', checkPosition, true)
|
|
180
|
+
|
|
181
|
+
return () => {
|
|
182
|
+
window.removeEventListener('resize', checkPosition)
|
|
183
|
+
window.removeEventListener('scroll', checkPosition, true)
|
|
184
|
+
}
|
|
185
|
+
}, [open, triggerRef, align, side, sideOffset])
|
|
186
|
+
|
|
187
|
+
if (!open) return null
|
|
188
|
+
|
|
189
|
+
const content = (
|
|
190
|
+
<div
|
|
191
|
+
ref={(node) => {
|
|
192
|
+
(contentRef as any).current = node
|
|
193
|
+
if (typeof ref === 'function') ref(node)
|
|
194
|
+
else if (ref) (ref as any).current = node
|
|
195
|
+
}}
|
|
196
|
+
className={cn(
|
|
197
|
+
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
198
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
199
|
+
!portal && "absolute",
|
|
200
|
+
portal && "fixed",
|
|
201
|
+
className
|
|
202
|
+
)}
|
|
203
|
+
style={{
|
|
204
|
+
top: portal ? position.top : undefined,
|
|
205
|
+
left: portal ? position.left : undefined,
|
|
206
|
+
...props.style
|
|
207
|
+
}}
|
|
208
|
+
{...props}
|
|
209
|
+
>
|
|
210
|
+
{children}
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if (portal && mounted) {
|
|
215
|
+
return createPortal(content, document.body)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return content
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
HoverCardContent.displayName = "HoverCardContent"
|
|
222
|
+
|
|
223
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent }
|