@moontra/moonui-pro 2.20.1 → 2.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7418 -4934
  3. package/package.json +4 -3
  4. package/scripts/postbuild.js +27 -0
  5. package/src/components/advanced-chart/index.tsx +5 -1
  6. package/src/components/advanced-forms/index.tsx +175 -16
  7. package/src/components/calendar/event-dialog.tsx +18 -13
  8. package/src/components/calendar/index.tsx +197 -50
  9. package/src/components/dashboard/dashboard-grid.tsx +21 -3
  10. package/src/components/dashboard/types.ts +3 -0
  11. package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
  12. package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
  13. package/src/components/dashboard/widgets/index.ts +5 -0
  14. package/src/components/dashboard/widgets/metric-card.tsx +21 -1
  15. package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
  16. package/src/components/error-boundary/index.tsx +160 -37
  17. package/src/components/form-wizard/form-wizard-context.tsx +54 -26
  18. package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
  19. package/src/components/form-wizard/types.ts +2 -1
  20. package/src/components/github-stars/hooks.ts +1 -0
  21. package/src/components/github-stars/variants.tsx +3 -1
  22. package/src/components/health-check/index.tsx +14 -14
  23. package/src/components/hover-card-3d/index.tsx +2 -3
  24. package/src/components/index.ts +5 -3
  25. package/src/components/kanban/kanban.tsx +23 -18
  26. package/src/components/license-error/index.tsx +2 -0
  27. package/src/components/magnetic-button/index.tsx +56 -7
  28. package/src/components/memory-efficient-data/index.tsx +117 -115
  29. package/src/components/navbar/index.tsx +781 -0
  30. package/src/components/performance-debugger/index.tsx +62 -38
  31. package/src/components/performance-monitor/index.tsx +47 -33
  32. package/src/components/phone-number-input/index.tsx +32 -27
  33. package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
  34. package/src/components/rich-text-editor/index.tsx +26 -28
  35. package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
  36. package/src/components/sidebar/index.tsx +32 -13
  37. package/src/components/timeline/index.tsx +84 -49
  38. package/src/components/ui/accordion.tsx +550 -42
  39. package/src/components/ui/avatar.tsx +2 -0
  40. package/src/components/ui/badge.tsx +2 -0
  41. package/src/components/ui/breadcrumb.tsx +2 -0
  42. package/src/components/ui/button.tsx +39 -33
  43. package/src/components/ui/card.tsx +2 -0
  44. package/src/components/ui/collapsible.tsx +546 -50
  45. package/src/components/ui/command.tsx +790 -67
  46. package/src/components/ui/dialog.tsx +510 -92
  47. package/src/components/ui/dropdown-menu.tsx +540 -52
  48. package/src/components/ui/index.ts +37 -5
  49. package/src/components/ui/input.tsx +2 -0
  50. package/src/components/ui/magnetic-button.tsx +1 -1
  51. package/src/components/ui/media-gallery.tsx +1 -2
  52. package/src/components/ui/navigation-menu.tsx +130 -0
  53. package/src/components/ui/pagination.tsx +2 -0
  54. package/src/components/ui/select.tsx +6 -2
  55. package/src/components/ui/spotlight-card.tsx +1 -1
  56. package/src/components/ui/table.tsx +2 -0
  57. package/src/components/ui/tabs-pro.tsx +542 -0
  58. package/src/components/ui/tabs.tsx +23 -167
  59. package/src/components/ui/toggle.tsx +12 -12
  60. package/src/index.ts +11 -3
  61. package/src/styles/index.css +596 -0
  62. package/src/use-performance-optimizer.ts +1 -1
  63. package/src/utils/chart-helpers.ts +1 -1
  64. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  65. package/src/__tests__/use-local-storage.test.tsx +0 -174
  66. package/src/__tests__/use-pro-access.test.tsx +0 -183
  67. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  68. package/src/components/data-table/data-table.test.tsx +0 -187
  69. package/src/components/enhanced/badge.tsx +0 -191
  70. package/src/components/enhanced/button.tsx +0 -362
  71. package/src/components/enhanced/card.tsx +0 -266
  72. package/src/components/enhanced/dialog.tsx +0 -246
  73. package/src/components/enhanced/index.ts +0 -4
  74. package/src/components/file-upload/file-upload.test.tsx +0 -243
  75. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  76. package/src/types/moonui.d.ts +0 -22
