@moontra/moonui-pro 2.20.1 → 2.20.3

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 (162) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7418 -4934
  3. package/package.json +11 -5
  4. package/plugin/index.d.ts +86 -0
  5. package/plugin/index.js +308 -0
  6. package/scripts/postbuild.js +27 -0
  7. package/scripts/postinstall.js +176 -23
  8. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  9. package/src/__tests__/use-local-storage.test.tsx +0 -174
  10. package/src/__tests__/use-pro-access.test.tsx +0 -183
  11. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  12. package/src/components/advanced-chart/index.tsx +0 -1242
  13. package/src/components/advanced-forms/index.tsx +0 -426
  14. package/src/components/animated-button/index.tsx +0 -385
  15. package/src/components/calendar/event-dialog.tsx +0 -372
  16. package/src/components/calendar/index.tsx +0 -1073
  17. package/src/components/calendar-pro/index.tsx +0 -1697
  18. package/src/components/color-picker/index.tsx +0 -432
  19. package/src/components/credit-card-input/index.tsx +0 -406
  20. package/src/components/dashboard/dashboard-grid.tsx +0 -462
  21. package/src/components/dashboard/demo.tsx +0 -425
  22. package/src/components/dashboard/index.tsx +0 -1046
  23. package/src/components/dashboard/time-range-picker.tsx +0 -336
  24. package/src/components/dashboard/types.ts +0 -222
  25. package/src/components/dashboard/widgets/activity-feed.tsx +0 -344
  26. package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
  27. package/src/components/dashboard/widgets/metric-card.tsx +0 -343
  28. package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
  29. package/src/components/data-table/data-table-column-toggle.tsx +0 -169
  30. package/src/components/data-table/data-table-export.ts +0 -156
  31. package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
  32. package/src/components/data-table/data-table.test.tsx +0 -187
  33. package/src/components/data-table/index.tsx +0 -845
  34. package/src/components/draggable-list/index.tsx +0 -100
  35. package/src/components/enhanced/badge.tsx +0 -191
  36. package/src/components/enhanced/button.tsx +0 -362
  37. package/src/components/enhanced/card.tsx +0 -266
  38. package/src/components/enhanced/dialog.tsx +0 -246
  39. package/src/components/enhanced/index.ts +0 -4
  40. package/src/components/error-boundary/index.tsx +0 -109
  41. package/src/components/file-upload/file-upload.test.tsx +0 -243
  42. package/src/components/file-upload/index.tsx +0 -1660
  43. package/src/components/floating-action-button/index.tsx +0 -206
  44. package/src/components/form-wizard/form-wizard-context.tsx +0 -307
  45. package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
  46. package/src/components/form-wizard/form-wizard-progress.tsx +0 -298
  47. package/src/components/form-wizard/form-wizard-step.tsx +0 -111
  48. package/src/components/form-wizard/index.tsx +0 -102
  49. package/src/components/form-wizard/types.ts +0 -76
  50. package/src/components/gesture-drawer/index.tsx +0 -551
  51. package/src/components/github-stars/github-api.ts +0 -426
  52. package/src/components/github-stars/hooks.ts +0 -516
  53. package/src/components/github-stars/index.tsx +0 -375
  54. package/src/components/github-stars/types.ts +0 -148
  55. package/src/components/github-stars/variants.tsx +0 -513
  56. package/src/components/health-check/index.tsx +0 -439
  57. package/src/components/hover-card-3d/index.tsx +0 -530
  58. package/src/components/index.ts +0 -128
  59. package/src/components/internal/index.ts +0 -78
  60. package/src/components/kanban/add-card-modal.tsx +0 -502
  61. package/src/components/kanban/card-detail-modal.tsx +0 -761
  62. package/src/components/kanban/index.ts +0 -13
  63. package/src/components/kanban/kanban.tsx +0 -1684
  64. package/src/components/kanban/types.ts +0 -168
  65. package/src/components/lazy-component/index.tsx +0 -823
  66. package/src/components/license-error/index.tsx +0 -29
  67. package/src/components/magnetic-button/index.tsx +0 -167
  68. package/src/components/memory-efficient-data/index.tsx +0 -1016
  69. package/src/components/moonui-quiz-form/index.tsx +0 -817
  70. package/src/components/optimized-image/index.tsx +0 -425
  71. package/src/components/performance-debugger/index.tsx +0 -589
  72. package/src/components/performance-monitor/index.tsx +0 -794
  73. package/src/components/phone-number-input/index.tsx +0 -338
  74. package/src/components/pinch-zoom/index.tsx +0 -566
  75. package/src/components/quiz-form/index.tsx +0 -479
  76. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  77. package/src/components/rich-text-editor/index.tsx +0 -2324
  78. package/src/components/rich-text-editor/slash-commands-extension.ts +0 -220
  79. package/src/components/rich-text-editor/slash-commands.css +0 -35
  80. package/src/components/rich-text-editor/table-styles.css +0 -65
  81. package/src/components/sidebar/index.tsx +0 -865
  82. package/src/components/spotlight-card/index.tsx +0 -191
  83. package/src/components/swipeable-card/index.tsx +0 -100
  84. package/src/components/timeline/index.tsx +0 -1148
  85. package/src/components/ui/accordion.tsx +0 -73
  86. package/src/components/ui/alert-dialog.tsx +0 -141
  87. package/src/components/ui/alert.tsx +0 -141
  88. package/src/components/ui/aspect-ratio.tsx +0 -245
  89. package/src/components/ui/avatar.tsx +0 -153
  90. package/src/components/ui/badge.tsx +0 -228
  91. package/src/components/ui/breadcrumb.tsx +0 -214
  92. package/src/components/ui/button.tsx +0 -222
  93. package/src/components/ui/calendar.tsx +0 -387
  94. package/src/components/ui/card.tsx +0 -214
  95. package/src/components/ui/checkbox.tsx +0 -259
  96. package/src/components/ui/collapsible.tsx +0 -135
  97. package/src/components/ui/color-picker.tsx +0 -97
  98. package/src/components/ui/command.tsx +0 -225
  99. package/src/components/ui/dialog.tsx +0 -334
  100. package/src/components/ui/dropdown-menu.tsx +0 -218
  101. package/src/components/ui/gesture-drawer.tsx +0 -11
  102. package/src/components/ui/hover-card.tsx +0 -29
  103. package/src/components/ui/index.ts +0 -190
  104. package/src/components/ui/input.tsx +0 -222
  105. package/src/components/ui/label.tsx +0 -29
  106. package/src/components/ui/lightbox.tsx +0 -606
  107. package/src/components/ui/magnetic-button.tsx +0 -129
  108. package/src/components/ui/media-gallery.tsx +0 -612
  109. package/src/components/ui/pagination.tsx +0 -123
  110. package/src/components/ui/popover.tsx +0 -185
  111. package/src/components/ui/progress.tsx +0 -30
  112. package/src/components/ui/radio-group.tsx +0 -257
  113. package/src/components/ui/scroll-area.tsx +0 -47
  114. package/src/components/ui/select.tsx +0 -374
  115. package/src/components/ui/separator.tsx +0 -145
  116. package/src/components/ui/sheet.tsx +0 -139
  117. package/src/components/ui/skeleton.tsx +0 -20
  118. package/src/components/ui/slider.tsx +0 -354
  119. package/src/components/ui/spotlight-card.tsx +0 -119
  120. package/src/components/ui/switch.tsx +0 -86
  121. package/src/components/ui/table.tsx +0 -329
  122. package/src/components/ui/tabs.tsx +0 -198
  123. package/src/components/ui/textarea.tsx +0 -28
  124. package/src/components/ui/toast.tsx +0 -317
  125. package/src/components/ui/toggle.tsx +0 -119
  126. package/src/components/ui/tooltip.tsx +0 -151
  127. package/src/components/virtual-list/index.tsx +0 -668
  128. package/src/hooks/use-chart.ts +0 -205
  129. package/src/hooks/use-data-table.ts +0 -182
  130. package/src/hooks/use-docs-pro-access.ts +0 -13
  131. package/src/hooks/use-license-check.ts +0 -65
  132. package/src/hooks/use-subscription.ts +0 -19
  133. package/src/hooks/use-toast.ts +0 -15
  134. package/src/index.ts +0 -14
  135. package/src/lib/ai-providers.ts +0 -377
  136. package/src/lib/component-metadata.ts +0 -18
  137. package/src/lib/micro-interactions.ts +0 -255
  138. package/src/lib/paddle.ts +0 -17
  139. package/src/lib/utils.ts +0 -6
  140. package/src/patterns/login-form/index.tsx +0 -276
  141. package/src/patterns/login-form/types.ts +0 -67
  142. package/src/setupTests.ts +0 -41
  143. package/src/styles/advanced-chart.css +0 -239
  144. package/src/styles/calendar.css +0 -35
  145. package/src/styles/design-system.css +0 -363
  146. package/src/styles/index.css +0 -85
  147. package/src/styles/tailwind.css +0 -7
  148. package/src/styles/tokens.css +0 -455
  149. package/src/types/moonui.d.ts +0 -22
  150. package/src/types/next-auth.d.ts +0 -21
  151. package/src/use-intersection-observer.tsx +0 -154
  152. package/src/use-local-storage.tsx +0 -71
  153. package/src/use-paddle.ts +0 -138
  154. package/src/use-performance-optimizer.ts +0 -389
  155. package/src/use-pro-access.ts +0 -141
  156. package/src/use-scroll-animation.ts +0 -219
  157. package/src/use-subscription.ts +0 -37
  158. package/src/use-toast.ts +0 -32
  159. package/src/utils/chart-helpers.ts +0 -357
  160. package/src/utils/cn.ts +0 -6
  161. package/src/utils/data-processing.ts +0 -151
  162. package/src/utils/license-validator.tsx +0 -183
