@moontra/moonui-pro 2.0.22 → 2.1.0

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.
Files changed (99) hide show
  1. package/dist/index.mjs +215 -214
  2. package/package.json +4 -2
  3. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  4. package/src/__tests__/use-local-storage.test.tsx +174 -0
  5. package/src/__tests__/use-pro-access.test.tsx +183 -0
  6. package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
  7. package/src/components/advanced-chart/index.tsx +412 -0
  8. package/src/components/advanced-forms/index.tsx +431 -0
  9. package/src/components/animated-button/index.tsx +202 -0
  10. package/src/components/calendar/event-dialog.tsx +372 -0
  11. package/src/components/calendar/index.tsx +557 -0
  12. package/src/components/color-picker/index.tsx +434 -0
  13. package/src/components/dashboard/index.tsx +334 -0
  14. package/src/components/data-table/data-table.test.tsx +187 -0
  15. package/src/components/data-table/index.tsx +368 -0
  16. package/src/components/draggable-list/index.tsx +100 -0
  17. package/src/components/enhanced/button.tsx +360 -0
  18. package/src/components/enhanced/card.tsx +272 -0
  19. package/src/components/enhanced/dialog.tsx +248 -0
  20. package/src/components/enhanced/index.ts +3 -0
  21. package/src/components/error-boundary/index.tsx +111 -0
  22. package/src/components/file-upload/file-upload.test.tsx +242 -0
  23. package/src/components/file-upload/index.tsx +362 -0
  24. package/src/components/floating-action-button/index.tsx +209 -0
  25. package/src/components/github-stars/index.tsx +414 -0
  26. package/src/components/health-check/index.tsx +441 -0
  27. package/src/components/hover-card-3d/index.tsx +170 -0
  28. package/src/components/index.ts +76 -0
  29. package/src/components/kanban/index.tsx +436 -0
  30. package/src/components/lazy-component/index.tsx +342 -0
  31. package/src/components/magnetic-button/index.tsx +170 -0
  32. package/src/components/memory-efficient-data/index.tsx +352 -0
  33. package/src/components/optimized-image/index.tsx +427 -0
  34. package/src/components/performance-debugger/index.tsx +591 -0
  35. package/src/components/performance-monitor/index.tsx +775 -0
  36. package/src/components/pinch-zoom/index.tsx +172 -0
  37. package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
  38. package/src/components/rich-text-editor/index.tsx +1537 -0
  39. package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
  40. package/src/components/rich-text-editor/slash-commands.css +35 -0
  41. package/src/components/rich-text-editor/table-styles.css +65 -0
  42. package/src/components/spotlight-card/index.tsx +194 -0
  43. package/src/components/swipeable-card/index.tsx +100 -0
  44. package/src/components/timeline/index.tsx +333 -0
  45. package/src/components/ui/animated-button.tsx +185 -0
  46. package/src/components/ui/avatar.tsx +135 -0
  47. package/src/components/ui/badge.tsx +225 -0
  48. package/src/components/ui/button.tsx +221 -0
  49. package/src/components/ui/card.tsx +141 -0
  50. package/src/components/ui/checkbox.tsx +256 -0
  51. package/src/components/ui/color-picker.tsx +95 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/hover-card-3d.tsx +103 -0
  55. package/src/components/ui/index.ts +33 -0
  56. package/src/components/ui/input.tsx +219 -0
  57. package/src/components/ui/label.tsx +26 -0
  58. package/src/components/ui/magnetic-button.tsx +129 -0
  59. package/src/components/ui/popover.tsx +183 -0
  60. package/src/components/ui/select.tsx +273 -0
  61. package/src/components/ui/separator.tsx +140 -0
  62. package/src/components/ui/slider.tsx +351 -0
  63. package/src/components/ui/spotlight-card.tsx +119 -0
  64. package/src/components/ui/switch.tsx +83 -0
  65. package/src/components/ui/tabs.tsx +195 -0
  66. package/src/components/ui/textarea.tsx +25 -0
  67. package/src/components/ui/toast.tsx +313 -0
  68. package/src/components/ui/tooltip.tsx +152 -0
  69. package/src/components/virtual-list/index.tsx +369 -0
  70. package/src/hooks/use-chart.ts +205 -0
  71. package/src/hooks/use-data-table.ts +182 -0
  72. package/src/hooks/use-docs-pro-access.ts +13 -0
  73. package/src/hooks/use-license-check.ts +65 -0
  74. package/src/hooks/use-subscription.ts +19 -0
  75. package/src/index.ts +14 -0
  76. package/src/lib/micro-interactions.ts +255 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/patterns/login-form/index.tsx +276 -0
  79. package/src/patterns/login-form/types.ts +67 -0
  80. package/src/setupTests.ts +41 -0
  81. package/src/styles/design-system.css +365 -0
  82. package/src/styles/index.css +4 -0
  83. package/src/styles/tailwind.css +6 -0
  84. package/src/styles/tokens.css +453 -0
  85. package/src/types/moonui.d.ts +22 -0
  86. package/src/use-intersection-observer.tsx +154 -0
  87. package/src/use-local-storage.tsx +71 -0
  88. package/src/use-paddle.ts +138 -0
  89. package/src/use-performance-optimizer.ts +379 -0
  90. package/src/use-pro-access.ts +141 -0
  91. package/src/use-scroll-animation.ts +221 -0
  92. package/src/use-subscription.ts +37 -0
  93. package/src/use-toast.ts +32 -0
  94. package/src/utils/chart-helpers.ts +257 -0
  95. package/src/utils/cn.ts +69 -0
  96. package/src/utils/data-processing.ts +151 -0
  97. package/src/utils/license-guard.tsx +177 -0
  98. package/src/utils/license-validator.tsx +183 -0
  99. package/src/utils/package-guard.ts +60 -0