@@ -38,7 +38,9 @@ import {
38
38
  Zap,
39
39
  Sun,
40
40
  Moon,
41
- Palette
41
+ Palette,
42
+ Menu,
43
+ X
42
44
  } from 'lucide-react'
43
45
  import { cn } from '../../lib/utils'
44
46
  import { EventDialog } from './event-dialog'
@@ -228,6 +230,8 @@ export function Calendar({
228
230
  const [showFiltersPanel, setShowFiltersPanel] = React.useState(false)
229
231
  const [selectedTags, setSelectedTags] = React.useState<string[]>([])
230
232
  const [miniCalendarDate, setMiniCalendarDate] = React.useState(new Date())
233
+ const [isSidebarOpen, setIsSidebarOpen] = React.useState(false) // Mobile sidebar state
234
+ const [isDesktopSidebarCollapsed, setIsDesktopSidebarCollapsed] = React.useState(false) // Desktop sidebar state
231
235
 
232
236
  const today = new Date()
233
237
  const currentMonth = currentDate.getMonth()
@@ -550,42 +554,184 @@ export function Calendar({
550
554
 
551
555
  return (
552
556
  <>
553
- <Card className={cn("w-full max-w-full overflow-hidden", className)} style={{ height: height ? `${height}px` : undefined }}>
554
- <CardHeader>
555
- <div className="flex items-center justify-between">
556
- <div>
557
- <CardTitle className="flex items-center gap-2">
558
- <CalendarIcon className="h-5 w-5" />
559
- Calendar
560
- </CardTitle>
561
- <CardDescription>
562
- {MONTHS[currentMonth]} {currentYear}
563
- </CardDescription>
564
- </div>
565
- <div className="flex items-center gap-2">
566
- <Button variant="outline" size="sm" onClick={goToToday}>
567
- Today
568
- </Button>
569
- <Button
570
- variant="outline"
571
- size="sm"
572
- onClick={() => navigateMonth('prev')}
573
- disabled={disabled}
574
- >
575
- <ChevronLeft className="h-4 w-4" />
576
- </Button>
577
- <Button
578
- variant="outline"
579
- size="sm"
580
- onClick={() => navigateMonth('next')}
581
- disabled={disabled}
582
- >
583
- <ChevronRight className="h-4 w-4" />
584
- </Button>
557
+ <div className={cn("w-full flex relative", className)} style={{ height: height ? `${height}px` : undefined }}>
558
+ {/* Mobile Sidebar Overlay */}
559
+ {isSidebarOpen && (
560
+ <div
561
+ className="fixed inset-0 bg-black/50 z-40 lg:hidden"
562
+ onClick={() => setIsSidebarOpen(false)}
563
+ />
564
+ )}
565
+
566
+ {/* Sidebar */}
567
+ <aside className={cn(
568
+ "bg-card border-r transition-all duration-300 flex-shrink-0 overflow-hidden",
569
+ // Mobile styles
570
+ "fixed inset-y-0 left-0 z-50 lg:relative lg:inset-auto",
571
+ isSidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0",
572
+ // Desktop styles
573
+ isDesktopSidebarCollapsed ? "lg:w-0" : "lg:w-64"
574
+ )}>
575
+ <div className="w-64 h-full flex flex-col">
576
+ {/* Sidebar Header */}
577
+ <div className="p-4 border-b">
578
+ <div className="flex items-center justify-between">
579
+ <h3 className="font-semibold text-sm">Calendar</h3>
580
+ <Button
581
+ variant="ghost"
582
+ size="sm"
583
+ className="lg:hidden"
584
+ onClick={() => setIsSidebarOpen(false)}
585
+ >
586
+ <X className="h-4 w-4" />
587
+ </Button>
588
+ </div>
589
+ </div>
590
+
591
+ {/* Mini Calendar */}
592
+ <div className="p-4 border-b">
593
+ <div className="text-xs font-medium mb-2">
594
+ {MONTHS[miniCalendarDate.getMonth()]} {miniCalendarDate.getFullYear()}
595
+ </div>
596
+ <div className="grid grid-cols-7 gap-1 text-xs">
597
+ {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day, index) => (
598
+ <div key={`mini-day-${index}`} className="text-center text-muted-foreground">
599
+ {day}
600
+ </div>
601
+ ))}
602
+ {Array.from({ length: 35 }, (_, i) => {
603
+ const date = new Date(miniCalendarDate.getFullYear(), miniCalendarDate.getMonth(), i - new Date(miniCalendarDate.getFullYear(), miniCalendarDate.getMonth(), 1).getDay() + 1)
604
+ const isCurrentMonth = date.getMonth() === miniCalendarDate.getMonth()
605
+ const isToday = date.toDateString() === today.toDateString()
606
+ const hasEvents = getEventsForDate(date).length > 0
607
+
608
+ return (
609
+ <button
610
+ key={i}
611
+ className={cn(
612
+ "p-1 rounded hover:bg-muted/50 transition-colors",
613
+ !isCurrentMonth && "text-muted-foreground/50",
614
+ isToday && "bg-primary text-primary-foreground",
615
+ hasEvents && !isToday && "font-bold"
616
+ )}
617
+ onClick={() => {
618
+ setCurrentDate(date)
619
+ setSelectedDate(date)
620
+ setIsSidebarOpen(false)
621
+ }}
622
+ >
623
+ {date.getDate()}
624
+ </button>
625
+ )
626
+ })}
627
+ </div>
628
+ </div>
629
+
630
+ {/* Categories Filter */}
631
+ <div className="p-4 space-y-2 flex-1 overflow-y-auto">
632
+ <h4 className="text-xs font-medium mb-2">Categories</h4>
633
+ <button
634
+ onClick={() => setFilterType('all')}
635
+ className={cn(
636
+ "w-full text-left px-2 py-1 rounded text-xs hover:bg-muted/50 transition-colors",
637
+ filterType === 'all' && "bg-muted"
638
+ )}
639
+ >
640
+ All Events
641
+ </button>
642
+ {['Personal', 'Work', 'Meeting', 'Task', 'Reminder', 'Holiday', 'Birthday', 'Other'].map((type) => (
643
+ <button
644
+ key={type}
645
+ onClick={() => setFilterType(type.toLowerCase())}
646
+ className={cn(
647
+ "w-full text-left px-2 py-1 rounded text-xs hover:bg-muted/50 transition-colors flex items-center gap-2",
648
+ filterType === type.toLowerCase() && "bg-muted"
649
+ )}
650
+ >
651
+ <div className={cn("w-2 h-2 rounded-full", EVENT_COLORS[type.toLowerCase() as keyof typeof EVENT_COLORS] || "bg-gray-500")} />
652
+ {type}
653
+ </button>
654
+ ))}
655
+
656
+ <div className="pt-4">
657
+ <Button
658
+ className="w-full"
659
+ size="sm"
660
+ onClick={() => {
661
+ setEventDialogMode('create')
662
+ setSelectedEvent(null)
663
+ setEventDialogOpen(true)
664
+ setIsSidebarOpen(false)
665
+ }}
666
+ disabled={disabled}
667
+ >
668
+ <Plus className="h-4 w-4 mr-2" />
669
+ New Event
670
+ </Button>
671
+ </div>
672
+ </div>
585
673
  </div>
586
- </div>
587
- </CardHeader>
588
- <CardContent>
674
+ </aside>
675
+
676
+ {/* Main Content */}
677
+ <Card className="flex-1 border-0 rounded-none">
678
+ <CardHeader>
679
+ <div className="flex items-center justify-between">
680
+ <div className="flex items-center gap-2">
681
+ {/* Mobile Menu Button */}
682
+ <Button
683
+ variant="ghost"
684
+ size="sm"
685
+ className="lg:hidden"
686
+ onClick={() => setIsSidebarOpen(true)}
687
+ >
688
+ <Menu className="h-4 w-4" />
689
+ </Button>
690
+
691
+ {/* Desktop Sidebar Toggle */}
692
+ <Button
693
+ variant="ghost"
694
+ size="sm"
695
+ className="hidden lg:block"
696
+ onClick={() => setIsDesktopSidebarCollapsed(!isDesktopSidebarCollapsed)}
697
+ >
698
+ <Menu className="h-4 w-4" />
699
+ </Button>
700
+
701
+ <div>
702
+ <CardTitle className="flex items-center gap-2">
703
+ <CalendarIcon className="h-5 w-5" />
704
+ {view === 'month' && `${MONTHS[currentMonth]} ${currentYear}`}
705
+ {view === 'week' && `Week of ${currentDate.toLocaleDateString()}`}
706
+ {view === 'day' && currentDate.toLocaleDateString()}
707
+ {view === 'agenda' && 'Agenda'}
708
+ </CardTitle>
709
+ </div>
710
+ </div>
711
+ <div className="flex items-center gap-2">
712
+ <Button variant="outline" size="sm" onClick={goToToday} className="hidden sm:flex">
713
+ Today
714
+ </Button>
715
+ <Button
716
+ variant="outline"
717
+ size="sm"
718
+ onClick={() => navigateMonth('prev')}
719
+ disabled={disabled}
720
+ >
721
+ <ChevronLeft className="h-4 w-4" />
722
+ </Button>
723
+ <Button
724
+ variant="outline"
725
+ size="sm"
726
+ onClick={() => navigateMonth('next')}
727
+ disabled={disabled}
728
+ >
729
+ <ChevronRight className="h-4 w-4" />
730
+ </Button>
731
+ </div>
732
+ </div>
733
+ </CardHeader>
734
+ <CardContent>
589
735
  <div className="space-y-4">
590
736
  {/* Calendar View */}
591
737
  {view === 'month' && (
@@ -1052,21 +1198,22 @@ export function Calendar({
1052
1198
  </div>
1053
1199
  </div>
1054
1200
  )}
1055
- </div>
1056
- </CardContent>
1057
- </Card>
1201
+ </div>
1202
+ </CardContent>
1203
+ </Card>
1204
+ </div>
1058
1205
 
1059
- {/* Event Dialog */}
1060
- <EventDialog
1061
- open={eventDialogOpen}
1062
- onOpenChange={setEventDialogOpen}
1063
- event={selectedEvent}
1064
- selectedDate={selectedDate}
1065
- onSave={handleEventSave}
1066
- onDelete={handleEventDialogDelete}
1067
- mode={eventDialogMode}
1068
- />
1069
- </>
1206
+ {/* Event Dialog */}
1207
+ <EventDialog
1208
+ open={eventDialogOpen}
1209
+ onOpenChange={setEventDialogOpen}
1210
+ event={selectedEvent}
1211
+ selectedDate={selectedDate}
1212
+ onSave={handleEventSave}
1213
+ onDelete={handleEventDialogDelete}
1214
+ mode={eventDialogMode}
1215
+ />
1216
+ </>
1070
1217
  )
1071
1218
  }
1072
1219
 
@@ -7,6 +7,8 @@ import { Widget } from './types'
7
7
  import { MetricCard } from './widgets/metric-card'
8
8
  import { ChartWidget } from './widgets/chart-widget'
9
9
  import { ActivityFeed } from './widgets/activity-feed'
10
+ import { ProgressWidget } from './widgets/progress-widget'
11
+ import { ComparisonWidget } from './widgets/comparison-widget'
10
12
  import { Responsive, WidthProvider, Layout, Layouts } from 'react-grid-layout'
11
13
  const ResponsiveGridLayout = WidthProvider(Responsive)
12
14
  import 'react-grid-layout/css/styles.css'
@@ -219,11 +221,27 @@ export function DashboardGrid({
219
221
  />
220
222
  )
221
223
 
224
+ case 'progress':
225
+ return (
226
+ <ProgressWidget
227
+ data={widget.data}
228
+ title={widget.title}
229
+ glassmorphism={glassmorphism}
230
+ />
231
+ )
232
+
233
+ case 'comparison':
234
+ return (
235
+ <ComparisonWidget
236
+ data={widget.data}
237
+ title={widget.title}
238
+ glassmorphism={glassmorphism}
239
+ />
240
+ )
241
+
222
242
  case 'table':
223
243
  case 'map':
224
244
  case 'calendar':
225
- case 'progress':
226
- case 'comparison':
227
245
  // Bu widget tipleri için geçici olarak basit bir görünüm
228
246
  return (
229
247
  <div className={cn(
@@ -260,7 +278,7 @@ export function DashboardGrid({
260
278
  transition={{ duration: 0.2 }}
261
279
  >
262
280
  {/* Widget içeriği */}
263
- <div className="h-full">
281
+ <div className="h-full dashboard-widget-container">
264
282
  {widgetContent()}
265
283
  </div>
266
284
 
@@ -100,6 +100,9 @@ export interface ProgressData {
100
100
  unit?: string
101
101
  color?: 'primary' | 'success' | 'warning' | 'danger'
102
102
  deadline?: Date
103
+ description?: string
104
+ milestone?: number
105
+ trend?: number
103
106
  }
104
107
 
105
108
  // Comparison widget
@@ -61,6 +61,11 @@ export function ActivityFeed({
61
61
  const [filter, setFilter] = React.useState<'all' | 'info' | 'success' | 'warning' | 'error'>('all')
62
62
  const [notificationsEnabled, setNotificationsEnabled] = React.useState(true)
63
63
  const [newItems, setNewItems] = React.useState<ActivityItem[]>([])
64
+ const [isMounted, setIsMounted] = React.useState(false)
65
+
66
+ React.useEffect(() => {
67
+ setIsMounted(true)
68
+ }, [])
64
69
 
65
70
  // Simüle edilmiş real-time güncellemeler
66
71
  React.useEffect(() => {
@@ -179,7 +184,7 @@ export function ActivityFeed({
179
184
  {/* Zaman damgası */}
180
185
  <div className="flex items-center gap-1 text-xs text-muted-foreground whitespace-nowrap">
181
186
  <Clock className="h-3 w-3" />
182
- {formatDistanceToNow(item.timestamp, { addSuffix: true })}
187
+ {isMounted ? formatDistanceToNow(item.timestamp, { addSuffix: true }) : 'Loading...'}
183
188
  </div>
184
189
  </div>
185
190
 
@@ -0,0 +1,177 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { motion } from 'framer-motion'
5
+ import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card'
6
+ import { cn } from '../../../lib/utils'
7
+ import { ComparisonData } from '../types'
8
+ import { ArrowUpRight, ArrowDownRight, Minus, BarChart3 } from 'lucide-react'
9
+ import { Badge } from '../../ui/badge'
10
+
11
+ interface ComparisonWidgetProps {
12
+ data: ComparisonData
13
+ title?: string
14
+ className?: string
15
+ glassmorphism?: boolean
16
+ }
17
+
18
+ export function ComparisonWidget({
19
+ data,
20
+ title = "Comparison",
21
+ className,
22
+ glassmorphism = false
23
+ }: ComparisonWidgetProps) {
24
+ const [isMounted, setIsMounted] = React.useState(false)
25
+
26
+ React.useEffect(() => {
27
+ setIsMounted(true)
28
+ }, [])
29
+
30
+ // En yüksek değeri bul
31
+ const maxValue = Math.max(...data.periods.map(p => p.value))
32
+
33
+ // İlk iki period arasındaki değişimi hesapla
34
+ const change = data.periods.length >= 2
35
+ ? ((data.periods[0].value - data.periods[1].value) / data.periods[1].value) * 100
36
+ : 0
37
+
38
+ const changeType = change > 0 ? 'increase' : change < 0 ? 'decrease' : 'neutral'
39
+
40
+ // Değer formatla
41
+ const formatValue = (value: number) => {
42
+ if (!isMounted) return value.toString()
43
+
44
+ if (value >= 1000000) {
45
+ return (value / 1000000).toFixed(1) + 'M'
46
+ } else if (value >= 1000) {
47
+ return (value / 1000).toFixed(0) + 'K'
48
+ }
49
+ return value.toLocaleString()
50
+ }
51
+
52
+ return (
53
+ <Card className={cn(
54
+ "h-full flex flex-col overflow-hidden",
55
+ glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
56
+ className
57
+ )}>
58
+ <CardHeader className="flex-shrink-0 pb-3 px-4">
59
+ <div className="flex items-start justify-between gap-2">
60
+ <div className="flex-1 min-w-0">
61
+ <CardTitle className="text-sm font-semibold flex items-center gap-2">
62
+ <BarChart3 className="h-4 w-4 text-muted-foreground flex-shrink-0" />
63
+ <span className="truncate">{title}</span>
64
+ </CardTitle>
65
+ {data.metric && (
66
+ <p className="text-xs text-muted-foreground mt-1 truncate">{data.metric}</p>
67
+ )}
68
+ </div>
69
+ {change !== 0 && (
70
+ <div
71
+ className={cn(
72
+ "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium flex-shrink-0",
73
+ changeType === 'increase' && "bg-green-500/10 text-green-600 dark:text-green-400 border border-green-500/20",
74
+ changeType === 'decrease' && "bg-red-500/10 text-red-600 dark:text-red-400 border border-red-500/20",
75
+ changeType === 'neutral' && "bg-muted text-muted-foreground border border-border"
76
+ )}
77
+ >
78
+ {changeType === 'increase' ? <ArrowUpRight className="h-3 w-3" /> :
79
+ changeType === 'decrease' ? <ArrowDownRight className="h-3 w-3" /> :
80
+ <Minus className="h-3 w-3" />}
81
+ <span>
82
+ {Math.abs(change).toFixed(1)}%
83
+ </span>
84
+ </div>
85
+ )}
86
+ </div>
87
+ </CardHeader>
88
+
89
+ <CardContent className="flex-1 px-4 pb-4 overflow-hidden flex flex-col gap-3">
90
+ {/* Bar list görünümü - scrollable area */}
91
+ <div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden pr-2 -mr-2">
92
+ <div className="space-y-2.5">
93
+ {data.periods.map((period, index) => {
94
+ const percentage = (period.value / maxValue) * 100
95
+ const isHighest = period.value === maxValue
96
+
97
+ return (
98
+ <motion.div
99
+ key={period.label}
100
+ initial={{ opacity: 0, x: -20 }}
101
+ animate={{ opacity: 1, x: 0 }}
102
+ transition={{ delay: index * 0.05 }}
103
+ >
104
+ {/* Label ve değer */}
105
+ <div className="flex items-center justify-between gap-3 mb-1">
106
+ <span className={cn(
107
+ "text-xs font-medium truncate flex-1",
108
+ isHighest ? "text-foreground" : "text-muted-foreground"
109
+ )}>
110
+ {period.label}
111
+ </span>
112
+ <span className={cn(
113
+ "text-xs font-semibold tabular-nums flex-shrink-0",
114
+ isHighest && "text-primary"
115
+ )}>
116
+ {formatValue(period.value)}
117
+ </span>
118
+ </div>
119
+
120
+ {/* Progress bar */}
121
+ <div className="w-full bg-muted rounded-full h-1.5 overflow-hidden">
122
+ <motion.div
123
+ className={cn(
124
+ "h-full rounded-full transition-colors",
125
+ isHighest ? "bg-primary" : "bg-primary/60"
126
+ )}
127
+ initial={{ width: 0 }}
128
+ animate={{ width: `${Math.min(percentage, 100)}%` }}
129
+ transition={{ duration: 0.5, delay: index * 0.05 }}
130
+ />
131
+ </div>
132
+ </motion.div>
133
+ )
134
+ })}
135
+ </div>
136
+ </div>
137
+
138
+ {/* Compact chart view - fixed height */}
139
+ {data.showChart && (
140
+ <div className="flex-shrink-0 pt-3 border-t">
141
+ <div className="h-14 flex items-end justify-between gap-1">
142
+ {data.periods.slice(0, 5).map((period, index) => {
143
+ const height = (period.value / maxValue) * 100
144
+ const isHighest = period.value === maxValue
145
+
146
+ return (
147
+ <motion.div
148
+ key={period.label}
149
+ className="flex-1 flex flex-col items-center gap-0.5"
150
+ initial={{ opacity: 0 }}
151
+ animate={{ opacity: 1 }}
152
+ transition={{ delay: index * 0.05 }}
153
+ >
154
+ <div className="w-full h-10 flex items-end justify-center px-0.5">
155
+ <motion.div
156
+ className={cn(
157
+ "w-full max-w-[20px] rounded-t transition-colors",
158
+ isHighest ? "bg-primary" : "bg-primary/40"
159
+ )}
160
+ initial={{ height: 0 }}
161
+ animate={{ height: `${height}%` }}
162
+ transition={{ duration: 0.5, delay: index * 0.05 }}
163
+ />
164
+ </div>
165
+ <span className="text-[9px] text-muted-foreground">
166
+ {period.label.slice(0, 3)}
167
+ </span>
168
+ </motion.div>
169
+ )
170
+ })}
171
+ </div>
172
+ </div>
173
+ )}
174
+ </CardContent>
175
+ </Card>
176
+ )
177
+ }
@@ -0,0 +1,5 @@
1
+ export { MetricCard } from './metric-card'
2
+ export { ChartWidget } from './chart-widget'
3
+ export { ActivityFeed } from './activity-feed'
4
+ export { ProgressWidget } from './progress-widget'
5
+ export { ComparisonWidget } from './comparison-widget'
@@ -47,6 +47,11 @@ export function MetricCard({
47
47
  glassmorphism = false
48
48
  }: MetricCardProps) {
49
49
  const [isHovered, setIsHovered] = React.useState(false)
50
+ const [isMounted, setIsMounted] = React.useState(false)
51
+
52
+ React.useEffect(() => {
53
+ setIsMounted(true)
54
+ }, [])
50
55
 
51
56
  // Renk sınıfları
52
57
  const colorClasses = {
@@ -173,13 +178,28 @@ export function MetricCard({
173
178
 
174
179
  // Değer formatı
175
180
  const formatValue = (value: string | number): string => {
181
+ if (!isMounted) {
182
+ // Server-side: basit format kullan
183
+ if (typeof value === 'number') {
184
+ if (value >= 1000000) {
185
+ return (value / 1000000).toFixed(1) + 'M'
186
+ } else if (value >= 1000) {
187
+ return (value / 1000).toFixed(1) + 'K'
188
+ }
189
+ return value.toString()
190
+ }
191
+ return value.toString()
192
+ }
193
+
194
+ // Client-side: locale formatting kullanabilir
176
195
  if (typeof value === 'number') {
177
196
  if (value >= 1000000) {
178
197
  return (value / 1000000).toFixed(1) + 'M'
179
198
  } else if (value >= 1000) {
180
199
  return (value / 1000).toFixed(1) + 'K'
181
200
  }
182
- return value.toLocaleString()
201
+ // Nokta kullan, virgül değil
202
+ return value.toFixed(2)
183
203
  }
184
204
  return value.toString()
185
205
  }