@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
|
@@ -2,72 +2,580 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
5
|
-
import { ChevronDown } from "lucide-react"
|
|
5
|
+
import { ChevronDown, Loader2, Hash, ExternalLink, Search, Play, Pause } from "lucide-react"
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
7
|
|
|
7
8
|
import { cn } from "../../lib/utils"
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
// Type definitions
|
|
11
|
+
type AccordionSize = "sm" | "md" | "lg";
|
|
12
|
+
type AccordionVariant = "default" | "bordered" | "elevated" | "gradient" | "glassmorphism";
|
|
13
|
+
type AnimationMode = "spring" | "smooth" | "fade" | "bounce";
|
|
14
|
+
|
|
15
|
+
// CVA definitions for variant styles
|
|
16
|
+
const accordionItemVariants = cva(
|
|
17
|
+
"group relative overflow-visible transition-all duration-300 ease-out",
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: "border-b border-border/40 dark:border-border/30 mt-1",
|
|
22
|
+
bordered: "border border-border/60 rounded-lg mb-2 mt-3 dark:border-border/40",
|
|
23
|
+
elevated: "border border-border/40 rounded-lg mb-3 mt-3 shadow-lg hover:shadow-xl dark:border-border/30 dark:shadow-gray-900/20",
|
|
24
|
+
gradient: "border-0 rounded-lg mb-3 mt-3 bg-gradient-to-r from-primary/5 via-accent/5 to-secondary/5 dark:from-primary/10 dark:via-accent/10 dark:to-secondary/10",
|
|
25
|
+
glassmorphism: "border border-white/20 rounded-lg mb-3 mt-3 backdrop-blur-md bg-white/10 dark:bg-black/10 dark:border-white/10"
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
sm: "text-sm",
|
|
29
|
+
md: "text-base",
|
|
30
|
+
lg: "text-lg"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "md"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const accordionTriggerVariants = cva(
|
|
41
|
+
"flex flex-1 items-center gap-3 font-medium transition-all duration-200 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 group",
|
|
42
|
+
{
|
|
43
|
+
variants: {
|
|
44
|
+
variant: {
|
|
45
|
+
default: "hover:text-primary dark:hover:text-primary/90",
|
|
46
|
+
bordered: "hover:text-primary hover:bg-primary/5 dark:hover:text-primary/90 dark:hover:bg-primary/10",
|
|
47
|
+
elevated: "hover:text-primary hover:bg-primary/5 dark:hover:text-primary/90 dark:hover:bg-primary/10",
|
|
48
|
+
gradient: "hover:text-primary",
|
|
49
|
+
glassmorphism: "hover:text-primary hover:bg-white/10 dark:hover:bg-white/5"
|
|
50
|
+
},
|
|
51
|
+
size: {
|
|
52
|
+
sm: "py-3 px-4 min-h-[3rem]",
|
|
53
|
+
md: "py-4 px-5 min-h-[3.5rem]",
|
|
54
|
+
lg: "py-5 px-6 min-h-[4rem]"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
defaultVariants: {
|
|
58
|
+
variant: "default",
|
|
59
|
+
size: "md"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// Root Accordion component
|
|
65
|
+
interface MoonUIAccordionProBaseProps {
|
|
66
|
+
/** Accordion size */
|
|
67
|
+
size?: AccordionSize;
|
|
68
|
+
/** Accordion visual variant */
|
|
69
|
+
variant?: AccordionVariant;
|
|
70
|
+
/** Animation mode */
|
|
71
|
+
animationMode?: AnimationMode;
|
|
72
|
+
/** Auto-expand based on URL hash */
|
|
73
|
+
autoExpandFromHash?: boolean;
|
|
74
|
+
/** Search term highlighting */
|
|
75
|
+
searchTerm?: string;
|
|
76
|
+
/** Analytics tracking callback */
|
|
77
|
+
onItemToggle?: (itemId: string, isOpen: boolean) => void;
|
|
78
|
+
/** Additional className */
|
|
79
|
+
className?: string;
|
|
80
|
+
/** Children elements */
|
|
81
|
+
children?: React.ReactNode;
|
|
82
|
+
/** Disabled state */
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
/** HTML dir attribute */
|
|
85
|
+
dir?: "ltr" | "rtl";
|
|
86
|
+
/** Orientation */
|
|
87
|
+
orientation?: "vertical" | "horizontal";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface MoonUIAccordionProSingleProps extends MoonUIAccordionProBaseProps {
|
|
91
|
+
type: "single";
|
|
92
|
+
value?: string;
|
|
93
|
+
defaultValue?: string;
|
|
94
|
+
onValueChange?: (value: string) => void;
|
|
95
|
+
collapsible?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface MoonUIAccordionProMultipleProps extends MoonUIAccordionProBaseProps {
|
|
99
|
+
type: "multiple";
|
|
100
|
+
value?: string[];
|
|
101
|
+
defaultValue?: string[];
|
|
102
|
+
onValueChange?: (value: string[]) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type MoonUIAccordionProProps = MoonUIAccordionProSingleProps | MoonUIAccordionProMultipleProps;
|
|
106
|
+
|
|
107
|
+
const MoonUIAccordionPro = React.forwardRef<
|
|
108
|
+
React.ElementRef<typeof AccordionPrimitive.Root>,
|
|
109
|
+
MoonUIAccordionProProps
|
|
110
|
+
>((props, ref) => {
|
|
111
|
+
const {
|
|
112
|
+
className,
|
|
113
|
+
size = "md",
|
|
114
|
+
variant = "default",
|
|
115
|
+
animationMode = "smooth",
|
|
116
|
+
autoExpandFromHash = false,
|
|
117
|
+
searchTerm,
|
|
118
|
+
onItemToggle,
|
|
119
|
+
children,
|
|
120
|
+
type = "single",
|
|
121
|
+
value,
|
|
122
|
+
defaultValue,
|
|
123
|
+
onValueChange,
|
|
124
|
+
orientation,
|
|
125
|
+
disabled,
|
|
126
|
+
dir,
|
|
127
|
+
...rest
|
|
128
|
+
} = props;
|
|
129
|
+
|
|
130
|
+
const collapsible = props.type === "single" ? (props as MoonUIAccordionProSingleProps).collapsible ?? true : undefined;
|
|
131
|
+
// Auto-expand based on URL hash
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
if (autoExpandFromHash && typeof window !== 'undefined') {
|
|
134
|
+
const hash = window.location.hash.replace('#', '');
|
|
135
|
+
if (hash) {
|
|
136
|
+
const element = document.getElementById(hash);
|
|
137
|
+
if (element) {
|
|
138
|
+
// Find and open accordion item
|
|
139
|
+
const accordionItem = element.closest('[data-accordion-item]');
|
|
140
|
+
if (accordionItem) {
|
|
141
|
+
const trigger = accordionItem.querySelector('[data-state]');
|
|
142
|
+
if (trigger && trigger.getAttribute('data-state') === 'closed') {
|
|
143
|
+
(trigger as HTMLElement).click();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, [autoExpandFromHash]);
|
|
150
|
+
|
|
151
|
+
// Analytics wrapper function
|
|
152
|
+
const handleValueChange = React.useCallback((newValue: any) => {
|
|
153
|
+
// Track item toggle for analytics
|
|
154
|
+
if (onItemToggle && newValue) {
|
|
155
|
+
if (Array.isArray(newValue)) {
|
|
156
|
+
newValue.forEach((v: string) => onItemToggle(v, true));
|
|
157
|
+
} else {
|
|
158
|
+
onItemToggle(newValue, true);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Call original onValueChange
|
|
162
|
+
onValueChange?.(newValue);
|
|
163
|
+
}, [onItemToggle, onValueChange]);
|
|
164
|
+
|
|
165
|
+
// Prepare AccordionPrimitive props
|
|
166
|
+
const accordionProps = type === "single"
|
|
167
|
+
? {
|
|
168
|
+
type: "single" as const,
|
|
169
|
+
value: value as string,
|
|
170
|
+
defaultValue: defaultValue as string,
|
|
171
|
+
onValueChange: handleValueChange,
|
|
172
|
+
collapsible
|
|
173
|
+
}
|
|
174
|
+
: {
|
|
175
|
+
type: "multiple" as const,
|
|
176
|
+
value: value as string[],
|
|
177
|
+
defaultValue: defaultValue as string[],
|
|
178
|
+
onValueChange: handleValueChange
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<AccordionPrimitive.Root
|
|
183
|
+
ref={ref}
|
|
184
|
+
{...accordionProps}
|
|
185
|
+
orientation={orientation}
|
|
186
|
+
disabled={disabled}
|
|
187
|
+
dir={dir}
|
|
188
|
+
className={cn(
|
|
189
|
+
"w-full",
|
|
190
|
+
// Styles based on animation modes
|
|
191
|
+
animationMode === "spring" && "[&_[data-state=open]]:animate-[accordion-down_0.3s_cubic-bezier(0.34,1.56,0.64,1)]",
|
|
192
|
+
animationMode === "bounce" && "[&_[data-state=open]]:animate-[accordion-down_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)]",
|
|
193
|
+
animationMode === "fade" && "[&_[data-state=open]]:animate-[accordion-down_0.2s_ease-in-out]",
|
|
194
|
+
className
|
|
195
|
+
)}
|
|
196
|
+
{...rest}
|
|
197
|
+
>
|
|
198
|
+
{/* Provide search term context */}
|
|
199
|
+
<AccordionContext.Provider value={{ size, variant, animationMode, searchTerm, onItemToggle }}>
|
|
200
|
+
{children}
|
|
201
|
+
</AccordionContext.Provider>
|
|
202
|
+
</AccordionPrimitive.Root>
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
MoonUIAccordionPro.displayName = "MoonUIAccordionPro"
|
|
206
|
+
|
|
207
|
+
// Context definition
|
|
208
|
+
const AccordionContext = React.createContext<{
|
|
209
|
+
size?: AccordionSize;
|
|
210
|
+
variant?: AccordionVariant;
|
|
211
|
+
animationMode?: AnimationMode;
|
|
212
|
+
searchTerm?: string;
|
|
213
|
+
onItemToggle?: (itemId: string, isOpen: boolean) => void;
|
|
214
|
+
}>({});
|
|
215
|
+
|
|
216
|
+
// Accordion Item component
|
|
217
|
+
interface MoonUIAccordionItemProProps extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> {
|
|
218
|
+
/** Loading state */
|
|
219
|
+
loading?: boolean;
|
|
220
|
+
/** Progress percentage (0-100) */
|
|
221
|
+
progress?: number;
|
|
222
|
+
/** Badge content */
|
|
223
|
+
badge?: React.ReactNode;
|
|
224
|
+
/** Badge variant */
|
|
225
|
+
badgeVariant?: "default" | "primary" | "secondary" | "success" | "warning" | "danger";
|
|
226
|
+
/** Completion status */
|
|
227
|
+
completed?: boolean;
|
|
228
|
+
/** Lazy loading - load content only when opened */
|
|
229
|
+
lazy?: boolean;
|
|
230
|
+
}
|
|
10
231
|
|
|
11
232
|
const MoonUIAccordionItemPro = React.forwardRef<
|
|
12
233
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
|
13
|
-
|
|
14
|
-
>(({ className, ...props }, ref) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
))
|
|
21
|
-
MoonUIAccordionItemPro.displayName = "MoonUIAccordionItemPro"
|
|
234
|
+
MoonUIAccordionItemProProps
|
|
235
|
+
>(({ className, loading, progress, badge, badgeVariant = "default", completed, lazy, children, ...props }, ref) => {
|
|
236
|
+
const { size, variant, onItemToggle } = React.useContext(AccordionContext);
|
|
237
|
+
const [hasBeenOpened, setHasBeenOpened] = React.useState(!lazy);
|
|
238
|
+
|
|
239
|
+
// Progress ring calculation
|
|
240
|
+
const progressOffset = progress ? 188.4 - (188.4 * progress) / 100 : 188.4;
|
|
22
241
|
|
|
23
|
-
|
|
24
|
-
React.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
242
|
+
// Listen for accordion open/close
|
|
243
|
+
React.useEffect(() => {
|
|
244
|
+
if (lazy && !hasBeenOpened) {
|
|
245
|
+
const item = document.querySelector(`[data-state="open"][value="${props.value}"]`);
|
|
246
|
+
if (item) {
|
|
247
|
+
setHasBeenOpened(true);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}, [lazy, hasBeenOpened, props.value]);
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<AccordionPrimitive.Item
|
|
29
254
|
ref={ref}
|
|
255
|
+
data-accordion-item
|
|
30
256
|
className={cn(
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
257
|
+
accordionItemVariants({ variant, size }),
|
|
258
|
+
loading && "opacity-60 pointer-events-none",
|
|
259
|
+
completed && "border-success/40 bg-success/5 dark:border-success/30 dark:bg-success/10",
|
|
34
260
|
className
|
|
35
261
|
)}
|
|
36
262
|
{...props}
|
|
37
263
|
>
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
264
|
+
{/* Progress indicator */}
|
|
265
|
+
{progress !== undefined && (
|
|
266
|
+
<div className="absolute top-2 right-2 w-6 h-6">
|
|
267
|
+
<svg className="w-6 h-6 -rotate-90" viewBox="0 0 64 64">
|
|
268
|
+
<circle
|
|
269
|
+
cx="32"
|
|
270
|
+
cy="32"
|
|
271
|
+
r="30"
|
|
272
|
+
fill="none"
|
|
273
|
+
stroke="currentColor"
|
|
274
|
+
strokeWidth="2"
|
|
275
|
+
className="text-muted-foreground/20"
|
|
276
|
+
/>
|
|
277
|
+
<circle
|
|
278
|
+
cx="32"
|
|
279
|
+
cy="32"
|
|
280
|
+
r="30"
|
|
281
|
+
fill="none"
|
|
282
|
+
stroke="currentColor"
|
|
283
|
+
strokeWidth="2"
|
|
284
|
+
strokeDasharray="188.4"
|
|
285
|
+
strokeDashoffset={progressOffset}
|
|
286
|
+
strokeLinecap="round"
|
|
287
|
+
className="text-primary transition-all duration-500"
|
|
288
|
+
/>
|
|
289
|
+
</svg>
|
|
290
|
+
<span className="absolute inset-0 flex items-center justify-center text-xs font-medium">
|
|
291
|
+
{Math.round(progress)}
|
|
292
|
+
</span>
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
|
|
296
|
+
{/* Badge */}
|
|
297
|
+
{badge && (
|
|
298
|
+
<div className="absolute -top-3 right-4 z-50">
|
|
299
|
+
<div className={cn(
|
|
300
|
+
"text-xs px-2.5 py-1 rounded-full font-semibold shadow-md",
|
|
301
|
+
badgeVariant === "default" && "bg-background text-foreground border border-border",
|
|
302
|
+
badgeVariant === "primary" && "bg-primary text-primary-foreground",
|
|
303
|
+
badgeVariant === "secondary" && "bg-secondary text-secondary-foreground",
|
|
304
|
+
badgeVariant === "success" && "bg-green-500 text-white dark:bg-green-600",
|
|
305
|
+
badgeVariant === "warning" && "bg-amber-500 text-white dark:bg-amber-600",
|
|
306
|
+
badgeVariant === "danger" && "bg-red-500 text-white dark:bg-red-600"
|
|
307
|
+
)}>
|
|
308
|
+
{badge}
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{/* Completion indicator */}
|
|
314
|
+
{completed && (
|
|
315
|
+
<div className="absolute top-3 right-3 w-5 h-5 bg-success text-success-foreground rounded-full flex items-center justify-center">
|
|
316
|
+
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
317
|
+
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
318
|
+
</svg>
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
321
|
+
|
|
322
|
+
{/* Loading spinner */}
|
|
323
|
+
{loading && (
|
|
324
|
+
<div className="absolute top-1/2 right-4 -translate-y-1/2">
|
|
325
|
+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{lazy && !hasBeenOpened ? (
|
|
330
|
+
<div className="p-4 text-muted-foreground">
|
|
331
|
+
<Play className="h-4 w-4 inline mr-2" />
|
|
332
|
+
Click to load content...
|
|
333
|
+
</div>
|
|
334
|
+
) : (
|
|
335
|
+
children
|
|
336
|
+
)}
|
|
337
|
+
</AccordionPrimitive.Item>
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
MoonUIAccordionItemPro.displayName = "MoonUIAccordionItemPro"
|
|
341
|
+
|
|
342
|
+
// Accordion Trigger component
|
|
343
|
+
interface MoonUIAccordionTriggerProProps extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> {
|
|
344
|
+
/** Icon to show on the left */
|
|
345
|
+
leftIcon?: React.ReactNode;
|
|
346
|
+
/** Icon to show on the right (excluding chevron) */
|
|
347
|
+
rightIcon?: React.ReactNode;
|
|
348
|
+
/** Subtitle / description */
|
|
349
|
+
description?: string;
|
|
350
|
+
/** Keyboard shortcut */
|
|
351
|
+
shortcut?: string;
|
|
352
|
+
/** External link indicator */
|
|
353
|
+
external?: boolean;
|
|
354
|
+
/** Custom chevron icon */
|
|
355
|
+
customChevron?: React.ReactNode;
|
|
356
|
+
/** Hide chevron */
|
|
357
|
+
hideChevron?: boolean;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const MoonUIAccordionTriggerPro = React.forwardRef<
|
|
361
|
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
|
362
|
+
MoonUIAccordionTriggerProProps
|
|
363
|
+
>(({
|
|
364
|
+
className,
|
|
365
|
+
children,
|
|
366
|
+
leftIcon,
|
|
367
|
+
rightIcon,
|
|
368
|
+
description,
|
|
369
|
+
shortcut,
|
|
370
|
+
external,
|
|
371
|
+
customChevron,
|
|
372
|
+
hideChevron,
|
|
373
|
+
...props
|
|
374
|
+
}, ref) => {
|
|
375
|
+
const { size, variant, searchTerm } = React.useContext(AccordionContext);
|
|
376
|
+
|
|
377
|
+
// Text highlighting function
|
|
378
|
+
const highlightText = (text: string, searchTerm?: string) => {
|
|
379
|
+
if (!searchTerm || typeof text !== 'string') return text;
|
|
380
|
+
|
|
381
|
+
const regex = new RegExp(`(${searchTerm})`, 'gi');
|
|
382
|
+
const parts = text.split(regex);
|
|
383
|
+
|
|
384
|
+
return parts.map((part, index) =>
|
|
385
|
+
part.toLowerCase() === searchTerm.toLowerCase() ? (
|
|
386
|
+
<mark key={index} className="bg-yellow-200 dark:bg-yellow-800 text-foreground px-0.5 rounded">
|
|
387
|
+
{part}
|
|
388
|
+
</mark>
|
|
389
|
+
) : (
|
|
390
|
+
part
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<AccordionPrimitive.Header className="flex">
|
|
397
|
+
<AccordionPrimitive.Trigger
|
|
398
|
+
ref={ref}
|
|
399
|
+
className={cn(
|
|
400
|
+
accordionTriggerVariants({ variant, size }),
|
|
401
|
+
"[&[data-state=open]>.chevron]:rotate-180",
|
|
402
|
+
"[&[data-state=open]]:text-primary dark:[&[data-state=open]]:text-primary/90",
|
|
403
|
+
className
|
|
404
|
+
)}
|
|
405
|
+
{...props}
|
|
406
|
+
>
|
|
407
|
+
{/* Left icon */}
|
|
408
|
+
{leftIcon && (
|
|
409
|
+
<span className="text-muted-foreground group-hover:text-primary transition-colors">
|
|
410
|
+
{leftIcon}
|
|
411
|
+
</span>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{/* Main content */}
|
|
415
|
+
<div className="flex-1 text-left">
|
|
416
|
+
<div className="flex items-center gap-2">
|
|
417
|
+
{/* Main title */}
|
|
418
|
+
<span className="font-medium">
|
|
419
|
+
{typeof children === 'string' ? highlightText(children, searchTerm) : children}
|
|
420
|
+
</span>
|
|
421
|
+
|
|
422
|
+
{/* External link indicator */}
|
|
423
|
+
{external && (
|
|
424
|
+
<ExternalLink className="h-3 w-3 text-muted-foreground" />
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
{/* Description */}
|
|
429
|
+
{description && (
|
|
430
|
+
<div className="text-sm text-muted-foreground mt-1">
|
|
431
|
+
{typeof description === 'string' ? highlightText(description, searchTerm) : description}
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
{/* Right side - icons and shortcut */}
|
|
437
|
+
<div className="flex items-center gap-2">
|
|
438
|
+
{/* Keyboard shortcut */}
|
|
439
|
+
{shortcut && (
|
|
440
|
+
<kbd className="hidden sm:inline-flex h-6 px-2 items-center rounded border bg-muted text-xs font-medium text-muted-foreground">
|
|
441
|
+
{shortcut}
|
|
442
|
+
</kbd>
|
|
443
|
+
)}
|
|
444
|
+
|
|
445
|
+
{/* Right icon */}
|
|
446
|
+
{rightIcon && (
|
|
447
|
+
<span className="text-muted-foreground group-hover:text-primary transition-colors">
|
|
448
|
+
{rightIcon}
|
|
449
|
+
</span>
|
|
450
|
+
)}
|
|
451
|
+
|
|
452
|
+
{/* Chevron */}
|
|
453
|
+
{!hideChevron && (
|
|
454
|
+
<span className="chevron transition-transform duration-200 text-muted-foreground">
|
|
455
|
+
{customChevron || <ChevronDown className="h-4 w-4" />}
|
|
456
|
+
</span>
|
|
457
|
+
)}
|
|
458
|
+
</div>
|
|
459
|
+
</AccordionPrimitive.Trigger>
|
|
460
|
+
</AccordionPrimitive.Header>
|
|
461
|
+
)
|
|
462
|
+
})
|
|
43
463
|
MoonUIAccordionTriggerPro.displayName = "MoonUIAccordionTriggerPro"
|
|
44
464
|
|
|
465
|
+
// Accordion Content component
|
|
466
|
+
interface MoonUIAccordionContentProProps extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> {
|
|
467
|
+
/** Content loading state */
|
|
468
|
+
contentLoading?: boolean;
|
|
469
|
+
/** Dynamic content loading function */
|
|
470
|
+
loadContent?: () => Promise<React.ReactNode>;
|
|
471
|
+
/** Remove padding */
|
|
472
|
+
noPadding?: boolean;
|
|
473
|
+
}
|
|
474
|
+
|
|
45
475
|
const MoonUIAccordionContentPro = React.forwardRef<
|
|
46
476
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
47
|
-
|
|
48
|
-
>(({ className, children, ...props }, ref) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
477
|
+
MoonUIAccordionContentProProps
|
|
478
|
+
>(({ className, children, contentLoading, loadContent, noPadding, ...props }, ref) => {
|
|
479
|
+
const { size, variant, animationMode } = React.useContext(AccordionContext);
|
|
480
|
+
const [dynamicContent, setDynamicContent] = React.useState<React.ReactNode>(null);
|
|
481
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
482
|
+
|
|
483
|
+
// Dynamic content loading
|
|
484
|
+
React.useEffect(() => {
|
|
485
|
+
if (loadContent && !dynamicContent) {
|
|
486
|
+
setIsLoading(true);
|
|
487
|
+
loadContent()
|
|
488
|
+
.then(setDynamicContent)
|
|
489
|
+
.finally(() => setIsLoading(false));
|
|
490
|
+
}
|
|
491
|
+
}, [loadContent, dynamicContent]);
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<AccordionPrimitive.Content
|
|
495
|
+
ref={ref}
|
|
496
|
+
className={cn(
|
|
497
|
+
"overflow-hidden transition-all",
|
|
498
|
+
"text-muted-foreground",
|
|
499
|
+
// Animation modes
|
|
500
|
+
animationMode === "spring" && "data-[state=closed]:animate-[accordion-up_0.3s_cubic-bezier(0.34,1.56,0.64,1)] data-[state=open]:animate-[accordion-down_0.3s_cubic-bezier(0.34,1.56,0.64,1)]",
|
|
501
|
+
animationMode === "bounce" && "data-[state=closed]:animate-[accordion-up_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)] data-[state=open]:animate-[accordion-down_0.4s_cubic-bezier(0.68,-0.55,0.265,1.55)]",
|
|
502
|
+
animationMode === "fade" && "data-[state=closed]:animate-[accordion-up_0.2s_ease-in-out] data-[state=open]:animate-[accordion-down_0.2s_ease-in-out]",
|
|
503
|
+
animationMode === "smooth" && "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
504
|
+
className
|
|
505
|
+
)}
|
|
506
|
+
{...props}
|
|
507
|
+
>
|
|
508
|
+
<div className={cn(
|
|
509
|
+
!noPadding && size === "sm" && "px-4 pb-3",
|
|
510
|
+
!noPadding && size === "md" && "px-5 pb-4",
|
|
511
|
+
!noPadding && size === "lg" && "px-6 pb-5",
|
|
512
|
+
// Variant-based background colors
|
|
513
|
+
variant === "elevated" && "bg-muted/20",
|
|
514
|
+
variant === "glassmorphism" && "bg-white/5 dark:bg-black/5"
|
|
515
|
+
)}>
|
|
516
|
+
{(contentLoading || isLoading) ? (
|
|
517
|
+
<div className="flex items-center gap-2 py-4">
|
|
518
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
519
|
+
<span className="text-sm">Loading content...</span>
|
|
520
|
+
</div>
|
|
521
|
+
) : (
|
|
522
|
+
dynamicContent || children
|
|
523
|
+
)}
|
|
524
|
+
</div>
|
|
525
|
+
</AccordionPrimitive.Content>
|
|
526
|
+
)
|
|
527
|
+
})
|
|
61
528
|
MoonUIAccordionContentPro.displayName = "MoonUIAccordionContentPro"
|
|
62
529
|
|
|
530
|
+
// Hooks
|
|
531
|
+
export const useAccordionAnalytics = () => {
|
|
532
|
+
const [analytics, setAnalytics] = React.useState<Record<string, { openCount: number, lastOpened: Date }>>({});
|
|
533
|
+
|
|
534
|
+
const trackItemOpen = React.useCallback((itemId: string) => {
|
|
535
|
+
setAnalytics(prev => ({
|
|
536
|
+
...prev,
|
|
537
|
+
[itemId]: {
|
|
538
|
+
openCount: (prev[itemId]?.openCount || 0) + 1,
|
|
539
|
+
lastOpened: new Date()
|
|
540
|
+
}
|
|
541
|
+
}));
|
|
542
|
+
}, []);
|
|
543
|
+
|
|
544
|
+
return { analytics, trackItemOpen };
|
|
545
|
+
};
|
|
546
|
+
|
|
63
547
|
// Internal aliases for Pro component usage
|
|
64
548
|
export const AccordionInternal = MoonUIAccordionPro
|
|
65
|
-
export const AccordionItemInternal = MoonUIAccordionItemPro
|
|
549
|
+
export const AccordionItemInternal = MoonUIAccordionItemPro
|
|
66
550
|
export const AccordionTriggerInternal = MoonUIAccordionTriggerPro
|
|
67
551
|
export const AccordionContentInternal = MoonUIAccordionContentPro
|
|
68
552
|
|
|
69
553
|
// Pro exports
|
|
70
|
-
export {
|
|
554
|
+
export {
|
|
555
|
+
MoonUIAccordionPro,
|
|
556
|
+
MoonUIAccordionItemPro,
|
|
557
|
+
MoonUIAccordionTriggerPro,
|
|
558
|
+
MoonUIAccordionContentPro
|
|
559
|
+
}
|
|
71
560
|
|
|
72
561
|
// Clean exports (without MoonUI prefix for easier usage)
|
|
73
|
-
export {
|
|
562
|
+
export {
|
|
563
|
+
MoonUIAccordionPro as Accordion,
|
|
564
|
+
MoonUIAccordionItemPro as AccordionItem,
|
|
565
|
+
MoonUIAccordionTriggerPro as AccordionTrigger,
|
|
566
|
+
MoonUIAccordionContentPro as AccordionContent
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Type exports
|
|
570
|
+
export type {
|
|
571
|
+
AccordionSize,
|
|
572
|
+
AccordionVariant,
|
|
573
|
+
AnimationMode,
|
|
574
|
+
MoonUIAccordionProProps,
|
|
575
|
+
MoonUIAccordionProBaseProps,
|
|
576
|
+
MoonUIAccordionProSingleProps,
|
|
577
|
+
MoonUIAccordionProMultipleProps,
|
|
578
|
+
MoonUIAccordionItemProProps,
|
|
579
|
+
MoonUIAccordionTriggerProProps,
|
|
580
|
+
MoonUIAccordionContentProProps
|
|
581
|
+
}
|