@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,227 +1,226 @@
|
|
|
1
|
-
|
|
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
|
-
interface PopoverContextValue {
|
|
9
|
-
open: boolean
|
|
10
|
-
onOpenChange: (open: boolean) => void
|
|
11
|
-
triggerRef: React.RefObject<HTMLButtonElement | null>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const PopoverContext = React.createContext<PopoverContextValue | null>(null)
|
|
15
|
-
|
|
16
|
-
interface PopoverProps {
|
|
17
|
-
children: React.ReactNode
|
|
18
|
-
open?: boolean
|
|
19
|
-
onOpenChange?: (open: boolean) => void
|
|
20
|
-
defaultOpen?: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Popover component for floating content
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* <Popover>
|
|
28
|
-
* <PopoverTrigger asChild>
|
|
29
|
-
* <Button>Open Popover</Button>
|
|
30
|
-
* </PopoverTrigger>
|
|
31
|
-
* <PopoverContent>
|
|
32
|
-
* Popover content here
|
|
33
|
-
* </PopoverContent>
|
|
34
|
-
* </Popover>
|
|
35
|
-
*/
|
|
36
|
-
function Popover({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: PopoverProps) {
|
|
37
|
-
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
|
|
38
|
-
const triggerRef = React.useRef<HTMLButtonElement>(null)
|
|
39
|
-
|
|
40
|
-
const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
|
|
41
|
-
const setOpen = onOpenChange || setUncontrolledOpen
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<PopoverContext.Provider value={{ open, onOpenChange: setOpen, triggerRef }}>
|
|
45
|
-
<div className="relative inline-block">
|
|
46
|
-
{children}
|
|
47
|
-
</div>
|
|
48
|
-
</PopoverContext.Provider>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface PopoverTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
53
|
-
asChild?: boolean
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const PopoverTrigger = React.forwardRef<HTMLButtonElement, PopoverTriggerProps>(
|
|
57
|
-
({ onClick, asChild, children, ...props }, ref) => {
|
|
58
|
-
const context = React.useContext(PopoverContext)
|
|
59
|
-
if (!context) throw new Error("PopoverTrigger must be used within Popover")
|
|
60
|
-
|
|
61
|
-
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
62
|
-
onClick?.(e)
|
|
63
|
-
context.onOpenChange(!context.open)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Combine refs
|
|
67
|
-
const combinedRef = (node: HTMLButtonElement | null) => {
|
|
68
|
-
(context.triggerRef as any).current = node
|
|
69
|
-
if (typeof ref === 'function') ref(node)
|
|
70
|
-
else if (ref) ref.current = node
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const Comp = asChild ? Slot : "button"
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Comp
|
|
77
|
-
ref={combinedRef}
|
|
78
|
-
aria-expanded={context.open}
|
|
79
|
-
aria-haspopup={true}
|
|
80
|
-
onClick={handleClick}
|
|
81
|
-
{...props}
|
|
82
|
-
>
|
|
83
|
-
{children}
|
|
84
|
-
</Comp>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
)
|
|
88
|
-
PopoverTrigger.displayName = "PopoverTrigger"
|
|
89
|
-
|
|
90
|
-
const PopoverContent = React.forwardRef<
|
|
91
|
-
HTMLDivElement,
|
|
92
|
-
React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end"; sideOffset?: number; portal?: boolean }
|
|
93
|
-
>(({ className, children, align = "center", sideOffset = 4, portal = true, ...props }, ref) => {
|
|
94
|
-
const context = React.useContext(PopoverContext)
|
|
95
|
-
if (!context) throw new Error("PopoverContent must be used within Popover")
|
|
96
|
-
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
97
|
-
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
98
|
-
|
|
99
|
-
// Reset scroll on mount/unmount if needed, but mainly we just need a portal container
|
|
100
|
-
const [mounted, setMounted] = React.useState(false)
|
|
101
|
-
React.useEffect(() => {
|
|
102
|
-
setMounted(true)
|
|
103
|
-
}, [])
|
|
104
|
-
|
|
105
|
-
React.useEffect(() => {
|
|
106
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
107
|
-
if (context.open) {
|
|
108
|
-
const target = e.target as Node
|
|
109
|
-
const content = contentRef.current
|
|
110
|
-
const trigger = context.triggerRef.current
|
|
111
|
-
|
|
112
|
-
// Don't close if clicking inside content or trigger
|
|
113
|
-
if (content?.contains(target) || (trigger && trigger.contains(target))) {
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
context.onOpenChange(false)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
121
|
-
if (e.key === "Escape" && context.open) {
|
|
122
|
-
context.onOpenChange(false)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const checkPosition = () => {
|
|
127
|
-
if (context.open && contentRef.current && context.triggerRef.current) {
|
|
128
|
-
const triggerRect = context.triggerRef.current.getBoundingClientRect()
|
|
129
|
-
const contentRect = contentRef.current.getBoundingClientRect()
|
|
130
|
-
const viewportHeight = window.innerHeight
|
|
131
|
-
const viewportWidth = window.innerWidth
|
|
132
|
-
|
|
133
|
-
let top = 0
|
|
134
|
-
let left = 0
|
|
135
|
-
|
|
136
|
-
// Vertical
|
|
137
|
-
const spaceBelow = viewportHeight - triggerRect.bottom
|
|
138
|
-
const spaceAbove = triggerRect.top
|
|
139
|
-
const neededHeight = contentRect.height + sideOffset
|
|
140
|
-
const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
|
|
141
|
-
|
|
142
|
-
if (onBottom) {
|
|
143
|
-
top = triggerRect.bottom + sideOffset
|
|
144
|
-
} else {
|
|
145
|
-
top = triggerRect.top - contentRect.height - sideOffset
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Horizontal (Alignment)
|
|
149
|
-
if (align === 'start') {
|
|
150
|
-
left = triggerRect.left
|
|
151
|
-
} else if (align === 'end') {
|
|
152
|
-
left = triggerRect.right - contentRect.width
|
|
153
|
-
} else {
|
|
154
|
-
// center
|
|
155
|
-
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Clamping
|
|
159
|
-
if (left < 4) left = 4
|
|
160
|
-
if (left + contentRect.width > viewportWidth - 4) {
|
|
161
|
-
left = viewportWidth - contentRect.width - 4
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
setPosition({ top, left })
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (context.open) {
|
|
169
|
-
requestAnimationFrame(checkPosition)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const timer = setTimeout(() => {
|
|
173
|
-
document.addEventListener("click", handleClickOutside)
|
|
174
|
-
}, 0)
|
|
175
|
-
document.addEventListener("keydown", handleEscape)
|
|
176
|
-
window.addEventListener("resize", checkPosition)
|
|
177
|
-
window.addEventListener("scroll", checkPosition, true)
|
|
178
|
-
|
|
179
|
-
return () => {
|
|
180
|
-
clearTimeout(timer)
|
|
181
|
-
document.removeEventListener("click", handleClickOutside)
|
|
182
|
-
document.removeEventListener("keydown", handleEscape)
|
|
183
|
-
window.removeEventListener("resize", checkPosition)
|
|
184
|
-
window.removeEventListener("scroll", checkPosition, true)
|
|
185
|
-
}
|
|
186
|
-
}, [context.open, context, align, sideOffset])
|
|
187
|
-
|
|
188
|
-
if (!context.open) return null
|
|
189
|
-
if (portal && !mounted) return null
|
|
190
|
-
|
|
191
|
-
const content = (
|
|
192
|
-
<div
|
|
193
|
-
ref={(node) => {
|
|
194
|
-
(contentRef as any).current = node
|
|
195
|
-
if (typeof ref === 'function') ref(node)
|
|
196
|
-
else if (ref) ref.current = node
|
|
197
|
-
}}
|
|
198
|
-
className={cn(
|
|
199
|
-
"z-50 min-w-[10rem] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
200
|
-
"animate-in fade-in-0 zoom-in-95",
|
|
201
|
-
!portal && "absolute",
|
|
202
|
-
!portal && "mt-2",
|
|
203
|
-
portal && "fixed",
|
|
204
|
-
className
|
|
205
|
-
)}
|
|
206
|
-
style={{
|
|
207
|
-
top: portal ? position.top : undefined,
|
|
208
|
-
left: portal ? position.left : undefined,
|
|
209
|
-
...props.style
|
|
210
|
-
}}
|
|
211
|
-
onClick={(e) => e.stopPropagation()}
|
|
212
|
-
{...props}
|
|
213
|
-
>
|
|
214
|
-
{children}
|
|
215
|
-
</div>
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
if (portal) {
|
|
219
|
-
return createPortal(content, document.body)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return content
|
|
223
|
-
})
|
|
224
|
-
PopoverContent.displayName = "PopoverContent"
|
|
225
|
-
|
|
226
|
-
export { Popover, PopoverTrigger, PopoverContent }
|
|
227
|
-
|
|
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
|
+
interface PopoverContextValue {
|
|
9
|
+
open: boolean
|
|
10
|
+
onOpenChange: (open: boolean) => void
|
|
11
|
+
triggerRef: React.RefObject<HTMLButtonElement | null>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PopoverContext = React.createContext<PopoverContextValue | null>(null)
|
|
15
|
+
|
|
16
|
+
interface PopoverProps {
|
|
17
|
+
children: React.ReactNode
|
|
18
|
+
open?: boolean
|
|
19
|
+
onOpenChange?: (open: boolean) => void
|
|
20
|
+
defaultOpen?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Popover component for floating content
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <Popover>
|
|
28
|
+
* <PopoverTrigger asChild>
|
|
29
|
+
* <Button>Open Popover</Button>
|
|
30
|
+
* </PopoverTrigger>
|
|
31
|
+
* <PopoverContent>
|
|
32
|
+
* Popover content here
|
|
33
|
+
* </PopoverContent>
|
|
34
|
+
* </Popover>
|
|
35
|
+
*/
|
|
36
|
+
function Popover({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: PopoverProps) {
|
|
37
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
|
|
38
|
+
const triggerRef = React.useRef<HTMLButtonElement>(null)
|
|
39
|
+
|
|
40
|
+
const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
|
|
41
|
+
const setOpen = onOpenChange || setUncontrolledOpen
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PopoverContext.Provider value={{ open, onOpenChange: setOpen, triggerRef }}>
|
|
45
|
+
<div className="relative inline-block">
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
</PopoverContext.Provider>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface PopoverTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
53
|
+
asChild?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const PopoverTrigger = React.forwardRef<HTMLButtonElement, PopoverTriggerProps>(
|
|
57
|
+
({ onClick, asChild, children, ...props }, ref) => {
|
|
58
|
+
const context = React.useContext(PopoverContext)
|
|
59
|
+
if (!context) throw new Error("PopoverTrigger must be used within Popover")
|
|
60
|
+
|
|
61
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
62
|
+
onClick?.(e)
|
|
63
|
+
context.onOpenChange(!context.open)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Combine refs
|
|
67
|
+
const combinedRef = (node: HTMLButtonElement | null) => {
|
|
68
|
+
(context.triggerRef as any).current = node
|
|
69
|
+
if (typeof ref === 'function') ref(node)
|
|
70
|
+
else if (ref) ref.current = node
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const Comp = asChild ? Slot : "button"
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Comp
|
|
77
|
+
ref={combinedRef}
|
|
78
|
+
aria-expanded={context.open}
|
|
79
|
+
aria-haspopup={true}
|
|
80
|
+
onClick={handleClick}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</Comp>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
PopoverTrigger.displayName = "PopoverTrigger"
|
|
89
|
+
|
|
90
|
+
const PopoverContent = React.forwardRef<
|
|
91
|
+
HTMLDivElement,
|
|
92
|
+
React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end"; sideOffset?: number; portal?: boolean }
|
|
93
|
+
>(({ className, children, align = "center", sideOffset = 4, portal = true, ...props }, ref) => {
|
|
94
|
+
const context = React.useContext(PopoverContext)
|
|
95
|
+
if (!context) throw new Error("PopoverContent must be used within Popover")
|
|
96
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
97
|
+
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
98
|
+
|
|
99
|
+
// Reset scroll on mount/unmount if needed, but mainly we just need a portal container
|
|
100
|
+
const [mounted, setMounted] = React.useState(false)
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
setMounted(true)
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
107
|
+
if (context.open) {
|
|
108
|
+
const target = e.target as Node
|
|
109
|
+
const content = contentRef.current
|
|
110
|
+
const trigger = context.triggerRef.current
|
|
111
|
+
|
|
112
|
+
// Don't close if clicking inside content or trigger
|
|
113
|
+
if (content?.contains(target) || (trigger && trigger.contains(target))) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
context.onOpenChange(false)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
121
|
+
if (e.key === "Escape" && context.open) {
|
|
122
|
+
context.onOpenChange(false)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const checkPosition = () => {
|
|
127
|
+
if (context.open && contentRef.current && context.triggerRef.current) {
|
|
128
|
+
const triggerRect = context.triggerRef.current.getBoundingClientRect()
|
|
129
|
+
const contentRect = contentRef.current.getBoundingClientRect()
|
|
130
|
+
const viewportHeight = window.innerHeight
|
|
131
|
+
const viewportWidth = window.innerWidth
|
|
132
|
+
|
|
133
|
+
let top = 0
|
|
134
|
+
let left = 0
|
|
135
|
+
|
|
136
|
+
// Vertical
|
|
137
|
+
const spaceBelow = viewportHeight - triggerRect.bottom
|
|
138
|
+
const spaceAbove = triggerRect.top
|
|
139
|
+
const neededHeight = contentRect.height + sideOffset
|
|
140
|
+
const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
|
|
141
|
+
|
|
142
|
+
if (onBottom) {
|
|
143
|
+
top = triggerRect.bottom + sideOffset
|
|
144
|
+
} else {
|
|
145
|
+
top = triggerRect.top - contentRect.height - sideOffset
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Horizontal (Alignment)
|
|
149
|
+
if (align === 'start') {
|
|
150
|
+
left = triggerRect.left
|
|
151
|
+
} else if (align === 'end') {
|
|
152
|
+
left = triggerRect.right - contentRect.width
|
|
153
|
+
} else {
|
|
154
|
+
// center
|
|
155
|
+
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Clamping
|
|
159
|
+
if (left < 4) left = 4
|
|
160
|
+
if (left + contentRect.width > viewportWidth - 4) {
|
|
161
|
+
left = viewportWidth - contentRect.width - 4
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
setPosition({ top, left })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (context.open) {
|
|
169
|
+
requestAnimationFrame(checkPosition)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
document.addEventListener("click", handleClickOutside)
|
|
174
|
+
}, 0)
|
|
175
|
+
document.addEventListener("keydown", handleEscape)
|
|
176
|
+
window.addEventListener("resize", checkPosition)
|
|
177
|
+
window.addEventListener("scroll", checkPosition, true)
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
clearTimeout(timer)
|
|
181
|
+
document.removeEventListener("click", handleClickOutside)
|
|
182
|
+
document.removeEventListener("keydown", handleEscape)
|
|
183
|
+
window.removeEventListener("resize", checkPosition)
|
|
184
|
+
window.removeEventListener("scroll", checkPosition, true)
|
|
185
|
+
}
|
|
186
|
+
}, [context.open, context, align, sideOffset])
|
|
187
|
+
|
|
188
|
+
if (!context.open) return null
|
|
189
|
+
if (portal && !mounted) return null
|
|
190
|
+
|
|
191
|
+
const content = (
|
|
192
|
+
<div
|
|
193
|
+
ref={(node) => {
|
|
194
|
+
(contentRef as any).current = node
|
|
195
|
+
if (typeof ref === 'function') ref(node)
|
|
196
|
+
else if (ref) ref.current = node
|
|
197
|
+
}}
|
|
198
|
+
className={cn(
|
|
199
|
+
"z-50 min-w-[10rem] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
200
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
201
|
+
!portal && "absolute",
|
|
202
|
+
!portal && "mt-2",
|
|
203
|
+
portal && "fixed",
|
|
204
|
+
className
|
|
205
|
+
)}
|
|
206
|
+
style={{
|
|
207
|
+
top: portal ? position.top : undefined,
|
|
208
|
+
left: portal ? position.left : undefined,
|
|
209
|
+
...props.style
|
|
210
|
+
}}
|
|
211
|
+
onClick={(e) => e.stopPropagation()}
|
|
212
|
+
{...props}
|
|
213
|
+
>
|
|
214
|
+
{children}
|
|
215
|
+
</div>
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if (portal) {
|
|
219
|
+
return createPortal(content, document.body)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return content
|
|
223
|
+
})
|
|
224
|
+
PopoverContent.displayName = "PopoverContent"
|
|
225
|
+
|
|
226
|
+
export { Popover, PopoverTrigger, PopoverContent }
|
|
@@ -1,52 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
|
|
6
|
-
interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
/**
|
|
8
|
-
* Progress value from 0 to 100
|
|
9
|
-
*/
|
|
10
|
-
value?: number
|
|
11
|
-
/**
|
|
12
|
-
* Maximum value
|
|
13
|
-
* @default 100
|
|
14
|
-
*/
|
|
15
|
-
max?: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Progress bar component
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* <Progress value={60} />
|
|
23
|
-
*/
|
|
24
|
-
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
25
|
-
({ className, value = 0, max = 100, ...props }, ref) => {
|
|
26
|
-
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div
|
|
30
|
-
ref={ref}
|
|
31
|
-
role="progressbar"
|
|
32
|
-
aria-valuenow={value}
|
|
33
|
-
aria-valuemin={0}
|
|
34
|
-
aria-valuemax={max}
|
|
35
|
-
className={cn(
|
|
36
|
-
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
|
37
|
-
className
|
|
38
|
-
)}
|
|
39
|
-
{...props}
|
|
40
|
-
>
|
|
41
|
-
<div
|
|
42
|
-
className="h-full w-full flex-1 bg-primary transition-all"
|
|
43
|
-
style={{ transform: `translateX(-${100 - percentage}%)` }}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
Progress.displayName = "Progress"
|
|
50
|
-
|
|
51
|
-
export { Progress }
|
|
52
|
-
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/**
|
|
8
|
+
* Progress value from 0 to 100
|
|
9
|
+
*/
|
|
10
|
+
value?: number
|
|
11
|
+
/**
|
|
12
|
+
* Maximum value
|
|
13
|
+
* @default 100
|
|
14
|
+
*/
|
|
15
|
+
max?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Progress bar component
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* <Progress value={60} />
|
|
23
|
+
*/
|
|
24
|
+
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
25
|
+
({ className, value = 0, max = 100, ...props }, ref) => {
|
|
26
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
ref={ref}
|
|
31
|
+
role="progressbar"
|
|
32
|
+
aria-valuenow={value}
|
|
33
|
+
aria-valuemin={0}
|
|
34
|
+
aria-valuemax={max}
|
|
35
|
+
className={cn(
|
|
36
|
+
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
className="h-full w-full flex-1 bg-primary transition-all"
|
|
43
|
+
style={{ transform: `translateX(-${100 - percentage}%)` }}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
Progress.displayName = "Progress"
|
|
50
|
+
|
|
51
|
+
export { Progress }
|