@moontra/moonui 2.2.0 → 2.2.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.
- package/dist/index.d.mts +91 -14
- package/dist/index.d.ts +91 -14
- package/dist/index.js +1714 -369
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1546 -371
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/ui/alert.tsx +123 -93
- package/src/components/ui/avatar.tsx +2 -0
- package/src/components/ui/badge.tsx +19 -17
- package/src/components/ui/breadcrumb.tsx +2 -0
- package/src/components/ui/button.stories.tsx +4 -2
- package/src/components/ui/button.tsx +39 -33
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/card-input.tsx +231 -0
- package/src/components/ui/card.stories.tsx +2 -0
- package/src/components/ui/card.tsx +2 -0
- package/src/components/ui/data-table.stories.tsx +4 -2
- package/src/components/ui/data-table.tsx +7 -7
- package/src/components/ui/date-picker.stories.tsx +2 -0
- package/src/components/ui/date-picker.tsx +18 -9
- package/src/components/ui/draggable-list.tsx +2 -0
- package/src/components/ui/index.ts +357 -44
- package/src/components/ui/input.stories.tsx +2 -0
- package/src/components/ui/input.tsx +2 -0
- package/src/components/ui/locked-component.tsx +222 -0
- package/src/components/ui/pagination.tsx +2 -0
- package/src/components/ui/phone-input.tsx +172 -0
- package/src/components/ui/popover-pro.tsx +424 -0
- package/src/components/ui/rich-text-editor/index.tsx +2 -1
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/separator.tsx +2 -2
- package/src/components/ui/swipeable-card.tsx +2 -0
- package/src/components/ui/table.tsx +2 -0
- package/src/components/ui/tabs.tsx +2 -0
- package/src/components/ui/tags-input.tsx +130 -0
- package/src/components/ui/textarea.tsx +2 -0
- package/src/components/ui/toast.tsx +2 -0
- package/src/index.tsx +4 -46
- package/src/lib/performance-profiler.ts +79 -0
- package/src/styles/index.css +315 -2
- package/src/use-performance-optimizer.ts +26 -26
- package/src/use-scroll-animation.ts +5 -7
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { Input } from "./input"
|
|
6
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select"
|
|
7
|
+
import { Phone } from "lucide-react"
|
|
8
|
+
|
|
9
|
+
// Country codes data
|
|
10
|
+
const countryCodes = [
|
|
11
|
+
{ code: "+1", country: "US", flag: "🇺🇸", name: "United States" },
|
|
12
|
+
{ code: "+44", country: "GB", flag: "🇬🇧", name: "United Kingdom" },
|
|
13
|
+
{ code: "+49", country: "DE", flag: "🇩🇪", name: "Germany" },
|
|
14
|
+
{ code: "+33", country: "FR", flag: "🇫🇷", name: "France" },
|
|
15
|
+
{ code: "+39", country: "IT", flag: "🇮🇹", name: "Italy" },
|
|
16
|
+
{ code: "+34", country: "ES", flag: "🇪🇸", name: "Spain" },
|
|
17
|
+
{ code: "+31", country: "NL", flag: "🇳🇱", name: "Netherlands" },
|
|
18
|
+
{ code: "+46", country: "SE", flag: "🇸🇪", name: "Sweden" },
|
|
19
|
+
{ code: "+47", country: "NO", flag: "🇳🇴", name: "Norway" },
|
|
20
|
+
{ code: "+45", country: "DK", flag: "🇩🇰", name: "Denmark" },
|
|
21
|
+
{ code: "+358", country: "FI", flag: "🇫🇮", name: "Finland" },
|
|
22
|
+
{ code: "+48", country: "PL", flag: "🇵🇱", name: "Poland" },
|
|
23
|
+
{ code: "+90", country: "TR", flag: "🇹🇷", name: "Turkey" },
|
|
24
|
+
{ code: "+86", country: "CN", flag: "🇨🇳", name: "China" },
|
|
25
|
+
{ code: "+81", country: "JP", flag: "🇯🇵", name: "Japan" },
|
|
26
|
+
{ code: "+82", country: "KR", flag: "🇰🇷", name: "South Korea" },
|
|
27
|
+
{ code: "+91", country: "IN", flag: "🇮🇳", name: "India" },
|
|
28
|
+
{ code: "+61", country: "AU", flag: "🇦🇺", name: "Australia" },
|
|
29
|
+
{ code: "+64", country: "NZ", flag: "🇳🇿", name: "New Zealand" },
|
|
30
|
+
{ code: "+27", country: "ZA", flag: "🇿🇦", name: "South Africa" },
|
|
31
|
+
{ code: "+55", country: "BR", flag: "🇧🇷", name: "Brazil" },
|
|
32
|
+
{ code: "+52", country: "MX", flag: "🇲🇽", name: "Mexico" },
|
|
33
|
+
{ code: "+54", country: "AR", flag: "🇦🇷", name: "Argentina" },
|
|
34
|
+
{ code: "+20", country: "EG", flag: "🇪🇬", name: "Egypt" },
|
|
35
|
+
{ code: "+966", country: "SA", flag: "🇸🇦", name: "Saudi Arabia" },
|
|
36
|
+
{ code: "+971", country: "AE", flag: "🇦🇪", name: "UAE" },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// Format phone number based on country
|
|
40
|
+
const formatPhoneNumber = (value: string, countryCode: string): string => {
|
|
41
|
+
const cleaned = value.replace(/\D/g, "")
|
|
42
|
+
|
|
43
|
+
switch (countryCode) {
|
|
44
|
+
case "+1": // US/Canada
|
|
45
|
+
if (cleaned.length <= 3) return cleaned
|
|
46
|
+
if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`
|
|
47
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6, 10)}`
|
|
48
|
+
|
|
49
|
+
case "+44": // UK
|
|
50
|
+
if (cleaned.length <= 4) return cleaned
|
|
51
|
+
if (cleaned.length <= 7) return `${cleaned.slice(0, 4)} ${cleaned.slice(4)}`
|
|
52
|
+
return `${cleaned.slice(0, 4)} ${cleaned.slice(4, 7)} ${cleaned.slice(7, 11)}`
|
|
53
|
+
|
|
54
|
+
case "+90": // Turkey
|
|
55
|
+
if (cleaned.length <= 3) return cleaned
|
|
56
|
+
if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`
|
|
57
|
+
if (cleaned.length <= 9) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)} ${cleaned.slice(6)}`
|
|
58
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)} ${cleaned.slice(6, 8)} ${cleaned.slice(8, 10)}`
|
|
59
|
+
|
|
60
|
+
default: // Generic formatting
|
|
61
|
+
let formatted = ""
|
|
62
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
63
|
+
if (i > 0 && i % 3 === 0) formatted += " "
|
|
64
|
+
formatted += cleaned[i]
|
|
65
|
+
}
|
|
66
|
+
return formatted
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get max length based on country
|
|
71
|
+
const getMaxLength = (countryCode: string): number => {
|
|
72
|
+
switch (countryCode) {
|
|
73
|
+
case "+1": return 10 // US/Canada
|
|
74
|
+
case "+44": return 11 // UK
|
|
75
|
+
case "+90": return 10 // Turkey
|
|
76
|
+
default: return 15 // Generic max
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PhoneInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
|
|
81
|
+
value?: string
|
|
82
|
+
onChange?: (value: string, countryCode: string) => void
|
|
83
|
+
defaultCountry?: string
|
|
84
|
+
showIcon?: boolean
|
|
85
|
+
showCountrySelect?: boolean
|
|
86
|
+
countries?: typeof countryCodes
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
90
|
+
({
|
|
91
|
+
className,
|
|
92
|
+
value = "",
|
|
93
|
+
onChange,
|
|
94
|
+
defaultCountry = "+1",
|
|
95
|
+
showIcon = true,
|
|
96
|
+
showCountrySelect = true,
|
|
97
|
+
countries = countryCodes,
|
|
98
|
+
size,
|
|
99
|
+
...props
|
|
100
|
+
}, ref) => {
|
|
101
|
+
const [countryCode, setCountryCode] = React.useState(defaultCountry)
|
|
102
|
+
const [phoneNumber, setPhoneNumber] = React.useState(value)
|
|
103
|
+
|
|
104
|
+
const selectedCountry = countries.find(c => c.code === countryCode) || countries[0]
|
|
105
|
+
|
|
106
|
+
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
107
|
+
const rawValue = e.target.value.replace(/\D/g, "")
|
|
108
|
+
const maxLength = getMaxLength(countryCode)
|
|
109
|
+
const truncated = rawValue.slice(0, maxLength)
|
|
110
|
+
const formatted = formatPhoneNumber(truncated, countryCode)
|
|
111
|
+
|
|
112
|
+
setPhoneNumber(truncated)
|
|
113
|
+
onChange?.(truncated, countryCode)
|
|
114
|
+
|
|
115
|
+
// Update the input value with formatted version
|
|
116
|
+
e.target.value = formatted
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const handleCountryChange = (newCountryCode: string) => {
|
|
120
|
+
setCountryCode(newCountryCode)
|
|
121
|
+
onChange?.(phoneNumber, newCountryCode)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const formattedValue = formatPhoneNumber(phoneNumber, countryCode)
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className={cn("relative flex gap-2", className)}>
|
|
128
|
+
{showCountrySelect && (
|
|
129
|
+
<Select value={countryCode} onValueChange={handleCountryChange}>
|
|
130
|
+
<SelectTrigger className="w-[120px]">
|
|
131
|
+
<SelectValue>
|
|
132
|
+
<div className="flex items-center gap-2">
|
|
133
|
+
<span>{selectedCountry.flag}</span>
|
|
134
|
+
<span>{selectedCountry.code}</span>
|
|
135
|
+
</div>
|
|
136
|
+
</SelectValue>
|
|
137
|
+
</SelectTrigger>
|
|
138
|
+
<SelectContent>
|
|
139
|
+
{countries.map((country) => (
|
|
140
|
+
<SelectItem key={country.code} value={country.code}>
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
<span>{country.flag}</span>
|
|
143
|
+
<span>{country.code}</span>
|
|
144
|
+
<span className="text-muted-foreground">{country.name}</span>
|
|
145
|
+
</div>
|
|
146
|
+
</SelectItem>
|
|
147
|
+
))}
|
|
148
|
+
</SelectContent>
|
|
149
|
+
</Select>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
<div className="relative flex-1">
|
|
153
|
+
<Input
|
|
154
|
+
ref={ref}
|
|
155
|
+
type="tel"
|
|
156
|
+
inputMode="numeric"
|
|
157
|
+
autoComplete="tel"
|
|
158
|
+
placeholder={countryCode === "+1" ? "(555) 123-4567" : "123 456 789"}
|
|
159
|
+
value={formattedValue}
|
|
160
|
+
onChange={handlePhoneChange}
|
|
161
|
+
className={cn(showIcon && "pl-10", className)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
{showIcon && (
|
|
165
|
+
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
PhoneInput.displayName = "PhoneInput"
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
5
|
+
import { motion, AnimatePresence, useSpring, useTransform } from "framer-motion"
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
8
|
+
|
|
9
|
+
const PopoverPro = PopoverPrimitive.Root
|
|
10
|
+
const PopoverTriggerPro = PopoverPrimitive.Trigger
|
|
11
|
+
const PopoverAnchorPro = PopoverPrimitive.Anchor
|
|
12
|
+
const PopoverPortalPro = PopoverPrimitive.Portal
|
|
13
|
+
|
|
14
|
+
// Animation variants
|
|
15
|
+
const popoverAnimations = {
|
|
16
|
+
// Basic animations
|
|
17
|
+
fade: {
|
|
18
|
+
initial: { opacity: 0 },
|
|
19
|
+
animate: { opacity: 1 },
|
|
20
|
+
exit: { opacity: 0 }
|
|
21
|
+
},
|
|
22
|
+
scale: {
|
|
23
|
+
initial: { opacity: 0, scale: 0.95 },
|
|
24
|
+
animate: { opacity: 1, scale: 1 },
|
|
25
|
+
exit: { opacity: 0, scale: 0.95 }
|
|
26
|
+
},
|
|
27
|
+
slide: {
|
|
28
|
+
initial: { opacity: 0, y: -10 },
|
|
29
|
+
animate: { opacity: 1, y: 0 },
|
|
30
|
+
exit: { opacity: 0, y: -10 }
|
|
31
|
+
},
|
|
32
|
+
// Advanced animations
|
|
33
|
+
spring: {
|
|
34
|
+
initial: { opacity: 0, scale: 0.8, y: -20 },
|
|
35
|
+
animate: {
|
|
36
|
+
opacity: 1,
|
|
37
|
+
scale: 1,
|
|
38
|
+
y: 0,
|
|
39
|
+
transition: {
|
|
40
|
+
type: "spring",
|
|
41
|
+
stiffness: 300,
|
|
42
|
+
damping: 20
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
exit: { opacity: 0, scale: 0.8, y: -20 }
|
|
46
|
+
},
|
|
47
|
+
elastic: {
|
|
48
|
+
initial: { opacity: 0, scale: 0.5, rotate: -10 },
|
|
49
|
+
animate: {
|
|
50
|
+
opacity: 1,
|
|
51
|
+
scale: 1,
|
|
52
|
+
rotate: 0,
|
|
53
|
+
transition: {
|
|
54
|
+
type: "spring",
|
|
55
|
+
stiffness: 400,
|
|
56
|
+
damping: 15
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
exit: { opacity: 0, scale: 0.5, rotate: 10 }
|
|
60
|
+
},
|
|
61
|
+
bounce: {
|
|
62
|
+
initial: { opacity: 0, y: -30 },
|
|
63
|
+
animate: {
|
|
64
|
+
opacity: 1,
|
|
65
|
+
y: 0,
|
|
66
|
+
transition: {
|
|
67
|
+
type: "spring",
|
|
68
|
+
stiffness: 500,
|
|
69
|
+
damping: 15,
|
|
70
|
+
mass: 1
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
exit: { opacity: 0, y: -30 }
|
|
74
|
+
},
|
|
75
|
+
flip: {
|
|
76
|
+
initial: { opacity: 0, rotateX: -90 },
|
|
77
|
+
animate: {
|
|
78
|
+
opacity: 1,
|
|
79
|
+
rotateX: 0,
|
|
80
|
+
transition: {
|
|
81
|
+
type: "spring",
|
|
82
|
+
stiffness: 300,
|
|
83
|
+
damping: 20
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
exit: { opacity: 0, rotateX: 90 }
|
|
87
|
+
},
|
|
88
|
+
slideFromBottom: {
|
|
89
|
+
initial: { opacity: 0, y: 20 },
|
|
90
|
+
animate: {
|
|
91
|
+
opacity: 1,
|
|
92
|
+
y: 0,
|
|
93
|
+
transition: {
|
|
94
|
+
type: "spring",
|
|
95
|
+
stiffness: 300,
|
|
96
|
+
damping: 25
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
exit: { opacity: 0, y: 20 }
|
|
100
|
+
},
|
|
101
|
+
zoom: {
|
|
102
|
+
initial: { opacity: 0, scale: 0 },
|
|
103
|
+
animate: {
|
|
104
|
+
opacity: 1,
|
|
105
|
+
scale: 1,
|
|
106
|
+
transition: {
|
|
107
|
+
type: "spring",
|
|
108
|
+
stiffness: 300,
|
|
109
|
+
damping: 20
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
exit: { opacity: 0, scale: 0 }
|
|
113
|
+
},
|
|
114
|
+
morph: {
|
|
115
|
+
initial: {
|
|
116
|
+
opacity: 0,
|
|
117
|
+
scale: 0.8,
|
|
118
|
+
borderRadius: "50%"
|
|
119
|
+
},
|
|
120
|
+
animate: {
|
|
121
|
+
opacity: 1,
|
|
122
|
+
scale: 1,
|
|
123
|
+
borderRadius: "12px",
|
|
124
|
+
transition: {
|
|
125
|
+
type: "spring",
|
|
126
|
+
stiffness: 300,
|
|
127
|
+
damping: 25
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
exit: {
|
|
131
|
+
opacity: 0,
|
|
132
|
+
scale: 0.8,
|
|
133
|
+
borderRadius: "50%"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const popoverContentVariants = cva(
|
|
139
|
+
"z-50 w-72 rounded-md p-4 shadow-md outline-none",
|
|
140
|
+
{
|
|
141
|
+
variants: {
|
|
142
|
+
variant: {
|
|
143
|
+
default: "border bg-popover text-popover-foreground",
|
|
144
|
+
glass: [
|
|
145
|
+
"border border-white/20 bg-white/80 backdrop-blur-xl",
|
|
146
|
+
"dark:border-gray-800/50 dark:bg-gray-900/80",
|
|
147
|
+
"shadow-xl"
|
|
148
|
+
],
|
|
149
|
+
gradient: [
|
|
150
|
+
"bg-gradient-to-br from-white to-gray-50",
|
|
151
|
+
"dark:from-gray-900 dark:to-gray-950",
|
|
152
|
+
"border border-gray-200 dark:border-gray-800",
|
|
153
|
+
"shadow-xl"
|
|
154
|
+
],
|
|
155
|
+
solid: [
|
|
156
|
+
"bg-white dark:bg-gray-900",
|
|
157
|
+
"border-2 border-gray-200 dark:border-gray-700",
|
|
158
|
+
"shadow-2xl"
|
|
159
|
+
],
|
|
160
|
+
floating: [
|
|
161
|
+
"bg-white dark:bg-gray-900",
|
|
162
|
+
"shadow-2xl shadow-black/20 dark:shadow-black/40",
|
|
163
|
+
"border border-gray-100 dark:border-gray-800"
|
|
164
|
+
],
|
|
165
|
+
neumorphic: [
|
|
166
|
+
"bg-gray-100 dark:bg-gray-900",
|
|
167
|
+
"shadow-[20px_20px_60px_#bebebe,-20px_-20px_60px_#ffffff]",
|
|
168
|
+
"dark:shadow-[20px_20px_60px_#1a1a1a,-20px_-20px_60px_#2a2a2a]",
|
|
169
|
+
"border-0"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
size: {
|
|
173
|
+
sm: "w-56 p-3",
|
|
174
|
+
default: "w-72 p-4",
|
|
175
|
+
lg: "w-96 p-6",
|
|
176
|
+
xl: "w-[32rem] p-8",
|
|
177
|
+
auto: "w-auto p-4"
|
|
178
|
+
},
|
|
179
|
+
overlayBlur: {
|
|
180
|
+
none: "",
|
|
181
|
+
sm: "backdrop-blur-sm",
|
|
182
|
+
md: "backdrop-blur-md",
|
|
183
|
+
lg: "backdrop-blur-lg",
|
|
184
|
+
xl: "backdrop-blur-xl"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
defaultVariants: {
|
|
188
|
+
variant: "default",
|
|
189
|
+
size: "default",
|
|
190
|
+
overlayBlur: "none"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
export interface PopoverContentProProps
|
|
196
|
+
extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>,
|
|
197
|
+
VariantProps<typeof popoverContentVariants> {
|
|
198
|
+
animation?: keyof typeof popoverAnimations
|
|
199
|
+
enableSpringPhysics?: boolean
|
|
200
|
+
showArrow?: boolean
|
|
201
|
+
arrowSize?: number
|
|
202
|
+
customBackdrop?: boolean
|
|
203
|
+
dragToDismiss?: boolean
|
|
204
|
+
pinnable?: boolean
|
|
205
|
+
resizable?: boolean
|
|
206
|
+
focusTrap?: boolean
|
|
207
|
+
closeOnEscape?: boolean
|
|
208
|
+
closeOnClickOutside?: boolean
|
|
209
|
+
staggerChildren?: boolean
|
|
210
|
+
glowEffect?: boolean
|
|
211
|
+
magneticEdges?: boolean
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const PopoverContentPro = React.forwardRef<
|
|
215
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
216
|
+
PopoverContentProProps
|
|
217
|
+
>(
|
|
218
|
+
(
|
|
219
|
+
{
|
|
220
|
+
className,
|
|
221
|
+
align = "center",
|
|
222
|
+
sideOffset = 4,
|
|
223
|
+
variant,
|
|
224
|
+
size,
|
|
225
|
+
overlayBlur,
|
|
226
|
+
animation = "spring",
|
|
227
|
+
enableSpringPhysics = false,
|
|
228
|
+
showArrow = false,
|
|
229
|
+
arrowSize = 8,
|
|
230
|
+
customBackdrop = false,
|
|
231
|
+
dragToDismiss = false,
|
|
232
|
+
pinnable = false,
|
|
233
|
+
resizable = false,
|
|
234
|
+
focusTrap = false,
|
|
235
|
+
closeOnEscape = true,
|
|
236
|
+
closeOnClickOutside = true,
|
|
237
|
+
staggerChildren = false,
|
|
238
|
+
glowEffect = false,
|
|
239
|
+
magneticEdges = false,
|
|
240
|
+
children,
|
|
241
|
+
...props
|
|
242
|
+
},
|
|
243
|
+
ref
|
|
244
|
+
) => {
|
|
245
|
+
const [isPinned, setIsPinned] = React.useState(false)
|
|
246
|
+
const [isDragging, setIsDragging] = React.useState(false)
|
|
247
|
+
const animationVariant = popoverAnimations[animation] || popoverAnimations.spring
|
|
248
|
+
|
|
249
|
+
// Spring physics for magnetic edges
|
|
250
|
+
const x = useSpring(0, { stiffness: 300, damping: 30 })
|
|
251
|
+
const y = useSpring(0, { stiffness: 300, damping: 30 })
|
|
252
|
+
|
|
253
|
+
const contentElement = (
|
|
254
|
+
<PopoverPrimitive.Content
|
|
255
|
+
ref={ref}
|
|
256
|
+
align={align}
|
|
257
|
+
sideOffset={sideOffset}
|
|
258
|
+
onEscapeKeyDown={(e) => {
|
|
259
|
+
if (!closeOnEscape || isPinned) {
|
|
260
|
+
e.preventDefault()
|
|
261
|
+
}
|
|
262
|
+
}}
|
|
263
|
+
onPointerDownOutside={(e) => {
|
|
264
|
+
if (!closeOnClickOutside || isPinned) {
|
|
265
|
+
e.preventDefault()
|
|
266
|
+
}
|
|
267
|
+
}}
|
|
268
|
+
className={cn(
|
|
269
|
+
popoverContentVariants({ variant, size, overlayBlur }),
|
|
270
|
+
glowEffect && "shadow-[0_0_30px_rgba(59,130,246,0.3)]",
|
|
271
|
+
resizable && "resize overflow-auto",
|
|
272
|
+
isDragging && "cursor-grabbing",
|
|
273
|
+
className
|
|
274
|
+
)}
|
|
275
|
+
{...props}
|
|
276
|
+
>
|
|
277
|
+
{pinnable && (
|
|
278
|
+
<button
|
|
279
|
+
onClick={() => setIsPinned(!isPinned)}
|
|
280
|
+
className="absolute top-2 right-2 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
281
|
+
aria-label={isPinned ? "Unpin popover" : "Pin popover"}
|
|
282
|
+
>
|
|
283
|
+
<svg
|
|
284
|
+
width="16"
|
|
285
|
+
height="16"
|
|
286
|
+
viewBox="0 0 24 24"
|
|
287
|
+
fill="none"
|
|
288
|
+
stroke="currentColor"
|
|
289
|
+
strokeWidth="2"
|
|
290
|
+
className={cn(
|
|
291
|
+
"transition-transform",
|
|
292
|
+
isPinned && "rotate-45"
|
|
293
|
+
)}
|
|
294
|
+
>
|
|
295
|
+
<path d="M12 17v5M9 10V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v6M9 10l-2 2v3h10v-3l-2-2" />
|
|
296
|
+
</svg>
|
|
297
|
+
</button>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{showArrow && (
|
|
301
|
+
<PopoverPrimitive.Arrow
|
|
302
|
+
width={arrowSize * 2}
|
|
303
|
+
height={arrowSize}
|
|
304
|
+
className={cn(
|
|
305
|
+
"fill-current",
|
|
306
|
+
variant === "glass" && "text-white/80 dark:text-gray-900/80",
|
|
307
|
+
variant === "gradient" && "text-white dark:text-gray-900",
|
|
308
|
+
variant === "default" && "text-popover"
|
|
309
|
+
)}
|
|
310
|
+
/>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
<div
|
|
314
|
+
className={cn(
|
|
315
|
+
staggerChildren && "space-y-2",
|
|
316
|
+
dragToDismiss && "select-none"
|
|
317
|
+
)}
|
|
318
|
+
>
|
|
319
|
+
{children}
|
|
320
|
+
</div>
|
|
321
|
+
</PopoverPrimitive.Content>
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if (enableSpringPhysics || magneticEdges) {
|
|
325
|
+
return (
|
|
326
|
+
<AnimatePresence mode="wait">
|
|
327
|
+
<motion.div
|
|
328
|
+
{...(animationVariant as any)}
|
|
329
|
+
style={magneticEdges ? { x, y } : undefined}
|
|
330
|
+
drag={dragToDismiss}
|
|
331
|
+
dragElastic={0.2}
|
|
332
|
+
dragConstraints={{ left: -50, right: 50, top: -50, bottom: 50 }}
|
|
333
|
+
onDragStart={() => setIsDragging(true)}
|
|
334
|
+
onDragEnd={() => setIsDragging(false)}
|
|
335
|
+
>
|
|
336
|
+
{contentElement}
|
|
337
|
+
</motion.div>
|
|
338
|
+
</AnimatePresence>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return contentElement
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
PopoverContentPro.displayName = PopoverPrimitive.Content.displayName
|
|
347
|
+
|
|
348
|
+
// Additional Pro components
|
|
349
|
+
const PopoverHeaderPro = React.forwardRef<
|
|
350
|
+
HTMLDivElement,
|
|
351
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
352
|
+
>(({ className, ...props }, ref) => (
|
|
353
|
+
<div
|
|
354
|
+
ref={ref}
|
|
355
|
+
className={cn("mb-4 space-y-1", className)}
|
|
356
|
+
{...props}
|
|
357
|
+
/>
|
|
358
|
+
))
|
|
359
|
+
PopoverHeaderPro.displayName = "PopoverHeaderPro"
|
|
360
|
+
|
|
361
|
+
const PopoverTitlePro = React.forwardRef<
|
|
362
|
+
HTMLHeadingElement,
|
|
363
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
364
|
+
>(({ className, ...props }, ref) => (
|
|
365
|
+
<h3
|
|
366
|
+
ref={ref}
|
|
367
|
+
className={cn("font-semibold text-lg leading-none tracking-tight", className)}
|
|
368
|
+
{...props}
|
|
369
|
+
/>
|
|
370
|
+
))
|
|
371
|
+
PopoverTitlePro.displayName = "PopoverTitlePro"
|
|
372
|
+
|
|
373
|
+
const PopoverDescriptionPro = React.forwardRef<
|
|
374
|
+
HTMLParagraphElement,
|
|
375
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
376
|
+
>(({ className, ...props }, ref) => (
|
|
377
|
+
<p
|
|
378
|
+
ref={ref}
|
|
379
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
380
|
+
{...props}
|
|
381
|
+
/>
|
|
382
|
+
))
|
|
383
|
+
PopoverDescriptionPro.displayName = "PopoverDescriptionPro"
|
|
384
|
+
|
|
385
|
+
const PopoverFooterPro = React.forwardRef<
|
|
386
|
+
HTMLDivElement,
|
|
387
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
388
|
+
>(({ className, ...props }, ref) => (
|
|
389
|
+
<div
|
|
390
|
+
ref={ref}
|
|
391
|
+
className={cn(
|
|
392
|
+
"mt-4 flex items-center justify-end space-x-2 pt-4 border-t",
|
|
393
|
+
className
|
|
394
|
+
)}
|
|
395
|
+
{...props}
|
|
396
|
+
/>
|
|
397
|
+
))
|
|
398
|
+
PopoverFooterPro.displayName = "PopoverFooterPro"
|
|
399
|
+
|
|
400
|
+
const PopoverSeparatorPro = React.forwardRef<
|
|
401
|
+
HTMLHRElement,
|
|
402
|
+
React.HTMLAttributes<HTMLHRElement>
|
|
403
|
+
>(({ className, ...props }, ref) => (
|
|
404
|
+
<hr
|
|
405
|
+
ref={ref}
|
|
406
|
+
className={cn("my-4 border-t", className)}
|
|
407
|
+
{...props}
|
|
408
|
+
/>
|
|
409
|
+
))
|
|
410
|
+
PopoverSeparatorPro.displayName = "PopoverSeparatorPro"
|
|
411
|
+
|
|
412
|
+
export {
|
|
413
|
+
PopoverPro,
|
|
414
|
+
PopoverTriggerPro,
|
|
415
|
+
PopoverContentPro,
|
|
416
|
+
PopoverAnchorPro,
|
|
417
|
+
PopoverPortalPro,
|
|
418
|
+
PopoverHeaderPro,
|
|
419
|
+
PopoverTitlePro,
|
|
420
|
+
PopoverDescriptionPro,
|
|
421
|
+
PopoverFooterPro,
|
|
422
|
+
PopoverSeparatorPro,
|
|
423
|
+
popoverContentVariants
|
|
424
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
const ScrollArea = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
11
|
+
>(({ className, children, ...props }, ref) => (
|
|
12
|
+
<ScrollAreaPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn("relative overflow-hidden", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
18
|
+
{children}
|
|
19
|
+
</ScrollAreaPrimitive.Viewport>
|
|
20
|
+
<ScrollBar />
|
|
21
|
+
<ScrollAreaPrimitive.Corner />
|
|
22
|
+
</ScrollAreaPrimitive.Root>
|
|
23
|
+
))
|
|
24
|
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|
25
|
+
|
|
26
|
+
const ScrollBar = React.forwardRef<
|
|
27
|
+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
|
28
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
29
|
+
>(({ className, orientation = "vertical", ...props }, ref) => (
|
|
30
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
31
|
+
ref={ref}
|
|
32
|
+
orientation={orientation}
|
|
33
|
+
className={cn(
|
|
34
|
+
"flex touch-none select-none transition-colors",
|
|
35
|
+
orientation === "vertical" &&
|
|
36
|
+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
|
37
|
+
orientation === "horizontal" &&
|
|
38
|
+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
|
44
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
45
|
+
))
|
|
46
|
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
|
47
|
+
|
|
48
|
+
export { ScrollArea, ScrollBar }
|
|
@@ -143,10 +143,14 @@ SelectScrollDownButton.displayName =
|
|
|
143
143
|
const SelectContent = React.forwardRef<
|
|
144
144
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
145
145
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
146
|
-
>(({ className, children, position = "
|
|
146
|
+
>(({ className, children, position = "popper", side = "bottom", sideOffset = 4, align = "start", ...props }, ref) => (
|
|
147
147
|
<SelectPrimitive.Portal>
|
|
148
148
|
<SelectPrimitive.Content
|
|
149
149
|
ref={ref}
|
|
150
|
+
side={side}
|
|
151
|
+
sideOffset={sideOffset}
|
|
152
|
+
align={align}
|
|
153
|
+
avoidCollisions={false}
|
|
150
154
|
className={cn(
|
|
151
155
|
["relative z-50 max-h-96 min-w-[8rem] overflow-hidden",
|
|
152
156
|
"rounded-md border border-gray-200 dark:border-gray-700",
|
|
@@ -105,7 +105,7 @@ const separatorVariants = cva(
|
|
|
105
105
|
)
|
|
106
106
|
|
|
107
107
|
export interface SeparatorProps
|
|
108
|
-
extends React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>,
|
|
108
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>, keyof VariantProps<typeof separatorVariants>>,
|
|
109
109
|
VariantProps<typeof separatorVariants> {}
|
|
110
110
|
|
|
111
111
|
const Separator = React.forwardRef<
|
|
@@ -126,7 +126,7 @@ const Separator = React.forwardRef<
|
|
|
126
126
|
<SeparatorPrimitive.Root
|
|
127
127
|
ref={ref}
|
|
128
128
|
decorative={decorative}
|
|
129
|
-
orientation={orientation}
|
|
129
|
+
orientation={orientation || undefined}
|
|
130
130
|
className={cn(
|
|
131
131
|
separatorVariants({ orientation, variant, size }),
|
|
132
132
|
className
|