@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.
Files changed (76) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7419 -4935
  3. package/package.json +4 -3
  4. package/scripts/postbuild.js +27 -0
  5. package/src/components/advanced-chart/index.tsx +5 -1
  6. package/src/components/advanced-forms/index.tsx +175 -16
  7. package/src/components/calendar/event-dialog.tsx +18 -13
  8. package/src/components/calendar/index.tsx +197 -50
  9. package/src/components/dashboard/dashboard-grid.tsx +21 -3
  10. package/src/components/dashboard/types.ts +3 -0
  11. package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
  12. package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
  13. package/src/components/dashboard/widgets/index.ts +5 -0
  14. package/src/components/dashboard/widgets/metric-card.tsx +21 -1
  15. package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
  16. package/src/components/error-boundary/index.tsx +160 -37
  17. package/src/components/form-wizard/form-wizard-context.tsx +54 -26
  18. package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
  19. package/src/components/form-wizard/types.ts +2 -1
  20. package/src/components/github-stars/hooks.ts +1 -0
  21. package/src/components/github-stars/variants.tsx +3 -1
  22. package/src/components/health-check/index.tsx +14 -14
  23. package/src/components/hover-card-3d/index.tsx +2 -3
  24. package/src/components/index.ts +5 -3
  25. package/src/components/kanban/kanban.tsx +23 -18
  26. package/src/components/license-error/index.tsx +2 -0
  27. package/src/components/magnetic-button/index.tsx +56 -7
  28. package/src/components/memory-efficient-data/index.tsx +117 -115
  29. package/src/components/navbar/index.tsx +781 -0
  30. package/src/components/performance-debugger/index.tsx +62 -38
  31. package/src/components/performance-monitor/index.tsx +47 -33
  32. package/src/components/phone-number-input/index.tsx +32 -27
  33. package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
  34. package/src/components/rich-text-editor/index.tsx +26 -28
  35. package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
  36. package/src/components/sidebar/index.tsx +32 -13
  37. package/src/components/timeline/index.tsx +84 -49
  38. package/src/components/ui/accordion.tsx +550 -42
  39. package/src/components/ui/avatar.tsx +2 -0
  40. package/src/components/ui/badge.tsx +2 -0
  41. package/src/components/ui/breadcrumb.tsx +2 -0
  42. package/src/components/ui/button.tsx +39 -33
  43. package/src/components/ui/card.tsx +2 -0
  44. package/src/components/ui/collapsible.tsx +546 -50
  45. package/src/components/ui/command.tsx +790 -67
  46. package/src/components/ui/dialog.tsx +510 -92
  47. package/src/components/ui/dropdown-menu.tsx +540 -52
  48. package/src/components/ui/index.ts +37 -5
  49. package/src/components/ui/input.tsx +2 -0
  50. package/src/components/ui/magnetic-button.tsx +1 -1
  51. package/src/components/ui/media-gallery.tsx +1 -2
  52. package/src/components/ui/navigation-menu.tsx +130 -0
  53. package/src/components/ui/pagination.tsx +2 -0
  54. package/src/components/ui/select.tsx +6 -2
  55. package/src/components/ui/spotlight-card.tsx +1 -1
  56. package/src/components/ui/table.tsx +2 -0
  57. package/src/components/ui/tabs-pro.tsx +542 -0
  58. package/src/components/ui/tabs.tsx +23 -167
  59. package/src/components/ui/toggle.tsx +13 -13
  60. package/src/index.ts +11 -3
  61. package/src/styles/index.css +596 -0
  62. package/src/use-performance-optimizer.ts +1 -1
  63. package/src/utils/chart-helpers.ts +1 -1
  64. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  65. package/src/__tests__/use-local-storage.test.tsx +0 -174
  66. package/src/__tests__/use-pro-access.test.tsx +0 -183
  67. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  68. package/src/components/data-table/data-table.test.tsx +0 -187
  69. package/src/components/enhanced/badge.tsx +0 -191
  70. package/src/components/enhanced/button.tsx +0 -362
  71. package/src/components/enhanced/card.tsx +0 -266
  72. package/src/components/enhanced/dialog.tsx +0 -246
  73. package/src/components/enhanced/index.ts +0 -4
  74. package/src/components/file-upload/file-upload.test.tsx +0 -243
  75. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  76. package/src/types/moonui.d.ts +0 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.20.0",
3
+ "version": "2.20.2",
4
4
  "description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "scripts": {
32
32
  "postinstall": "node scripts/postinstall.js",
