@moontra/moonui-pro 2.11.4 → 2.13.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.
@@ -0,0 +1,111 @@
1
+ export interface KanbanAssignee {
2
+ id: string
3
+ name: string
4
+ avatar?: string
5
+ email?: string
6
+ }
7
+
8
+ export interface KanbanLabel {
9
+ id: string
10
+ name: string
11
+ color: string
12
+ }
13
+
14
+ export interface KanbanChecklist {
15
+ id: string
16
+ title: string
17
+ items: {
18
+ id: string
19
+ text: string
20
+ completed: boolean
21
+ }[]
22
+ }
23
+
24
+ export interface KanbanActivity {
25
+ id: string
26
+ user: KanbanAssignee
27
+ action: string
28
+ timestamp: Date
29
+ details?: string
30
+ }
31
+
32
+ export interface KanbanCard {
33
+ id: string
34
+ title: string
35
+ description?: string
36
+ coverImage?: string
37
+ assignees?: KanbanAssignee[]
38
+ dueDate?: Date
39
+ startDate?: Date
40
+ priority?: 'low' | 'medium' | 'high' | 'urgent'
41
+ tags?: string[]
42
+ labels?: KanbanLabel[]
43
+ attachments?: {
44
+ id: string
45
+ name: string
46
+ type: string
47
+ url: string
48
+ size: number
49
+ }[]
50
+ comments?: number
51
+ completed?: boolean
52
+ progress?: number
53
+ checklist?: KanbanChecklist
54
+ activities?: KanbanActivity[]
55
+ customFields?: Record<string, any>
56
+ position: number
57
+ }
58
+
59
+ export interface KanbanColumn {
60
+ id: string
61
+ title: string
62
+ color?: string
63
+ cards: KanbanCard[]
64
+ limit?: number
65
+ collapsed?: boolean
66
+ locked?: boolean
67
+ template?: 'todo' | 'inProgress' | 'done' | 'custom'
68
+ }
69
+
70
+ export interface KanbanFilter {
71
+ id: string
72
+ name: string
73
+ query: string
74
+ assignees?: string[]
75
+ labels?: string[]
76
+ priority?: string[]
77
+ tags?: string[]
78
+ dueDate?: {
79
+ from?: Date
80
+ to?: Date
81
+ }
82
+ }
83
+
84
+ export interface KanbanProps {
85
+ columns: KanbanColumn[]
86
+ onCardMove?: (cardId: string, fromColumn: string, toColumn: string, newPosition: number) => void
87
+ onCardClick?: (card: KanbanCard) => void
88
+ onCardEdit?: (card: KanbanCard) => void
89
+ onCardDelete?: (card: KanbanCard) => void
90
+ onCardUpdate?: (card: KanbanCard) => void
91
+ onAddCard?: (columnId: string, card?: Partial<KanbanCard>) => void
92
+ onAddColumn?: (column?: Partial<KanbanColumn>) => void
93
+ onColumnUpdate?: (column: KanbanColumn) => void
94
+ onColumnDelete?: (columnId: string) => void
95
+ onBulkAction?: (action: string, cardIds: string[]) => void
96
+ onExport?: (format: 'json' | 'csv') => void
97
+ className?: string
98
+ showAddColumn?: boolean
99
+ showCardDetails?: boolean
100
+ showFilters?: boolean
101
+ showSearch?: boolean
102
+ enableKeyboardShortcuts?: boolean
103
+ cardTemplates?: Partial<KanbanCard>[]
104
+ columnTemplates?: Partial<KanbanColumn>[]
105
+ filters?: KanbanFilter[]
106
+ defaultFilter?: string
107
+ loading?: boolean
108
+ disabled?: boolean
109
+ labels?: KanbanLabel[]
110
+ users?: KanbanAssignee[]
111
+ }
@@ -83,16 +83,29 @@ const MoonUIAvatarFallbackPro = React.forwardRef<
83
83
  MoonUIAvatarFallbackPro.displayName = AvatarPrimitive.Fallback.displayName;
