@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,557 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
5
+ import { Button } from '../ui/button'
6
+ import { Badge } from '../ui/badge'
7
+ import {
8
+ ChevronLeft,
9
+ ChevronRight,
10
+ Calendar as CalendarIcon,
11
+ Plus,
12
+ Edit,
13
+ Trash2,
14
+ Clock,
15
+ MapPin,
16
+ User,
17
+ Lock,
18
+ Sparkles
19
+ } from 'lucide-react'
20
+ import { cn } from '../../lib/utils'
21
+ import { EventDialog } from './event-dialog'
22
+ import { withLicenseGuard } from '../../utils/license-guard'
23
+
24
+ export interface CalendarEvent {
25
+ id: string
26
+ title: string
27
+ description?: string
28
+ date: Date
29
+ startTime?: string
30
+ endTime?: string
31
+ location?: string
32
+ attendees?: string[]
33
+ color?: string
34
+ type?: 'meeting' | 'task' | 'reminder' | 'event'
35
+ }
36
+
37
+ export interface CalendarProps {
38
+ events?: CalendarEvent[]
39
+ onEventClick?: (event: CalendarEvent) => void
40
+ onEventAdd?: (eventData: Omit<CalendarEvent, 'id'> & { id?: string }) => void
41
+ onEventEdit?: (eventData: Omit<CalendarEvent, 'id'> & { id: string }) => void
42
+ onEventDelete?: (eventId: string) => void
43
+ className?: string
44
+ showWeekends?: boolean
45
+ showEventDetails?: boolean
46
+ disabled?: boolean
47
+ minDate?: Date
48
+ maxDate?: Date
49
+ highlightToday?: boolean
50
+ height?: number
51
+ // For NPM package usage - allows external control of pro access
52
+ showProUpgrade?: boolean
53
+ }
54
+
55
+ const DAYS_OF_WEEK = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
56
+ const MONTHS = [
57
+ 'January', 'February', 'March', 'April', 'May', 'June',
58
+ 'July', 'August', 'September', 'October', 'November', 'December'
59
+ ]
60
+
61
+ const EVENT_COLORS = {
62
+ meeting: 'bg-blue-500',
63
+ task: 'bg-green-500',
64
+ reminder: 'bg-yellow-500',
65
+ event: 'bg-purple-500'
66
+ }
67
+
68
+ export function Calendar({
69
+ events = [],
70
+ onEventClick,
71
+ onEventAdd,
72
+ onEventEdit,
73
+ onEventDelete,
74
+ className,
75
+ showWeekends = true,
76
+ showEventDetails = true,
77
+ disabled = false,
78
+ minDate,
79
+ maxDate,
80
+ highlightToday = true,
81
+ height,
82
+ showProUpgrade = false
83
+ }: CalendarProps) {
84
+ // For NPM package usage, show upgrade prompt if specified
85
+ if (showProUpgrade) {
86
+ return (
87
+ <Card className={cn("w-full", className)}>
88
+ <CardContent className="py-12 text-center">
89
+ <div className="max-w-md mx-auto space-y-4">
90
+ <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
91
+ <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
92
+ </div>
93
+ <div>
94
+ <h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
95
+ <p className="text-muted-foreground text-sm mb-4">
96
+ Calendar is available exclusively to MoonUI Pro subscribers.
97
+ </p>
98
+ <div className="flex gap-3 justify-center">
99
+ <a href="/pricing">
100
+ <Button size="sm">
101
+ <Sparkles className="mr-2 h-4 w-4" />
102
+ Upgrade to Pro
103
+ </Button>
104
+ </a>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </CardContent>
109
+ </Card>
110
+ )
111
+ }
112
+
113
+ const [currentDate, setCurrentDate] = React.useState(new Date())
114
+ const [selectedDate, setSelectedDate] = React.useState<Date | null>(null)
115
+ const [view, setView] = React.useState<'month' | 'week' | 'day'>('month')
116
+ const [eventDialogOpen, setEventDialogOpen] = React.useState(false)
117
+ const [eventDialogMode, setEventDialogMode] = React.useState<'create' | 'edit'>('create')
118
+ const [selectedEvent, setSelectedEvent] = React.useState<CalendarEvent | null>(null)
119
+ const [draggedEvent, setDraggedEvent] = React.useState<CalendarEvent | null>(null)
120
+ const [dragTargetDate, setDragTargetDate] = React.useState<Date | null>(null)
121
+
122
+ const today = new Date()
123
+ const currentMonth = currentDate.getMonth()
124
+ const currentYear = currentDate.getFullYear()
125
+
126
+ const firstDayOfMonth = new Date(currentYear, currentMonth, 1)
127
+ const lastDayOfMonth = new Date(currentYear, currentMonth + 1, 0)
128
+ const startDate = new Date(firstDayOfMonth)
129
+ startDate.setDate(startDate.getDate() - startDate.getDay())
130
+
131
+ const endDate = new Date(lastDayOfMonth)
132
+ endDate.setDate(endDate.getDate() + (6 - endDate.getDay()))
133
+
134
+ const calendarDays = []
135
+ const currentDateIterator = new Date(startDate)
136
+
137
+ while (currentDateIterator <= endDate) {
138
+ calendarDays.push(new Date(currentDateIterator))
139
+ currentDateIterator.setDate(currentDateIterator.getDate() + 1)
140
+ }
141
+
142
+ const getEventsForDate = (date: Date) => {
143
+ return events.filter(event => {
144
+ const eventDate = new Date(event.date)
145
+ return eventDate.toDateString() === date.toDateString()
146
+ })
147
+ }
148
+
149
+ const isToday = (date: Date) => {
150
+ return date.toDateString() === today.toDateString()
151
+ }
152
+
153
+ const isCurrentMonth = (date: Date) => {
154
+ return date.getMonth() === currentMonth
155
+ }
156
+
157
+ const isSelected = (date: Date) => {
158
+ return selectedDate && date.toDateString() === selectedDate.toDateString()
159
+ }
160
+
161
+ const isDisabled = (date: Date) => {
162
+ if (disabled) return true
163
+ if (minDate && date < minDate) return true
164
+ if (maxDate && date > maxDate) return true
165
+ return false
166
+ }
167
+
168
+ const navigateMonth = (direction: 'prev' | 'next') => {
169
+ const newDate = new Date(currentDate)
170
+ if (direction === 'prev') {
171
+ newDate.setMonth(currentMonth - 1)
172
+ } else {
173
+ newDate.setMonth(currentMonth + 1)
174
+ }
175
+ setCurrentDate(newDate)
176
+ }
177
+
178
+ const handleDateClick = (date: Date) => {
179
+ if (isDisabled(date)) return
180
+
181
+ setSelectedDate(date)
182
+ const dateEvents = getEventsForDate(date)
183
+
184
+ if (dateEvents.length === 0) {
185
+ // Open dialog for creating new event
186
+ setEventDialogMode('create')
187
+ setSelectedEvent(null)
188
+ setEventDialogOpen(true)
189
+ } else if (dateEvents.length === 1) {
190
+ onEventClick?.(dateEvents[0])
191
+ }
192
+ }
193
+
194
+ const handleEventClick = (event: CalendarEvent, e: React.MouseEvent) => {
195
+ e.stopPropagation()
196
+ onEventClick?.(event)
197
+ }
198
+
199
+ const handleEventDragStart = (event: CalendarEvent, e: React.DragEvent) => {
200
+ setDraggedEvent(event)
201
+ e.dataTransfer.effectAllowed = 'move'
202
+ e.dataTransfer.setData('text/plain', event.id)
203
+
204
+ // Add visual feedback
205
+ const target = e.target as HTMLElement
206
+ target.style.opacity = '0.5'
207
+ }
208
+
209
+ const handleEventDragEnd = (e: React.DragEvent) => {
210
+ setDraggedEvent(null)
211
+ setDragTargetDate(null)
212
+
213
+ // Remove visual feedback
214
+ const target = e.target as HTMLElement
215
+ target.style.opacity = '1'
216
+ }
217
+
218
+ const handleDateDragOver = (date: Date, e: React.DragEvent) => {
219
+ if (isDisabled(date) || !draggedEvent) return
220
+ e.preventDefault()
221
+ e.dataTransfer.dropEffect = 'move'
222
+ setDragTargetDate(date)
223
+ }
224
+
225
+ const handleDateDrop = (date: Date, e: React.DragEvent) => {
226
+ e.preventDefault()
227
+
228
+ if (!draggedEvent || isDisabled(date)) return
229
+
230
+ // Only move if it's a different date
231
+ if (draggedEvent.date.toDateString() !== date.toDateString()) {
232
+ const updatedEvent = {
233
+ ...draggedEvent,
234
+ date: date
235
+ }
236
+
237
+ onEventEdit?.(updatedEvent)
238
+ }
239
+
240
+ setDraggedEvent(null)
241
+ setDragTargetDate(null)
242
+ }
243
+
244
+ const handleEventEdit = (event: CalendarEvent, e: React.MouseEvent) => {
245
+ e.stopPropagation()
246
+ setEventDialogMode('edit')
247
+ setSelectedEvent(event)
248
+ setEventDialogOpen(true)
249
+ }
250
+
251
+ const handleEventDelete = (event: CalendarEvent, e: React.MouseEvent) => {
252
+ e.stopPropagation()
253
+ onEventDelete?.(event.id)
254
+ }
255
+
256
+ const handleEventSave = (eventData: Omit<CalendarEvent, 'id'> & { id?: string }) => {
257
+ if (eventDialogMode === 'create') {
258
+ onEventAdd?.(eventData)
259
+ } else if (eventDialogMode === 'edit' && eventData.id) {
260
+ onEventEdit?.(eventData as Omit<CalendarEvent, 'id'> & { id: string })
261
+ }
262
+ }
263
+
264
+ const handleEventDialogDelete = (eventId: string) => {
265
+ onEventDelete?.(eventId)
266
+ }
267
+
268
+ const goToToday = () => {
269
+ setCurrentDate(new Date())
270
+ setSelectedDate(new Date())
271
+ }
272
+
273
+ const filteredDays = showWeekends ? calendarDays : calendarDays.filter(day => {
274
+ const dayOfWeek = day.getDay()
275
+ return dayOfWeek !== 0 && dayOfWeek !== 6
276
+ })
277
+
278
+ const visibleDaysOfWeek = showWeekends ? DAYS_OF_WEEK : DAYS_OF_WEEK.slice(1, 6)
279
+
280
+ return (
281
+ <>
282
+ <Card className={cn("w-full max-w-full overflow-hidden", className)} style={{ height: height ? `${height}px` : undefined }}>
283
+ <CardHeader>
284
+ <div className="flex items-center justify-between">
285
+ <div>
286
+ <CardTitle className="flex items-center gap-2">
287
+ <CalendarIcon className="h-5 w-5" />
288
+ Calendar
289
+ </CardTitle>
290
+ <CardDescription>
291
+ {MONTHS[currentMonth]} {currentYear}
292
+ </CardDescription>
293
+ </div>
294
+ <div className="flex items-center gap-2">
295
+ <Button variant="outline" size="sm" onClick={goToToday}>
296
+ Today
297
+ </Button>
298
+ <Button
299
+ variant="outline"
300
+ size="sm"
301
+ onClick={() => navigateMonth('prev')}
302
+ disabled={disabled}
303
+ >
304
+ <ChevronLeft className="h-4 w-4" />
305
+ </Button>
306
+ <Button
307
+ variant="outline"
308
+ size="sm"
309
+ onClick={() => navigateMonth('next')}
310
+ disabled={disabled}
311
+ >
312
+ <ChevronRight className="h-4 w-4" />
313
+ </Button>
314
+ </div>
315
+ </div>
316
+ </CardHeader>
317
+ <CardContent className="overflow-x-auto">
318
+ <div className="space-y-4">
319
+ {/* Calendar Grid */}
320
+ <div className="grid grid-cols-7 gap-1 w-full" style={{ minWidth: "500px" }}>
321
+ {/* Day Headers */}
322
+ {visibleDaysOfWeek.map((day) => (
323
+ <div key={day} className="p-1 text-center text-xs font-medium text-muted-foreground min-w-[70px]">
324
+ {day}
325
+ </div>
326
+ ))}
327
+
328
+ {/* Calendar Days */}
329
+ {filteredDays.map((date, index) => {
330
+ const dayEvents = getEventsForDate(date)
331
+ const isCurrentMonthDate = isCurrentMonth(date)
332
+ const isTodayDate = isToday(date)
333
+ const isSelectedDate = isSelected(date)
334
+ const isDisabledDate = isDisabled(date)
335
+
336
+ return (
337
+ <div
338
+ key={index}
339
+ className={cn(
340
+ "min-h-[80px] min-w-[70px] p-1 border border-border/50 cursor-pointer hover:bg-muted/50 transition-colors text-xs flex flex-col",
341
+ !isCurrentMonthDate && "text-muted-foreground bg-muted/20",
342
+ isTodayDate && highlightToday && "bg-primary/10 border-primary/50",
343
+ isSelectedDate && "bg-primary/20 border-primary",
344
+ isDisabledDate && "cursor-not-allowed opacity-50",
345
+ dragTargetDate && dragTargetDate.toDateString() === date.toDateString() && "bg-primary/30 border-primary"
346
+ )}
347
+ onClick={() => handleDateClick(date)}
348
+ onDragOver={(e) => handleDateDragOver(date, e)}
349
+ onDrop={(e) => handleDateDrop(date, e)}
350
+ >
351
+ <div className="flex items-center justify-between mb-1">
352
+ <span className={cn(
353
+ "text-sm font-medium",
354
+ isTodayDate && "text-primary font-bold"
355
+ )}>
356
+ {date.getDate()}
357
+ </span>
358
+ {dayEvents.length > 0 && (
359
+ <Badge variant="secondary" className="text-xs px-1">
360
+ {dayEvents.length}
361
+ </Badge>
362
+ )}
363
+ </div>
364
+
365
+ {/* Events */}
366
+ <div className="flex-1 space-y-1 overflow-hidden">
367
+ {dayEvents.slice(0, 3).map((event) => (
368
+ <div
369
+ key={event.id}
370
+ className={cn(
371
+ "text-xs p-1 mb-1 rounded text-white cursor-move group relative select-none block w-full truncate",
372
+ event.color || EVENT_COLORS[event.type || 'event']
373
+ )}
374
+ draggable={!disabled}
375
+ onClick={(e) => handleEventClick(event, e)}
376
+ onDragStart={(e) => handleEventDragStart(event, e)}
377
+ onDragEnd={handleEventDragEnd}
378
+ style={{
379
+ backgroundColor: event.color || undefined
380
+ }}
381
+ >
382
+ <div className="flex items-center justify-between">
383
+ <span className="truncate flex-1">{event.title}</span>
384
+ {showEventDetails && (
385
+ <div className="hidden group-hover:flex items-center gap-1 ml-1">
386
+ <Button
387
+ variant="ghost"
388
+ size="sm"
389
+ className="h-4 w-4 p-0 text-white/80 hover:text-white"
390
+ onClick={(e) => handleEventEdit(event, e)}
391
+ >
392
+ <Edit className="h-3 w-3" />
393
+ </Button>
394
+ <Button
395
+ variant="ghost"
396
+ size="sm"
397
+ className="h-4 w-4 p-0 text-white/80 hover:text-white"
398
+ onClick={(e) => handleEventDelete(event, e)}
399
+ >
400
+ <Trash2 className="h-3 w-3" />
401
+ </Button>
402
+ </div>
403
+ )}
404
+ </div>
405
+ {event.startTime && (
406
+ <div className="flex items-center gap-1 mt-1">
407
+ <Clock className="h-3 w-3" />
408
+ <span>{event.startTime}</span>
409
+ </div>
410
+ )}
411
+ </div>
412
+ ))}
413
+ {dayEvents.length > 3 && (
414
+ <div className="text-xs text-muted-foreground text-center">
415
+ +{dayEvents.length - 3} more
416
+ </div>
417
+ )}
418
+ </div>
419
+ </div>
420
+ )
421
+ })}
422
+ </div>
423
+
424
+ {/* Selected Date Details */}
425
+ {selectedDate && (
426
+ <div className="border rounded-lg p-4 bg-muted/50">
427
+ <h4 className="font-medium mb-2">
428
+ {selectedDate.toLocaleDateString('en-US', {
429
+ weekday: 'long',
430
+ year: 'numeric',
431
+ month: 'long',
432
+ day: 'numeric'
433
+ })}
434
+ </h4>
435
+
436
+ <div className="space-y-2">
437
+ {getEventsForDate(selectedDate).map((event) => (
438
+ <div key={event.id} className="flex items-start gap-3 p-2 border rounded">
439
+ <div className={cn(
440
+ "w-3 h-3 rounded-full mt-1 flex-shrink-0",
441
+ event.color || EVENT_COLORS[event.type || 'event']
442
+ )} />
443
+ <div className="flex-1">
444
+ <div className="flex items-center justify-between">
445
+ <h5 className="font-medium">{event.title}</h5>
446
+ <div className="flex items-center gap-1">
447
+ <Button
448
+ variant="ghost"
449
+ size="sm"
450
+ onClick={(e) => handleEventEdit(event, e)}
451
+ >
452
+ <Edit className="h-4 w-4" />
453
+ </Button>
454
+ <Button
455
+ variant="ghost"
456
+ size="sm"
457
+ onClick={(e) => handleEventDelete(event, e)}
458
+ >
459
+ <Trash2 className="h-4 w-4" />
460
+ </Button>
461
+ </div>
462
+ </div>
463
+ {event.description && (
464
+ <p className="text-sm text-muted-foreground mt-1">
465
+ {event.description}
466
+ </p>
467
+ )}
468
+ <div className="flex items-center gap-4 text-xs text-muted-foreground mt-2">
469
+ {event.startTime && (
470
+ <div className="flex items-center gap-1">
471
+ <Clock className="h-3 w-3" />
472
+ <span>{event.startTime} - {event.endTime}</span>
473
+ </div>
474
+ )}
475
+ {event.location && (
476
+ <div className="flex items-center gap-1">
477
+ <MapPin className="h-3 w-3" />
478
+ <span>{event.location}</span>
479
+ </div>
480
+ )}
481
+ {event.attendees && event.attendees.length > 0 && (
482
+ <div className="flex items-center gap-1">
483
+ <User className="h-3 w-3" />
484
+ <span>{event.attendees.length} attendees</span>
485
+ </div>
486
+ )}
487
+ </div>
488
+ </div>
489
+ </div>
490
+ ))}
491
+
492
+ {getEventsForDate(selectedDate).length === 0 && (
493
+ <div className="text-center py-4">
494
+ <p className="text-sm text-muted-foreground mb-2">
495
+ No events scheduled for this date
496
+ </p>
497
+ <Button
498
+ variant="outline"
499
+ size="sm"
500
+ onClick={() => {
501
+ setEventDialogMode('create')
502
+ setSelectedEvent(null)
503
+ setEventDialogOpen(true)
504
+ }}
505
+ >
506
+ <Plus className="h-4 w-4 mr-1" />
507
+ Add Event
508
+ </Button>
509
+ </div>
510
+ )}
511
+ </div>
512
+ </div>
513
+ )}
514
+ </div>
515
+ </CardContent>
516
+ </Card>
517
+
518
+ {/* Event Dialog */}
519
+ <EventDialog
520
+ open={eventDialogOpen}
521
+ onOpenChange={setEventDialogOpen}
522
+ event={selectedEvent}
523
+ selectedDate={selectedDate}
524
+ onSave={handleEventSave}
525
+ onDelete={handleEventDialogDelete}
526
+ mode={eventDialogMode}
527
+ />
528
+ </>
529
+ )
530
+ }
531
+
532
+ // Export the component with license guard
533
+ const CalendarWithLicense = withLicenseGuard(Calendar, {
534
+ fallback: (
535
+ <Card className="w-full">
536
+ <CardHeader>
537
+ <CardTitle className="flex items-center gap-2">
538
+ <Lock className="h-5 w-5" />
539
+ Pro Component
540
+ </CardTitle>
541
+ <CardDescription>
542
+ This calendar component requires a MoonUI Pro license.
543
+ </CardDescription>
544
+ </CardHeader>
545
+ <CardContent className="text-center py-8">
546
+ <Button asChild>
547
+ <a href="https://moonui.dev/pricing">
548
+ Get Pro License
549
+ </a>
550
+ </Button>
551
+ </CardContent>
552
+ </Card>
553
+ )
554
+ });
555
+
556
+ export default CalendarWithLicense
557
+ export { Calendar as CalendarCore } // Export unguarded version for internal use