@@ -1,1148 +0,0 @@
1
- "use client"
2
-
3
- import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react'
4
- import { motion, AnimatePresence, Reorder } from 'framer-motion'
5
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
6
- import { MoonUIBadgePro as Badge } from '../ui/badge'
7
- import { MoonUIAvatarPro, MoonUIAvatarFallbackPro, MoonUIAvatarImagePro } from '../ui/avatar'
8
- import { Button } from '../ui/button'
9
- import { Input } from '../ui/input'
10
- import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
11
- import {
12
- Select,
13
- SelectContent,
14
- SelectItem,
15
- SelectTrigger,
16
- SelectValue,
17
- } from '../ui/select'
18
- import {
19
- DropdownMenu,
20
- DropdownMenuContent,
21
- DropdownMenuItem,
22
- DropdownMenuLabel,
23
- DropdownMenuSeparator,
24
- DropdownMenuTrigger,
25
- } from '../ui/dropdown-menu'
26
- import {
27
- Clock,
28
- CheckCircle2,
29
- AlertCircle,
30
- XCircle,
31
- Circle,
32
- Calendar,
33
- User,
34
- MessageCircle,
35
- Paperclip,
36
- ExternalLink,
37
- Lock,
38
- Sparkles,
39
- Filter,
40
- Search,
41
- Download,
42
- Printer,
43
- ChevronRight,
44
- ChevronDown,
45
- MoreVertical,
46
- Edit,
47
- Trash,
48
- Copy,
49
- Share,
50
- Flag,
51
- Zap,
52
- Target,
53
- TrendingUp,
54
- Repeat,
55
- GitBranch,
56
- Layers,
57
- ArrowRight,
58
- ArrowLeft,
59
- Maximize2,
60
- Minimize2
61
- } from 'lucide-react'
62
- import { cn } from '../../lib/utils'
63
- import { VariantProps, cva } from 'class-variance-authority'
64
-
65
- // Enhanced Event Types
66
- export type TimelineEventType = 'success' | 'warning' | 'error' | 'info' | 'pending' | 'milestone' | 'custom'
67
- export type TimelineLayout = 'vertical' | 'vertical-left' | 'vertical-right' | 'horizontal' | 'alternating' | 'grouped'
68
- export type TimelineTheme = 'default' | 'minimal' | 'detailed' | 'compact'
69
- export type TimelineAnimation = 'fade' | 'slide' | 'scale' | 'none'
70
- export type TimelineGroupBy = 'none' | 'date' | 'week' | 'month' | 'year'
71
-
72
- // Event interfaces
73
- export interface TimelineEventBase {
74
- id: string
75
- title: string
76
- description?: string
77
- date: Date
78
- type: TimelineEventType
79
- icon?: React.ReactNode
80
- color?: string
81
- metadata?: TimelineEventMetadata
82
- user?: TimelineUser
83
- }
84
-
85
- export interface TimelineEventMetadata {
86
- location?: string
87
- duration?: string
88
- tags?: string[]
89
- attachments?: number
90
- comments?: number
91
- externalLink?: string
92
- priority?: 'low' | 'medium' | 'high'
93
- progress?: number
94
- custom?: Record<string, any>
95
- }
96
-
97
- export interface TimelineUser {
98
- name: string
99
- avatar?: string
100
- email?: string
101
- role?: string
102
- }
103
-
104
- // Extended event types
105
- export interface MilestoneEvent extends TimelineEventBase {
106
- type: 'milestone'
107
- milestone: {
108
- target?: string
109
- achievement?: string
110
- impact?: string
111
- }
112
- }
113
-
114
- export interface RangeEvent extends TimelineEventBase {
115
- endDate: Date
116
- isActive?: boolean
117
- }
118
-
119
- export interface RecurringEvent extends TimelineEventBase {
120
- recurrence: {
121
- frequency: 'daily' | 'weekly' | 'monthly' | 'yearly'
122
- interval?: number
123
- endDate?: Date
124
- occurrences?: number
125
- }
126
- }
127
-
128
- export interface NestedEvent extends TimelineEventBase {
129
- subEvents?: TimelineEvent[]
130
- expanded?: boolean
131
- }
132
-
133
- export type TimelineEvent = TimelineEventBase | MilestoneEvent | RangeEvent | RecurringEvent | NestedEvent
134
-
135
- // Render function types
136
- export type EventRenderer = (event: TimelineEvent, index: number) => React.ReactNode
137
- export type ConnectorRenderer = (fromEvent: TimelineEvent, toEvent: TimelineEvent) => React.ReactNode
138
- export type IconRenderer = (event: TimelineEvent) => React.ReactNode
139
-
140
- // Timeline Props
141
- export interface TimelineProps extends VariantProps<typeof timelineVariants> {
142
- events: TimelineEvent[]
143
- onEventClick?: (event: TimelineEvent) => void
144
- onEventEdit?: (event: TimelineEvent) => void
145
- onEventDelete?: (event: TimelineEvent) => void
146
- onEventsReorder?: (events: TimelineEvent[]) => void
147
- className?: string
148
-
149
- // Layout options
150
- layout?: TimelineLayout
151
- theme?: TimelineTheme
152
- animation?: TimelineAnimation
153
- groupBy?: TimelineGroupBy
154
-
155
- // Display options
156
- showUserInfo?: boolean
157
- showMetadata?: boolean
158
- showRelativeTime?: boolean
159
- showSearch?: boolean
160
- showFilter?: boolean
161
- showExport?: boolean
162
-
163
- // Customization
164
- eventRenderer?: EventRenderer
165
- connectorRenderer?: ConnectorRenderer
166
- iconRenderer?: IconRenderer
167
- colorScheme?: Record<TimelineEventType, string>
168
-
169
- // Interactive features
170
- draggable?: boolean
171
- expandable?: boolean
172
- editable?: boolean
173
- selectable?: boolean
174
- virtualScroll?: boolean
175
-
176
- // Advanced features
177
- maxEventsPerGroup?: number
178
- printMode?: boolean
179
- compactMode?: boolean
180
- parallaxEffect?: boolean
181
- gradientConnectors?: boolean
182
- animatedProgress?: boolean
183
-
184
- // Accessibility
185
- ariaLabel?: string
186
- keyboardNavigation?: boolean
187
- }
188
-
189
- // Timeline variants
190
- const timelineVariants = cva("w-full", {
191
- variants: {
192
- theme: {
193
- default: "",
194
- minimal: "border-0 shadow-none",
195
- detailed: "border-2",
196
- compact: "p-0"
197
- }
198
- },
199
- defaultVariants: {
200
- theme: "default"
201
- }
202
- })
203
-
204
- // Default color schemes
205
- const DEFAULT_COLORS: Record<TimelineEventType, string> = {
206
- success: 'bg-green-500 border-green-500',
207
- warning: 'bg-yellow-500 border-yellow-500',
208
- error: 'bg-red-500 border-red-500',
209
- info: 'bg-blue-500 border-blue-500',
210
- pending: 'bg-muted-foreground/40 border-muted-foreground/40',
211
- milestone: 'bg-purple-500 border-purple-500',
212
- custom: 'bg-slate-500 border-slate-500'
213
- }
214
-
215
- const DEFAULT_ICONS: Record<TimelineEventType, React.ReactNode> = {
216
- success: <CheckCircle2 className="h-4 w-4 text-white" />,
217
- warning: <AlertCircle className="h-4 w-4 text-white" />,
218
- error: <XCircle className="h-4 w-4 text-white" />,
219
- info: <Circle className="h-4 w-4 text-white" />,
220
- pending: <Clock className="h-4 w-4 text-white" />,
221
- milestone: <Flag className="h-4 w-4 text-white" />,
222
- custom: <Sparkles className="h-4 w-4 text-white" />
223
- }
224
-
225
- const TEXT_COLORS: Record<TimelineEventType, string> = {
226
- success: 'text-green-700 dark:text-green-400',
227
- warning: 'text-yellow-700 dark:text-yellow-400',
228
- error: 'text-red-700 dark:text-red-400',
229
- info: 'text-blue-700 dark:text-blue-400',
230
- pending: 'text-muted-foreground',
231
- milestone: 'text-purple-700 dark:text-purple-400',
232
- custom: 'text-slate-700 dark:text-slate-400'
233
- }
234
-
235
- // Animation variants
236
- const animationVariants = {
237
- fade: {
238
- initial: { opacity: 0 },
239
- animate: { opacity: 1 },
240
- exit: { opacity: 0 }
241
- },
242
- slide: {
243
- initial: { opacity: 0, x: -20 },
244
- animate: { opacity: 1, x: 0 },
245
- exit: { opacity: 0, x: 20 }
246
- },
247
- scale: {
248
- initial: { opacity: 0, scale: 0.8 },
249
- animate: { opacity: 1, scale: 1 },
250
- exit: { opacity: 0, scale: 0.8 }
251
- }
252
- }
253
-
254
- export function Timeline({
255
- events: initialEvents,
256
- onEventClick,
257
- onEventEdit,
258
- onEventDelete,
259
- onEventsReorder,
260
- className,
261
- layout = 'vertical',
262
- theme = 'default',
263
- animation = 'fade',
264
- groupBy = 'none',
265
- showUserInfo = true,
266
- showMetadata = true,
267
- showRelativeTime = true,
268
- showSearch = true,
269
- showFilter = true,
270
- showExport = true,
271
- eventRenderer,
272
- connectorRenderer,
273
- iconRenderer,
274
- colorScheme = DEFAULT_COLORS,
275
- draggable = false,
276
- expandable = true,
277
- editable = false,
278
- selectable = false,
279
- virtualScroll = false,
280
- maxEventsPerGroup = 20,
281
- printMode = false,
282
- compactMode = false,
283
- parallaxEffect = false,
284
- gradientConnectors = false,
285
- animatedProgress = false,
286
- ariaLabel = "Timeline of events",
287
- keyboardNavigation = true,
288
- ...props
289
- }: TimelineProps) {
290
- const [events, setEvents] = useState<TimelineEvent[]>(initialEvents)
291
- const [searchQuery, setSearchQuery] = useState('')
292
- const [filterType, setFilterType] = useState<TimelineEventType | 'all'>('all')
293
- const [selectedEvents, setSelectedEvents] = useState<Set<string>>(new Set())
294
- const [expandedEvents, setExpandedEvents] = useState<Set<string>>(new Set())
295
- const [focusedEventId, setFocusedEventId] = useState<string | null>(null)
296
-
297
- const timelineRef = useRef<HTMLDivElement>(null)
298
- const eventRefs = useRef<Map<string, HTMLDivElement>>(new Map())
299
-
300
- // Update events when prop changes
301
- useEffect(() => {
302
- setEvents(initialEvents)
303
- }, [initialEvents])
304
-
305
- // Keyboard navigation
306
- useEffect(() => {
307
- if (!keyboardNavigation) return
308
-
309
- const handleKeyDown = (e: KeyboardEvent) => {
310
- const currentIndex = events.findIndex(event => event.id === focusedEventId)
311
-
312
- switch (e.key) {
313
- case 'ArrowDown':
314
- case 'ArrowRight':
315
- e.preventDefault()
316
- if (currentIndex < events.length - 1) {
317
- const nextEvent = events[currentIndex + 1]
318
- setFocusedEventId(nextEvent.id)
319
- eventRefs.current.get(nextEvent.id)?.focus()
320
- }
321
- break
322
- case 'ArrowUp':
323
- case 'ArrowLeft':
324
- e.preventDefault()
325
- if (currentIndex > 0) {
326
- const prevEvent = events[currentIndex - 1]
327
- setFocusedEventId(prevEvent.id)
328
- eventRefs.current.get(prevEvent.id)?.focus()
329
- }
330
- break
331
- case 'Enter':
332
- case ' ':
333
- e.preventDefault()
334
- if (focusedEventId) {
335
- const event = events.find(e => e.id === focusedEventId)
336
- if (event && onEventClick) {
337
- onEventClick(event)
338
- }
339
- }
340
- break
341
- case 'Delete':
342
- if (editable && focusedEventId && onEventDelete) {
343
- const event = events.find(e => e.id === focusedEventId)
344
- if (event) {
345
- onEventDelete(event)
346
- }
347
- }
348
- break
349
- }
350
- }
351
-
352
- timelineRef.current?.addEventListener('keydown', handleKeyDown)
353
- return () => timelineRef.current?.removeEventListener('keydown', handleKeyDown)
354
- }, [keyboardNavigation, events, focusedEventId, onEventClick, onEventDelete, editable])
355
-
356
- // Utility functions
357
- const formatDate = useCallback((date: Date) => {
358
- return date.toLocaleDateString('en-US', {
359
- year: 'numeric',
360
- month: 'long',
361
- day: 'numeric'
362
- })
363
- }, [])
364
-
365
- const formatTime = useCallback((date: Date) => {
366
- return date.toLocaleTimeString('en-US', {
367
- hour: '2-digit',
368
- minute: '2-digit',
369
- hour12: true
370
- })
371
- }, [])
372
-
373
- const getRelativeTime = useCallback((date: Date) => {
374
- const now = new Date()
375
- const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
376
-
377
- if (diffInSeconds < 0) {
378
- const futureDiff = Math.abs(diffInSeconds)
379
- if (futureDiff < 3600) {
380
- const minutes = Math.floor(futureDiff / 60)
381
- return `In ${minutes} minute${minutes !== 1 ? 's' : ''}`
382
- } else if (futureDiff < 86400) {
383
- const hours = Math.floor(futureDiff / 3600)
384
- return `In ${hours} hour${hours !== 1 ? 's' : ''}`
385
- } else {
386
- const days = Math.floor(futureDiff / 86400)
387
- return `In ${days} day${days !== 1 ? 's' : ''}`
388
- }
389
- }
390
-
391
- if (diffInSeconds < 60) {
392
- return 'Just now'
393
- } else if (diffInSeconds < 3600) {
394
- const minutes = Math.floor(diffInSeconds / 60)
395
- return `${minutes} minute${minutes > 1 ? 's' : ''} ago`
396
- } else if (diffInSeconds < 86400) {
397
- const hours = Math.floor(diffInSeconds / 3600)
398
- return `${hours} hour${hours > 1 ? 's' : ''} ago`
399
- } else if (diffInSeconds < 604800) {
400
- const days = Math.floor(diffInSeconds / 86400)
401
- return `${days} day${days > 1 ? 's' : ''} ago`
402
- } else {
403
- return formatDate(date)
404
- }
405
- }, [formatDate])
406
-
407
- const getInitials = useCallback((name: string) => {
408
- return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
409
- }, [])
410
-
411
- // Grouping functions
412
- const groupEventsByDate = useCallback((events: TimelineEvent[]) => {
413
- const groups: { [key: string]: TimelineEvent[] } = {}
414
-
415
- events.forEach(event => {
416
- let dateKey: string
417
-
418
- switch (groupBy) {
419
- case 'week':
420
- const weekStart = new Date(event.date)
421
- weekStart.setDate(weekStart.getDate() - weekStart.getDay())
422
- dateKey = `Week of ${formatDate(weekStart)}`
423
- break
424
- case 'month':
425
- dateKey = event.date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' })
426
- break
427
- case 'year':
428
- dateKey = event.date.getFullYear().toString()
429
- break
430
- case 'date':
431
- default:
432
- dateKey = formatDate(event.date)
433
- }
434
-
435
- if (!groups[dateKey]) {
436
- groups[dateKey] = []
437
- }
438
- groups[dateKey].push(event)
439
- })
440
-
441
- return groups
442
- }, [groupBy, formatDate])
443
-
444
- // Filtering and sorting
445
- const filteredAndSortedEvents = useMemo(() => {
446
- let filtered = [...events]
447
-
448
- // Apply search filter
449
- if (searchQuery) {
450
- filtered = filtered.filter(event =>
451
- event.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
452
- event.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
453
- event.metadata?.tags?.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
454
- )
455
- }
456
-
457
- // Apply type filter
458
- if (filterType !== 'all') {
459
- filtered = filtered.filter(event => event.type === filterType)
460
- }
461
-
462
- // Sort by date
463
- return filtered.sort((a, b) => b.date.getTime() - a.date.getTime())
464
- }, [events, searchQuery, filterType])
465
-
466
- // Event handlers
467
- const handleEventClick = useCallback((event: TimelineEvent) => {
468
- if (selectable) {
469
- setSelectedEvents(prev => {
470
- const newSet = new Set(prev)
471
- if (newSet.has(event.id)) {
472
- newSet.delete(event.id)
473
- } else {
474
- newSet.add(event.id)
475
- }
476
- return newSet
477
- })
478
- }
479
-
480
- if (expandable && 'subEvents' in event) {
481
- setExpandedEvents(prev => {
482
- const newSet = new Set(prev)
483
- if (newSet.has(event.id)) {
484
- newSet.delete(event.id)
485
- } else {
486
- newSet.add(event.id)
487
- }
488
- return newSet
489
- })
490
- }
491
-
492
- onEventClick?.(event)
493
- }, [selectable, expandable, onEventClick])
494
-
495
- const handleExport = useCallback(() => {
496
- const dataStr = JSON.stringify(filteredAndSortedEvents, null, 2)
497
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)
498
-
499
- const exportFileDefaultName = `timeline-export-${new Date().toISOString()}.json`
500
-
501
- const linkElement = document.createElement('a')
502
- linkElement.setAttribute('href', dataUri)
503
- linkElement.setAttribute('download', exportFileDefaultName)
504
- linkElement.click()
505
- }, [filteredAndSortedEvents])
506
-
507
- const handlePrint = useCallback(() => {
508
- window.print()
509
- }, [])
510
-
511
- // Render helpers
512
- const renderEventIcon = useCallback((event: TimelineEvent) => {
513
- if (iconRenderer) {
514
- return iconRenderer(event)
515
- }
516
-
517
- if (event.icon) {
518
- return event.icon
519
- }
520
-
521
- if ('milestone' in event) {
522
- return <Target className="h-4 w-4 text-white" />
523
- }
524
-
525
- if ('recurrence' in event) {
526
- return <Repeat className="h-4 w-4 text-white" />
527
- }
528
-
529
- if ('subEvents' in event) {
530
- return <GitBranch className="h-4 w-4 text-white" />
531
- }
532
-
533
- return DEFAULT_ICONS[event.type] || DEFAULT_ICONS.custom
534
- }, [iconRenderer])
535
-
536
- const renderConnector = useCallback((fromEvent: TimelineEvent, toEvent: TimelineEvent, isLast: boolean) => {
537
- if (connectorRenderer) {
538
- return connectorRenderer(fromEvent, toEvent)
539
- }
540
-
541
- if (isLast) return null
542
-
543
- const baseClasses = cn(
544
- "absolute",
545
- layout === 'horizontal' ? "h-0.5 top-4" : "w-0.5 left-1/2 -ml-px transform -translate-x-1/2",
546
- gradientConnectors && "bg-gradient-to-b from-border to-transparent"
547
- )
548
-
549
- if (layout === 'horizontal') {
550
- return <div className={cn(baseClasses, "bg-border")} style={{ width: '100px' }} />
551
- }
552
-
553
- return (
554
- <div className={cn(
555
- baseClasses,
556
- "bg-border",
557
- layout === 'alternating' && "hidden"
558
- )}
559
- style={{
560
- height: 'calc(100% + 2rem)',
561
- top: '2rem'
562
- }} />
563
- )
564
- }, [connectorRenderer, layout, gradientConnectors])
565
-
566
- const renderEvent = useCallback((event: TimelineEvent, index: number, isLast: boolean = false) => {
567
- const eventColor = event.color || colorScheme[event.type] || DEFAULT_COLORS[event.type]
568
- const textColor = TEXT_COLORS[event.type] || TEXT_COLORS.custom
569
- const isSelected = selectedEvents.has(event.id)
570
- const isExpanded = expandedEvents.has(event.id)
571
- const isFocused = focusedEventId === event.id
572
-
573
- // Custom renderer
574
- if (eventRenderer) {
575
- return eventRenderer(event, index)
576
- }
577
-
578
- // Check if event is a range event
579
- const isRangeEvent = 'endDate' in event
580
- const isNestedEvent = 'subEvents' in event
581
- const isMilestoneEvent = 'milestone' in event
582
-
583
- const eventContent = (
584
- <div
585
- ref={(el) => {
586
- if (el) eventRefs.current.set(event.id, el)
587
- }}
588
- tabIndex={keyboardNavigation ? 0 : -1}
589
- className={cn(
590
- "relative flex gap-4",
591
- compactMode ? "pb-2" : "pb-8",
592
- layout === 'alternating' && index % 2 === 0 && "flex-row-reverse",
593
- layout === 'horizontal' && "flex-col items-center",
594
- "cursor-pointer rounded-lg p-3 -m-1 transition-all duration-200",
595
- "hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-ring",
596
- isSelected && "bg-muted/70",
597
- isFocused && "ring-2 ring-ring",
598
- printMode && "print:break-inside-avoid"
599
- )}
600
- onClick={() => handleEventClick(event)}
601
- onFocus={() => setFocusedEventId(event.id)}
602
- role="button"
603
- aria-label={`Event: ${event.title}`}
604
- aria-expanded={isNestedEvent ? isExpanded : undefined}
605
- >
606
- {/* Timeline Node */}
607
- <div className={cn(
608
- "flex flex-col items-center relative",
609
- layout === 'horizontal' && "flex-row"
610
- )}>
611
- <motion.div
612
- className={cn(
613
- "flex items-center justify-center rounded-full border-2 bg-background z-10",
614
- isMilestoneEvent ? "w-10 h-10" : "w-8 h-8",
615
- eventColor,
616
- isSelected && "ring-2 ring-ring ring-offset-2"
617
- )}
618
- whileHover={{ scale: 1.1 }}
619
- whileTap={{ scale: 0.95 }}
620
- >
621
- {renderEventIcon(event)}
622
- </motion.div>
623
-
624
- {/* Connector */}
625
- {!isLast && renderConnector(event, events[index + 1], isLast)}
626
- </div>
627
-
628
- {/* Event Content */}
629
- <div className={cn(
630
- "flex-1 min-w-0",
631
- layout === 'alternating' && index % 2 === 0 && "text-right"
632
- )}>
633
- {/* Header */}
634
- <div className="flex items-start justify-between gap-2 mb-2">
635
- <div className="flex-1">
636
- <h4 className={cn(
637
- "font-semibold",
638
- compactMode ? "text-sm" : "text-base",
639
- isMilestoneEvent && "text-lg"
640
- )}>
641
- {event.title}
642
- {isMilestoneEvent && (
643
- <Badge variant="secondary" className="ml-2 bg-purple-500">
644
- Milestone
645
- </Badge>
646
- )}
647
- </h4>
648
-
649
- {/* Date/Time display */}
650
- <div className="flex items-center gap-2 mt-1 text-xs text-muted-foreground">
651
- <Clock className="h-3 w-3" />
652
- {showRelativeTime ? (
653
- <span>{getRelativeTime(event.date)}</span>
654
- ) : (
655
- <span>{formatTime(event.date)}</span>
656
- )}
657
- {isRangeEvent && (
658
- <>
659
- <ArrowRight className="h-3 w-3" />
660
- <span>{formatTime((event as RangeEvent).endDate)}</span>
661
- </>
662
- )}
663
- </div>
664
- </div>
665
-
666
- {/* Actions */}
667
- {(editable || event.metadata?.externalLink) && (
668
- <DropdownMenu>
669
- <DropdownMenuTrigger asChild>
670
- <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
671
- <MoreVertical className="h-4 w-4" />
672
- </Button>
673
- </DropdownMenuTrigger>
674
- <DropdownMenuContent align="end">
675
- {editable && (
676
- <>
677
- <DropdownMenuItem onClick={() => onEventEdit?.(event)}>
678
- <Edit className="mr-2 h-4 w-4" />
679
- Edit
680
- </DropdownMenuItem>
681
- <DropdownMenuItem onClick={() => onEventDelete?.(event)}>
682
- <Trash className="mr-2 h-4 w-4" />
683
- Delete
684
- </DropdownMenuItem>
685
- <DropdownMenuSeparator />
686
- </>
687
- )}
688
- <DropdownMenuItem onClick={() => navigator.clipboard.writeText(event.title)}>
689
- <Copy className="mr-2 h-4 w-4" />
690
- Copy
691
- </DropdownMenuItem>
692
- <DropdownMenuItem>
693
- <Share className="mr-2 h-4 w-4" />
694
- Share
695
- </DropdownMenuItem>
696
- {event.metadata?.externalLink && (
697
- <DropdownMenuItem onClick={() => window.open(event.metadata?.externalLink, '_blank')}>
698
- <ExternalLink className="mr-2 h-4 w-4" />
699
- View Details
700
- </DropdownMenuItem>
701
- )}
702
- </DropdownMenuContent>
703
- </DropdownMenu>
704
- )}
705
- </div>
706
-
707
- {/* Description */}
708
- {event.description && (
709
- <p className={cn(
710
- "text-muted-foreground mb-2",
711
- compactMode ? "text-xs" : "text-sm"
712
- )}>
713
- {event.description}
714
- </p>
715
- )}
716
-
717
- {/* Milestone details */}
718
- {isMilestoneEvent && (event as MilestoneEvent).milestone && (
719
- <div className="bg-purple-50 dark:bg-purple-950/20 rounded-md p-3 mb-2">
720
- {(event as MilestoneEvent).milestone.target && (
721
- <div className="flex items-center gap-2 text-sm">
722
- <Target className="h-4 w-4 text-purple-600" />
723
- <span className="font-medium">Target:</span>
724
- <span>{(event as MilestoneEvent).milestone.target}</span>
725
- </div>
726
- )}
727
- {(event as MilestoneEvent).milestone.achievement && (
728
- <div className="flex items-center gap-2 text-sm mt-1">
729
- <TrendingUp className="h-4 w-4 text-purple-600" />
730
- <span className="font-medium">Achievement:</span>
731
- <span>{(event as MilestoneEvent).milestone.achievement}</span>
732
- </div>
733
- )}
734
- </div>
735
- )}
736
-
737
- {/* Progress bar for events with progress */}
738
- {event.metadata?.progress !== undefined && (
739
- <div className="mb-2">
740
- <div className="flex justify-between text-xs mb-1">
741
- <span>Progress</span>
742
- <span>{event.metadata.progress}%</span>
743
- </div>
744
- <div className="w-full bg-muted rounded-full h-2 overflow-hidden">
745
- <motion.div
746
- className="h-full bg-primary"
747
- initial={{ width: 0 }}
748
- animate={{ width: `${event.metadata.progress}%` }}
749
- transition={{ duration: 0.5, ease: "easeOut" }}
750
- />
751
- </div>
752
- </div>
753
- )}
754
-
755
- {/* User info */}
756
- {showUserInfo && event.user && (
757
- <div className="flex items-center gap-2 mb-2">
758
- <MoonUIAvatarPro className="h-6 w-6">
759
- <MoonUIAvatarImagePro src={event.user.avatar} />
760
- <MoonUIAvatarFallbackPro className="text-xs">
761
- {getInitials(event.user.name)}
762
- </MoonUIAvatarFallbackPro>
763
- </MoonUIAvatarPro>
764
- <div className="flex flex-col">
765
- <span className="text-sm font-medium">{event.user.name}</span>
766
- {event.user.role && (
767
- <span className="text-xs text-muted-foreground">{event.user.role}</span>
768
- )}
769
- </div>
770
- </div>
771
- )}
772
-
773
- {/* Metadata */}
774
- {showMetadata && event.metadata && (
775
- <div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
776
- {event.metadata.duration && (
777
- <div className="flex items-center gap-1">
778
- <Clock className="h-3 w-3" />
779
- <span>{event.metadata.duration}</span>
780
- </div>
781
- )}
782
- {event.metadata.location && (
783
- <div className="flex items-center gap-1">
784
- <Calendar className="h-3 w-3" />
785
- <span>{event.metadata.location}</span>
786
- </div>
787
- )}
788
- {event.metadata.comments !== undefined && event.metadata.comments > 0 && (
789
- <div className="flex items-center gap-1">
790
- <MessageCircle className="h-3 w-3" />
791
- <span>{event.metadata.comments}</span>
792
- </div>
793
- )}
794
- {event.metadata.attachments !== undefined && event.metadata.attachments > 0 && (
795
- <div className="flex items-center gap-1">
796
- <Paperclip className="h-3 w-3" />
797
- <span>{event.metadata.attachments}</span>
798
- </div>
799
- )}
800
- {event.metadata.priority && (
801
- <Badge
802
- variant={
803
- event.metadata.priority === 'high' ? 'destructive' :
804
- event.metadata.priority === 'medium' ? 'outline' :
805
- 'secondary'
806
- }
807
- className="text-xs"
808
- >
809
- {event.metadata.priority}
810
- </Badge>
811
- )}
812
- </div>
813
- )}
814
-
815
- {/* Tags */}
816
- {event.metadata?.tags && event.metadata.tags.length > 0 && (
817
- <div className="flex flex-wrap gap-1 mt-2">
818
- {event.metadata.tags.map((tag, tagIndex) => (
819
- <Badge key={tagIndex} variant="outline" className="text-xs">
820
- {tag}
821
- </Badge>
822
- ))}
823
- </div>
824
- )}
825
-
826
- {/* Nested events */}
827
- {isNestedEvent && isExpanded && (event as NestedEvent).subEvents && (
828
- <div className="mt-3 ml-4 pl-4 border-l-2 border-muted">
829
- {(event as NestedEvent).subEvents!.map((subEvent, subIndex) => (
830
- <div key={subEvent.id} className="mb-2 last:mb-0">
831
- <div className="flex items-start gap-2">
832
- <div className={cn(
833
- "w-2 h-2 rounded-full mt-1.5",
834
- colorScheme[subEvent.type] || DEFAULT_COLORS[subEvent.type]
835
- )} />
836
- <div className="flex-1">
837
- <h5 className="text-sm font-medium">{subEvent.title}</h5>
838
- {subEvent.description && (
839
- <p className="text-xs text-muted-foreground">{subEvent.description}</p>
840
- )}
841
- <span className="text-xs text-muted-foreground">
842
- {getRelativeTime(subEvent.date)}
843
- </span>
844
- </div>
845
- </div>
846
- </div>
847
- ))}
848
- </div>
849
- )}
850
-
851
- {/* Expand/Collapse button for nested events */}
852
- {isNestedEvent && (event as NestedEvent).subEvents && (event as NestedEvent).subEvents!.length > 0 && (
853
- <Button
854
- variant="ghost"
855
- size="sm"
856
- className="mt-2 h-6 text-xs"
857
- onClick={(e) => {
858
- e.stopPropagation()
859
- setExpandedEvents(prev => {
860
- const newSet = new Set(prev)
861
- if (newSet.has(event.id)) {
862
- newSet.delete(event.id)
863
- } else {
864
- newSet.add(event.id)
865
- }
866
- return newSet
867
- })
868
- }}
869
- >
870
- {isExpanded ? (
871
- <>
872
- <ChevronDown className="mr-1 h-3 w-3" />
873
- Hide {(event as NestedEvent).subEvents!.length} sub-events
874
- </>
875
- ) : (
876
- <>
877
- <ChevronRight className="mr-1 h-3 w-3" />
878
- Show {(event as NestedEvent).subEvents!.length} sub-events
879
- </>
880
- )}
881
- </Button>
882
- )}
883
- </div>
884
- </div>
885
- )
886
-
887
- if (draggable && onEventsReorder) {
888
- return (
889
- <Reorder.Item
890
- key={event.id}
891
- value={event}
892
- dragListener={false}
893
- dragControls={undefined}
894
- >
895
- {eventContent}
896
- </Reorder.Item>
897
- )
898
- }
899
-
900
- const variants = animation !== 'none' ? animationVariants[animation] : undefined
901
-
902
- return (
903
- <motion.div
904
- key={event.id}
905
- variants={variants}
906
- initial={variants?.initial}
907
- animate={variants?.animate}
908
- exit={variants?.exit}
909
- transition={{ duration: 0.3, delay: index * 0.05 }}
910
- >
911
- {eventContent}
912
- </motion.div>
913
- )
914
- }, [
915
- eventRenderer,
916
- colorScheme,
917
- selectedEvents,
918
- expandedEvents,
919
- focusedEventId,
920
- keyboardNavigation,
921
- layout,
922
- compactMode,
923
- printMode,
924
- handleEventClick,
925
- renderEventIcon,
926
- renderConnector,
927
- events,
928
- showRelativeTime,
929
- getRelativeTime,
930
- formatTime,
931
- editable,
932
- onEventEdit,
933
- onEventDelete,
934
- showUserInfo,
935
- getInitials,
936
- showMetadata,
937
- draggable,
938
- onEventsReorder,
939
- animation
940
- ])
941
-
942
- const renderGroupedEvents = useCallback(() => {
943
- const groups = groupEventsByDate(filteredAndSortedEvents)
944
-
945
- return Object.entries(groups).map(([dateGroup, groupEvents], groupIndex) => {
946
- const displayEvents = maxEventsPerGroup
947
- ? groupEvents.slice(0, maxEventsPerGroup)
948
- : groupEvents
949
- const remainingCount = groupEvents.length - displayEvents.length
950
-
951
- return (
952
- <div key={dateGroup} className="mb-8">
953
- <div className={cn(
954
- "sticky top-0 z-20 bg-background/80 backdrop-blur-sm border-b p-3 mb-4 -mx-6 px-6",
955
- printMode && "print:relative print:bg-transparent"
956
- )}>
957
- <h3 className="font-semibold text-sm text-muted-foreground flex items-center justify-between">
958
- <span>{dateGroup}</span>
959
- <Badge variant="secondary" className="text-xs">
960
- {groupEvents.length} event{groupEvents.length !== 1 ? 's' : ''}
961
- </Badge>
962
- </h3>
963
- </div>
964
-
965
- <AnimatePresence mode="popLayout">
966
- {displayEvents.map((event, index) =>
967
- renderEvent(event, index, index === displayEvents.length - 1)
968
- )}
969
- </AnimatePresence>
970
-
971
- {remainingCount > 0 && (
972
- <Button
973
- variant="ghost"
974
- size="sm"
975
- className="w-full mt-2"
976
- onClick={() => {
977
- // In a real implementation, this would expand the group
978
- console.log(`Show ${remainingCount} more events`)
979
- }}
980
- >
981
- Show {remainingCount} more event{remainingCount !== 1 ? 's' : ''}
982
- </Button>
983
- )}
984
- </div>
985
- )
986
- })
987
- }, [groupEventsByDate, filteredAndSortedEvents, maxEventsPerGroup, printMode, renderEvent])
988
-
989
- const renderTimeline = () => {
990
- const content = groupBy !== 'none'
991
- ? renderGroupedEvents()
992
- : (
993
- <AnimatePresence mode="popLayout">
994
- {draggable && onEventsReorder ? (
995
- <Reorder.Group
996
- axis={layout === 'horizontal' ? 'x' : 'y'}
997
- values={filteredAndSortedEvents}
998
- onReorder={(newOrder) => {
999
- setEvents(newOrder)
1000
- onEventsReorder(newOrder)
1001
- }}
1002
- className={cn(
1003
- "space-y-0",
1004
- layout === 'horizontal' && "flex overflow-x-auto gap-6 pb-4"
1005
- )}
1006
- >
1007
- {filteredAndSortedEvents.map((event, index) =>
1008
- renderEvent(event, index, index === filteredAndSortedEvents.length - 1)
1009
- )}
1010
- </Reorder.Group>
1011
- ) : (
1012
- <div className={cn(
1013
- "space-y-0",
1014
- layout === 'horizontal' && "flex overflow-x-auto gap-6 pb-4",
1015
- layout === 'alternating' && "relative"
1016
- )}>
1017
- {filteredAndSortedEvents.map((event, index) =>
1018
- renderEvent(event, index, index === filteredAndSortedEvents.length - 1)
1019
- )}
1020
- </div>
1021
- )}
1022
- </AnimatePresence>
1023
- )
1024
-
1025
- if (virtualScroll && filteredAndSortedEvents.length > 50) {
1026
- // In a real implementation, use a virtual scrolling library
1027
- return (
1028
- <div className="h-[600px] overflow-y-auto">
1029
- {content}
1030
- </div>
1031
- )
1032
- }
1033
-
1034
- return content
1035
- }
1036
-
1037
- return (
1038
- <Card
1039
- className={cn(
1040
- timelineVariants({ theme }),
1041
- "relative",
1042
- printMode && "print:shadow-none print:border-0",
1043
- className
1044
- )}
1045
- ref={timelineRef}
1046
- role="region"
1047
- aria-label={ariaLabel}
1048
- {...props}
1049
- >
1050
- <CardHeader className={cn(
1051
- compactMode && "pb-3",
1052
- printMode && "print:pb-2"
1053
- )}>
1054
- <div className="flex items-center justify-between">
1055
- <div>
1056
- <CardTitle className="flex items-center gap-2">
1057
- <Clock className="h-5 w-5" />
1058
- Timeline
1059
- </CardTitle>
1060
- <CardDescription>
1061
- {filteredAndSortedEvents.length} event{filteredAndSortedEvents.length !== 1 ? 's' : ''}
1062
- {searchQuery && ` matching "${searchQuery}"`}
1063
- {filterType !== 'all' && ` of type ${filterType}`}
1064
- </CardDescription>
1065
- </div>
1066
-
1067
- {/* Actions */}
1068
- <div className="flex items-center gap-2">
1069
- {showExport && (
1070
- <Button
1071
- variant="ghost"
1072
- size="sm"
1073
- onClick={handleExport}
1074
- className="print:hidden"
1075
- >
1076
- <Download className="h-4 w-4" />
1077
- </Button>
1078
- )}
1079
- <Button
1080
- variant="ghost"
1081
- size="sm"
1082
- onClick={handlePrint}
1083
- className="print:hidden"
1084
- >
1085
- <Printer className="h-4 w-4" />
1086
- </Button>
1087
- </div>
1088
- </div>
1089
-
1090
- {/* Search and Filter */}
1091
- {(showSearch || showFilter) && (
1092
- <div className="flex gap-2 mt-4 print:hidden">
1093
- {showSearch && (
1094
- <div className="relative flex-1">
1095
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
1096
- <Input
1097
- type="search"
1098
- placeholder="Search events..."
1099
- value={searchQuery}
1100
- onChange={(e) => setSearchQuery(e.target.value)}
1101
- className="pl-9"
1102
- />
1103
- </div>
1104
- )}
1105
-
1106
- {showFilter && (
1107
- <Select value={filterType} onValueChange={(value: any) => setFilterType(value)}>
1108
- <SelectTrigger className="w-[150px]">
1109
- <Filter className="mr-2 h-4 w-4" />
1110
- <SelectValue placeholder="Filter by type" />
1111
- </SelectTrigger>
1112
- <SelectContent>
1113
- <SelectItem value="all">All Types</SelectItem>
1114
- <SelectItem value="success">Success</SelectItem>
1115
- <SelectItem value="warning">Warning</SelectItem>
1116
- <SelectItem value="error">Error</SelectItem>
1117
- <SelectItem value="info">Info</SelectItem>
1118
- <SelectItem value="pending">Pending</SelectItem>
1119
- <SelectItem value="milestone">Milestone</SelectItem>
1120
- </SelectContent>
1121
- </Select>
1122
- )}
1123
- </div>
1124
- )}
1125
- </CardHeader>
1126
-
1127
- <CardContent className={cn(
1128
- compactMode ? "pt-2" : "pt-6",
1129
- "relative"
1130
- )}>
1131
- {filteredAndSortedEvents.length > 0 ? (
1132
- renderTimeline()
1133
- ) : (
1134
- <div className="text-center py-12">
1135
- <Clock className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
1136
- <p className="text-muted-foreground">
1137
- {searchQuery || filterType !== 'all'
1138
- ? "No events match your filters"
1139
- : "No events to display"}
1140
- </p>
1141
- </div>
1142
- )}
1143
- </CardContent>
1144
- </Card>
1145
- )
1146
- }
1147
-
1148
- export default Timeline