@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,342 @@
1
+ "use client"
2
+
3
+ import React, { useState, useEffect, useRef, Suspense } from "react"
4
+ import { motion } from "framer-motion"
5
+ import { Card, CardContent } from "../ui/card"
6
+ import { Button } from "../ui/button"
7
+ import { Skeleton } from "@moontra/moonui"
8
+ import { cn } from "../../lib/utils"
9
+ import { Eye, Loader2, Lock, Sparkles, RefreshCw } from "lucide-react"
10
+ import { useSubscription } from "../../hooks/use-subscription"
11
+
12
+ export interface LazyComponentProps {
13
+ children: React.ReactNode
14
+ fallback?: React.ReactNode
15
+ threshold?: number
16
+ rootMargin?: string
17
+ triggerOnce?: boolean
18
+ disabled?: boolean
19
+ onLoad?: () => void
20
+ onVisible?: () => void
21
+ showLoadingState?: boolean
22
+ delay?: number
23
+ className?: string
24
+ }
25
+
26
+ const LazyComponentInternal: React.FC<LazyComponentProps> = ({
27
+ children,
28
+ fallback,
29
+ threshold = 0.1,
30
+ rootMargin = "50px",
31
+ triggerOnce = true,
32
+ disabled = false,
33
+ onLoad,
34
+ onVisible,
35
+ showLoadingState = true,
36
+ delay = 0,
37
+ className
38
+ }) => {
39
+ const [isVisible, setIsVisible] = useState(disabled)
40
+ const [isLoaded, setIsLoaded] = useState(disabled)
41
+ const [hasTriggered, setHasTriggered] = useState(false)
42
+ const containerRef = useRef<HTMLDivElement>(null)
43
+
44
+ useEffect(() => {
45
+ if (disabled) {
46
+ setIsVisible(true)
47
+ setIsLoaded(true)
48
+ return
49
+ }
50
+
51
+ const observer = new IntersectionObserver(
52
+ ([entry]) => {
53
+ if (entry.isIntersecting && (!triggerOnce || !hasTriggered)) {
54
+ if (delay > 0) {
55
+ setTimeout(() => {
56
+ setIsVisible(true)
57
+ setHasTriggered(true)
58
+ onVisible?.()
59
+
60
+ // Simulate loading delay
61
+ setTimeout(() => {
62
+ setIsLoaded(true)
63
+ onLoad?.()
64
+ }, delay)
65
+ }, 100)
66
+ } else {
67
+ setIsVisible(true)
68
+ setIsLoaded(true)
69
+ setHasTriggered(true)
70
+ onVisible?.()
71
+ onLoad?.()
72
+ }
73
+ } else if (!entry.isIntersecting && !triggerOnce) {
74
+ setIsVisible(false)
75
+ setIsLoaded(false)
76
+ }
77
+ },
78
+ {
79
+ threshold,
80
+ rootMargin
81
+ }
82
+ )
83
+
84
+ if (containerRef.current) {
85
+ observer.observe(containerRef.current)
86
+ }
87
+
88
+ return () => {
89
+ if (containerRef.current) {
90
+ observer.unobserve(containerRef.current)
91
+ }
92
+ }
93
+ }, [threshold, rootMargin, triggerOnce, delay, disabled, hasTriggered, onLoad, onVisible])
94
+
95
+ const renderFallback = () => {
96
+ if (fallback) {
97
+ return fallback
98
+ }
99
+
100
+ if (showLoadingState) {
101
+ return (
102
+ <motion.div
103
+ initial={{ opacity: 0 }}
104
+ animate={{ opacity: 1 }}
105
+ className="flex items-center justify-center p-8"
106
+ >
107
+ <div className="text-center space-y-2">
108
+ {delay > 0 && isVisible && !isLoaded ? (
109
+ <Loader2 className="h-6 w-6 animate-spin mx-auto text-muted-foreground" />
110
+ ) : (
111
+ <Eye className="h-6 w-6 mx-auto text-muted-foreground" />
112
+ )}
113
+ <p className="text-sm text-muted-foreground">
114
+ {isVisible && !isLoaded ? "Loading..." : "Scroll to load content"}
115
+ </p>
116
+ </div>
117
+ </motion.div>
118
+ )
119
+ }
120
+
121
+ return <Skeleton className="w-full h-32" />
122
+ }
123
+
124
+ return (
125
+ <div ref={containerRef} className={cn("w-full", className)}>
126
+ {isLoaded ? (
127
+ <motion.div
128
+ initial={{ opacity: 0, y: 20 }}
129
+ animate={{ opacity: 1, y: 0 }}
130
+ transition={{ duration: 0.3 }}
131
+ >
132
+ {children}
133
+ </motion.div>
134
+ ) : (
135
+ renderFallback()
136
+ )}
137
+ </div>
138
+ )
139
+ }
140
+
141
+ // Lazy Image Component
142
+ export interface LazyImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
143
+ src: string
144
+ alt: string
145
+ fallbackSrc?: string
146
+ showPlaceholder?: boolean
147
+ threshold?: number
148
+ rootMargin?: string
149
+ onLoad?: () => void
150
+ onError?: () => void
151
+ className?: string
152
+ }
153
+
154
+ export const LazyImage: React.FC<LazyImageProps> = ({
155
+ src,
156
+ alt,
157
+ fallbackSrc,
158
+ showPlaceholder = true,
159
+ threshold = 0.1,
160
+ rootMargin = "50px",
161
+ onLoad,
162
+ onError,
163
+ className,
164
+ ...props
165
+ }) => {
166
+ const [imageSrc, setImageSrc] = useState<string | null>(null)
167
+ const [imageError, setImageError] = useState(false)
168
+ const [isVisible, setIsVisible] = useState(false)
169
+ const imgRef = useRef<HTMLImageElement>(null)
170
+ const containerRef = useRef<HTMLDivElement>(null)
171
+
172
+ useEffect(() => {
173
+ const observer = new IntersectionObserver(
174
+ ([entry]) => {
175
+ if (entry.isIntersecting) {
176
+ setIsVisible(true)
177
+ observer.disconnect()
178
+ }
179
+ },
180
+ { threshold, rootMargin }
181
+ )
182
+
183
+ if (containerRef.current) {
184
+ observer.observe(containerRef.current)
185
+ }
186
+
187
+ return () => observer.disconnect()
188
+ }, [threshold, rootMargin])
189
+
190
+ useEffect(() => {
191
+ if (!isVisible) return
192
+
193
+ const img = new Image()
194
+ img.onload = () => {
195
+ setImageSrc(src)
196
+ onLoad?.()
197
+ }
198
+ img.onerror = () => {
199
+ setImageError(true)
200
+ if (fallbackSrc) {
201
+ setImageSrc(fallbackSrc)
202
+ }
203
+ onError?.()
204
+ }
205
+ img.src = src
206
+ }, [isVisible, src, fallbackSrc, onLoad, onError])
207
+
208
+ return (
209
+ <div ref={containerRef} className={cn("relative overflow-hidden", className)}>
210
+ {imageSrc ? (
211
+ <motion.img
212
+ ref={imgRef}
213
+ src={imageSrc}
214
+ alt={alt}
215
+ initial={{ opacity: 0 }}
216
+ animate={{ opacity: 1 }}
217
+ transition={{ duration: 0.3 }}
218
+ className="w-full h-full object-cover"
219
+ {...props}
220
+ />
221
+ ) : showPlaceholder ? (
222
+ <Skeleton className="w-full h-full min-h-32" />
223
+ ) : null}
224
+
225
+ {imageError && !fallbackSrc && (
226
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
227
+ <p className="text-sm text-muted-foreground">Failed to load image</p>
228
+ </div>
229
+ )}
230
+ </div>
231
+ )
232
+ }
233
+
234
+ // Lazy List Component
235
+ export interface LazyListProps<T> {
236
+ items: T[]
237
+ renderItem: (item: T, index: number) => React.ReactNode
238
+ itemHeight?: number
239
+ batchSize?: number
240
+ threshold?: number
241
+ className?: string
242
+ }
243
+
244
+ export function LazyList<T>({
245
+ items,
246
+ renderItem,
247
+ itemHeight = 100,
248
+ batchSize = 10,
249
+ threshold = 0.5,
250
+ className
251
+ }: LazyListProps<T>) {
252
+ const [visibleItems, setVisibleItems] = useState<T[]>([])
253
+ const [currentBatch, setCurrentBatch] = useState(0)
254
+ const loadingRef = useRef<HTMLDivElement>(null)
255
+
256
+ useEffect(() => {
257
+ setVisibleItems(items.slice(0, batchSize))
258
+ setCurrentBatch(1)
259
+ }, [items, batchSize])
260
+
261
+ useEffect(() => {
262
+ const observer = new IntersectionObserver(
263
+ ([entry]) => {
264
+ if (entry.isIntersecting && currentBatch * batchSize < items.length) {
265
+ const nextBatch = currentBatch + 1
266
+ const newItems = items.slice(0, nextBatch * batchSize)
267
+ setVisibleItems(newItems)
268
+ setCurrentBatch(nextBatch)
269
+ }
270
+ },
271
+ { threshold }
272
+ )
273
+
274
+ if (loadingRef.current) {
275
+ observer.observe(loadingRef.current)
276
+ }
277
+
278
+ return () => observer.disconnect()
279
+ }, [currentBatch, items, batchSize, threshold])
280
+
281
+ return (
282
+ <div className={cn("space-y-2", className)}>
283
+ {visibleItems.map((item, index) => (
284
+ <motion.div
285
+ key={index}
286
+ initial={{ opacity: 0, y: 20 }}
287
+ animate={{ opacity: 1, y: 0 }}
288
+ transition={{ delay: (index % batchSize) * 0.05 }}
289
+ style={{ minHeight: itemHeight }}
290
+ >
291
+ {renderItem(item, index)}
292
+ </motion.div>
293
+ ))}
294
+
295
+ {currentBatch * batchSize < items.length && (
296
+ <div ref={loadingRef} className="flex justify-center py-4">
297
+ <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
298
+ </div>
299
+ )}
300
+ </div>
301
+ )
302
+ }
303
+
304
+ export const LazyComponent: React.FC<LazyComponentProps> = ({ className, ...props }) => {
305
+ // Check if we're in docs mode or have pro access
306
+ const docsProAccess = { hasAccess: true } // Pro access assumed in package
307
+ const { hasProAccess, isLoading } = useSubscription()
308
+
309
+ // In docs mode, always show the component
310
+ const canShowComponent = docsProAccess.isDocsMode || hasProAccess
311
+
312
+ // If not in docs mode and no pro access, show upgrade prompt
313
+ if (!docsProAccess.isDocsMode && !isLoading && !hasProAccess) {
314
+ return (
315
+ <Card className={cn("w-fit", className)}>
316
+ <CardContent className="py-6 text-center">
317
+ <div className="space-y-4">
318
+ <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
319
+ <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
320
+ </div>
321
+ <div>
322
+ <h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
323
+ <p className="text-muted-foreground text-xs mb-4">
324
+ Lazy Component is available exclusively to MoonUI Pro subscribers.
325
+ </p>
326
+ <a href="/pricing">
327
+ <Button size="sm">
328
+ <Sparkles className="mr-2 h-4 w-4" />
329
+ Upgrade to Pro
330
+ </Button>
331
+ </a>
332
+ </div>
333
+ </div>
334
+ </CardContent>
335
+ </Card>
336
+ )
337
+ }
338
+
339
+ return <LazyComponentInternal className={className} {...props} />
340
+ }
341
+
342
+ export type { LazyComponentProps, LazyImageProps, LazyListProps }
@@ -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 MagneticButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
12
+ strength?: number
13
+ range?: number
14
+ springConfig?: {
15
+ stiffness?: number
16
+ damping?: number
17
+ }
18
+ }
19
+
20
+ const MagneticButtonInternal = React.forwardRef<HTMLButtonElement, MagneticButtonProps>(
21
+ ({
22
+ children,
23
+ className,
24
+ strength = 0.3,
25
+ range = 100,
26
+ springConfig = { stiffness: 200, damping: 15 },
27
+ ...props
28
+ }, ref) => {
29
+ const buttonRef = useRef<HTMLButtonElement>(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, [-range, range], [5, -5])
39
+ const rotateY = useTransform(springX, [-range, range], [-5, 5])
40
+
41
+ const handleMouseMove = (e: React.MouseEvent) => {
42
+ if (!buttonRef.current) return
43
+
44
+ const rect = buttonRef.current.getBoundingClientRect()
45
+ const centerX = rect.left + rect.width / 2
46
+ const centerY = rect.top + rect.height / 2
47
+
48
+ const deltaX = e.clientX - centerX
49
+ const deltaY = e.clientY - centerY
50
+
51
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
52
+
53
+ if (distance < range) {
54
+ x.set(deltaX * strength)
55
+ y.set(deltaY * strength)
56
+ }
57
+ }
58
+
59
+ const handleMouseLeave = () => {
60
+ setIsHovered(false)
61
+ x.set(0)
62
+ y.set(0)
63
+ }
64
+
65
+ const handleMouseEnter = () => {
66
+ setIsHovered(true)
67
+ }
68
+
69
+ return (
70
+ <motion.button
71
+ ref={(node) => {
72
+ buttonRef.current = node
73
+ if (typeof ref === "function") {
74
+ ref(node)
75
+ } else if (ref) {
76
+ ref.current = node
77
+ }
78
+ }}
79
+ className={cn(
80
+ "relative inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
81
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
82
+ "h-9 px-4 py-2",
83
+ className
84
+ )}
85
+ onMouseMove={handleMouseMove}
86
+ onMouseLeave={handleMouseLeave}
87
+ onMouseEnter={handleMouseEnter}
88
+ style={{
89
+ x: springX,
90
+ y: springY,
91
+ rotateX,
92
+ rotateY,
93
+ transformPerspective: 1000,
94
+ }}
95
+ whileHover={{ scale: 1.05 }}
96
+ whileTap={{ scale: 0.95 }}
97
+ animate={{
98
+ rotateX: isHovered ? rotateX : 0,
99
+ rotateY: isHovered ? rotateY : 0,
100
+ }}
101
+ transition={{ duration: 0.3 }}
102
+ {...props}
103
+ >
104
+ <motion.div
105
+ className="relative z-10"
106
+ animate={{
107
+ scale: isHovered ? 1.02 : 1,
108
+ }}
109
+ transition={{ duration: 0.2 }}
110
+ >
111
+ {children}
112
+ </motion.div>
113
+
114
+ {/* Glow effect */}
115
+ <motion.div
116
+ className="absolute inset-0 rounded-md bg-primary/20 blur-md"
117
+ animate={{
118
+ opacity: isHovered ? 0.8 : 0,
119
+ scale: isHovered ? 1.1 : 1,
120
+ }}
121
+ transition={{ duration: 0.3 }}
122
+ />
123
+ </motion.button>
124
+ )
125
+ }
126
+ )
127
+
128
+ MagneticButtonInternal.displayName = "MagneticButtonInternal"
129
+
130
+ export const MagneticButton = React.forwardRef<HTMLButtonElement, MagneticButtonProps>(
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
+ Magnetic Button 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 <MagneticButtonInternal className={className} ref={ref} {...props} />
167
+ }
168
+ )
169
+
170
+ MagneticButton.displayName = "MagneticButton"