@moontra/moonui-pro 2.5.13 → 2.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.5.13",
3
+ "version": "2.6.0",
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",
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React from 'react'
4
4
  import { Dashboard } from './index'
5
- import { Widget, MetricData, ChartData, ActivityItem } from './types'
5
+ import { Widget, MetricData, ChartData, ActivityItem, DashboardNotification } from './types'
6
6
  import { MetricCard } from './widgets/metric-card'
7
7
  import { ChartWidget } from './widgets/chart-widget'
8
8
  import { ActivityFeed } from './widgets/activity-feed'
@@ -14,7 +14,11 @@ import {
14
14
  Package,
15
15
  CreditCard,
16
16
  Activity,
17
- AlertCircle
17
+ AlertCircle,
18
+ User,
19
+ Settings2,
20
+ LogOut,
21
+ HelpCircle
18
22
  } from 'lucide-react'
19
23
 
20
24
  // Örnek metrik verileri
@@ -220,7 +224,107 @@ const sampleWidgets: Widget[] = [
220
224
  }
221
225
  ]
222
226
 
227
+ // Örnek notification verileri
228
+ const sampleNotifications: DashboardNotification[] = [
229
+ {
230
+ id: 'notif-1',
231
+ type: 'success',
232
+ title: 'Payment Received',
233
+ message: 'Payment of $1,250 has been processed successfully',
234
+ timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 dakika önce
235
+ read: false
236
+ },
237
+ {
238
+ id: 'notif-2',
239
+ type: 'warning',
240
+ title: 'Low Stock Alert',
241
+ message: 'Product "Premium Widget" is running low on stock',
242
+ timestamp: new Date(Date.now() - 1000 * 60 * 30), // 30 dakika önce
243
+ read: false
244
+ },
245
+ {
246
+ id: 'notif-3',
247
+ type: 'info',
248
+ title: 'New Feature Available',
249
+ message: 'Check out our new analytics dashboard features',
250
+ timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 saat önce
251
+ read: true
252
+ },
253
+ {
254
+ id: 'notif-4',
255
+ type: 'error',
256
+ title: 'Sync Failed',
257
+ message: 'Failed to sync data with external service',
258
+ timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 gün önce
259
+ read: true
260
+ }
261
+ ]
262
+
263
+ // Örnek user bilgileri
264
+ const sampleUser = {
265
+ name: 'John Doe',
266
+ email: 'john@example.com',
267
+ avatar: 'https://github.com/shadcn.png',
268
+ role: 'Admin'
269
+ }
270
+
271
+ // Örnek custom user menu items
272
+ const customUserMenuItems = [
273
+ {
274
+ id: 'profile',
275
+ label: 'My Profile',
276
+ icon: <User className="h-4 w-4" />,
277
+ onClick: () => console.log('Profile clicked')
278
+ },
279
+ {
280
+ id: 'billing',
281
+ label: 'Billing & Plans',
282
+ icon: <CreditCard className="h-4 w-4" />,
283
+ onClick: () => console.log('Billing clicked')
284
+ },
285
+ { id: 'sep1', separator: true },
286
+ {
287
+ id: 'settings',
288
+ label: 'Settings',
289
+ icon: <Settings2 className="h-4 w-4" />,
290
+ onClick: () => console.log('Settings clicked')
291
+ },
292
+ {
293
+ id: 'help',
294
+ label: 'Help & Support',
295
+ icon: <HelpCircle className="h-4 w-4" />,
296
+ onClick: () => console.log('Help clicked')
297
+ },
298
+ { id: 'sep2', separator: true },
299
+ {
300
+ id: 'logout',
301
+ label: 'Sign Out',
302
+ icon: <LogOut className="h-4 w-4" />,
303
+ onClick: () => console.log('Logout clicked')
304
+ }
305
+ ]
306
+
223
307
  export function DashboardDemo() {
308
+ const [notifications, setNotifications] = React.useState(sampleNotifications)
309
+
310
+ const handleNotificationMarkAsRead = (notificationId: string) => {
311
+ setNotifications(prev =>
312
+ prev.map(n => n.id === notificationId ? { ...n, read: true } : n)
313
+ )
314
+ }
315
+
316
+ const handleNotificationClear = (notificationId: string) => {
317
+ setNotifications(prev => prev.filter(n => n.id !== notificationId))
318
+ }
319
+
320
+ const handleNotificationMarkAllAsRead = () => {
321
+ setNotifications(prev => prev.map(n => ({ ...n, read: true })))
322
+ }
323
+
324
+ const handleNotificationClearAll = () => {
325
+ setNotifications([])
326
+ }
327
+
224
328
  return (
225
329
  <div className="min-h-screen bg-background">
226
330
  <Dashboard
@@ -230,6 +334,16 @@ export function DashboardDemo() {
230
334
  editable={true}
231
335
  realtime={true}
232
336
  glassmorphism={true}
337
+ user={sampleUser}
338
+ userMenuItems={customUserMenuItems}
339
+ notifications={notifications}
340
+ onNotificationClick={(notification) => {
341
+ console.log('Notification clicked:', notification)
342
+ }}
343
+ onNotificationMarkAsRead={handleNotificationMarkAsRead}
344
+ onNotificationMarkAllAsRead={handleNotificationMarkAllAsRead}
345
+ onNotificationClear={handleNotificationClear}
346
+ onNotificationClearAll={handleNotificationClearAll}
233
347
  onWidgetAdd={(widget) => {
234
348
  console.log('Widget added:', widget)
235
349
  }}
@@ -7,6 +7,8 @@ import { Badge } from '../ui/badge'
7
7
  import { Input } from '../ui/input'
8
8
  import { ScrollArea } from '../ui/scroll-area'
9
9
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'
10
+ import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'
11
+ import { Separator } from '../ui/separator'
10
12
  import {
11
13
  Activity,
12
14
  Download,
@@ -36,7 +38,13 @@ import {
36
38
  Clock,
37
39
  Target,
38
40
  ArrowUpRight,
39
- CheckCircle
41
+ CheckCircle,
42
+ User,
43
+ LogOut,
44
+ Settings2,
45
+ AlertCircle,
46
+ Info,
47
+ CheckCheck
40
48
  } from 'lucide-react'
41
49
  import { cn } from '../../lib/utils'
42
50
  import { DashboardGrid } from './dashboard-grid'
@@ -53,7 +61,8 @@ import {
53
61
  ChartData,
54
62
  ActivityItem,
55
63
  DashboardTemplate,
56
- WidgetType
64
+ WidgetType,
65
+ DashboardNotification
57
66
  } from './types'
58
67
  import {
59
68
  DropdownMenu,
@@ -99,6 +108,49 @@ interface DashboardProps {
99
108
  editable?: boolean
100
109
  realtime?: boolean
101
110
  glassmorphism?: boolean
111
+
112
+ // Notification yönetimi
113
+ notifications?: DashboardNotification[]
114
+ onNotificationClick?: (notification: DashboardNotification) => void
115
+ onNotificationMarkAsRead?: (notificationId: string) => void
116
+ onNotificationMarkAllAsRead?: () => void
117
+ onNotificationClear?: (notificationId: string) => void
118
+ onNotificationClearAll?: () => void
119
+
120
+ // User yönetimi
121
+ user?: {
122
+ name: string
123
+ email?: string
124
+ avatar?: string
125
+ role?: string
126
+ }
127
+ userMenuItems?: Array<{
128
+ id: string
129
+ label: string
130
+ icon?: React.ReactNode
131
+ onClick?: () => void
132
+ separator?: boolean
133
+ }>
134
+ onUserMenuClick?: () => void
135
+ onProfileClick?: () => void
136
+ onLogout?: () => void
137
+
138
+ // Header actions
139
+ onSearch?: (query: string) => void
140
+ onThemeChange?: (theme: DashboardTheme) => void
141
+ onMenuClick?: () => void
142
+ onRefresh?: () => void
143
+
144
+ // Custom header actions
145
+ headerActions?: React.ReactNode
146
+
147
+ // Time range yönetimi
148
+ timeRange?: TimeRange
149
+ onTimeRangeChange?: (range: TimeRange) => void
150
+
151
+ // Custom branding
152
+ logo?: React.ReactNode
153
+ brandName?: string
102
154
  }
103
155
 
104
156
  // Dashboard template'leri
@@ -230,6 +282,19 @@ const THEME_COLORS: Record<DashboardTheme, string> = {
230
282
  custom: 'from-gray-500/10 to-gray-600/10'
231
283
  }
232
284
 
285
+ // Relative time formatter
286
+ function formatRelativeTime(date: Date): string {
287
+ const now = new Date();
288
+ const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
289
+
290
+ if (diffInSeconds < 60) return 'just now';
291
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
292
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
293
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
294
+
295
+ return date.toLocaleDateString();
296
+ }
297
+
233
298
  export function Dashboard({
234
299
  config,
235
300
  widgets: initialWidgets = [],
@@ -246,24 +311,51 @@ export function Dashboard({
246
311
  description = 'Real-time analytics and insights',
247
312
  editable = true,
248
313
  realtime = false,
249
- glassmorphism = true
314
+ glassmorphism = true,
315
+ notifications = [],
316
+ onNotificationClick,
317
+ onNotificationMarkAsRead,
318
+ onNotificationMarkAllAsRead,
319
+ onNotificationClear,
320
+ onNotificationClearAll,
321
+ user,
322
+ userMenuItems,
323
+ onUserMenuClick,
324
+ onProfileClick,
325
+ onLogout,
326
+ onSearch,
327
+ onThemeChange,
328
+ onMenuClick,
329
+ onRefresh,
330
+ headerActions,
331
+ timeRange: propTimeRange,
332
+ onTimeRangeChange,
333
+ logo,
334
+ brandName
250
335
  }: DashboardProps) {
251
336
  // State yönetimi
252
337
  const [editMode, setEditMode] = React.useState(false)
253
338
  const [widgets, setWidgets] = React.useState<Widget[]>(initialWidgets)
254
339
  const [selectedTheme, setSelectedTheme] = React.useState<DashboardTheme>('analytics')
255
- const [timeRange, setTimeRange] = React.useState<TimeRange>()
340
+ const [timeRange, setTimeRange] = React.useState<TimeRange | undefined>(propTimeRange)
256
341
  const [searchQuery, setSearchQuery] = React.useState('')
257
342
  const [showWidgetLibrary, setShowWidgetLibrary] = React.useState(false)
258
343
  const [showTemplates, setShowTemplates] = React.useState(false)
259
344
  const [refreshing, setRefreshing] = React.useState(false)
260
- const [notifications, setNotifications] = React.useState<number>(3)
345
+ const [showNotifications, setShowNotifications] = React.useState(false)
261
346
 
262
347
  // initialWidgets değiştiğinde state'i güncelle
263
348
  React.useEffect(() => {
264
349
  setWidgets(initialWidgets)
265
350
  }, [initialWidgets])
266
351
 
352
+ // propTimeRange değiştiğinde state'i güncelle
353
+ React.useEffect(() => {
354
+ if (propTimeRange) {
355
+ setTimeRange(propTimeRange)
356
+ }
357
+ }, [propTimeRange])
358
+
267
359
  // WebSocket bağlantısı (real-time için)
268
360
  React.useEffect(() => {
269
361
  if (!realtime) return
@@ -375,7 +467,10 @@ export function Dashboard({
375
467
  <Input
376
468
  placeholder="Search widgets..."
377
469
  value={searchQuery}
378
- onChange={(e) => setSearchQuery(e.target.value)}
470
+ onChange={(e) => {
471
+ setSearchQuery(e.target.value);
472
+ onSearch?.(e.target.value);
473
+ }}
379
474
  className="pl-10"
380
475
  />
381
476
  </div>
@@ -498,11 +593,26 @@ export function Dashboard({
498
593
  whileHover={{ scale: 1.05 }}
499
594
  whileTap={{ scale: 0.95 }}
500
595
  >
501
- <Button variant="ghost" size="sm" className="lg:hidden">
596
+ <Button
597
+ variant="ghost"
598
+ size="sm"
599
+ className="lg:hidden"
600
+ onClick={onMenuClick}
601
+ >
502
602
  <Menu className="h-5 w-5" />
503
603
  </Button>
504
604
  </motion.div>
505
605
 
606
+ {/* Logo and Brand */}
607
+ {(logo || brandName) && (
608
+ <div className="flex items-center gap-2">
609
+ {logo}
610
+ {brandName && (
611
+ <span className="font-semibold text-lg">{brandName}</span>
612
+ )}
613
+ </div>
614
+ )}
615
+
506
616
  <div>
507
617
  <div className="flex items-center gap-2">
508
618
  <h1 className="text-2xl font-bold tracking-tight">{title}</h1>
@@ -522,21 +632,127 @@ export function Dashboard({
522
632
  {/* Time range picker */}
523
633
  <TimeRangePicker
524
634
  value={timeRange}
525
- onChange={setTimeRange}
635
+ onChange={(range) => {
636
+ setTimeRange(range);
637
+ onTimeRangeChange?.(range);
638
+ }}
526
639
  glassmorphism={glassmorphism}
527
640
  />
528
641
 
529
642
  {/* Notifications */}
530
- <div className="relative">
531
- <Button variant="ghost" size="sm" className="relative">
532
- <Bell className="h-4 w-4" />
533
- {notifications > 0 && (
534
- <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground flex items-center justify-center">
535
- {notifications}
536
- </span>
537
- )}
538
- </Button>
539
- </div>
643
+ <DropdownMenu open={showNotifications} onOpenChange={setShowNotifications}>
644
+ <DropdownMenuTrigger asChild>
645
+ <Button variant="ghost" size="sm" className="relative h-9 w-9 p-0">
646
+ <Bell className="h-5 w-5" />
647
+ {notifications.filter(n => !n.read).length > 0 && (
648
+ <span className="absolute -top-1 -right-1 min-w-[1rem] h-4 px-1 rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground flex items-center justify-center">
649
+ {notifications.filter(n => !n.read).length}
650
+ </span>
651
+ )}
652
+ </Button>
653
+ </DropdownMenuTrigger>
654
+ <DropdownMenuContent
655
+ align="end"
656
+ className="w-80"
657
+ sideOffset={8}
658
+ >
659
+ <div className="flex items-center justify-between px-4 py-2">
660
+ <h4 className="text-sm font-semibold">Notifications</h4>
661
+ {notifications.length > 0 && (
662
+ <div className="flex items-center gap-2">
663
+ <Button
664
+ variant="ghost"
665
+ size="sm"
666
+ className="h-auto p-1 text-xs"
667
+ onClick={() => onNotificationMarkAllAsRead?.()}
668
+ >
669
+ <CheckCheck className="h-3 w-3 mr-1" />
670
+ Mark all read
671
+ </Button>
672
+ <Button
673
+ variant="ghost"
674
+ size="sm"
675
+ className="h-auto p-1 text-xs"
676
+ onClick={() => onNotificationClearAll?.()}
677
+ >
678
+ Clear all
679
+ </Button>
680
+ </div>
681
+ )}
682
+ </div>
683
+ <Separator />
684
+ <ScrollArea className="h-[400px]">
685
+ {notifications.length === 0 ? (
686
+ <div className="p-8 text-center text-sm text-muted-foreground">
687
+ <Bell className="h-8 w-8 mx-auto mb-2 opacity-20" />
688
+ <p>No notifications</p>
689
+ </div>
690
+ ) : (
691
+ <div className="p-1">
692
+ {notifications.map((notification) => {
693
+ const Icon = notification.type === 'error' ? AlertCircle :
694
+ notification.type === 'warning' ? AlertCircle :
695
+ notification.type === 'success' ? CheckCircle :
696
+ Info;
697
+
698
+ return (
699
+ <motion.div
700
+ key={notification.id}
701
+ initial={{ opacity: 0, x: -20 }}
702
+ animate={{ opacity: 1, x: 0 }}
703
+ className={cn(
704
+ "flex items-start gap-3 p-3 rounded-lg cursor-pointer transition-colors",
705
+ "hover:bg-muted/50",
706
+ !notification.read && "bg-muted/30"
707
+ )}
708
+ onClick={() => {
709
+ onNotificationClick?.(notification);
710
+ if (!notification.read) {
711
+ onNotificationMarkAsRead?.(notification.id);
712
+ }
713
+ }}
714
+ >
715
+ <div className={cn(
716
+ "mt-0.5 p-1.5 rounded-full",
717
+ notification.type === 'error' && "bg-destructive/10 text-destructive",
718
+ notification.type === 'warning' && "bg-yellow-500/10 text-yellow-600 dark:text-yellow-500",
719
+ notification.type === 'success' && "bg-green-500/10 text-green-600 dark:text-green-500",
720
+ notification.type === 'info' && "bg-blue-500/10 text-blue-600 dark:text-blue-500"
721
+ )}>
722
+ <Icon className="h-3.5 w-3.5" />
723
+ </div>
724
+ <div className="flex-1 space-y-1">
725
+ <p className="text-sm font-medium leading-none">
726
+ {notification.title}
727
+ </p>
728
+ {notification.message && (
729
+ <p className="text-xs text-muted-foreground">
730
+ {notification.message}
731
+ </p>
732
+ )}
733
+ <p className="text-xs text-muted-foreground">
734
+ {formatRelativeTime(notification.timestamp)}
735
+ </p>
736
+ </div>
737
+ <Button
738
+ variant="ghost"
739
+ size="sm"
740
+ className="h-6 w-6 p-0"
741
+ onClick={(e) => {
742
+ e.stopPropagation();
743
+ onNotificationClear?.(notification.id);
744
+ }}
745
+ >
746
+ <X className="h-3 w-3" />
747
+ </Button>
748
+ </motion.div>
749
+ );
750
+ })}
751
+ </div>
752
+ )}
753
+ </ScrollArea>
754
+ </DropdownMenuContent>
755
+ </DropdownMenu>
540
756
 
541
757
  {/* Theme selector */}
542
758
  <DropdownMenu>
@@ -546,19 +762,28 @@ export function Dashboard({
546
762
  </Button>
547
763
  </DropdownMenuTrigger>
548
764
  <DropdownMenuContent align="end">
549
- <DropdownMenuItem onClick={() => setSelectedTheme('analytics')}>
765
+ <DropdownMenuItem onClick={() => {
766
+ setSelectedTheme('analytics');
767
+ onThemeChange?.('analytics');
768
+ }}>
550
769
  <div className="flex items-center gap-2">
551
770
  <div className="h-4 w-4 rounded bg-gradient-to-br from-blue-500 to-purple-500" />
552
771
  Analytics
553
772
  </div>
554
773
  </DropdownMenuItem>
555
- <DropdownMenuItem onClick={() => setSelectedTheme('sales')}>
774
+ <DropdownMenuItem onClick={() => {
775
+ setSelectedTheme('sales');
776
+ onThemeChange?.('sales');
777
+ }}>
556
778
  <div className="flex items-center gap-2">
557
779
  <div className="h-4 w-4 rounded bg-gradient-to-br from-green-500 to-emerald-500" />
558
780
  Sales
559
781
  </div>
560
782
  </DropdownMenuItem>
561
- <DropdownMenuItem onClick={() => setSelectedTheme('monitoring')}>
783
+ <DropdownMenuItem onClick={() => {
784
+ setSelectedTheme('monitoring');
785
+ onThemeChange?.('monitoring');
786
+ }}>
562
787
  <div className="flex items-center gap-2">
563
788
  <div className="h-4 w-4 rounded bg-gradient-to-br from-orange-500 to-red-500" />
564
789
  Monitoring
@@ -567,6 +792,73 @@ export function Dashboard({
567
792
  </DropdownMenuContent>
568
793
  </DropdownMenu>
569
794
 
795
+ {/* Custom header actions */}
796
+ {headerActions}
797
+
798
+ {/* User Profile */}
799
+ {user && (
800
+ <DropdownMenu>
801
+ <DropdownMenuTrigger asChild>
802
+ <Button variant="ghost" size="sm" className="relative h-8 w-8 rounded-full">
803
+ <Avatar className="h-8 w-8">
804
+ <AvatarImage src={user.avatar} alt={user.name} />
805
+ <AvatarFallback>
806
+ {user.name.split(' ').map(n => n[0]).join('').toUpperCase()}
807
+ </AvatarFallback>
808
+ </Avatar>
809
+ </Button>
810
+ </DropdownMenuTrigger>
811
+ <DropdownMenuContent align="end" className="w-56">
812
+ <div className="flex items-center justify-start gap-2 p-2">
813
+ <div className="flex flex-col space-y-1 leading-none">
814
+ {user.name && (
815
+ <p className="font-medium">{user.name}</p>
816
+ )}
817
+ {user.email && (
818
+ <p className="text-xs text-muted-foreground">
819
+ {user.email}
820
+ </p>
821
+ )}
822
+ </div>
823
+ </div>
824
+ <DropdownMenuSeparator />
825
+ {userMenuItems ? (
826
+ // Custom menu items
827
+ userMenuItems.map((item, index) => (
828
+ item.separator ? (
829
+ <DropdownMenuSeparator key={item.id || `sep-${index}`} />
830
+ ) : (
831
+ <DropdownMenuItem
832
+ key={item.id}
833
+ onClick={() => item.onClick?.()}
834
+ >
835
+ {item.icon && <span className="mr-2">{item.icon}</span>}
836
+ {item.label}
837
+ </DropdownMenuItem>
838
+ )
839
+ ))
840
+ ) : (
841
+ // Default menu items
842
+ <>
843
+ <DropdownMenuItem onClick={() => onProfileClick?.()}>
844
+ <User className="mr-2 h-4 w-4" />
845
+ Profile
846
+ </DropdownMenuItem>
847
+ <DropdownMenuItem onClick={() => onUserMenuClick?.()}>
848
+ <Settings2 className="mr-2 h-4 w-4" />
849
+ Settings
850
+ </DropdownMenuItem>
851
+ <DropdownMenuSeparator />
852
+ <DropdownMenuItem onClick={() => onLogout?.()}>
853
+ <LogOut className="mr-2 h-4 w-4" />
854
+ Log out
855
+ </DropdownMenuItem>
856
+ </>
857
+ )}
858
+ </DropdownMenuContent>
859
+ </DropdownMenu>
860
+ )}
861
+
570
862
  {/* More actions */}
571
863
  <DropdownMenu>
572
864
  <DropdownMenuTrigger asChild>
@@ -619,7 +911,11 @@ export function Dashboard({
619
911
 
620
912
  <DropdownMenuSeparator />
621
913
 
622
- <DropdownMenuItem onClick={() => setRefreshing(true)}>
914
+ <DropdownMenuItem onClick={() => {
915
+ setRefreshing(true);
916
+ onRefresh?.();
917
+ setTimeout(() => setRefreshing(false), 1000);
918
+ }}>
623
919
  <RefreshCw className="mr-2 h-4 w-4" />
624
920
  Refresh Data
625
921
  </DropdownMenuItem>