@moontra/moonui-pro 2.0.22 → 2.0.23
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/package.json +2 -1
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
- package/src/components/advanced-chart/index.tsx +412 -0
- package/src/components/advanced-forms/index.tsx +431 -0
- package/src/components/animated-button/index.tsx +202 -0
- package/src/components/calendar/event-dialog.tsx +372 -0
- package/src/components/calendar/index.tsx +531 -0
- package/src/components/color-picker/index.tsx +434 -0
- package/src/components/dashboard/index.tsx +334 -0
- package/src/components/data-table/data-table.test.tsx +187 -0
- package/src/components/data-table/index.tsx +368 -0
- package/src/components/draggable-list/index.tsx +100 -0
- package/src/components/enhanced/button.tsx +360 -0
- package/src/components/enhanced/card.tsx +272 -0
- package/src/components/enhanced/dialog.tsx +248 -0
- package/src/components/enhanced/index.ts +3 -0
- package/src/components/error-boundary/index.tsx +111 -0
- package/src/components/file-upload/file-upload.test.tsx +242 -0
- package/src/components/file-upload/index.tsx +362 -0
- package/src/components/floating-action-button/index.tsx +209 -0
- package/src/components/github-stars/index.tsx +414 -0
- package/src/components/health-check/index.tsx +441 -0
- package/src/components/hover-card-3d/index.tsx +170 -0
- package/src/components/index.ts +76 -0
- package/src/components/kanban/index.tsx +436 -0
- package/src/components/lazy-component/index.tsx +342 -0
- package/src/components/magnetic-button/index.tsx +170 -0
- package/src/components/memory-efficient-data/index.tsx +352 -0
- package/src/components/optimized-image/index.tsx +427 -0
- package/src/components/performance-debugger/index.tsx +591 -0
- package/src/components/performance-monitor/index.tsx +775 -0
- package/src/components/pinch-zoom/index.tsx +172 -0
- package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
- package/src/components/rich-text-editor/index.tsx +1537 -0
- package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
- package/src/components/rich-text-editor/slash-commands.css +35 -0
- package/src/components/rich-text-editor/table-styles.css +65 -0
- package/src/components/spotlight-card/index.tsx +194 -0
- package/src/components/swipeable-card/index.tsx +100 -0
- package/src/components/timeline/index.tsx +333 -0
- package/src/components/ui/animated-button.tsx +185 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/color-picker.tsx +95 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/hover-card-3d.tsx +103 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/magnetic-button.tsx +129 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/spotlight-card.tsx +119 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +25 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/components/virtual-list/index.tsx +369 -0
- package/src/hooks/use-chart.ts +205 -0
- package/src/hooks/use-data-table.ts +182 -0
- package/src/hooks/use-docs-pro-access.ts +13 -0
- package/src/hooks/use-license-check.ts +65 -0
- package/src/hooks/use-subscription.ts +19 -0
- package/src/index.ts +11 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/utils.ts +6 -0
- package/src/patterns/login-form/index.tsx +276 -0
- package/src/patterns/login-form/types.ts +67 -0
- package/src/setupTests.ts +41 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/types/moonui.d.ts +22 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
- package/src/utils/chart-helpers.ts +257 -0
- package/src/utils/cn.ts +69 -0
- package/src/utils/data-processing.ts +151 -0
- package/src/utils/license-validator.tsx +183 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState, useEffect } from "react"
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
5
|
+
import { Loader2, Check, X, Sparkles } from "lucide-react"
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
8
|
+
|
|
9
|
+
const enhancedButtonVariants = cva(
|
|
10
|
+
"relative inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 overflow-hidden",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
15
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
16
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
gradient: "bg-gradient-to-r from-purple-600 to-pink-600 text-white hover:from-purple-700 hover:to-pink-700",
|
|
21
|
+
glow: "bg-primary text-primary-foreground shadow-lg shadow-primary/50 hover:shadow-xl hover:shadow-primary/60",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
default: "h-10 px-4 py-2",
|
|
25
|
+
sm: "h-9 rounded-md px-3",
|
|
26
|
+
lg: "h-11 rounded-md px-8",
|
|
27
|
+
icon: "h-10 w-10",
|
|
28
|
+
},
|
|
29
|
+
animation: {
|
|
30
|
+
ripple: "ripple-effect",
|
|
31
|
+
morph: "morph-effect",
|
|
32
|
+
particles: "particles-effect",
|
|
33
|
+
magnetic: "magnetic-effect",
|
|
34
|
+
glitch: "glitch-effect",
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
size: "default",
|
|
40
|
+
animation: "ripple",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
interface RippleEffect {
|
|
46
|
+
x: number
|
|
47
|
+
y: number
|
|
48
|
+
id: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EnhancedButtonProps
|
|
52
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
53
|
+
VariantProps<typeof enhancedButtonVariants> {
|
|
54
|
+
state?: "idle" | "loading" | "success" | "error"
|
|
55
|
+
onStateChange?: (state: "idle" | "loading" | "success" | "error") => void
|
|
56
|
+
enableRipple?: boolean
|
|
57
|
+
enableMorph?: boolean
|
|
58
|
+
enableParticles?: boolean
|
|
59
|
+
enableMagnetic?: boolean
|
|
60
|
+
enableGlitch?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const EnhancedButton = React.forwardRef<HTMLButtonElement, EnhancedButtonProps>(
|
|
64
|
+
({
|
|
65
|
+
className,
|
|
66
|
+
variant,
|
|
67
|
+
size,
|
|
68
|
+
animation,
|
|
69
|
+
state = "idle",
|
|
70
|
+
onStateChange,
|
|
71
|
+
children,
|
|
72
|
+
onClick,
|
|
73
|
+
enableRipple = true,
|
|
74
|
+
enableMorph = true,
|
|
75
|
+
enableParticles = false,
|
|
76
|
+
enableMagnetic = false,
|
|
77
|
+
enableGlitch = false,
|
|
78
|
+
...props
|
|
79
|
+
}, ref) => {
|
|
80
|
+
const [internalState, setInternalState] = useState<"idle" | "loading" | "success" | "error">("idle")
|
|
81
|
+
const [ripples, setRipples] = useState<RippleEffect[]>([])
|
|
82
|
+
const [particles, setParticles] = useState<{ x: number; y: number; id: number }[]>([])
|
|
83
|
+
const [magneticPosition, setMagneticPosition] = useState({ x: 0, y: 0 })
|
|
84
|
+
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
85
|
+
const currentState = state !== "idle" ? state : internalState
|
|
86
|
+
|
|
87
|
+
// Ripple effect
|
|
88
|
+
const createRipple = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
89
|
+
if (!enableRipple) return
|
|
90
|
+
|
|
91
|
+
const button = event.currentTarget
|
|
92
|
+
const rect = button.getBoundingClientRect()
|
|
93
|
+
const ripple = {
|
|
94
|
+
x: event.clientX - rect.left,
|
|
95
|
+
y: event.clientY - rect.top,
|
|
96
|
+
id: Date.now()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setRipples([...ripples, ripple])
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
setRipples(prev => prev.filter(r => r.id !== ripple.id))
|
|
102
|
+
}, 1000)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Particle effect
|
|
106
|
+
const createParticles = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
107
|
+
if (!enableParticles) return
|
|
108
|
+
|
|
109
|
+
const button = event.currentTarget
|
|
110
|
+
const rect = button.getBoundingClientRect()
|
|
111
|
+
const newParticles = Array.from({ length: 8 }, (_, i) => ({
|
|
112
|
+
x: event.clientX - rect.left,
|
|
113
|
+
y: event.clientY - rect.top,
|
|
114
|
+
id: Date.now() + i
|
|
115
|
+
}))
|
|
116
|
+
|
|
117
|
+
setParticles([...particles, ...newParticles])
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
setParticles([])
|
|
120
|
+
}, 1000)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Magnetic effect
|
|
124
|
+
const handleMouseMove = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
125
|
+
if (!enableMagnetic) return
|
|
126
|
+
|
|
127
|
+
const button = event.currentTarget
|
|
128
|
+
const rect = button.getBoundingClientRect()
|
|
129
|
+
const centerX = rect.width / 2
|
|
130
|
+
const centerY = rect.height / 2
|
|
131
|
+
const x = event.clientX - rect.left - centerX
|
|
132
|
+
const y = event.clientY - rect.top - centerY
|
|
133
|
+
|
|
134
|
+
setMagneticPosition({
|
|
135
|
+
x: x * 0.2,
|
|
136
|
+
y: y * 0.2
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const handleMouseLeave = () => {
|
|
141
|
+
if (enableMagnetic) {
|
|
142
|
+
setMagneticPosition({ x: 0, y: 0 })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
147
|
+
createRipple(e)
|
|
148
|
+
createParticles(e)
|
|
149
|
+
|
|
150
|
+
if (currentState === "loading") return
|
|
151
|
+
|
|
152
|
+
if (enableMorph) {
|
|
153
|
+
setInternalState("loading")
|
|
154
|
+
onStateChange?.("loading")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (onClick) {
|
|
158
|
+
try {
|
|
159
|
+
await onClick(e)
|
|
160
|
+
if (enableMorph) {
|
|
161
|
+
setInternalState("success")
|
|
162
|
+
onStateChange?.("success")
|
|
163
|
+
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
setInternalState("idle")
|
|
166
|
+
onStateChange?.("idle")
|
|
167
|
+
}, 2000)
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (enableMorph) {
|
|
171
|
+
setInternalState("error")
|
|
172
|
+
onStateChange?.("error")
|
|
173
|
+
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
setInternalState("idle")
|
|
176
|
+
onStateChange?.("idle")
|
|
177
|
+
}, 2000)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const getIcon = () => {
|
|
184
|
+
switch (currentState) {
|
|
185
|
+
case "loading":
|
|
186
|
+
return <Loader2 className="h-4 w-4 animate-spin" />
|
|
187
|
+
case "success":
|
|
188
|
+
return <Check className="h-4 w-4" />
|
|
189
|
+
case "error":
|
|
190
|
+
return <X className="h-4 w-4" />
|
|
191
|
+
default:
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const getBackgroundColor = () => {
|
|
197
|
+
switch (currentState) {
|
|
198
|
+
case "success":
|
|
199
|
+
return "bg-green-600"
|
|
200
|
+
case "error":
|
|
201
|
+
return "bg-red-600"
|
|
202
|
+
default:
|
|
203
|
+
return ""
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<motion.button
|
|
209
|
+
className={cn(enhancedButtonVariants({ variant, size }), className)}
|
|
210
|
+
ref={buttonRef || ref}
|
|
211
|
+
onClick={handleClick}
|
|
212
|
+
onMouseMove={handleMouseMove}
|
|
213
|
+
onMouseLeave={handleMouseLeave}
|
|
214
|
+
disabled={currentState === "loading" || props.disabled}
|
|
215
|
+
animate={{
|
|
216
|
+
x: magneticPosition.x,
|
|
217
|
+
y: magneticPosition.y,
|
|
218
|
+
}}
|
|
219
|
+
transition={{
|
|
220
|
+
type: "spring",
|
|
221
|
+
stiffness: 150,
|
|
222
|
+
damping: 15,
|
|
223
|
+
mass: 0.1
|
|
224
|
+
}}
|
|
225
|
+
{...props}
|
|
226
|
+
>
|
|
227
|
+
{/* Ripple effects */}
|
|
228
|
+
<AnimatePresence>
|
|
229
|
+
{ripples.map((ripple) => (
|
|
230
|
+
<motion.span
|
|
231
|
+
key={ripple.id}
|
|
232
|
+
className="absolute rounded-full bg-white/30 pointer-events-none"
|
|
233
|
+
style={{
|
|
234
|
+
left: ripple.x,
|
|
235
|
+
top: ripple.y,
|
|
236
|
+
}}
|
|
237
|
+
initial={{ width: 0, height: 0, x: 0, y: 0, opacity: 1 }}
|
|
238
|
+
animate={{
|
|
239
|
+
width: 200,
|
|
240
|
+
height: 200,
|
|
241
|
+
x: -100,
|
|
242
|
+
y: -100,
|
|
243
|
+
opacity: 0
|
|
244
|
+
}}
|
|
245
|
+
exit={{ opacity: 0 }}
|
|
246
|
+
transition={{ duration: 1, ease: "easeOut" }}
|
|
247
|
+
/>
|
|
248
|
+
))}
|
|
249
|
+
</AnimatePresence>
|
|
250
|
+
|
|
251
|
+
{/* Particle effects */}
|
|
252
|
+
<AnimatePresence>
|
|
253
|
+
{particles.map((particle, i) => (
|
|
254
|
+
<motion.span
|
|
255
|
+
key={particle.id}
|
|
256
|
+
className="absolute w-1 h-1 bg-primary rounded-full pointer-events-none"
|
|
257
|
+
style={{
|
|
258
|
+
left: particle.x,
|
|
259
|
+
top: particle.y,
|
|
260
|
+
}}
|
|
261
|
+
initial={{ scale: 0, opacity: 1 }}
|
|
262
|
+
animate={{
|
|
263
|
+
scale: [0, 1, 0],
|
|
264
|
+
x: (i % 2 === 0 ? 1 : -1) * (20 + Math.random() * 30),
|
|
265
|
+
y: -20 - Math.random() * 30,
|
|
266
|
+
opacity: [1, 1, 0]
|
|
267
|
+
}}
|
|
268
|
+
exit={{ opacity: 0 }}
|
|
269
|
+
transition={{ duration: 0.6, ease: "easeOut" }}
|
|
270
|
+
/>
|
|
271
|
+
))}
|
|
272
|
+
</AnimatePresence>
|
|
273
|
+
|
|
274
|
+
{/* Morph content */}
|
|
275
|
+
<motion.div
|
|
276
|
+
className="flex items-center gap-2"
|
|
277
|
+
animate={{
|
|
278
|
+
width: currentState !== "idle" ? "auto" : "100%"
|
|
279
|
+
}}
|
|
280
|
+
transition={{ duration: 0.2 }}
|
|
281
|
+
>
|
|
282
|
+
<AnimatePresence mode="wait">
|
|
283
|
+
{currentState !== "idle" && (
|
|
284
|
+
<motion.div
|
|
285
|
+
initial={{ scale: 0, opacity: 0 }}
|
|
286
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
287
|
+
exit={{ scale: 0, opacity: 0 }}
|
|
288
|
+
transition={{ duration: 0.2 }}
|
|
289
|
+
className="flex items-center"
|
|
290
|
+
>
|
|
291
|
+
{getIcon()}
|
|
292
|
+
</motion.div>
|
|
293
|
+
)}
|
|
294
|
+
</AnimatePresence>
|
|
295
|
+
|
|
296
|
+
<motion.span
|
|
297
|
+
animate={{
|
|
298
|
+
opacity: currentState === "idle" ? 1 : 0,
|
|
299
|
+
x: currentState !== "idle" ? -20 : 0
|
|
300
|
+
}}
|
|
301
|
+
transition={{ duration: 0.2 }}
|
|
302
|
+
>
|
|
303
|
+
{children}
|
|
304
|
+
</motion.span>
|
|
305
|
+
</motion.div>
|
|
306
|
+
|
|
307
|
+
{/* Background animation for success/error states */}
|
|
308
|
+
<AnimatePresence>
|
|
309
|
+
{(currentState === "success" || currentState === "error") && (
|
|
310
|
+
<motion.div
|
|
311
|
+
className={cn(
|
|
312
|
+
"absolute inset-0 rounded-md -z-10",
|
|
313
|
+
getBackgroundColor()
|
|
314
|
+
)}
|
|
315
|
+
initial={{ scale: 0, opacity: 0 }}
|
|
316
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
317
|
+
exit={{ scale: 0, opacity: 0 }}
|
|
318
|
+
transition={{ duration: 0.3 }}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</AnimatePresence>
|
|
322
|
+
|
|
323
|
+
{/* Glitch effect overlay */}
|
|
324
|
+
{enableGlitch && (
|
|
325
|
+
<motion.div
|
|
326
|
+
className="absolute inset-0 pointer-events-none"
|
|
327
|
+
animate={{
|
|
328
|
+
opacity: [0, 0.1, 0, 0.2, 0],
|
|
329
|
+
clipPath: [
|
|
330
|
+
"inset(0 0 100% 0)",
|
|
331
|
+
"inset(20% 0 60% 0)",
|
|
332
|
+
"inset(40% 0 40% 0)",
|
|
333
|
+
"inset(80% 0 10% 0)",
|
|
334
|
+
"inset(0 0 100% 0)",
|
|
335
|
+
]
|
|
336
|
+
}}
|
|
337
|
+
transition={{
|
|
338
|
+
duration: 0.5,
|
|
339
|
+
repeat: Infinity,
|
|
340
|
+
repeatDelay: 5,
|
|
341
|
+
}}
|
|
342
|
+
style={{
|
|
343
|
+
background: "linear-gradient(45deg, #ff0080, #00ff88, #0080ff)",
|
|
344
|
+
mixBlendMode: "screen"
|
|
345
|
+
}}
|
|
346
|
+
/>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
{/* Sparkle decoration for special variants */}
|
|
350
|
+
{variant === "gradient" && (
|
|
351
|
+
<Sparkles className="absolute top-1 right-1 h-3 w-3 text-white/50 animate-pulse" />
|
|
352
|
+
)}
|
|
353
|
+
</motion.button>
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
EnhancedButton.displayName = "EnhancedButton"
|
|
359
|
+
|
|
360
|
+
export { enhancedButtonVariants }
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState } from "react"
|
|
4
|
+
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
export interface EnhancedCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
enableGlassmorphism?: boolean
|
|
9
|
+
enableParallax?: boolean
|
|
10
|
+
enableTilt?: boolean
|
|
11
|
+
enableGlow?: boolean
|
|
12
|
+
enableReveal?: boolean
|
|
13
|
+
tiltMaxAngle?: number
|
|
14
|
+
glowColor?: string
|
|
15
|
+
parallaxOffset?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const EnhancedCard = React.forwardRef<HTMLDivElement, EnhancedCardProps>(
|
|
19
|
+
({
|
|
20
|
+
className,
|
|
21
|
+
children,
|
|
22
|
+
enableGlassmorphism = true,
|
|
23
|
+
enableParallax = false,
|
|
24
|
+
enableTilt = true,
|
|
25
|
+
enableGlow = false,
|
|
26
|
+
enableReveal = false,
|
|
27
|
+
tiltMaxAngle = 15,
|
|
28
|
+
glowColor = "rgba(59, 130, 246, 0.5)",
|
|
29
|
+
parallaxOffset = 20,
|
|
30
|
+
...props
|
|
31
|
+
}, ref) => {
|
|
32
|
+
const cardRef = useRef<HTMLDivElement>(null)
|
|
33
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
34
|
+
|
|
35
|
+
// Motion values for tilt effect
|
|
36
|
+
const mouseX = useMotionValue(0)
|
|
37
|
+
const mouseY = useMotionValue(0)
|
|
38
|
+
|
|
39
|
+
// Spring physics for smooth animations
|
|
40
|
+
const springConfig = { damping: 20, stiffness: 300 }
|
|
41
|
+
const mouseXSpring = useSpring(mouseX, springConfig)
|
|
42
|
+
const mouseYSpring = useSpring(mouseY, springConfig)
|
|
43
|
+
|
|
44
|
+
// Transform values for 3D tilt
|
|
45
|
+
const rotateX = useTransform(mouseYSpring, [-1, 1], [tiltMaxAngle, -tiltMaxAngle])
|
|
46
|
+
const rotateY = useTransform(mouseXSpring, [-1, 1], [-tiltMaxAngle, tiltMaxAngle])
|
|
47
|
+
|
|
48
|
+
// Glow effect position
|
|
49
|
+
const glowX = useTransform(mouseXSpring, [-1, 1], [0, 100])
|
|
50
|
+
const glowY = useTransform(mouseYSpring, [-1, 1], [0, 100])
|
|
51
|
+
|
|
52
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
53
|
+
if (!cardRef.current || (!enableTilt && !enableGlow)) return
|
|
54
|
+
|
|
55
|
+
const rect = cardRef.current.getBoundingClientRect()
|
|
56
|
+
const centerX = rect.left + rect.width / 2
|
|
57
|
+
const centerY = rect.top + rect.height / 2
|
|
58
|
+
|
|
59
|
+
const x = (e.clientX - centerX) / (rect.width / 2)
|
|
60
|
+
const y = (e.clientY - centerY) / (rect.height / 2)
|
|
61
|
+
|
|
62
|
+
mouseX.set(x)
|
|
63
|
+
mouseY.set(y)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleMouseEnter = () => {
|
|
67
|
+
setIsHovered(true)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleMouseLeave = () => {
|
|
71
|
+
setIsHovered(false)
|
|
72
|
+
mouseX.set(0)
|
|
73
|
+
mouseY.set(0)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<motion.div
|
|
78
|
+
ref={cardRef || ref}
|
|
79
|
+
className={cn(
|
|
80
|
+
"relative rounded-xl transition-all duration-300",
|
|
81
|
+
enableGlassmorphism && [
|
|
82
|
+
"backdrop-blur-md",
|
|
83
|
+
"bg-white/10 dark:bg-gray-900/10",
|
|
84
|
+
"border border-white/20 dark:border-gray-700/20",
|
|
85
|
+
"shadow-xl"
|
|
86
|
+
],
|
|
87
|
+
!enableGlassmorphism && [
|
|
88
|
+
"bg-card",
|
|
89
|
+
"border border-border",
|
|
90
|
+
"shadow-sm"
|
|
91
|
+
],
|
|
92
|
+
isHovered && "shadow-2xl",
|
|
93
|
+
className
|
|
94
|
+
)}
|
|
95
|
+
onMouseMove={handleMouseMove}
|
|
96
|
+
onMouseEnter={handleMouseEnter}
|
|
97
|
+
onMouseLeave={handleMouseLeave}
|
|
98
|
+
style={{
|
|
99
|
+
transformStyle: "preserve-3d",
|
|
100
|
+
perspective: "1000px"
|
|
101
|
+
}}
|
|
102
|
+
animate={{
|
|
103
|
+
rotateX: enableTilt ? rotateX : 0,
|
|
104
|
+
rotateY: enableTilt ? rotateY : 0,
|
|
105
|
+
}}
|
|
106
|
+
transition={{ type: "spring", ...springConfig }}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
{/* Glow effect overlay */}
|
|
110
|
+
{enableGlow && (
|
|
111
|
+
<motion.div
|
|
112
|
+
className="absolute inset-0 rounded-xl pointer-events-none opacity-0"
|
|
113
|
+
style={{
|
|
114
|
+
background: `radial-gradient(600px circle at ${glowX}% ${glowY}%, ${glowColor}, transparent 40%)`,
|
|
115
|
+
}}
|
|
116
|
+
animate={{
|
|
117
|
+
opacity: isHovered ? 1 : 0
|
|
118
|
+
}}
|
|
119
|
+
transition={{ duration: 0.3 }}
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{/* Glassmorphism noise texture */}
|
|
124
|
+
{enableGlassmorphism && (
|
|
125
|
+
<div
|
|
126
|
+
className="absolute inset-0 rounded-xl opacity-[0.03] pointer-events-none"
|
|
127
|
+
style={{
|
|
128
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' /%3E%3C/filter%3E%3Crect width='100' height='100' filter='url(%23noise)' /%3E%3C/svg%3E")`,
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{/* Content with parallax effect */}
|
|
134
|
+
<motion.div
|
|
135
|
+
className="relative z-10"
|
|
136
|
+
style={{
|
|
137
|
+
transform: enableParallax ? "translateZ(50px)" : undefined,
|
|
138
|
+
}}
|
|
139
|
+
animate={{
|
|
140
|
+
x: enableParallax && isHovered ? mouseXSpring.get() * parallaxOffset : 0,
|
|
141
|
+
y: enableParallax && isHovered ? mouseYSpring.get() * parallaxOffset : 0,
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{children}
|
|
145
|
+
</motion.div>
|
|
146
|
+
|
|
147
|
+
{/* Reveal effect border */}
|
|
148
|
+
{enableReveal && (
|
|
149
|
+
<motion.div
|
|
150
|
+
className="absolute inset-0 rounded-xl pointer-events-none"
|
|
151
|
+
style={{
|
|
152
|
+
background: `conic-gradient(from ${glowX}deg, transparent, ${glowColor}, transparent 30%)`,
|
|
153
|
+
opacity: 0,
|
|
154
|
+
filter: "blur(5px)",
|
|
155
|
+
}}
|
|
156
|
+
animate={{
|
|
157
|
+
opacity: isHovered ? 0.5 : 0,
|
|
158
|
+
rotate: isHovered ? 360 : 0,
|
|
159
|
+
}}
|
|
160
|
+
transition={{
|
|
161
|
+
opacity: { duration: 0.3 },
|
|
162
|
+
rotate: { duration: 20, repeat: Infinity, ease: "linear" }
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{/* Soft shadow layers for depth */}
|
|
168
|
+
<div className="absolute inset-0 rounded-xl -z-10">
|
|
169
|
+
<motion.div
|
|
170
|
+
className="absolute inset-0 rounded-xl bg-black/5 dark:bg-white/5"
|
|
171
|
+
animate={{
|
|
172
|
+
scale: isHovered ? 1.02 : 1,
|
|
173
|
+
opacity: isHovered ? 0.5 : 0,
|
|
174
|
+
}}
|
|
175
|
+
transition={{ duration: 0.3 }}
|
|
176
|
+
style={{ filter: "blur(10px)" }}
|
|
177
|
+
/>
|
|
178
|
+
<motion.div
|
|
179
|
+
className="absolute inset-0 rounded-xl bg-black/5 dark:bg-white/5"
|
|
180
|
+
animate={{
|
|
181
|
+
scale: isHovered ? 1.05 : 1,
|
|
182
|
+
opacity: isHovered ? 0.3 : 0,
|
|
183
|
+
}}
|
|
184
|
+
transition={{ duration: 0.3 }}
|
|
185
|
+
style={{ filter: "blur(20px)" }}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
</motion.div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
EnhancedCard.displayName = "EnhancedCard"
|
|
194
|
+
|
|
195
|
+
// Enhanced Card sub-components with animations
|
|
196
|
+
export const EnhancedCardHeader = React.forwardRef<
|
|
197
|
+
HTMLDivElement,
|
|
198
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
199
|
+
>(({ className, ...props }, ref) => (
|
|
200
|
+
<motion.div
|
|
201
|
+
ref={ref}
|
|
202
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
203
|
+
initial={{ opacity: 0, y: -10 }}
|
|
204
|
+
animate={{ opacity: 1, y: 0 }}
|
|
205
|
+
transition={{ duration: 0.3, delay: 0.1 }}
|
|
206
|
+
{...props}
|
|
207
|
+
/>
|
|
208
|
+
))
|
|
209
|
+
EnhancedCardHeader.displayName = "EnhancedCardHeader"
|
|
210
|
+
|
|
211
|
+
export const EnhancedCardTitle = React.forwardRef<
|
|
212
|
+
HTMLParagraphElement,
|
|
213
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
214
|
+
>(({ className, ...props }, ref) => (
|
|
215
|
+
<motion.h3
|
|
216
|
+
ref={ref}
|
|
217
|
+
className={cn(
|
|
218
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
219
|
+
className
|
|
220
|
+
)}
|
|
221
|
+
initial={{ opacity: 0, x: -10 }}
|
|
222
|
+
animate={{ opacity: 1, x: 0 }}
|
|
223
|
+
transition={{ duration: 0.3, delay: 0.2 }}
|
|
224
|
+
{...props}
|
|
225
|
+
/>
|
|
226
|
+
))
|
|
227
|
+
EnhancedCardTitle.displayName = "EnhancedCardTitle"
|
|
228
|
+
|
|
229
|
+
export const EnhancedCardDescription = React.forwardRef<
|
|
230
|
+
HTMLParagraphElement,
|
|
231
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
232
|
+
>(({ className, ...props }, ref) => (
|
|
233
|
+
<motion.p
|
|
234
|
+
ref={ref}
|
|
235
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
236
|
+
initial={{ opacity: 0 }}
|
|
237
|
+
animate={{ opacity: 1 }}
|
|
238
|
+
transition={{ duration: 0.3, delay: 0.3 }}
|
|
239
|
+
{...props}
|
|
240
|
+
/>
|
|
241
|
+
))
|
|
242
|
+
EnhancedCardDescription.displayName = "EnhancedCardDescription"
|
|
243
|
+
|
|
244
|
+
export const EnhancedCardContent = React.forwardRef<
|
|
245
|
+
HTMLDivElement,
|
|
246
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
247
|
+
>(({ className, ...props }, ref) => (
|
|
248
|
+
<motion.div
|
|
249
|
+
ref={ref}
|
|
250
|
+
className={cn("p-6 pt-0", className)}
|
|
251
|
+
initial={{ opacity: 0 }}
|
|
252
|
+
animate={{ opacity: 1 }}
|
|
253
|
+
transition={{ duration: 0.3, delay: 0.4 }}
|
|
254
|
+
{...props}
|
|
255
|
+
/>
|
|
256
|
+
))
|
|
257
|
+
EnhancedCardContent.displayName = "EnhancedCardContent"
|
|
258
|
+
|
|
259
|
+
export const EnhancedCardFooter = React.forwardRef<
|
|
260
|
+
HTMLDivElement,
|
|
261
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
262
|
+
>(({ className, ...props }, ref) => (
|
|
263
|
+
<motion.div
|
|
264
|
+
ref={ref}
|
|
265
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
266
|
+
initial={{ opacity: 0, y: 10 }}
|
|
267
|
+
animate={{ opacity: 1, y: 0 }}
|
|
268
|
+
transition={{ duration: 0.3, delay: 0.5 }}
|
|
269
|
+
{...props}
|
|
270
|
+
/>
|
|
271
|
+
))
|
|
272
|
+
EnhancedCardFooter.displayName = "EnhancedCardFooter"
|