@moontra/moonui-pro 2.4.6 → 2.5.1

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,343 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { motion } from 'framer-motion'
5
+ import { Card } from '../../ui/card'
6
+ import { cn } from '../../../lib/utils'
7
+ import {
8
+ TrendingUp,
9
+ TrendingDown,
10
+ Minus,
11
+ ArrowUpRight,
12
+ ArrowDownRight,
13
+ MoreVertical,
14
+ Maximize2,
15
+ Download,
16
+ Share2
17
+ } from 'lucide-react'
18
+ import { MetricData } from '../types'
19
+ import { Button } from '../../ui/button'
20
+ import {
21
+ DropdownMenu,
22
+ DropdownMenuContent,
23
+ DropdownMenuItem,
24
+ DropdownMenuTrigger,
25
+ } from '../../ui/dropdown-menu'
26
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'
27
+
28
+ interface MetricCardProps {
29
+ data: MetricData
30
+ onClick?: () => void
31
+ onAction?: (action: string) => void
32
+ className?: string
33
+ showSparkline?: boolean
34
+ showForecast?: boolean
35
+ interactive?: boolean
36
+ glassmorphism?: boolean
37
+ }
38
+
39
+ export function MetricCard({
40
+ data,
41
+ onClick,
42
+ onAction,
43
+ className,
44
+ showSparkline = true,
45
+ showForecast = false,
46
+ interactive = true,
47
+ glassmorphism = false
48
+ }: MetricCardProps) {
49
+ const [isHovered, setIsHovered] = React.useState(false)
50
+
51
+ // Renk sınıfları
52
+ const colorClasses = {
53
+ primary: 'text-blue-600 dark:text-blue-400',
54
+ success: 'text-green-600 dark:text-green-400',
55
+ warning: 'text-yellow-600 dark:text-yellow-400',
56
+ danger: 'text-red-600 dark:text-red-400',
57
+ info: 'text-purple-600 dark:text-purple-400'
58
+ }
59
+
60
+ // Değişim renkleri
61
+ const getChangeColor = (type: 'increase' | 'decrease' | 'neutral') => {
62
+ switch (type) {
63
+ case 'increase':
64
+ return 'text-green-600 dark:text-green-400'
65
+ case 'decrease':
66
+ return 'text-red-600 dark:text-red-400'
67
+ default:
68
+ return 'text-gray-600 dark:text-gray-400'
69
+ }
70
+ }
71
+
72
+ // Değişim ikonu
73
+ const getChangeIcon = (type: 'increase' | 'decrease' | 'neutral') => {
74
+ switch (type) {
75
+ case 'increase':
76
+ return <ArrowUpRight className="h-4 w-4" />
77
+ case 'decrease':
78
+ return <ArrowDownRight className="h-4 w-4" />
79
+ default:
80
+ return <Minus className="h-4 w-4" />
81
+ }
82
+ }
83
+
84
+ // Sparkline çizimi
85
+ const renderSparkline = () => {
86
+ if (!data.sparkline || data.sparkline.length < 2) return null
87
+
88
+ const max = Math.max(...data.sparkline)
89
+ const min = Math.min(...data.sparkline)
90
+ const range = max - min || 1
91
+
92
+ const points = data.sparkline
93
+ .map((value, index) => {
94
+ const x = (index / (data.sparkline!.length - 1)) * 100
95
+ const y = 100 - ((value - min) / range) * 100
96
+ return `${x},${y}`
97
+ })
98
+ .join(' ')
99
+
100
+ const gradientId = `gradient-${data.id}`
101
+
102
+ return (
103
+ <motion.div
104
+ className="absolute bottom-0 left-0 right-0 h-16 overflow-hidden rounded-b-lg opacity-20"
105
+ initial={{ opacity: 0 }}
106
+ animate={{ opacity: isHovered ? 0.3 : 0.2 }}
107
+ transition={{ duration: 0.2 }}
108
+ >
109
+ <svg
110
+ className="w-full h-full"
111
+ viewBox="0 0 100 100"
112
+ preserveAspectRatio="none"
113
+ >
114
+ <defs>
115
+ <linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
116
+ <stop offset="0%" stopColor="currentColor" stopOpacity="0.3" />
117
+ <stop offset="100%" stopColor="currentColor" stopOpacity="0" />
118
+ </linearGradient>
119
+ </defs>
120
+ <polyline
121
+ fill="none"
122
+ stroke="currentColor"
123
+ strokeWidth="2"
124
+ points={points}
125
+ className={colorClasses[data.color || 'primary']}
126
+ />
127
+ <polygon
128
+ fill={`url(#${gradientId})`}
129
+ points={`0,100 ${points} 100,100`}
130
+ className={colorClasses[data.color || 'primary']}
131
+ />
132
+ </svg>
133
+ </motion.div>
134
+ )
135
+ }
136
+
137
+ // İlerleme barı (hedef varsa)
138
+ const renderProgress = () => {
139
+ if (!data.target) return null
140
+
141
+ const currentValue = typeof data.value === 'number' ? data.value : parseFloat(data.value as string)
142
+ const progress = (currentValue / data.target) * 100
143
+
144
+ return (
145
+ <motion.div
146
+ className="mt-3"
147
+ initial={{ opacity: 0, y: 10 }}
148
+ animate={{ opacity: 1, y: 0 }}
149
+ transition={{ delay: 0.1 }}
150
+ >
151
+ <div className="flex justify-between text-xs text-muted-foreground mb-1">
152
+ <span>Progress to target</span>
153
+ <span>{progress.toFixed(1)}%</span>
154
+ </div>
155
+ <div className="h-1.5 bg-muted rounded-full overflow-hidden">
156
+ <motion.div
157
+ className={cn(
158
+ "h-full rounded-full",
159
+ progress >= 100 ? 'bg-green-500' : 'bg-primary'
160
+ )}
161
+ initial={{ width: 0 }}
162
+ animate={{ width: `${Math.min(progress, 100)}%` }}
163
+ transition={{ duration: 0.5, ease: "easeOut" }}
164
+ />
165
+ </div>
166
+ <div className="flex justify-between text-xs text-muted-foreground mt-1">
167
+ <span>{currentValue} {data.unit}</span>
168
+ <span>Target: {data.target} {data.unit}</span>
169
+ </div>
170
+ </motion.div>
171
+ )
172
+ }
173
+
174
+ // Değer formatı
175
+ const formatValue = (value: string | number): string => {
176
+ if (typeof value === 'number') {
177
+ if (value >= 1000000) {
178
+ return (value / 1000000).toFixed(1) + 'M'
179
+ } else if (value >= 1000) {
180
+ return (value / 1000).toFixed(1) + 'K'
181
+ }
182
+ return value.toLocaleString()
183
+ }
184
+ return value.toString()
185
+ }
186
+
187
+ // Card variants
188
+ const cardVariants = {
189
+ initial: { opacity: 0, y: 20 },
190
+ animate: {
191
+ opacity: 1,
192
+ y: 0,
193
+ transition: { duration: 0.3, ease: "easeOut" }
194
+ },
195
+ hover: interactive ? {
196
+ y: -2,
197
+ transition: { duration: 0.2 }
198
+ } : {}
199
+ }
200
+
201
+ return (
202
+ <motion.div
203
+ variants={cardVariants}
204
+ initial="initial"
205
+ animate="animate"
206
+ whileHover="hover"
207
+ onHoverStart={() => setIsHovered(true)}
208
+ onHoverEnd={() => setIsHovered(false)}
209
+ >
210
+ <Card
211
+ className={cn(
212
+ "relative overflow-hidden cursor-pointer transition-all duration-300",
213
+ interactive && "hover:shadow-lg hover:shadow-primary/5",
214
+ glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
215
+ className
216
+ )}
217
+ onClick={onClick}
218
+ >
219
+ {/* Açık arka plan efekti */}
220
+ {glassmorphism && (
221
+ <div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent" />
222
+ )}
223
+
224
+ {/* Üst bar */}
225
+ <div className="flex items-center justify-between p-4 pb-2">
226
+ <div className="flex items-center gap-3">
227
+ <motion.div
228
+ className={cn(
229
+ "p-2 rounded-lg",
230
+ glassmorphism ? "bg-white/10 backdrop-blur" : "bg-muted"
231
+ )}
232
+ whileHover={{ scale: 1.05 }}
233
+ whileTap={{ scale: 0.95 }}
234
+ >
235
+ <div className={cn("h-5 w-5", colorClasses[data.color || 'primary'])}>
236
+ {data.icon}
237
+ </div>
238
+ </motion.div>
239
+ <div>
240
+ <h3 className="text-sm font-medium text-muted-foreground">{data.title}</h3>
241
+ </div>
242
+ </div>
243
+
244
+ {/* Aksiyonlar */}
245
+ {interactive && (
246
+ <DropdownMenu>
247
+ <DropdownMenuTrigger asChild>
248
+ <Button
249
+ variant="ghost"
250
+ size="sm"
251
+ className="h-8 w-8 p-0"
252
+ onClick={(e) => e.stopPropagation()}
253
+ >
254
+ <MoreVertical className="h-4 w-4" />
255
+ </Button>
256
+ </DropdownMenuTrigger>
257
+ <DropdownMenuContent align="end">
258
+ <DropdownMenuItem onClick={() => onAction?.('fullscreen')}>
259
+ <Maximize2 className="mr-2 h-4 w-4" />
260
+ Fullscreen
261
+ </DropdownMenuItem>
262
+ <DropdownMenuItem onClick={() => onAction?.('export')}>
263
+ <Download className="mr-2 h-4 w-4" />
264
+ Export Data
265
+ </DropdownMenuItem>
266
+ <DropdownMenuItem onClick={() => onAction?.('share')}>
267
+ <Share2 className="mr-2 h-4 w-4" />
268
+ Share
269
+ </DropdownMenuItem>
270
+ </DropdownMenuContent>
271
+ </DropdownMenu>
272
+ )}
273
+ </div>
274
+
275
+ {/* Ana değer */}
276
+ <div className="px-4 pb-4">
277
+ <TooltipProvider>
278
+ <Tooltip>
279
+ <TooltipTrigger asChild>
280
+ <motion.div
281
+ className="text-3xl font-bold tracking-tight"
282
+ animate={{
283
+ scale: isHovered ? 1.02 : 1,
284
+ }}
285
+ transition={{ duration: 0.2 }}
286
+ >
287
+ {formatValue(data.value)}
288
+ {data.unit && <span className="text-lg ml-1 text-muted-foreground">{data.unit}</span>}
289
+ </motion.div>
290
+ </TooltipTrigger>
291
+ <TooltipContent>
292
+ <p>Exact value: {data.value} {data.unit}</p>
293
+ </TooltipContent>
294
+ </Tooltip>
295
+ </TooltipProvider>
296
+
297
+ {/* Değişim göstergesi */}
298
+ {data.change && (
299
+ <motion.div
300
+ className={cn(
301
+ "flex items-center gap-1 text-sm mt-2",
302
+ getChangeColor(data.change.type)
303
+ )}
304
+ initial={{ opacity: 0, x: -10 }}
305
+ animate={{ opacity: 1, x: 0 }}
306
+ transition={{ delay: 0.1 }}
307
+ >
308
+ {getChangeIcon(data.change.type)}
309
+ <span className="font-medium">
310
+ {data.change.type === 'neutral' ? '' : data.change.type === 'increase' ? '+' : '-'}
311
+ {Math.abs(data.change.value)}%
312
+ </span>
313
+ <span className="text-muted-foreground">
314
+ from {data.change.period}
315
+ </span>
316
+ </motion.div>
317
+ )}
318
+
319
+ {/* Tahmin */}
320
+ {showForecast && data.forecast && (
321
+ <motion.div
322
+ className="flex items-center gap-1 text-xs text-muted-foreground mt-1"
323
+ initial={{ opacity: 0 }}
324
+ animate={{ opacity: 1 }}
325
+ transition={{ delay: 0.2 }}
326
+ >
327
+ <TrendingUp className="h-3 w-3" />
328
+ <span>Forecast: {formatValue(data.forecast)} {data.unit}</span>
329
+ </motion.div>
330
+ )}
331
+
332
+ {/* İlerleme barı */}
333
+ {renderProgress()}
334
+ </div>
335
+
336
+ {/* Sparkline */}
337
+ {showSparkline && renderSparkline()}
338
+ </Card>
339
+ </motion.div>
340
+ )
341
+ }
342
+
343
+ export default MetricCard
@@ -24,8 +24,8 @@ export * from "./pinch-zoom"
24
24
  // Spotlight Card
25
25
  export * from "./spotlight-card"
26
26
 
27
- // Calendar
28
- export * from "./calendar"
27
+ // Calendar component (Advanced version with events)
28
+ export { Calendar as AdvancedCalendar } from "./calendar"
29
29
 
30
30
  // Kanban
31
31
  export * from "./kanban"
@@ -81,4 +81,7 @@ export * from "./performance-monitor"
81
81
  export * from "./file-upload"
82
82
 
83
83
  // Data Table
84
- export * from "./data-table"
84
+ export * from "./data-table"
85
+
86
+ // Sidebar
87
+ export * from "./sidebar"