84
84
 
85
85
  // Avatar Group Component for displaying multiple avatars
86
- interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
87
- limit?: number;
88
- avatars: React.ReactNode[];
86
+ interface MoonUIAvatarGroupProProps extends React.HTMLAttributes<HTMLDivElement> {
87
+ max?: number;
88
+ size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
89
+ children?: React.ReactNode;
89
90
  overlapOffset?: number;
90
91
  }
91
92
 
92
- const MoonUIAvatarGroupPro = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
93
- ({ className, limit, avatars, overlapOffset = -8, ...props }, ref) => {
94
- const visibleAvatars = limit ? avatars.slice(0, limit) : avatars;
95
- const remainingCount = limit ? Math.max(0, avatars.length - limit) : 0;
93
+ const MoonUIAvatarGroupPro = React.forwardRef<HTMLDivElement, MoonUIAvatarGroupProProps>(
94
+ ({ className, max = 3, size = "md", children, overlapOffset, ...props }, ref) => {
95
+ const childrenArray = React.Children.toArray(children);
96
+ const visibleChildren = max ? childrenArray.slice(0, max) : childrenArray;
97
+ const remainingCount = max ? Math.max(0, childrenArray.length - max) : 0;
98
+
99
+ // Calculate overlap offset based on size
100
+ const defaultOffsets = {
101
+ xs: -4,
102
+ sm: -6,
103
+ md: -8,
104
+ lg: -10,
105
+ xl: -12,
106
+ "2xl": -16,
107
+ };
108
+ const finalOffset = overlapOffset ?? defaultOffsets[size as keyof typeof defaultOffsets] ?? -8;
96
109
 
97
110
  return (
98
111
  <div
@@ -101,25 +114,27 @@ const MoonUIAvatarGroupPro = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
101
114
  {...props}
102
115
  >
103
116
  <div className="flex">
104
- {visibleAvatars.map((avatar, index) => (
117
+ {visibleChildren.map((child, index) => (
105
118
  <div
106
119
  key={index}
107
- className="relative"
120
+ className="relative ring-2 ring-background rounded-full"
108
121
  style={{
109
- marginLeft: index === 0 ? 0 : `${overlapOffset}px`,
110
- zIndex: visibleAvatars.length - index
122
+ marginLeft: index === 0 ? 0 : `${finalOffset}px`,
123
+ zIndex: visibleChildren.length - index
111
124
  }}
112
125
  >
113
- {avatar}
126
+ {React.isValidElement(child) && child.type === MoonUIAvatarPro
127
+ ? React.cloneElement(child as React.ReactElement<any>, { size })
128
+ : child}
114
129
  </div>
115
130
  ))}
116
131
  {remainingCount > 0 && (
117
132
  <div
118
- className="relative z-0"
119
- style={{ marginLeft: `${overlapOffset}px` }}
133
+ className="relative z-0 ring-2 ring-background rounded-full"
134
+ style={{ marginLeft: `${finalOffset}px` }}
120
135
  >
121
- <MoonUIAvatarPro variant="border">
122
- <MoonUIAvatarFallbackPro>
136
+ <MoonUIAvatarPro size={size} className="bg-muted">
137
+ <MoonUIAvatarFallbackPro className="text-xs font-medium">
123
138
  +{remainingCount}
124
139
  </MoonUIAvatarFallbackPro>
125
140
  </MoonUIAvatarPro>
@@ -130,9 +145,9 @@ const MoonUIAvatarGroupPro = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
130
145
  );
131
146
  }
132
147
  );
133
- MoonUIAvatarGroupPro.displayName = "AvatarGroup";
148
+ MoonUIAvatarGroupPro.displayName = "MoonUIAvatarGroupPro";
134
149
 
135
- export { MoonUIAvatarPro, MoonUIAvatarImagePro, MoonUIAvatarFallbackPro, MoonUIAvatarGroupPro as AvatarGroup };
150
+ export { MoonUIAvatarPro, MoonUIAvatarImagePro, MoonUIAvatarFallbackPro, MoonUIAvatarGroupPro };
136
151
 
137
152
  // Backward compatibility exports
138
- export { MoonUIAvatarPro as Avatar, MoonUIAvatarImagePro as AvatarImage, MoonUIAvatarFallbackPro as AvatarFallback };
153
+ export { MoonUIAvatarPro as Avatar, MoonUIAvatarImagePro as AvatarImage, MoonUIAvatarFallbackPro as AvatarFallback, MoonUIAvatarGroupPro as AvatarGroup };
@@ -0,0 +1,15 @@
1
+ interface ToastOptions {
2
+ title: string
3
+ description?: string
4
+ variant?: 'default' | 'destructive'
5
+ }
6
+
7
+ export function useToast() {
8
+ const toast = (options: ToastOptions) => {
9
+ // Simple console implementation for now
10
+ // In production, this would integrate with a proper toast system
11
+ console.log(`[Toast] ${options.title}${options.description ? ': ' + options.description : ''}`)
12
+ }
13
+
14
+ return { toast }
15
+ }
@@ -1,434 +0,0 @@
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 { MoonUIBadgePro as Badge } from '../ui/badge'
7
- import { MoonUIAvatarPro, MoonUIAvatarFallbackPro, MoonUIAvatarImagePro } from '../ui/avatar'
8
- import {
9
- Plus,
10
- MoreHorizontal,
11
- User,
12
- Calendar,
13
- MessageCircle,
14
- Paperclip,
15
- Edit,
16
- Trash2,
17
- GripVertical,
18
- Lock,
19
- Sparkles
20
- } from 'lucide-react'
21
- import { cn } from '../../lib/utils'
22
- // Note: DocsProAccess should be handled by consuming application
23
- import { useSubscription } from '../../hooks/use-subscription'
24
-
25
- interface KanbanCard {
26
- id: string
27
- title: string
28
- description?: string
29
- assignee?: {
30
- name: string
31
- avatar?: string
32
- email?: string
33
- }
34
- dueDate?: Date
35
- priority?: 'low' | 'medium' | 'high' | 'urgent'
36
- tags?: string[]
37
- attachments?: number
38
- comments?: number
39
- completed?: boolean
40
- }
41
-
42
- interface KanbanColumn {
43
- id: string
44
- title: string
45
- color?: string
46
- cards: KanbanCard[]
47
- limit?: number
48
- }
49
-
50
- interface KanbanProps {
51
- columns: KanbanColumn[]
52
- onCardMove?: (cardId: string, fromColumn: string, toColumn: string, newIndex: number) => void
53
- onCardClick?: (card: KanbanCard) => void
54
- onCardEdit?: (card: KanbanCard) => void
55
- onCardDelete?: (card: KanbanCard) => void
56
- onAddCard?: (columnId: string) => void
57
- onAddColumn?: () => void
58
- className?: string
59
- showAddColumn?: boolean
60
- showCardDetails?: boolean
61
- disabled?: boolean
62
- }
63
-
64
- const PRIORITY_COLORS = {
65
- low: 'bg-green-100 text-green-800 border-green-200',
66
- medium: 'bg-yellow-100 text-yellow-800 border-yellow-200',
67
- high: 'bg-orange-100 text-orange-800 border-orange-200',
68
- urgent: 'bg-red-100 text-red-800 border-red-200'
69
- }
70
-
71
- const PRIORITY_DOTS = {
72
- low: 'bg-green-500',
73
- medium: 'bg-yellow-500',
74
- high: 'bg-orange-500',
75
- urgent: 'bg-red-500'
76
- }
77
-
78
- export function Kanban({
79
- columns,
80
- onCardMove,
81
- onCardClick,
82
- onCardEdit,
83
- onCardDelete,
84
- onAddCard,
85
- onAddColumn,
86
- className,
87
- showAddColumn = true,
88
- showCardDetails = true,
89
- disabled = false
90
- }: KanbanProps) {
91
- // Check if we're in docs mode or have pro access
92
- const { hasProAccess, isLoading } = useSubscription()
93
-
94
- // In docs mode, always show the component
95
-
96
- // If not in docs mode and no pro access, show upgrade prompt
97
- if (!isLoading && !hasProAccess) {
98
- return (
99
- <Card className={cn("w-full", className)}>
100
- <CardContent className="py-12 text-center">
101
- <div className="max-w-md mx-auto space-y-4">
102
- <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
103
- <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
104
- </div>
105
- <div>
106
- <h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
107
- <p className="text-muted-foreground text-sm mb-4">
108
- Kanban Board is available exclusively to MoonUI Pro subscribers.
109
- </p>
110
- <div className="flex gap-3 justify-center">
111
- <a href="/pricing">
112
- <Button size="sm">
113
- <Sparkles className="mr-2 h-4 w-4" />
114
- Upgrade to Pro
115
- </Button>
116
- </a>
117
- </div>
118
- </div>
119
- </div>
120
- </CardContent>
121
- </Card>
122
- )
123
- }
124
-
125
- const [draggedCard, setDraggedCard] = React.useState<string | null>(null)
126
- const [draggedOverColumn, setDraggedOverColumn] = React.useState<string | null>(null)
127
- const [draggedFromColumn, setDraggedFromColumn] = React.useState<string | null>(null)
128
-
129
- const handleDragStart = (e: React.DragEvent, cardId: string) => {
130
- if (disabled) return
131
-
132
- // Find which column this card belongs to
133
- const sourceColumn = columns.find(col =>
134
- col.cards.some(card => card.id === cardId)
135
- )
136
-
137
- setDraggedCard(cardId)
138
- setDraggedFromColumn(sourceColumn?.id || null)
139
- e.dataTransfer.effectAllowed = 'move'
140
- e.dataTransfer.setData('text/plain', cardId)
141
-
142
- // Add visual feedback
143
- e.currentTarget.classList.add('opacity-50')
144
- }
145
-
146
- const handleDragEnd = (e: React.DragEvent) => {
147
- if (disabled) return
148
-
149
- // Reset all states
150
- setDraggedCard(null)
151
- setDraggedOverColumn(null)
152
- setDraggedFromColumn(null)
153
-
154
- // Remove visual feedback
155
- e.currentTarget.classList.remove('opacity-50')
156
- }
157
-
158
- const handleDragOver = (e: React.DragEvent, columnId: string) => {
159
- if (disabled) return
160
- e.preventDefault()
161
- e.dataTransfer.dropEffect = 'move'
162
- setDraggedOverColumn(columnId)
163
- }
164
-
165
- const handleDragEnter = (e: React.DragEvent, columnId: string) => {
166
- if (disabled) return
167
- e.preventDefault()
168
- setDraggedOverColumn(columnId)
169
- }
170
-
171
- const handleDragLeave = (e: React.DragEvent, columnId: string) => {
172
- if (disabled) return
173
- e.preventDefault()
174
-
175
- // Only clear if we're leaving the column entirely
176
- const rect = e.currentTarget.getBoundingClientRect()
177
- const isLeavingColumn = (
178
- e.clientX < rect.left ||
179
- e.clientX > rect.right ||
180
- e.clientY < rect.top ||
181
- e.clientY > rect.bottom
182
- )
183
-
184
- if (isLeavingColumn) {
185
- setDraggedOverColumn(null)
186
- }
187
- }
188
-
189
- const handleDrop = (e: React.DragEvent, columnId: string) => {
190
- if (disabled) return
191
- e.preventDefault()
192
-
193
- const cardId = e.dataTransfer.getData('text/plain') || draggedCard
194
-
195
- if (cardId && onCardMove && draggedFromColumn && draggedFromColumn !== columnId) {
196
- const targetColumn = columns.find(col => col.id === columnId)
197
- const newIndex = targetColumn?.cards.length || 0
198
- onCardMove(cardId, draggedFromColumn, columnId, newIndex)
199
- }
200
-
201
- // Reset all states
202
- setDraggedCard(null)
203
- setDraggedOverColumn(null)
204
- setDraggedFromColumn(null)
205
- }
206
-
207
- const handleCardClick = (card: KanbanCard) => {
208
- if (disabled) return
209
- onCardClick?.(card)
210
- }
211
-
212
- const handleCardEdit = (card: KanbanCard, e: React.MouseEvent) => {
213
- if (disabled) return
214
- e.stopPropagation()
215
- onCardEdit?.(card)
216
- }
217
-
218
- const handleCardDelete = (card: KanbanCard, e: React.MouseEvent) => {
219
- if (disabled) return
220
- e.stopPropagation()
221
- onCardDelete?.(card)
222
- }
223
-
224
- const formatDate = (date: Date) => {
225
- return date.toLocaleDateString('en-US', {
226
- month: 'short',
227
- day: 'numeric'
228
- })
229
- }
230
-
231
- const isOverdue = (dueDate: Date) => {
232
- return dueDate < new Date()
233
- }
234
-
235
- const getInitials = (name: string) => {
236
- return name.split(' ').map(n => n[0]).join('').toUpperCase()
237
- }
238
-
239
- return (
240
- <div className={cn("w-full", className)}>
241
- <div className="flex gap-6 overflow-x-auto pb-4">
242
- {columns.map((column) => {
243
- const isOverLimit = column.limit && column.cards.length > column.limit
244
- const isDraggedOver = draggedOverColumn === column.id
245
-
246
- return (
247
- <div
248
- key={column.id}
249
- className={cn(
250
- "flex-shrink-0 w-80 transition-colors duration-200",
251
- isDraggedOver && "bg-primary/5 rounded-lg border-2 border-primary/20"
252
- )}
253
- onDragOver={(e) => handleDragOver(e, column.id)}
254
- onDragEnter={(e) => handleDragEnter(e, column.id)}
255
- onDragLeave={(e) => handleDragLeave(e, column.id)}
256
- onDrop={(e) => handleDrop(e, column.id)}
257
- >
258
- <Card className="h-full">
259
- <CardHeader className="pb-3">
260
- <div className="flex items-center justify-between">
261
- <div className="flex items-center gap-2">
262
- {column.color && (
263
- <div
264
- className="w-3 h-3 rounded-full"
265
- style={{ backgroundColor: column.color }}
266
- />
267
- )}
268
- <CardTitle className="text-sm font-medium">
269
- {column.title}
270
- </CardTitle>
271
- <Badge variant="secondary" className="text-xs">
272
- {column.cards.length}
273
- </Badge>
274
- </div>
275
- <Button variant="ghost" size="sm">
276
- <MoreHorizontal className="h-4 w-4" />
277
- </Button>
278
- </div>
279
- {column.limit && (
280
- <CardDescription className={cn(
281
- "text-xs",
282
- isOverLimit && "text-destructive"
283
- )}>
284
- {isOverLimit ? 'Over limit' : `${column.cards.length}/${column.limit} cards`}
285
- </CardDescription>
286
- )}
287
- </CardHeader>
288
-
289
- <CardContent className="space-y-3">
290
- {/* Cards */}
291
- {column.cards.map((card) => (
292
- <div
293
- key={card.id}
294
- draggable={!disabled}
295
- onDragStart={(e) => handleDragStart(e, card.id)}
296
- onDragEnd={handleDragEnd}
297
- onClick={() => handleCardClick(card)}
298
- className={cn(
299
- "p-3 bg-background border rounded-lg cursor-pointer hover:shadow-md transition-all duration-200",
300
- "group relative select-none",
301
- draggedCard === card.id && "opacity-50 scale-95",
302
- disabled && "cursor-not-allowed"
303
- )}
304
- >
305
- <div className="flex items-start justify-between gap-2">
306
- <div className="flex-1">
307
- <h4 className="font-medium text-sm mb-1">{card.title}</h4>
308
- {card.description && (
309
- <p className="text-xs text-muted-foreground mb-2 line-clamp-2">
310
- {card.description}
311
- </p>
312
- )}
313
- </div>
314
- <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
315
- <Button
316
- variant="ghost"
317
- size="sm"
318
- className="h-6 w-6 p-0"
319
- onClick={(e) => handleCardEdit(card, e)}
320
- >
321
- <Edit className="h-3 w-3" />
322
- </Button>
323
- <Button
324
- variant="ghost"
325
- size="sm"
326
- className="h-6 w-6 p-0"
327
- onClick={(e) => handleCardDelete(card, e)}
328
- >
329
- <Trash2 className="h-3 w-3" />
330
- </Button>
331
- <div className="cursor-move">
332
- <GripVertical className="h-3 w-3 text-muted-foreground" />
333
- </div>
334
- </div>
335
- </div>
336
-
337
- {/* Tags */}
338
- {card.tags && card.tags.length > 0 && (
339
- <div className="flex flex-wrap gap-1 mb-2">
340
- {card.tags.map((tag, index) => (
341
- <Badge key={index} variant="outline" className="text-xs px-1 py-0">
342
- {tag}
343
- </Badge>
344
- ))}
345
- </div>
346
- )}
347
-
348
- {/* Card Details */}
349
- {showCardDetails && (
350
- <div className="flex items-center justify-between text-xs text-muted-foreground">
351
- <div className="flex items-center gap-2">
352
- {card.priority && (
353
- <div className="flex items-center gap-1">
354
- <div className={cn("w-2 h-2 rounded-full", PRIORITY_DOTS[card.priority])} />
355
- <span className="capitalize">{card.priority}</span>
356
- </div>
357
- )}
358
- {card.dueDate && (
359
- <div className={cn(
360
- "flex items-center gap-1",
361
- isOverdue(card.dueDate) && "text-destructive"
362
- )}>
363
- <Calendar className="h-3 w-3" />
364
- <span>{formatDate(card.dueDate)}</span>
365
- </div>
366
- )}
367
- </div>
368
-
369
- <div className="flex items-center gap-2">
370
- {card.comments && card.comments > 0 && (
371
- <div className="flex items-center gap-1">
372
- <MessageCircle className="h-3 w-3" />
373
- <span>{card.comments}</span>
374
- </div>
375
- )}
376
- {card.attachments && card.attachments > 0 && (
377
- <div className="flex items-center gap-1">
378
- <Paperclip className="h-3 w-3" />
379
- <span>{card.attachments}</span>
380
- </div>
381
- )}
382
- {card.assignee && (
383
- <MoonUIAvatarPro className="h-5 w-5">
384
- <MoonUIAvatarImagePro src={card.assignee.avatar} />
385
- <MoonUIAvatarFallbackPro className="text-xs">
386
- {getInitials(card.assignee.name)}
387
- </MoonUIAvatarFallbackPro>
388
- </MoonUIAvatarPro>
389
- )}
390
- </div>
391
- </div>
392
- )}
393
- </div>
394
- ))}
395
-
396
- {/* Add Card Button */}
397
- {onAddCard && (
398
- <Button
399
- variant="ghost"
400
- size="sm"
401
- onClick={() => onAddCard(column.id)}
402
- className="w-full justify-start text-muted-foreground hover:text-foreground"
403
- disabled={disabled}
404
- >
405
- <Plus className="h-4 w-4 mr-2" />
406
- Add card
407
- </Button>
408
- )}
409
- </CardContent>
410
- </Card>
411
- </div>
412
- )
413
- })}
414
-
415
- {/* Add Column Button */}
416
- {showAddColumn && onAddColumn && (
417
- <div className="flex-shrink-0 w-80">
418
- <Button
419
- variant="outline"
420
- onClick={onAddColumn}
421
- className="w-full h-full min-h-[200px] border-dashed justify-center items-center"
422
- disabled={disabled}
423
- >
424
- <Plus className="h-6 w-6 mr-2" />
425
- Add column
426
- </Button>
427
- </div>
428
- )}
429
- </div>
430
- </div>
431
- )
432
- }
433
-
434
- export default Kanban