@moontra/moonui-pro 2.20.0 → 2.20.2
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.ts +691 -261
- package/dist/index.mjs +7419 -4935
- package/package.json +4 -3
- package/scripts/postbuild.js +27 -0
- package/src/components/advanced-chart/index.tsx +5 -1
- package/src/components/advanced-forms/index.tsx +175 -16
- package/src/components/calendar/event-dialog.tsx +18 -13
- package/src/components/calendar/index.tsx +197 -50
- package/src/components/dashboard/dashboard-grid.tsx +21 -3
- package/src/components/dashboard/types.ts +3 -0
- package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
- package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
- package/src/components/dashboard/widgets/index.ts +5 -0
- package/src/components/dashboard/widgets/metric-card.tsx +21 -1
- package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
- package/src/components/error-boundary/index.tsx +160 -37
- package/src/components/form-wizard/form-wizard-context.tsx +54 -26
- package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
- package/src/components/form-wizard/types.ts +2 -1
- package/src/components/github-stars/hooks.ts +1 -0
- package/src/components/github-stars/variants.tsx +3 -1
- package/src/components/health-check/index.tsx +14 -14
- package/src/components/hover-card-3d/index.tsx +2 -3
- package/src/components/index.ts +5 -3
- package/src/components/kanban/kanban.tsx +23 -18
- package/src/components/license-error/index.tsx +2 -0
- package/src/components/magnetic-button/index.tsx +56 -7
- package/src/components/memory-efficient-data/index.tsx +117 -115
- package/src/components/navbar/index.tsx +781 -0
- package/src/components/performance-debugger/index.tsx +62 -38
- package/src/components/performance-monitor/index.tsx +47 -33
- package/src/components/phone-number-input/index.tsx +32 -27
- package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
- package/src/components/rich-text-editor/index.tsx +26 -28
- package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
- package/src/components/sidebar/index.tsx +32 -13
- package/src/components/timeline/index.tsx +84 -49
- package/src/components/ui/accordion.tsx +550 -42
- package/src/components/ui/avatar.tsx +2 -0
- package/src/components/ui/badge.tsx +2 -0
- package/src/components/ui/breadcrumb.tsx +2 -0
- package/src/components/ui/button.tsx +39 -33
- package/src/components/ui/card.tsx +2 -0
- package/src/components/ui/collapsible.tsx +546 -50
- package/src/components/ui/command.tsx +790 -67
- package/src/components/ui/dialog.tsx +510 -92
- package/src/components/ui/dropdown-menu.tsx +540 -52
- package/src/components/ui/index.ts +37 -5
- package/src/components/ui/input.tsx +2 -0
- package/src/components/ui/magnetic-button.tsx +1 -1
- package/src/components/ui/media-gallery.tsx +1 -2
- package/src/components/ui/navigation-menu.tsx +130 -0
- package/src/components/ui/pagination.tsx +2 -0
- package/src/components/ui/select.tsx +6 -2
- package/src/components/ui/spotlight-card.tsx +1 -1
- package/src/components/ui/table.tsx +2 -0
- package/src/components/ui/tabs-pro.tsx +542 -0
- package/src/components/ui/tabs.tsx +23 -167
- package/src/components/ui/toggle.tsx +13 -13
- package/src/index.ts +11 -3
- package/src/styles/index.css +596 -0
- package/src/use-performance-optimizer.ts +1 -1
- package/src/utils/chart-helpers.ts +1 -1
- package/src/__tests__/use-intersection-observer.test.tsx +0 -216
- package/src/__tests__/use-local-storage.test.tsx +0 -174
- package/src/__tests__/use-pro-access.test.tsx +0 -183
- package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
- package/src/components/data-table/data-table.test.tsx +0 -187
- package/src/components/enhanced/badge.tsx +0 -191
- package/src/components/enhanced/button.tsx +0 -362
- package/src/components/enhanced/card.tsx +0 -266
- package/src/components/enhanced/dialog.tsx +0 -246
- package/src/components/enhanced/index.ts +0 -4
- package/src/components/file-upload/file-upload.test.tsx +0 -243
- package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
- package/src/types/moonui.d.ts +0 -22
|
@@ -3,56 +3,371 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
|
5
5
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ChevronDown,
|
|
8
|
+
ChevronRight,
|
|
9
|
+
ChevronUp,
|
|
10
|
+
Loader2,
|
|
11
|
+
ExternalLink,
|
|
12
|
+
AlertCircle,
|
|
13
|
+
CheckCircle2,
|
|
14
|
+
XCircle,
|
|
15
|
+
Info,
|
|
16
|
+
Clock,
|
|
17
|
+
Hash
|
|
18
|
+
} from "lucide-react"
|
|
7
19
|
|
|
8
20
|
import { cn } from "../../lib/utils"
|
|
9
21
|
|
|
10
22
|
/**
|
|
11
|
-
* Collapsible
|
|
23
|
+
* MoonUICollapsiblePro - Advanced Collapsible Component
|
|
12
24
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
25
|
+
* A feature-rich collapsible component with professional features:
|
|
26
|
+
* - Multiple visual variants (elevated, bordered, gradient, glassmorphism, card)
|
|
27
|
+
* - Size options (xs, sm, md, lg, xl)
|
|
28
|
+
* - Animation modes (spring, smooth, fade, bounce, slide)
|
|
29
|
+
* - Loading states with skeletons
|
|
30
|
+
* - Icon support (left/right icons, custom chevrons)
|
|
31
|
+
* - Progress indicators
|
|
32
|
+
* - Badge system with variants
|
|
33
|
+
* - Lazy loading for performance
|
|
34
|
+
* - Auto-collapse timer
|
|
35
|
+
* - Keyboard shortcuts
|
|
36
|
+
* - Analytics tracking
|
|
37
|
+
* - Memory persistence
|
|
38
|
+
* - Nested collapsibles support
|
|
15
39
|
*/
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
// Type definitions
|
|
42
|
+
type CollapsibleSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
43
|
+
type CollapsibleVariant = "default" | "bordered" | "elevated" | "gradient" | "glassmorphism" | "card";
|
|
44
|
+
type AnimationMode = "spring" | "smooth" | "fade" | "bounce" | "slide";
|
|
45
|
+
type BadgeVariant = "default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info";
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
// Enhanced Collapsible Root component
|
|
48
|
+
interface MoonUICollapsibleProProps {
|
|
49
|
+
/** Visual variant */
|
|
50
|
+
variant?: CollapsibleVariant;
|
|
51
|
+
/** Size preset */
|
|
52
|
+
size?: CollapsibleSize;
|
|
53
|
+
/** Animation mode */
|
|
54
|
+
animationMode?: AnimationMode;
|
|
55
|
+
/** Auto-collapse after X milliseconds */
|
|
56
|
+
autoCollapseAfter?: number;
|
|
57
|
+
/** Analytics tracking callback */
|
|
58
|
+
onToggleChange?: (isOpen: boolean) => void;
|
|
59
|
+
/** Memory persistence key */
|
|
60
|
+
persistKey?: string;
|
|
61
|
+
/** Loading state */
|
|
62
|
+
loading?: boolean;
|
|
63
|
+
/** Progress percentage (0-100) */
|
|
64
|
+
progress?: number;
|
|
65
|
+
/** Badge content */
|
|
66
|
+
badge?: React.ReactNode;
|
|
67
|
+
/** Badge variant */
|
|
68
|
+
badgeVariant?: BadgeVariant;
|
|
69
|
+
/** Custom keyboard shortcut */
|
|
70
|
+
shortcut?: string;
|
|
71
|
+
/** Lazy load content */
|
|
72
|
+
lazy?: boolean;
|
|
73
|
+
/** Additional className */
|
|
74
|
+
className?: string;
|
|
75
|
+
/** Control open state */
|
|
76
|
+
open?: boolean;
|
|
77
|
+
/** Default open state */
|
|
78
|
+
defaultOpen?: boolean;
|
|
79
|
+
/** Callback when open state changes */
|
|
80
|
+
onOpenChange?: (open: boolean) => void;
|
|
81
|
+
/** Children elements */
|
|
82
|
+
children?: React.ReactNode;
|
|
83
|
+
/** Disabled state */
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const MoonUICollapsiblePro = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof CollapsiblePrimitive.Root>,
|
|
89
|
+
MoonUICollapsibleProProps
|
|
90
|
+
>(({
|
|
91
|
+
className,
|
|
92
|
+
variant = "default",
|
|
93
|
+
size = "md",
|
|
94
|
+
animationMode = "smooth",
|
|
95
|
+
autoCollapseAfter,
|
|
96
|
+
onToggleChange,
|
|
97
|
+
persistKey,
|
|
98
|
+
loading,
|
|
99
|
+
progress,
|
|
100
|
+
badge,
|
|
101
|
+
badgeVariant = "default",
|
|
102
|
+
shortcut,
|
|
103
|
+
lazy,
|
|
104
|
+
open: controlledOpen,
|
|
105
|
+
defaultOpen,
|
|
106
|
+
onOpenChange,
|
|
107
|
+
children,
|
|
108
|
+
disabled
|
|
109
|
+
}, ref) => {
|
|
110
|
+
const [localOpen, setLocalOpen] = React.useState(() => {
|
|
111
|
+
// Check memory persistence
|
|
112
|
+
if (persistKey && typeof window !== 'undefined') {
|
|
113
|
+
const stored = localStorage.getItem(`collapsible-${persistKey}`);
|
|
114
|
+
if (stored !== null) {
|
|
115
|
+
return stored === 'true';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return defaultOpen || false;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const [hasBeenOpened, setHasBeenOpened] = React.useState(!lazy);
|
|
122
|
+
|
|
123
|
+
const isOpen = controlledOpen !== undefined ? controlledOpen : localOpen;
|
|
124
|
+
|
|
125
|
+
const handleOpenChange = React.useCallback((open: boolean) => {
|
|
126
|
+
if (!loading) {
|
|
127
|
+
setLocalOpen(open);
|
|
128
|
+
onOpenChange?.(open);
|
|
129
|
+
onToggleChange?.(open);
|
|
130
|
+
|
|
131
|
+
// Persist state
|
|
132
|
+
if (persistKey && typeof window !== 'undefined') {
|
|
133
|
+
localStorage.setItem(`collapsible-${persistKey}`, String(open));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Track lazy loading
|
|
137
|
+
if (lazy && open && !hasBeenOpened) {
|
|
138
|
+
setHasBeenOpened(true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Auto-collapse timer
|
|
142
|
+
if (open && autoCollapseAfter) {
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
setLocalOpen(false);
|
|
145
|
+
onOpenChange?.(false);
|
|
146
|
+
onToggleChange?.(false);
|
|
147
|
+
}, autoCollapseAfter);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}, [loading, onOpenChange, onToggleChange, persistKey, lazy, hasBeenOpened, autoCollapseAfter]);
|
|
151
|
+
|
|
152
|
+
// Keyboard shortcut handler
|
|
153
|
+
React.useEffect(() => {
|
|
154
|
+
if (shortcut) {
|
|
155
|
+
const handleKeyPress = (e: KeyboardEvent) => {
|
|
156
|
+
const keys = shortcut.toLowerCase().split('+');
|
|
157
|
+
const ctrlKey = keys.includes('ctrl') || keys.includes('cmd');
|
|
158
|
+
const shiftKey = keys.includes('shift');
|
|
159
|
+
const altKey = keys.includes('alt');
|
|
160
|
+
const key = keys[keys.length - 1];
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
e.ctrlKey === ctrlKey &&
|
|
164
|
+
e.shiftKey === shiftKey &&
|
|
165
|
+
e.altKey === altKey &&
|
|
166
|
+
e.key.toLowerCase() === key
|
|
167
|
+
) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
handleOpenChange(!isOpen);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
window.addEventListener('keydown', handleKeyPress);
|
|
174
|
+
return () => window.removeEventListener('keydown', handleKeyPress);
|
|
175
|
+
}
|
|
176
|
+
}, [shortcut, isOpen, handleOpenChange]);
|
|
177
|
+
|
|
178
|
+
// Progress ring calculation
|
|
179
|
+
const progressOffset = progress ? 188.4 - (188.4 * progress) / 100 : 188.4;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className={cn(
|
|
183
|
+
"relative",
|
|
184
|
+
variant === "bordered" && "border border-border rounded-lg",
|
|
185
|
+
variant === "elevated" && "border border-border rounded-lg shadow-lg hover:shadow-xl transition-shadow",
|
|
186
|
+
variant === "gradient" && "rounded-lg bg-gradient-to-r from-primary/5 via-accent/5 to-secondary/5",
|
|
187
|
+
variant === "glassmorphism" && "rounded-lg backdrop-blur-md bg-white/10 border border-white/20",
|
|
188
|
+
variant === "card" && "border border-border rounded-lg bg-card",
|
|
189
|
+
loading && "opacity-60 pointer-events-none",
|
|
190
|
+
className
|
|
191
|
+
)}>
|
|
192
|
+
{/* Progress indicator */}
|
|
193
|
+
{progress !== undefined && (
|
|
194
|
+
<div className="absolute top-2 right-2 w-6 h-6 z-10">
|
|
195
|
+
<svg className="w-6 h-6 -rotate-90" viewBox="0 0 64 64">
|
|
196
|
+
<circle
|
|
197
|
+
cx="32"
|
|
198
|
+
cy="32"
|
|
199
|
+
r="30"
|
|
200
|
+
fill="none"
|
|
201
|
+
stroke="currentColor"
|
|
202
|
+
strokeWidth="2"
|
|
203
|
+
className="text-muted-foreground/20"
|
|
204
|
+
/>
|
|
205
|
+
<circle
|
|
206
|
+
cx="32"
|
|
207
|
+
cy="32"
|
|
208
|
+
r="30"
|
|
209
|
+
fill="none"
|
|
210
|
+
stroke="currentColor"
|
|
211
|
+
strokeWidth="2"
|
|
212
|
+
strokeDasharray="188.4"
|
|
213
|
+
strokeDashoffset={progressOffset}
|
|
214
|
+
strokeLinecap="round"
|
|
215
|
+
className="text-primary transition-all duration-500"
|
|
216
|
+
/>
|
|
217
|
+
</svg>
|
|
218
|
+
<span className="absolute inset-0 flex items-center justify-center text-xs font-medium">
|
|
219
|
+
{Math.round(progress)}
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{/* Badge */}
|
|
225
|
+
{badge && (
|
|
226
|
+
<div className="absolute -top-3 right-4 z-20">
|
|
227
|
+
<div className={cn(
|
|
228
|
+
"text-xs px-2.5 py-1 rounded-full font-semibold shadow-md",
|
|
229
|
+
badgeVariant === "default" && "bg-background text-foreground border border-border",
|
|
230
|
+
badgeVariant === "primary" && "bg-primary text-primary-foreground",
|
|
231
|
+
badgeVariant === "secondary" && "bg-secondary text-secondary-foreground",
|
|
232
|
+
badgeVariant === "success" && "bg-green-500 text-white",
|
|
233
|
+
badgeVariant === "warning" && "bg-amber-500 text-white",
|
|
234
|
+
badgeVariant === "danger" && "bg-red-500 text-white",
|
|
235
|
+
badgeVariant === "info" && "bg-blue-500 text-white"
|
|
236
|
+
)}>
|
|
237
|
+
{badge}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
|
|
242
|
+
{/* Loading spinner */}
|
|
243
|
+
{loading && (
|
|
244
|
+
<div className="absolute top-1/2 right-4 -translate-y-1/2 z-10">
|
|
245
|
+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<CollapsiblePrimitive.Root
|
|
250
|
+
ref={ref}
|
|
251
|
+
open={isOpen}
|
|
252
|
+
onOpenChange={handleOpenChange}
|
|
253
|
+
disabled={disabled || loading}
|
|
254
|
+
className={cn(
|
|
255
|
+
"w-full",
|
|
256
|
+
animationMode === "spring" && "[&_[data-state=open]]:animate-[collapsible-down_0.3s_cubic-bezier(0.34,1.56,0.64,1)]",
|
|
257
|
+
animationMode === "bounce" && "[&_[data-state=open]]:animate-[collapsible-down_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)]",
|
|
258
|
+
animationMode === "fade" && "[&_[data-state=open]]:animate-[collapsible-down_0.2s_ease-in-out]",
|
|
259
|
+
animationMode === "slide" && "[&_[data-state=open]]:animate-[slide-down_0.3s_ease-out]"
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
{/* Provide context for children */}
|
|
263
|
+
<CollapsibleContext.Provider value={{
|
|
264
|
+
variant,
|
|
265
|
+
size,
|
|
266
|
+
animationMode,
|
|
267
|
+
lazy: lazy && !hasBeenOpened,
|
|
268
|
+
shortcut
|
|
269
|
+
}}>
|
|
270
|
+
{children}
|
|
271
|
+
</CollapsibleContext.Provider>
|
|
272
|
+
</CollapsiblePrimitive.Root>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
MoonUICollapsiblePro.displayName = "MoonUICollapsiblePro"
|
|
278
|
+
|
|
279
|
+
// Context definition
|
|
280
|
+
const CollapsibleContext = React.createContext<{
|
|
281
|
+
variant?: CollapsibleVariant;
|
|
282
|
+
size?: CollapsibleSize;
|
|
283
|
+
animationMode?: AnimationMode;
|
|
284
|
+
lazy?: boolean;
|
|
285
|
+
shortcut?: string;
|
|
286
|
+
}>({});
|
|
287
|
+
|
|
288
|
+
// Enhanced Trigger component with variants
|
|
289
|
+
const collapsibleTriggerVariants = cva(
|
|
290
|
+
"flex w-full items-center gap-3 transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
21
291
|
{
|
|
22
292
|
variants: {
|
|
23
293
|
variant: {
|
|
24
294
|
default: "text-foreground hover:text-primary",
|
|
25
|
-
|
|
26
|
-
|
|
295
|
+
bordered: "text-foreground hover:text-primary hover:bg-primary/5 rounded-t-lg",
|
|
296
|
+
elevated: "text-foreground hover:text-primary hover:bg-primary/5 rounded-t-lg",
|
|
297
|
+
gradient: "text-foreground hover:text-primary",
|
|
298
|
+
glassmorphism: "text-foreground hover:text-primary hover:bg-white/10",
|
|
299
|
+
card: "text-foreground hover:text-primary hover:bg-accent/5 rounded-t-lg"
|
|
27
300
|
},
|
|
28
301
|
size: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
md: "text-base py-
|
|
32
|
-
lg: "text-lg py-
|
|
302
|
+
xs: "text-xs py-2 px-3 min-h-[2.5rem]",
|
|
303
|
+
sm: "text-sm py-2.5 px-3.5 min-h-[3rem]",
|
|
304
|
+
md: "text-base py-3 px-4 min-h-[3.5rem]",
|
|
305
|
+
lg: "text-lg py-4 px-5 min-h-[4rem]",
|
|
306
|
+
xl: "text-xl py-5 px-6 min-h-[4.5rem]"
|
|
33
307
|
},
|
|
34
308
|
},
|
|
35
309
|
defaultVariants: {
|
|
36
310
|
variant: "default",
|
|
37
|
-
size: "
|
|
311
|
+
size: "md",
|
|
38
312
|
},
|
|
39
313
|
}
|
|
40
314
|
)
|
|
41
315
|
|
|
42
|
-
interface
|
|
316
|
+
interface MoonUICollapsibleTriggerProProps
|
|
43
317
|
extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Trigger>,
|
|
44
|
-
VariantProps<typeof
|
|
318
|
+
VariantProps<typeof collapsibleTriggerVariants> {
|
|
319
|
+
/** Icon to show on the left */
|
|
320
|
+
leftIcon?: React.ReactNode;
|
|
321
|
+
/** Icon to show on the right (excluding chevron) */
|
|
322
|
+
rightIcon?: React.ReactNode;
|
|
323
|
+
/** Subtitle / description */
|
|
324
|
+
description?: string;
|
|
325
|
+
/** External link indicator */
|
|
326
|
+
external?: boolean;
|
|
327
|
+
/** Custom chevron icon */
|
|
328
|
+
customChevron?: React.ReactNode;
|
|
329
|
+
/** Hide chevron */
|
|
330
|
+
hideChevron?: boolean;
|
|
331
|
+
/** Status indicator */
|
|
332
|
+
status?: "success" | "warning" | "error" | "info";
|
|
333
|
+
}
|
|
45
334
|
|
|
46
335
|
const MoonUICollapsibleTriggerPro = React.forwardRef<
|
|
47
336
|
React.ElementRef<typeof CollapsiblePrimitive.Trigger>,
|
|
48
|
-
|
|
49
|
-
>(({
|
|
50
|
-
|
|
337
|
+
MoonUICollapsibleTriggerProProps
|
|
338
|
+
>(({
|
|
339
|
+
className,
|
|
340
|
+
children,
|
|
341
|
+
variant: triggerVariant,
|
|
342
|
+
size: triggerSize,
|
|
343
|
+
asChild,
|
|
344
|
+
leftIcon,
|
|
345
|
+
rightIcon,
|
|
346
|
+
description,
|
|
347
|
+
external,
|
|
348
|
+
customChevron,
|
|
349
|
+
hideChevron,
|
|
350
|
+
status,
|
|
351
|
+
...props
|
|
352
|
+
}, ref) => {
|
|
353
|
+
const { variant, size, shortcut } = React.useContext(CollapsibleContext);
|
|
354
|
+
const finalVariant = triggerVariant || variant || "default";
|
|
355
|
+
const finalSize = triggerSize || size || "md";
|
|
356
|
+
|
|
357
|
+
// Status icon mapping
|
|
358
|
+
const statusIcon = status && {
|
|
359
|
+
success: <CheckCircle2 className="h-4 w-4 text-green-500" />,
|
|
360
|
+
warning: <AlertCircle className="h-4 w-4 text-amber-500" />,
|
|
361
|
+
error: <XCircle className="h-4 w-4 text-red-500" />,
|
|
362
|
+
info: <Info className="h-4 w-4 text-blue-500" />
|
|
363
|
+
}[status];
|
|
364
|
+
|
|
365
|
+
// If asChild, render minimal wrapper
|
|
51
366
|
if (asChild) {
|
|
52
367
|
return (
|
|
53
368
|
<CollapsiblePrimitive.Trigger
|
|
54
369
|
ref={ref}
|
|
55
|
-
className={cn(
|
|
370
|
+
className={cn(collapsibleTriggerVariants({ variant: finalVariant, size: finalSize }), className)}
|
|
56
371
|
asChild={asChild}
|
|
57
372
|
{...props}
|
|
58
373
|
>
|
|
@@ -64,72 +379,253 @@ const MoonUICollapsibleTriggerPro = React.forwardRef<
|
|
|
64
379
|
return (
|
|
65
380
|
<CollapsiblePrimitive.Trigger
|
|
66
381
|
ref={ref}
|
|
67
|
-
className={cn(
|
|
382
|
+
className={cn(
|
|
383
|
+
collapsibleTriggerVariants({ variant: finalVariant, size: finalSize }),
|
|
384
|
+
"[&[data-state=open]>.chevron]:rotate-180",
|
|
385
|
+
"[&[data-state=open]]:text-primary",
|
|
386
|
+
className
|
|
387
|
+
)}
|
|
68
388
|
{...props}
|
|
69
389
|
>
|
|
70
|
-
{
|
|
71
|
-
|
|
390
|
+
{/* Left icon */}
|
|
391
|
+
{leftIcon && (
|
|
392
|
+
<span className="text-muted-foreground">{leftIcon}</span>
|
|
393
|
+
)}
|
|
394
|
+
|
|
395
|
+
{/* Status icon */}
|
|
396
|
+
{statusIcon && !leftIcon && statusIcon}
|
|
397
|
+
|
|
398
|
+
{/* Main content */}
|
|
399
|
+
<div className="flex-1 text-left">
|
|
400
|
+
<div className="flex items-center gap-2">
|
|
401
|
+
{/* Main title */}
|
|
402
|
+
<span className="font-medium">{children}</span>
|
|
403
|
+
|
|
404
|
+
{/* External link indicator */}
|
|
405
|
+
{external && (
|
|
406
|
+
<ExternalLink className="h-3 w-3 text-muted-foreground" />
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
{/* Description */}
|
|
411
|
+
{description && (
|
|
412
|
+
<div className="text-sm text-muted-foreground mt-1">
|
|
413
|
+
{description}
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
{/* Right side elements */}
|
|
419
|
+
<div className="flex items-center gap-2">
|
|
420
|
+
{/* Keyboard shortcut */}
|
|
421
|
+
{shortcut && (
|
|
422
|
+
<kbd className="hidden sm:inline-flex h-6 px-2 items-center rounded border bg-muted text-xs font-medium text-muted-foreground">
|
|
423
|
+
{shortcut}
|
|
424
|
+
</kbd>
|
|
425
|
+
)}
|
|
426
|
+
|
|
427
|
+
{/* Right icon */}
|
|
428
|
+
{rightIcon && (
|
|
429
|
+
<span className="text-muted-foreground">{rightIcon}</span>
|
|
430
|
+
)}
|
|
431
|
+
|
|
432
|
+
{/* Chevron */}
|
|
433
|
+
{!hideChevron && (
|
|
434
|
+
<span className="chevron transition-transform duration-200 text-muted-foreground">
|
|
435
|
+
{customChevron || <ChevronDown className="h-4 w-4" />}
|
|
436
|
+
</span>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
72
439
|
</CollapsiblePrimitive.Trigger>
|
|
73
440
|
)
|
|
74
441
|
})
|
|
75
|
-
MoonUICollapsibleTriggerPro.displayName =
|
|
442
|
+
MoonUICollapsibleTriggerPro.displayName = "MoonUICollapsibleTriggerPro"
|
|
76
443
|
|
|
77
|
-
|
|
78
|
-
|
|
444
|
+
// Enhanced Content component with animations
|
|
445
|
+
const collapsibleContentVariants = cva(
|
|
446
|
+
"overflow-hidden transition-all",
|
|
79
447
|
{
|
|
80
448
|
variants: {
|
|
81
449
|
variant: {
|
|
82
450
|
default: "text-muted-foreground",
|
|
83
|
-
|
|
84
|
-
|
|
451
|
+
bordered: "text-muted-foreground border-x border-b border-border rounded-b-lg",
|
|
452
|
+
elevated: "text-muted-foreground bg-muted/20",
|
|
453
|
+
gradient: "text-muted-foreground",
|
|
454
|
+
glassmorphism: "text-muted-foreground bg-white/5",
|
|
455
|
+
card: "text-muted-foreground border-x border-b border-border rounded-b-lg bg-card/50"
|
|
85
456
|
},
|
|
86
457
|
size: {
|
|
87
|
-
|
|
88
|
-
|
|
458
|
+
xs: "text-xs",
|
|
459
|
+
sm: "text-sm",
|
|
89
460
|
md: "text-base",
|
|
90
461
|
lg: "text-lg",
|
|
462
|
+
xl: "text-xl"
|
|
91
463
|
},
|
|
92
464
|
},
|
|
93
465
|
defaultVariants: {
|
|
94
466
|
variant: "default",
|
|
95
|
-
size: "
|
|
467
|
+
size: "md",
|
|
96
468
|
},
|
|
97
469
|
}
|
|
98
470
|
)
|
|
99
471
|
|
|
100
|
-
interface
|
|
472
|
+
interface MoonUICollapsibleContentProProps
|
|
101
473
|
extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Content>,
|
|
102
|
-
VariantProps<typeof
|
|
474
|
+
VariantProps<typeof collapsibleContentVariants> {
|
|
475
|
+
/** Content loading state */
|
|
476
|
+
contentLoading?: boolean;
|
|
477
|
+
/** Dynamic content loading function */
|
|
478
|
+
loadContent?: () => Promise<React.ReactNode>;
|
|
479
|
+
/** Remove padding */
|
|
480
|
+
noPadding?: boolean;
|
|
481
|
+
/** Custom padding */
|
|
482
|
+
padding?: string;
|
|
483
|
+
}
|
|
103
484
|
|
|
104
485
|
const MoonUICollapsibleContentPro = React.forwardRef<
|
|
105
486
|
React.ElementRef<typeof CollapsiblePrimitive.Content>,
|
|
106
|
-
|
|
107
|
-
>(({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
487
|
+
MoonUICollapsibleContentProProps
|
|
488
|
+
>(({
|
|
489
|
+
className,
|
|
490
|
+
children,
|
|
491
|
+
variant: contentVariant,
|
|
492
|
+
size: contentSize,
|
|
493
|
+
contentLoading,
|
|
494
|
+
loadContent,
|
|
495
|
+
noPadding,
|
|
496
|
+
padding,
|
|
497
|
+
...props
|
|
498
|
+
}, ref) => {
|
|
499
|
+
const { variant, size, animationMode, lazy } = React.useContext(CollapsibleContext);
|
|
500
|
+
const finalVariant = contentVariant || variant || "default";
|
|
501
|
+
const finalSize = contentSize || size || "md";
|
|
502
|
+
|
|
503
|
+
const [dynamicContent, setDynamicContent] = React.useState<React.ReactNode>(null);
|
|
504
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
505
|
+
|
|
506
|
+
// Dynamic content loading
|
|
507
|
+
React.useEffect(() => {
|
|
508
|
+
if (loadContent && !dynamicContent) {
|
|
509
|
+
setIsLoading(true);
|
|
510
|
+
loadContent()
|
|
511
|
+
.then(setDynamicContent)
|
|
512
|
+
.finally(() => setIsLoading(false));
|
|
513
|
+
}
|
|
514
|
+
}, [loadContent, dynamicContent]);
|
|
117
515
|
|
|
516
|
+
// Determine padding based on size
|
|
517
|
+
const defaultPadding = !noPadding && {
|
|
518
|
+
xs: "p-3",
|
|
519
|
+
sm: "p-3.5",
|
|
520
|
+
md: "p-4",
|
|
521
|
+
lg: "p-5",
|
|
522
|
+
xl: "p-6"
|
|
523
|
+
}[finalSize];
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<CollapsiblePrimitive.Content
|
|
527
|
+
ref={ref}
|
|
528
|
+
className={cn(
|
|
529
|
+
collapsibleContentVariants({ variant: finalVariant, size: finalSize }),
|
|
530
|
+
// Animation modes
|
|
531
|
+
animationMode === "spring" && "data-[state=closed]:animate-[collapsible-up_0.3s_cubic-bezier(0.34,1.56,0.64,1)] data-[state=open]:animate-[collapsible-down_0.3s_cubic-bezier(0.34,1.56,0.64,1)]",
|
|
532
|
+
animationMode === "bounce" && "data-[state=closed]:animate-[collapsible-up_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)] data-[state=open]:animate-[collapsible-down_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)]",
|
|
533
|
+
animationMode === "fade" && "data-[state=closed]:animate-[collapsible-up_0.2s_ease-in-out] data-[state=open]:animate-[collapsible-down_0.2s_ease-in-out]",
|
|
534
|
+
animationMode === "slide" && "data-[state=closed]:animate-[slide-up_0.3s_ease-out] data-[state=open]:animate-[slide-down_0.3s_ease-out]",
|
|
535
|
+
animationMode === "smooth" && "data-[state=closed]:animate-collapse-up data-[state=open]:animate-collapse-down",
|
|
536
|
+
className
|
|
537
|
+
)}
|
|
538
|
+
{...props}
|
|
539
|
+
>
|
|
540
|
+
<div className={cn(padding || defaultPadding)}>
|
|
541
|
+
{lazy ? (
|
|
542
|
+
<div className="flex items-center gap-2 py-4">
|
|
543
|
+
<Clock className="h-4 w-4 animate-pulse" />
|
|
544
|
+
<span className="text-sm">Click to load content...</span>
|
|
545
|
+
</div>
|
|
546
|
+
) : (contentLoading || isLoading) ? (
|
|
547
|
+
<div className="flex items-center gap-2 py-4">
|
|
548
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
549
|
+
<span className="text-sm">Loading content...</span>
|
|
550
|
+
</div>
|
|
551
|
+
) : (
|
|
552
|
+
dynamicContent || children
|
|
553
|
+
)}
|
|
554
|
+
</div>
|
|
555
|
+
</CollapsiblePrimitive.Content>
|
|
556
|
+
)
|
|
557
|
+
})
|
|
558
|
+
MoonUICollapsibleContentPro.displayName = "MoonUICollapsibleContentPro"
|
|
559
|
+
|
|
560
|
+
// Utility Hooks
|
|
561
|
+
export const useCollapsibleAnalytics = () => {
|
|
562
|
+
const [analytics, setAnalytics] = React.useState<{
|
|
563
|
+
openCount: number;
|
|
564
|
+
lastOpened: Date | null;
|
|
565
|
+
totalTimeOpen: number;
|
|
566
|
+
}>({
|
|
567
|
+
openCount: 0,
|
|
568
|
+
lastOpened: null,
|
|
569
|
+
totalTimeOpen: 0
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const trackOpen = React.useCallback(() => {
|
|
573
|
+
setAnalytics(prev => ({
|
|
574
|
+
...prev,
|
|
575
|
+
openCount: prev.openCount + 1,
|
|
576
|
+
lastOpened: new Date()
|
|
577
|
+
}));
|
|
578
|
+
}, []);
|
|
579
|
+
|
|
580
|
+
const trackClose = React.useCallback(() => {
|
|
581
|
+
setAnalytics(prev => {
|
|
582
|
+
if (prev.lastOpened) {
|
|
583
|
+
const timeOpen = Date.now() - prev.lastOpened.getTime();
|
|
584
|
+
return {
|
|
585
|
+
...prev,
|
|
586
|
+
totalTimeOpen: prev.totalTimeOpen + timeOpen,
|
|
587
|
+
lastOpened: null
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
return prev;
|
|
591
|
+
});
|
|
592
|
+
}, []);
|
|
593
|
+
|
|
594
|
+
return { analytics, trackOpen, trackClose };
|
|
595
|
+
};
|
|
118
596
|
|
|
119
597
|
// Internal aliases for Pro component usage
|
|
120
598
|
export const CollapsibleInternal = MoonUICollapsiblePro
|
|
121
|
-
export const collapsibleTriggerVariantsInternal =
|
|
599
|
+
export const collapsibleTriggerVariantsInternal = collapsibleTriggerVariants
|
|
122
600
|
export const CollapsibleTriggerInternal = MoonUICollapsibleTriggerPro
|
|
123
|
-
export const collapsibleContentVariantsInternal =
|
|
601
|
+
export const collapsibleContentVariantsInternal = collapsibleContentVariants
|
|
124
602
|
export const CollapsibleContentInternal = MoonUICollapsibleContentPro
|
|
125
603
|
|
|
126
604
|
// Pro exports
|
|
127
|
-
export {
|
|
605
|
+
export {
|
|
606
|
+
MoonUICollapsiblePro,
|
|
607
|
+
collapsibleTriggerVariants as MoonUIcollapsibleTriggerVariantsPro,
|
|
608
|
+
MoonUICollapsibleTriggerPro,
|
|
609
|
+
collapsibleContentVariants as MoonUIcollapsibleContentVariantsPro,
|
|
610
|
+
MoonUICollapsibleContentPro
|
|
611
|
+
}
|
|
128
612
|
|
|
129
613
|
// Clean exports (without MoonUI prefix for easier usage)
|
|
130
|
-
export {
|
|
614
|
+
export {
|
|
615
|
+
MoonUICollapsiblePro as Collapsible,
|
|
616
|
+
collapsibleTriggerVariants,
|
|
617
|
+
MoonUICollapsibleTriggerPro as CollapsibleTrigger,
|
|
618
|
+
collapsibleContentVariants,
|
|
619
|
+
MoonUICollapsibleContentPro as CollapsibleContent
|
|
620
|
+
}
|
|
131
621
|
|
|
622
|
+
// Type exports
|
|
132
623
|
export type {
|
|
133
|
-
|
|
134
|
-
|
|
624
|
+
MoonUICollapsibleProProps,
|
|
625
|
+
MoonUICollapsibleTriggerProProps,
|
|
626
|
+
MoonUICollapsibleContentProProps,
|
|
627
|
+
CollapsibleSize,
|
|
628
|
+
CollapsibleVariant,
|
|
629
|
+
AnimationMode,
|
|
630
|
+
BadgeVariant
|
|
135
631
|
}
|