@@ -0,0 +1,441 @@
1
+ "use client"
2
+
3
+ import React, { useState, useEffect, useCallback } from "react"
4
+ import { motion, AnimatePresence } from "framer-motion"
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
6
+ import { Button } from "../ui/button"
7
+ import { Badge } from "../ui/badge"
8
+ import { Progress } from "../ui/progress"
9
+ import { cn } from "../../lib/utils"
10
+ import {
11
+ Activity,
12
+ CheckCircle,
13
+ XCircle,
14
+ AlertCircle,
15
+ Clock,
16
+ Zap,
17
+ Globe,
18
+ Database,
19
+ Server,
20
+ Lock,
21
+ Sparkles,
22
+ RefreshCw,
23
+ TrendingUp,
24
+ Timer
25
+ } from "lucide-react"
26
+ import { useSubscription } from "../../hooks/use-subscription"
27
+
28
+ export interface HealthCheckEndpoint {
29
+ id: string
30
+ name: string
31
+ url: string
32
+ method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD"
33
+ expectedStatus?: number[]
34
+ timeout?: number
35
+ headers?: Record<string, string>
36
+ body?: string
37
+ critical?: boolean
38
+ }
39
+
40
+ export interface HealthCheckResult {
41
+ id: string
42
+ name: string
43
+ status: "healthy" | "unhealthy" | "warning" | "pending"
44
+ responseTime: number
45
+ statusCode?: number
46
+ error?: string
47
+ timestamp: Date
48
+ uptime?: number
49
+ }
50
+
51
+ export interface HealthCheckProps {
52
+ endpoints: HealthCheckEndpoint[]
53
+ interval?: number
54
+ autoRefresh?: boolean
55
+ showResponseTime?: boolean
56
+ showUptime?: boolean
57
+ showHistory?: boolean
58
+ maxHistory?: number
59
+ onStatusChange?: (results: HealthCheckResult[]) => void
60
+ className?: string
61
+ }
62
+
63
+ const HealthCheckInternal: React.FC<HealthCheckProps> = ({
64
+ endpoints,
65
+ interval = 30000, // 30 seconds
66
+ autoRefresh = true,
67
+ showResponseTime = true,
68
+ showUptime = true,
69
+ showHistory = true,
70
+ maxHistory = 20,
71
+ onStatusChange,
72
+ className
73
+ }) => {
74
+ const [results, setResults] = useState<HealthCheckResult[]>([])
75
+ const [history, setHistory] = useState<Record<string, HealthCheckResult[]>>({})
76
+ const [isChecking, setIsChecking] = useState(false)
77
+ const [lastCheck, setLastCheck] = useState<Date | null>(null)
78
+
79
+ const checkEndpoint = async (endpoint: HealthCheckEndpoint): Promise<HealthCheckResult> => {
80
+ const startTime = Date.now()
81
+
82
+ try {
83
+ const controller = new AbortController()
84
+ const timeoutId = setTimeout(() => controller.abort(), endpoint.timeout || 5000)
85
+
86
+ const response = await fetch(endpoint.url, {
87
+ method: endpoint.method,
88
+ headers: {
89
+ 'Content-Type': 'application/json',
90
+ ...endpoint.headers
91
+ },
92
+ body: endpoint.body,
93
+ signal: controller.signal
94
+ })
95
+
96
+ clearTimeout(timeoutId)
97
+ const responseTime = Date.now() - startTime
98
+ const expectedStatuses = endpoint.expectedStatus || [200, 201, 204]
99
+
100
+ const isHealthy = expectedStatuses.includes(response.status)
101
+
102
+ return {
103
+ id: endpoint.id,
104
+ name: endpoint.name,
105
+ status: isHealthy ? "healthy" : "unhealthy",
106
+ responseTime,
107
+ statusCode: response.status,
108
+ timestamp: new Date()
109
+ }
110
+ } catch (error) {
111
+ const responseTime = Date.now() - startTime
112
+
113
+ return {
114
+ id: endpoint.id,
115
+ name: endpoint.name,
116
+ status: "unhealthy",
117
+ responseTime,
118
+ error: error instanceof Error ? error.message : "Unknown error",
119
+ timestamp: new Date()
120
+ }
121
+ }
122
+ }
123
+
124
+ const checkAllEndpoints = useCallback(async () => {
125
+ setIsChecking(true)
126
+
127
+ try {
128
+ const checks = endpoints.map(endpoint => checkEndpoint(endpoint))
129
+ const newResults = await Promise.all(checks)
130
+
131
+ setResults(newResults)
132
+ setLastCheck(new Date())
133
+
134
+ // Update history
135
+ setHistory(prevHistory => {
136
+ const newHistory = { ...prevHistory }
137
+
138
+ newResults.forEach(result => {
139
+ if (!newHistory[result.id]) {
140
+ newHistory[result.id] = []
141
+ }
142
+
143
+ newHistory[result.id] = [
144
+ result,
145
+ ...newHistory[result.id].slice(0, maxHistory - 1)
146
+ ]
147
+ })
148
+
149
+ return newHistory
150
+ })
151
+
152
+ onStatusChange?.(newResults)
153
+ } catch (error) {
154
+ console.error("Health check failed:", error)
155
+ } finally {
156
+ setIsChecking(false)
157
+ }
158
+ }, [endpoints, maxHistory, onStatusChange])
159
+
160
+ // Initial check
161
+ useEffect(() => {
162
+ checkAllEndpoints()
163
+ }, [checkAllEndpoints])
164
+
165
+ // Auto refresh
166
+ useEffect(() => {
167
+ if (!autoRefresh) return
168
+
169
+ const intervalId = setInterval(checkAllEndpoints, interval)
170
+ return () => clearInterval(intervalId)
171
+ }, [autoRefresh, interval, checkAllEndpoints])
172
+
173
+ const getOverallStatus = (): "healthy" | "unhealthy" | "warning" => {
174
+ if (results.length === 0) return "warning"
175
+
176
+ const criticalEndpoints = endpoints.filter(e => e.critical !== false)
177
+ const criticalResults = results.filter(r =>
178
+ criticalEndpoints.some(e => e.id === r.id)
179
+ )
180
+
181
+ const hasUnhealthy = criticalResults.some(r => r.status === "unhealthy")
182
+ const hasWarning = results.some(r => r.status === "warning")
183
+
184
+ if (hasUnhealthy) return "unhealthy"
185
+ if (hasWarning) return "warning"
186
+ return "healthy"
187
+ }
188
+
189
+ const getStatusIcon = (status: string) => {
190
+ switch (status) {
191
+ case "healthy":
192
+ return <CheckCircle className="h-4 w-4 text-green-500" />
193
+ case "unhealthy":
194
+ return <XCircle className="h-4 w-4 text-red-500" />
195
+ case "warning":
196
+ return <AlertCircle className="h-4 w-4 text-yellow-500" />
197
+ default:
198
+ return <Clock className="h-4 w-4 text-gray-500" />
199
+ }
200
+ }
201
+
202
+ const getStatusColor = (status: string) => {
203
+ switch (status) {
204
+ case "healthy":
205
+ return "bg-green-500"
206
+ case "unhealthy":
207
+ return "bg-red-500"
208
+ case "warning":
209
+ return "bg-yellow-500"
210
+ default:
211
+ return "bg-gray-500"
212
+ }
213
+ }
214
+
215
+ const formatResponseTime = (time: number) => {
216
+ if (time < 1000) return `${time}ms`
217
+ return `${(time / 1000).toFixed(2)}s`
218
+ }
219
+
220
+ const calculateUptime = (endpointId: string): number => {
221
+ const endpointHistory = history[endpointId] || []
222
+ if (endpointHistory.length === 0) return 0
223
+
224
+ const healthyCount = endpointHistory.filter(r => r.status === "healthy").length
225
+ return (healthyCount / endpointHistory.length) * 100
226
+ }
227
+
228
+ const overallStatus = getOverallStatus()
229
+ const healthyCount = results.filter(r => r.status === "healthy").length
230
+ const overallUptime = results.length > 0
231
+ ? results.reduce((acc, result) => acc + calculateUptime(result.id), 0) / results.length
232
+ : 0
233
+
234
+ return (
235
+ <Card className={cn("w-full", className)}>
236
+ <CardHeader>
237
+ <div className="flex items-center justify-between">
238
+ <div className="flex items-center gap-3">
239
+ <div className="flex items-center gap-2">
240
+ <Activity className="h-6 w-6" />
241
+ <div>
242
+ <CardTitle className="flex items-center gap-2">
243
+ System Health
244
+ <Badge
245
+ variant={overallStatus === "healthy" ? "default" : "destructive"}
246
+ className={cn(
247
+ "text-white",
248
+ overallStatus === "healthy" && "bg-green-500",
249
+ overallStatus === "warning" && "bg-yellow-500",
250
+ overallStatus === "unhealthy" && "bg-red-500"
251
+ )}
252
+ >
253
+ {overallStatus.toUpperCase()}
254
+ </Badge>
255
+ </CardTitle>
256
+ {lastCheck && (
257
+ <CardDescription>
258
+ Last checked: {lastCheck.toLocaleTimeString()}
259
+ </CardDescription>
260
+ )}
261
+ </div>
262
+ </div>
263
+ </div>
264
+
265
+ <div className="flex items-center gap-2">
266
+ {showUptime && (
267
+ <div className="text-right">
268
+ <div className="text-sm font-medium">{overallUptime.toFixed(1)}%</div>
269
+ <div className="text-xs text-muted-foreground">Uptime</div>
270
+ </div>
271
+ )}
272
+
273
+ <Button
274
+ onClick={checkAllEndpoints}
275
+ disabled={isChecking}
276
+ variant="outline"
277
+ size="sm"
278
+ >
279
+ <RefreshCw className={cn("h-4 w-4", isChecking && "animate-spin")} />
280
+ </Button>
281
+ </div>
282
+ </div>
283
+
284
+ {/* Overall Stats */}
285
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
286
+ <div className="text-center">
287
+ <div className="text-2xl font-bold text-green-500">{healthyCount}</div>
288
+ <div className="text-xs text-muted-foreground">Healthy</div>
289
+ </div>
290
+ <div className="text-center">
291
+ <div className="text-2xl font-bold text-red-500">
292
+ {results.filter(r => r.status === "unhealthy").length}
293
+ </div>
294
+ <div className="text-xs text-muted-foreground">Unhealthy</div>
295
+ </div>
296
+ {showResponseTime && (
297
+ <div className="text-center">
298
+ <div className="text-lg font-bold">
299
+ {results.length > 0
300
+ ? formatResponseTime(
301
+ results.reduce((acc, r) => acc + r.responseTime, 0) / results.length
302
+ )
303
+ : "0ms"
304
+ }
305
+ </div>
306
+ <div className="text-xs text-muted-foreground">Avg Response</div>
307
+ </div>
308
+ )}
309
+ <div className="text-center">
310
+ <div className="text-lg font-bold">{results.length}</div>
311
+ <div className="text-xs text-muted-foreground">Services</div>
312
+ </div>
313
+ </div>
314
+ </CardHeader>
315
+
316
+ <CardContent>
317
+ <div className="space-y-4">
318
+ <AnimatePresence>
319
+ {results.map((result, index) => (
320
+ <motion.div
321
+ key={result.id}
322
+ initial={{ opacity: 0, x: -20 }}
323
+ animate={{ opacity: 1, x: 0 }}
324
+ exit={{ opacity: 0, x: 20 }}
325
+ transition={{ delay: index * 0.05 }}
326
+ >
327
+ <Card>
328
+ <CardContent className="p-4">
329
+ <div className="flex items-center justify-between">
330
+ <div className="flex items-center gap-3">
331
+ {getStatusIcon(result.status)}
332
+ <div>
333
+ <div className="font-medium">{result.name}</div>
334
+ {result.error && (
335
+ <div className="text-sm text-red-500">{result.error}</div>
336
+ )}
337
+ {result.statusCode && (
338
+ <div className="text-xs text-muted-foreground">
339
+ HTTP {result.statusCode}
340
+ </div>
341
+ )}
342
+ </div>
343
+ </div>
344
+
345
+ <div className="flex items-center gap-4 text-sm">
346
+ {showResponseTime && (
347
+ <div className="text-right">
348
+ <div className="font-medium">
349
+ {formatResponseTime(result.responseTime)}
350
+ </div>
351
+ <div className="text-xs text-muted-foreground">Response</div>
352
+ </div>
353
+ )}
354
+
355
+ {showUptime && (
356
+ <div className="text-right">
357
+ <div className="font-medium">
358
+ {calculateUptime(result.id).toFixed(1)}%
359
+ </div>
360
+ <div className="text-xs text-muted-foreground">Uptime</div>
361
+ </div>
362
+ )}
363
+ </div>
364
+ </div>
365
+
366
+ {/* Mini history chart */}
367
+ {showHistory && history[result.id] && (
368
+ <div className="mt-3">
369
+ <div className="flex items-center gap-1 h-2">
370
+ {history[result.id].slice(0, 20).reverse().map((historyResult, i) => (
371
+ <div
372
+ key={i}
373
+ className={cn(
374
+ "flex-1 h-full rounded-sm",
375
+ getStatusColor(historyResult.status)
376
+ )}
377
+ />
378
+ ))}
379
+ </div>
380
+ <div className="text-xs text-muted-foreground mt-1">
381
+ Last {Math.min(history[result.id].length, 20)} checks
382
+ </div>
383
+ </div>
384
+ )}
385
+ </CardContent>
386
+ </Card>
387
+ </motion.div>
388
+ ))}
389
+ </AnimatePresence>
390
+
391
+ {results.length === 0 && !isChecking && (
392
+ <div className="text-center py-8">
393
+ <Server className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
394
+ <p className="text-muted-foreground">No endpoints configured</p>
395
+ </div>
396
+ )}
397
+ </div>
398
+ </CardContent>
399
+ </Card>
400
+ )
401
+ }
402
+
403
+ export const HealthCheck: React.FC<HealthCheckProps> = ({ className, ...props }) => {
404
+ // Check if we're in docs mode or have pro access
405
+ const docsProAccess = { hasAccess: true } // Pro access assumed in package
406
+ const { hasProAccess, isLoading } = useSubscription()
407
+
408
+ // In docs mode, always show the component
409
+ const canShowComponent = docsProAccess.isDocsMode || hasProAccess
410
+
411
+ // If not in docs mode and no pro access, show upgrade prompt
412
+ if (!docsProAccess.isDocsMode && !isLoading && !hasProAccess) {
413
+ return (
414
+ <Card className={cn("w-fit", className)}>
415
+ <CardContent className="py-6 text-center">
416
+ <div className="space-y-4">
417
+ <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
418
+ <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
419
+ </div>
420
+ <div>
421
+ <h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
422
+ <p className="text-muted-foreground text-xs mb-4">
423
+ Health Check is available exclusively to MoonUI Pro subscribers.
424
+ </p>
425
+ <a href="/pricing">
426
+ <Button size="sm">
427
+ <Sparkles className="mr-2 h-4 w-4" />
428
+ Upgrade to Pro
429
+ </Button>
430
+ </a>
431
+ </div>
432
+ </div>
433
+ </CardContent>
434
+ </Card>
435
+ )
436
+ }
437
+
438
+ return <HealthCheckInternal className={className} {...props} />
439
+ }
440
+
441
+ export type { HealthCheckEndpoint, HealthCheckResult, HealthCheckProps }
@@ -0,0 +1,170 @@
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
+ import { Card, CardContent } from "../ui/card"
7
+ import { Button } from "../ui/button"
8
+ import { Lock, Sparkles } from "lucide-react"
9
+ import { useSubscription } from "../../hooks/use-subscription"
10
+
11
+ export interface HoverCard3DProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ perspective?: number
13
+ rotationIntensity?: number
14
+ springConfig?: {
15
+ stiffness?: number
16
+ damping?: number
17
+ }
18
+ }
19
+
20
+ const HoverCard3DInternal = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
21
+ ({
22
+ children,
23
+ className,
24
+ perspective = 1000,
25
+ rotationIntensity = 15,
26
+ springConfig = { stiffness: 200, damping: 15 },
27
+ ...props
28
+ }, ref) => {
29
+ const cardRef = useRef<HTMLDivElement>(null)
30
+ const [isHovered, setIsHovered] = useState(false)
31
+
32
+ const x = useMotionValue(0)
33
+ const y = useMotionValue(0)
34
+
35
+ const springX = useSpring(x, springConfig)
36
+ const springY = useSpring(y, springConfig)
37
+
38
+ const rotateX = useTransform(springY, [-0.5, 0.5], [rotationIntensity, -rotationIntensity])
39
+ const rotateY = useTransform(springX, [-0.5, 0.5], [-rotationIntensity, rotationIntensity])
40
+
41
+ const handleMouseMove = (e: React.MouseEvent) => {
42
+ if (!cardRef.current) return
43
+
44
+ const rect = cardRef.current.getBoundingClientRect()
45
+ const centerX = rect.left + rect.width / 2
46
+ const centerY = rect.top + rect.height / 2
47
+
48
+ const rotateXValue = (e.clientY - centerY) / (rect.height / 2)
49
+ const rotateYValue = (e.clientX - centerX) / (rect.width / 2)
50
+
51
+ x.set(rotateYValue)
52
+ y.set(rotateXValue)
53
+ }
54
+
55
+ const handleMouseEnter = () => {
56
+ setIsHovered(true)
57
+ }
58
+
59
+ const handleMouseLeave = () => {
60
+ setIsHovered(false)
61
+ x.set(0)
62
+ y.set(0)
63
+ }
64
+
65
+ return (
66
+ <motion.div
67
+ ref={(node) => {
68
+ cardRef.current = node
69
+ if (typeof ref === "function") {
70
+ ref(node)
71
+ } else if (ref) {
72
+ ref.current = node
73
+ }
74
+ }}
75
+ className={cn(
76
+ "relative rounded-lg border bg-card text-card-foreground shadow-sm transition-colors duration-200 hover:bg-card/90",
77
+ className
78
+ )}
79
+ onMouseMove={handleMouseMove}
80
+ onMouseEnter={handleMouseEnter}
81
+ onMouseLeave={handleMouseLeave}
82
+ style={{
83
+ transformPerspective: perspective,
84
+ transformStyle: "preserve-3d",
85
+ }}
86
+ animate={{
87
+ rotateX: isHovered ? rotateX : 0,
88
+ rotateY: isHovered ? rotateY : 0,
89
+ scale: isHovered ? 1.05 : 1,
90
+ }}
91
+ transition={{ duration: 0.15 }}
92
+ {...props}
93
+ >
94
+ <motion.div
95
+ className="relative z-10"
96
+ animate={{
97
+ z: isHovered ? 20 : 0,
98
+ }}
99
+ transition={{ duration: 0.2 }}
100
+ >
101
+ {children}
102
+ </motion.div>
103
+
104
+ {/* Gradient overlay */}
105
+ <motion.div
106
+ className="absolute inset-0 rounded-lg bg-gradient-to-br from-white/5 via-transparent to-black/5 pointer-events-none"
107
+ animate={{
108
+ opacity: isHovered ? 1 : 0,
109
+ }}
110
+ transition={{ duration: 0.3 }}
111
+ />
112
+
113
+ {/* Glow effect */}
114
+ <motion.div
115
+ className="absolute inset-0 rounded-lg bg-primary/5 blur-xl"
116
+ animate={{
117
+ opacity: isHovered ? 0.8 : 0,
118
+ scale: isHovered ? 1.2 : 1,
119
+ }}
120
+ transition={{ duration: 0.3 }}
121
+ style={{ zIndex: -1 }}
122
+ />
123
+ </motion.div>
124
+ )
125
+ }
126
+ )
127
+
128
+ HoverCard3DInternal.displayName = "HoverCard3DInternal"
129
+
130
+ export const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
131
+ ({ className, ...props }, ref) => {
132
+ // Check if we're in docs mode or have pro access
133
+ const docsProAccess = { hasAccess: true } // Pro access assumed in package
134
+ const { hasProAccess, isLoading } = useSubscription()
135
+
136
+ // In docs mode, always show the component
137
+ const canShowComponent = docsProAccess.isDocsMode || hasProAccess
138
+
139
+ // If not in docs mode and no pro access, show upgrade prompt
140
+ if (!docsProAccess.isDocsMode && !isLoading && !hasProAccess) {
141
+ return (
142
+ <Card className={cn("w-fit", className)}>
143
+ <CardContent className="py-6 text-center">
144
+ <div className="space-y-4">
145
+ <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
146
+ <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
147
+ </div>
148
+ <div>
149
+ <h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
150
+ <p className="text-muted-foreground text-xs mb-4">
151
+ 3D Hover Card is available exclusively to MoonUI Pro subscribers.
152
+ </p>
153
+ <a href="/pricing">
154
+ <Button size="sm">
155
+ <Sparkles className="mr-2 h-4 w-4" />
156
+ Upgrade to Pro
157
+ </Button>
158
+ </a>
159
+ </div>
160
+ </div>
161
+ </CardContent>
162
+ </Card>
163
+ )
164
+ }
165
+
166
+ return <HoverCard3DInternal className={className} ref={ref} {...props} />
167
+ }
168
+ )
169
+
170
+ HoverCard3D.displayName = "HoverCard3D"
@@ -0,0 +1,76 @@
1
+ // Pro Components Export - Source of Truth: packages/moonui-pro
2
+
3
+ // Animated Button
4
+ export * from "./animated-button"
5
+
6
+ // Error Boundary
7
+ export * from "./error-boundary"
8
+
9
+ // Floating Action Button
10
+ export * from "./floating-action-button"
11
+
12
+ // Hover Card 3D
13
+ export * from "./hover-card-3d"
14
+
15
+ // Magnetic Button
16
+ export * from "./magnetic-button"
17
+
18
+ // Pinch Zoom
19
+ export * from "./pinch-zoom"
20
+
21
+ // Spotlight Card
22
+ export * from "./spotlight-card"
23
+
24
+ // Calendar
25
+ export * from "./calendar"
26
+
27
+ // Kanban
28
+ export * from "./kanban"
29
+
30
+ // Rich Text Editor
31
+ export * from "./rich-text-editor"
32
+
33
+ // Memory Efficient Data
34
+ export * from "./memory-efficient-data"
35
+
36
+ // Virtual List
37
+ export * from "./virtual-list"
38
+
39
+ // Swipeable Card
40
+ export * from "./swipeable-card"
41
+
42
+ // Timeline
43
+ export * from "./timeline"
44
+
45
+ // Advanced Chart
46
+ export * from "./advanced-chart"
47
+
48
+ // Dashboard
49
+ export * from "./dashboard"
50
+
51
+ // Draggable List
52
+ export * from "./draggable-list"
53
+
54
+ // Advanced Forms
55
+ export * from "./advanced-forms"
56
+
57
+ // Color Picker
58
+ export * from "./color-picker"
59
+
60
+ // GitHub Stars
61
+ export * from "./github-stars"
62
+
63
+ // Health Check
64
+ export * from "./health-check"
65
+
66
+ // Lazy Component
67
+ export * from "./lazy-component"
68
+
69
+ // Optimized Image
70
+ export * from "./optimized-image"
71
+
72
+ // Performance Debugger
73
+ export * from "./performance-debugger"
74
+
75
+ // Performance Monitor
76
+ export * from "./performance-monitor"