33
- "build": "tsup",
33
+ "build": "tsup && node scripts/postbuild.js",
34
34
  "build:dts": "tsup --dts",
35
35
  "dev": "tsup --watch --sourcemap",
36
36
  "clean": "rm -rf dist",
@@ -75,7 +75,6 @@
75
75
  "react-dom": ">=18.0.0 || ^19.0.0"
76
76
  },
77
77
  "dependencies": {
78
- "@moontra/moonui": "^2.1.9",
79
78
  "@radix-ui/react-accordion": "^1.2.11",
80
79
  "@radix-ui/react-avatar": "^1.1.10",
81
80
  "@radix-ui/react-checkbox": "^1.3.2",
@@ -95,8 +94,10 @@
95
94
  "canvas-confetti": "^1.9.3",
96
95
  "class-variance-authority": "^0.7.0",
97
96
  "clsx": "^2.1.1",
97
+ "cmdk": "^1.0.4",
98
98
  "date-fns": "^3.6.0",
99
99
  "framer-motion": "^11.11.17",
100
+ "fuse.js": "^7.0.0",
100
101
  "lucide-react": "^0.525.0",
101
102
  "react-beautiful-dnd": "^13.1.1",
102
103
  "react-colorful": "^5.6.1",
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // Add 'use client' to the beginning of dist files
11
+ const files = ['dist/index.mjs', 'dist/index.js'];
12
+
13
+ files.forEach(file => {
14
+ const filePath = path.join(__dirname, '..', file);
15
+
16
+ if (fs.existsSync(filePath)) {
17
+ let content = fs.readFileSync(filePath, 'utf8');
18
+
19
+ if (!content.startsWith('"use client"') && !content.startsWith("'use client'")) {
20
+ content = '"use client";\n' + content;
21
+ fs.writeFileSync(filePath, content);
22
+ console.log(`✅ Added 'use client' to ${file}`);
23
+ } else {
24
+ console.log(`✔️ 'use client' already exists in ${file}`);
25
+ }
26
+ }
27
+ });
@@ -775,9 +775,13 @@ export function AdvancedChart({
775
775
  animationBegin={index * 100}
776
776
  shape={(props: any) => {
777
777
  const isHovered = hoveredDataPoint === props.payload
778
+ // Extract only the necessary props for the circle element
779
+ const { cx, cy, fill } = props
778
780
  return (
779
781
  <circle
780
- {...props}
782
+ cx={cx}
783
+ cy={cy}
784
+ fill={fill}
781
785
  r={isHovered ? 6 : 4}
782
786
  className="transition-all duration-200 hover:r-6"
783
787
  onMouseEnter={() => setHoveredDataPoint(props.payload)}
@@ -15,6 +15,7 @@ import { MoonUIBadgePro as Badge } from "../ui/badge"
15
15
  import { cn } from "../../lib/utils"
16
16
  import { Lock, Sparkles, CheckCircle, AlertCircle, Eye, EyeOff, Upload, X } from "lucide-react"
17
17
  import { useSubscription } from "../../hooks/use-subscription"
18
+ import { MoonUIPhoneNumberInputPro as PhoneNumberInput } from "../phone-number-input"
18
19
 
19
20
  interface AdvancedFormField {
20
21
  name: string
@@ -31,6 +32,7 @@ interface AdvancedFormField {
31
32
  }
32
33
  description?: string
33
34
  defaultValue?: any
35
+ defaultCountry?: string // For phone number fields
34
36
  }
35
37
 
36
38
  interface AdvancedFormsProps {
@@ -61,6 +63,19 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
61
63
  const [isSubmitting, setIsSubmitting] = useState(false)
62
64
  const [showPasswords, setShowPasswords] = useState<Record<string, boolean>>({})
63
65
  const [uploadedFiles, setUploadedFiles] = useState<Record<string, File[]>>({})
66
+ const [phoneNumbers, setPhoneNumbers] = useState<Record<string, { country: string; number: string }>>(() => {
67
+ // Initialize phone numbers from default values
68
+ const initial: Record<string, { country: string; number: string }> = {}
69
+ fields.forEach(field => {
70
+ if (field.type === "tel" && field.defaultValue) {
71
+ initial[field.name] = {
72
+ country: field.defaultCountry || "US",
73
+ number: field.defaultValue
74
+ }
75
+ }
76
+ })
77
+ return initial
78
+ })
64
79
 
65
80
  const {
66
81
  register,
@@ -68,18 +83,72 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
68
83
  watch,
69
84
  formState: { errors, isValid, touchedFields },
70
85
  setValue,
71
- getValues
86
+ getValues,
87
+ setError,
88
+ clearErrors
72
89
  } = useForm<any>({
73
90
  mode: "onChange",
74
91
  defaultValues: fields.reduce((acc, field) => ({
75
92
  ...acc,
76
- [field.name]: field.defaultValue || (field.type === "checkbox" ? false : "")
93
+ [field.name]: field.defaultValue || (field.type === "checkbox" || field.type === "switch" ? false : "")
77
94
  }), {})
78
95
  })
79
96
 
80
97
  const watchedValues = watch()
81
- const completedFields = Object.keys(touchedFields).length
82
- const progress = fields.length > 0 ? (completedFields / fields.length) * 100 : 0
98
+
99
+ // Calculate progress based on valid and filled fields
100
+ const calculateProgress = () => {
101
+ let validFields = 0
102
+ let totalFields = 0
103
+
104
+ fields.forEach(field => {
105
+ const value = watchedValues[field.name]
106
+ const hasError = errors[field.name]
107
+
108
+ // Only count required fields or optional fields that have been touched
109
+ if (!field.required && !touchedFields[field.name] && !value) {
110
+ return // Skip untouched optional fields
111
+ }
112
+
113
+ totalFields++
114
+
115
+ // Check if field has value and no errors
116
+ if (field.type === "checkbox" || field.type === "switch") {
117
+ if (!field.required || value === true) {
118
+ if (!hasError) validFields++
119
+ }
120
+ } else if (field.type === "file") {
121
+ if (!field.required || uploadedFiles[field.name]?.length > 0) {
122
+ if (!hasError) validFields++
123
+ }
124
+ } else {
125
+ if (value && !hasError && value.toString().trim() !== "") {
126
+ validFields++
127
+ }
128
+ }
129
+ })
130
+
131
+ return totalFields > 0 ? (validFields / totalFields) * 100 : 0
132
+ }
133
+
134
+ const progress = calculateProgress()
135
+
136
+ // Check if all required fields are valid
137
+ const isFormValid = () => {
138
+ for (const field of fields) {
139
+ if (field.required) {
140
+ const value = watchedValues[field.name]
141
+ const hasError = errors[field.name]
142
+
143
+ if (hasError) return false
144
+
145
+ if (field.type === "checkbox" && value !== true) return false
146
+ if (field.type === "file" && (!uploadedFiles[field.name] || uploadedFiles[field.name].length === 0)) return false
147
+ if (!value || (typeof value === 'string' && value.trim() === '')) return false
148
+ }
149
+ }
150
+ return true
151
+ }
83
152
 
84
153
  const handleFormSubmit = async (data: any) => {
85
154
  setIsSubmitting(true)
@@ -144,7 +213,7 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
144
213
  </div>
145
214
 
146
215
  <div className="relative">
147
- {field.type === "text" || field.type === "email" || field.type === "number" || field.type === "url" || field.type === "tel" && (
216
+ {(field.type === "text" || field.type === "email" || field.type === "number" || field.type === "url") && (
148
217
  <Input
149
218
  id={field.name}
150
219
  type={field.type}
@@ -163,12 +232,58 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
163
232
  pattern: field.validation?.pattern ? {
164
233
  value: field.validation.pattern,
165
234
  message: "Invalid format"
235
+ } : field.type === "email" ? {
236
+ value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
237
+ message: "Please enter a valid email address"
238
+ } : field.type === "url" ? {
239
+ value: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
240
+ message: "Please enter a valid URL"
166
241
  } : undefined,
167
242
  validate: field.validation?.custom
168
243
  })}
169
244
  />
170
245
  )}
171
246
 
247
+ {field.type === "tel" && (
248
+ <PhoneNumberInput
249
+ id={field.name}
250
+ placeholder={field.placeholder}
251
+ className={cn(error && "border-destructive")}
252
+ value={phoneNumbers[field.name] || undefined}
253
+ onChange={(phoneData: { country: string; number: string; fullNumber: string; isValid: boolean }) => {
254
+ // Update local state
255
+ setPhoneNumbers(prev => ({
256
+ ...prev,
257
+ [field.name]: { country: phoneData.country, number: phoneData.number }
258
+ }))
259
+
260
+ // Update form value
261
+ setValue(field.name, phoneData.fullNumber)
262
+ clearErrors(field.name)
263
+
264
+ // Check if required
265
+ if (field.required && !phoneData.fullNumber) {
266
+ setError(field.name, { type: 'required', message: `${field.label} is required` })
267
+ }
268
+
269
+ // Check custom validation
270
+ if (phoneData.fullNumber && field.validation?.custom) {
271
+ const validationResult = field.validation.custom(phoneData.fullNumber)
272
+ if (validationResult !== true) {
273
+ setError(field.name, { type: 'custom', message: validationResult })
274
+ }
275
+ }
276
+ }}
277
+ defaultCountry={field.defaultCountry || "US"}
278
+ showFlags={true}
279
+ showDialCode={true}
280
+ autoFormat={true}
281
+ validateOnChange={true}
282
+ showValidationIcon={true}
283
+ error={error?.message as string}
284
+ />
285
+ )}
286
+
172
287
  {field.type === "password" && (
173
288
  <div className="relative">
174
289
  <Input
@@ -181,7 +296,16 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
181
296
  minLength: field.validation?.minLength ? {
182
297
  value: field.validation.minLength,
183
298
  message: `Minimum ${field.validation.minLength} characters required`
184
- } : undefined
299
+ } : undefined,
300
+ maxLength: field.validation?.maxLength ? {
301
+ value: field.validation.maxLength,
302
+ message: `Maximum ${field.validation.maxLength} characters allowed`
303
+ } : undefined,
304
+ pattern: field.validation?.pattern ? {
305
+ value: field.validation.pattern,
306
+ message: "Invalid format"
307
+ } : undefined,
308
+ validate: field.validation?.custom
185
309
  })}
186
310
  />
187
311
  <Button
@@ -208,17 +332,29 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
208
332
  rows={3}
209
333
  {...register(field.name, {
210
334
  required: field.required ? `${field.label} is required` : false,
335
+ minLength: field.validation?.minLength ? {
336
+ value: field.validation.minLength,
337
+ message: `Minimum ${field.validation.minLength} characters required`
338
+ } : undefined,
211
339
  maxLength: field.validation?.maxLength ? {
212
340
  value: field.validation.maxLength,
213
341
  message: `Maximum ${field.validation.maxLength} characters allowed`
214
- } : undefined
342
+ } : undefined,
343
+ validate: field.validation?.custom
215
344
  })}
216
345
  />
217
346
  )}
218
347
 
219
348
  {field.type === "select" && (
220
349
  <Select
221
- onValueChange={(value) => setValue(field.name, value)}
350
+ onValueChange={(value) => {
351
+ setValue(field.name, value)
352
+ if (field.required && !value) {
353
+ setError(field.name, { type: 'required', message: `${field.label} is required` })
354
+ } else {
355
+ clearErrors(field.name)
356
+ }
357
+ }}
222
358
  defaultValue={field.defaultValue}
223
359
  >
224
360
  <SelectTrigger className={cn(error && "border-destructive")}>
@@ -238,10 +374,20 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
238
374
  <div className="flex items-center space-x-2">
239
375
  <Checkbox
240
376
  id={field.name}
241
- {...register(field.name)}
242
- onCheckedChange={(checked: boolean) => setValue(field.name, checked)}
377
+ {...register(field.name, {
378
+ required: field.required ? `${field.label} must be checked` : false
379
+ })}
380
+ onCheckedChange={(checked: boolean) => {
381
+ setValue(field.name, checked)
382
+ if (field.required && !checked) {
383
+ setError(field.name, { type: 'required', message: `${field.label} must be checked` })
384
+ } else {
385
+ clearErrors(field.name)
386
+ }
387
+ }}
388
+ defaultChecked={field.defaultValue}
243
389
  />
244
- <Label htmlFor={field.name} className="text-sm">
390
+ <Label htmlFor={field.name} className="text-sm cursor-pointer">
245
391
  {field.placeholder || "Check this option"}
246
392
  </Label>
247
393
  </div>
@@ -253,6 +399,7 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
253
399
  id={field.name}
254
400
  {...register(field.name)}
255
401
  onCheckedChange={(checked: boolean) => setValue(field.name, checked)}
402
+ defaultChecked={field.defaultValue}
256
403
  />
257
404
  <Label htmlFor={field.name} className="text-sm">
258
405
  {field.placeholder || "Toggle this option"}
@@ -328,7 +475,7 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
328
475
 
329
476
  return (
330
477
  <Card className={cn("w-full max-w-4xl", className)}>
331
- <CardHeader>
478
+ <CardHeader className={cn(showProgress && "pb-0")}>
332
479
  <div className="flex items-center justify-between">
333
480
  <div>
334
481
  <CardTitle>{title}</CardTitle>
@@ -340,7 +487,7 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
340
487
  </div>
341
488
 
342
489
  {showProgress && (
343
- <div className="space-y-2">
490
+ <div className="space-y-2 mt-4">
344
491
  <div className="flex justify-between text-sm">
345
492
  <span>Progress</span>
346
493
  <span>{Math.round(progress)}%</span>
@@ -357,7 +504,7 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
357
504
  )}
358
505
  </CardHeader>
359
506
 
360
- <CardContent>
507
+ <CardContent className={cn(showProgress && "pt-8")}>
361
508
  <form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-6">
362
509
  <div className={gridClassName}>
363
510
  {fields.map((field, index) => renderField(field, index))}
@@ -365,12 +512,24 @@ const AdvancedFormsInternal: React.FC<AdvancedFormsProps> = ({
365
512
 
366
513
  <div className="flex items-center justify-between pt-6">
367
514
  <div className="text-sm text-muted-foreground">
368
- {completedFields}/{fields.length} fields completed
515
+ {(() => {
516
+ const requiredFields = fields.filter(f => f.required).length
517
+ const completedRequired = fields.filter(f => {
518
+ if (!f.required) return false
519
+ const value = watchedValues[f.name]
520
+ const hasError = errors[f.name]
521
+ if (hasError) return false
522
+ if (f.type === "checkbox") return value === true
523
+ if (f.type === "file") return uploadedFiles[f.name]?.length > 0
524
+ return value && value.toString().trim() !== ""
525
+ }).length
526
+ return `${completedRequired}/${requiredFields} required fields completed`
527
+ })()}
369
528
  </div>
370
529
 
371
530
  <Button
372
531
  type="submit"
373
- disabled={isSubmitting || !isValid}
532
+ disabled={isSubmitting || !isFormValid()}
374
533
  className="min-w-32"
375
534
  >
376
535
  {isSubmitting ? (
@@ -36,9 +36,10 @@ const EVENT_TYPES = [
36
36
  ] as const
37
37
 
38
38
  const PRESET_COLORS = [
39
- '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444',
40
- '#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1',
41
- '#14b8a6', '#eab308', '#f43f5e', '#a855f7', '#22c55e',
39
+ '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6',
40
+ '#ef4444', '#06b6d4', '#84cc16', '#f97316',
41
+ '#ec4899', '#6366f1', '#14b8a6', '#eab308',
42
+ '#f43f5e', '#a855f7', '#22c55e', '#64748b',
42
43
  ]
43
44
 
44
45
  export function EventDialog({
@@ -303,24 +304,28 @@ export function EventDialog({
303
304
  <Palette className="w-4 h-4" />
304
305
  Color
305
306
  </Label>
306
- <div className="flex items-center gap-2">
307
- <input
308
- type="color"
309
- value={formData.color}
310
- onChange={(e) => setFormData(prev => ({ ...prev, color: e.target.value }))}
311
- className="w-8 h-8 rounded border cursor-pointer"
312
- />
313
- <div className="flex gap-1">
307
+ <div className="space-y-2">
308
+ <div className="flex items-center gap-2">
309
+ <input
310
+ type="color"
311
+ value={formData.color}
312
+ onChange={(e) => setFormData(prev => ({ ...prev, color: e.target.value }))}
313
+ className="w-8 h-8 rounded border cursor-pointer flex-shrink-0"
314
+ />
315
+ <span className="text-sm text-muted-foreground">Custom color</span>
316
+ </div>
317
+ <div className="grid grid-cols-8 gap-1">
314
318
  {PRESET_COLORS.map((color) => (
315
319
  <button
316
320
  key={color}
317
321
  type="button"
318
322
  onClick={() => setFormData(prev => ({ ...prev, color }))}
319
323
  className={cn(
320
- "w-6 h-6 rounded border-2 hover:scale-110 transition-transform",
321
- formData.color === color ? "border-foreground" : "border-transparent"
324
+ "w-full aspect-square rounded border-2 hover:scale-110 transition-transform",
325
+ formData.color === color ? "border-foreground shadow-sm" : "border-transparent"
322
326
  )}
323
327
  style={{ backgroundColor: color }}
328
+ aria-label={`Select color ${color}`}
324
329
  />
325
330
  ))}
326
331
  </div>