@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.
- package/dist/index.d.ts +234 -16
- package/dist/index.mjs +2493 -187
- package/package.json +4 -1
- package/src/components/advanced-chart/index.tsx +11 -1
- package/src/components/dashboard/dashboard-grid.tsx +462 -0
- package/src/components/dashboard/demo.tsx +311 -0
- package/src/components/dashboard/index.tsx +691 -275
- package/src/components/dashboard/time-range-picker.tsx +269 -0
- package/src/components/dashboard/types.ts +222 -0
- package/src/components/dashboard/widgets/activity-feed.tsx +344 -0
- package/src/components/dashboard/widgets/chart-widget.tsx +418 -0
- package/src/components/dashboard/widgets/metric-card.tsx +343 -0
- package/src/components/index.ts +6 -3
- package/src/components/sidebar/index.tsx +579 -0
- package/src/components/ui/calendar.tsx +65 -0
- package/src/components/ui/index.ts +12 -0
- package/src/components/ui/scroll-area.tsx +47 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/styles/index.css +69 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
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",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
84
84
|
"@radix-ui/react-label": "^2.1.7",
|
|
85
85
|
"@radix-ui/react-popover": "^1.1.14",
|
|
86
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
86
87
|
"@radix-ui/react-select": "^2.2.5",
|
|
87
88
|
"@radix-ui/react-separator": "^1.1.7",
|
|
88
89
|
"@radix-ui/react-slot": "^1.2.3",
|
|
@@ -96,7 +97,9 @@
|
|
|
96
97
|
"date-fns": "^3.6.0",
|
|
97
98
|
"framer-motion": "^11.11.17",
|
|
98
99
|
"lucide-react": "^0.525.0",
|
|
100
|
+
"react-day-picker": "^9.8.0",
|
|
99
101
|
"react-dropzone": "^14.3.5",
|
|
102
|
+
"react-grid-layout": "^1.5.2",
|
|
100
103
|
"react-hook-form": "^7.53.2",
|
|
101
104
|
"react-intersection-observer": "^9.13.1",
|
|
102
105
|
"react-virtualized-auto-sizer": "^1.0.24",
|
|
@@ -340,15 +340,20 @@ export function AdvancedChart({
|
|
|
340
340
|
|
|
341
341
|
// Calculate trend for the first series
|
|
342
342
|
const trend = React.useMemo(() => {
|
|
343
|
-
if (!data.length || !series.length) return null
|
|
343
|
+
if (!data || !Array.isArray(data) || !data.length || !series || !Array.isArray(series) || !series.length) return null
|
|
344
344
|
|
|
345
345
|
const firstSeries = series[0]
|
|
346
|
+
if (!firstSeries || !firstSeries.dataKey) return null
|
|
347
|
+
|
|
346
348
|
const values = data.map(d => Number(d[firstSeries.dataKey])).filter(v => !isNaN(v))
|
|
347
349
|
|
|
348
350
|
if (values.length < 2) return null
|
|
349
351
|
|
|
350
352
|
const first = values[0]
|
|
351
353
|
const last = values[values.length - 1]
|
|
354
|
+
|
|
355
|
+
if (first === 0) return null // Avoid division by zero
|
|
356
|
+
|
|
352
357
|
const change = ((last - first) / first) * 100
|
|
353
358
|
|
|
354
359
|
return {
|
|
@@ -411,6 +416,11 @@ export function AdvancedChart({
|
|
|
411
416
|
)
|
|
412
417
|
|
|
413
418
|
const renderChart = () => {
|
|
419
|
+
// Güvenlik kontrolleri
|
|
420
|
+
if (!data || !Array.isArray(data) || !series || !Array.isArray(series)) {
|
|
421
|
+
return <div className="flex items-center justify-center h-full text-muted-foreground">No data available</div>
|
|
422
|
+
}
|
|
423
|
+
|
|
414
424
|
const commonProps = {
|
|
415
425
|
data,
|
|
416
426
|
width: typeof width === 'string' ? undefined : width,
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion'
|
|
5
|
+
import { cn } from '../../lib/utils'
|
|
6
|
+
import { Widget } from './types'
|
|
7
|
+
import { MetricCard } from './widgets/metric-card'
|
|
8
|
+
import { ChartWidget } from './widgets/chart-widget'
|
|
9
|
+
import { ActivityFeed } from './widgets/activity-feed'
|
|
10
|
+
import { Responsive, WidthProvider, Layout, Layouts } from 'react-grid-layout'
|
|
11
|
+
const ResponsiveGridLayout = WidthProvider(Responsive)
|
|
12
|
+
import 'react-grid-layout/css/styles.css'
|
|
13
|
+
import 'react-resizable/css/styles.css'
|
|
14
|
+
import {
|
|
15
|
+
Grip,
|
|
16
|
+
X,
|
|
17
|
+
Maximize2,
|
|
18
|
+
Minimize2,
|
|
19
|
+
Lock,
|
|
20
|
+
Unlock,
|
|
21
|
+
Settings
|
|
22
|
+
} from 'lucide-react'
|
|
23
|
+
import { Button } from '../ui/button'
|
|
24
|
+
|
|
25
|
+
interface DashboardGridProps {
|
|
26
|
+
widgets: Widget[]
|
|
27
|
+
onLayoutChange?: (layout: Layout[]) => void
|
|
28
|
+
onWidgetRemove?: (widgetId: string) => void
|
|
29
|
+
onWidgetAction?: (widgetId: string, action: string, data?: any) => void
|
|
30
|
+
editMode?: boolean
|
|
31
|
+
className?: string
|
|
32
|
+
glassmorphism?: boolean
|
|
33
|
+
breakpoints?: { lg: number; md: number; sm: number; xs: number; xxs: number }
|
|
34
|
+
cols?: { lg: number; md: number; sm: number; xs: number; xxs: number }
|
|
35
|
+
rowHeight?: number
|
|
36
|
+
margin?: [number, number]
|
|
37
|
+
containerPadding?: [number, number]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function DashboardGrid({
|
|
41
|
+
widgets,
|
|
42
|
+
onLayoutChange,
|
|
43
|
+
onWidgetRemove,
|
|
44
|
+
onWidgetAction,
|
|
45
|
+
editMode = false,
|
|
46
|
+
className,
|
|
47
|
+
glassmorphism = false,
|
|
48
|
+
breakpoints = { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
|
|
49
|
+
cols = { lg: 12, md: 8, sm: 4, xs: 2, xxs: 1 },
|
|
50
|
+
rowHeight = 80,
|
|
51
|
+
margin = [16, 24],
|
|
52
|
+
containerPadding = [0, 0]
|
|
53
|
+
}: DashboardGridProps) {
|
|
54
|
+
const [layouts, setLayouts] = React.useState<Layouts>({})
|
|
55
|
+
const [currentBreakpoint, setCurrentBreakpoint] = React.useState('lg')
|
|
56
|
+
const [lockedWidgets, setLockedWidgets] = React.useState<Set<string>>(new Set())
|
|
57
|
+
const [fullscreenWidget, setFullscreenWidget] = React.useState<string | null>(null)
|
|
58
|
+
const [containerWidth, setContainerWidth] = React.useState(1200)
|
|
59
|
+
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
60
|
+
const [compactType, setCompactType] = React.useState<'vertical' | 'horizontal' | null>('vertical')
|
|
61
|
+
|
|
62
|
+
// Container genişliğini ölç ve breakpoint'i güncelle
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
const updateWidth = () => {
|
|
65
|
+
if (containerRef.current) {
|
|
66
|
+
const width = containerRef.current.offsetWidth
|
|
67
|
+
setContainerWidth(width)
|
|
68
|
+
|
|
69
|
+
// Breakpoint'i manuel olarak belirle
|
|
70
|
+
if (width < 480) {
|
|
71
|
+
setCurrentBreakpoint('xxs')
|
|
72
|
+
} else if (width < 768) {
|
|
73
|
+
setCurrentBreakpoint('xs')
|
|
74
|
+
} else if (width < 996) {
|
|
75
|
+
setCurrentBreakpoint('sm')
|
|
76
|
+
} else if (width < 1200) {
|
|
77
|
+
setCurrentBreakpoint('md')
|
|
78
|
+
} else {
|
|
79
|
+
setCurrentBreakpoint('lg')
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
updateWidth()
|
|
85
|
+
window.addEventListener('resize', updateWidth)
|
|
86
|
+
return () => window.removeEventListener('resize', updateWidth)
|
|
87
|
+
}, [])
|
|
88
|
+
|
|
89
|
+
// Responsive layout oluştur
|
|
90
|
+
const generateResponsiveLayouts = (): Layouts => {
|
|
91
|
+
const baseLayout = widgets.map((widget, index) => ({
|
|
92
|
+
i: widget.id,
|
|
93
|
+
x: widget.position?.x ?? (index * 3) % 12,
|
|
94
|
+
y: widget.position?.y ?? Math.floor((index * 3) / 12) * 3,
|
|
95
|
+
w: widget.size?.w ?? 3,
|
|
96
|
+
h: widget.size?.h ?? 3,
|
|
97
|
+
minW: widget.size?.minW || 2,
|
|
98
|
+
maxW: widget.size?.maxW || 12,
|
|
99
|
+
minH: widget.size?.minH || 2,
|
|
100
|
+
maxH: widget.size?.maxH || 8,
|
|
101
|
+
isDraggable: editMode && !lockedWidgets.has(widget.id) && widget.permissions?.canMove !== false,
|
|
102
|
+
isResizable: editMode && !lockedWidgets.has(widget.id) && widget.permissions?.canResize !== false,
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
// Farklı breakpoint'ler için layout'ları ayarla
|
|
106
|
+
const sortedLayout = [...baseLayout].sort((a, b) => {
|
|
107
|
+
if (a.y === b.y) return a.x - b.x;
|
|
108
|
+
return a.y - b.y;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
lg: baseLayout,
|
|
113
|
+
md: sortedLayout.map((item, index) => {
|
|
114
|
+
const cols = 8;
|
|
115
|
+
const maxW = Math.min(item.w, 4);
|
|
116
|
+
const row = Math.floor(index / 2);
|
|
117
|
+
const col = (index % 2) * 4;
|
|
118
|
+
return {
|
|
119
|
+
...item,
|
|
120
|
+
w: maxW,
|
|
121
|
+
x: col,
|
|
122
|
+
y: row * 3,
|
|
123
|
+
h: Math.min(item.h, 3)
|
|
124
|
+
};
|
|
125
|
+
}),
|
|
126
|
+
sm: sortedLayout.map((item, index) => {
|
|
127
|
+
const maxW = Math.min(item.w, 4);
|
|
128
|
+
return {
|
|
129
|
+
...item,
|
|
130
|
+
w: maxW,
|
|
131
|
+
x: 0,
|
|
132
|
+
y: index * 3,
|
|
133
|
+
h: Math.min(item.h, 3)
|
|
134
|
+
};
|
|
135
|
+
}),
|
|
136
|
+
xs: sortedLayout.map((item, index) => ({
|
|
137
|
+
...item,
|
|
138
|
+
w: 2, // Full width on mobile
|
|
139
|
+
x: 0,
|
|
140
|
+
y: index * 2,
|
|
141
|
+
h: Math.min(item.h, 2)
|
|
142
|
+
})),
|
|
143
|
+
xxs: sortedLayout.map((item, index) => ({
|
|
144
|
+
...item,
|
|
145
|
+
w: 1, // Full width on extra small
|
|
146
|
+
x: 0,
|
|
147
|
+
y: index * 2,
|
|
148
|
+
h: Math.min(item.h, 2)
|
|
149
|
+
}))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Layout oluştur (geriye uyumluluk için)
|
|
154
|
+
const generateLayout = (): Layout[] => {
|
|
155
|
+
const responsiveLayouts = generateResponsiveLayouts()
|
|
156
|
+
return responsiveLayouts[currentBreakpoint as keyof Layouts] || responsiveLayouts.lg
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Widget kilit durumunu değiştir
|
|
160
|
+
const toggleWidgetLock = (widgetId: string) => {
|
|
161
|
+
setLockedWidgets(prev => {
|
|
162
|
+
const newSet = new Set(prev)
|
|
163
|
+
if (newSet.has(widgetId)) {
|
|
164
|
+
newSet.delete(widgetId)
|
|
165
|
+
} else {
|
|
166
|
+
newSet.add(widgetId)
|
|
167
|
+
}
|
|
168
|
+
return newSet
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Widget'ı tam ekran yap
|
|
173
|
+
const toggleFullscreen = (widgetId: string) => {
|
|
174
|
+
if (fullscreenWidget === widgetId) {
|
|
175
|
+
setFullscreenWidget(null)
|
|
176
|
+
} else {
|
|
177
|
+
setFullscreenWidget(widgetId)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Widget render
|
|
182
|
+
const renderWidget = (widget: Widget) => {
|
|
183
|
+
const isLocked = lockedWidgets.has(widget.id)
|
|
184
|
+
const isFullscreen = fullscreenWidget === widget.id
|
|
185
|
+
|
|
186
|
+
const widgetContent = () => {
|
|
187
|
+
switch (widget.type) {
|
|
188
|
+
case 'metric':
|
|
189
|
+
return (
|
|
190
|
+
<MetricCard
|
|
191
|
+
data={widget.data}
|
|
192
|
+
onAction={(action) => onWidgetAction?.(widget.id, action)}
|
|
193
|
+
glassmorphism={glassmorphism}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
case 'chart':
|
|
198
|
+
return (
|
|
199
|
+
<ChartWidget
|
|
200
|
+
id={widget.id}
|
|
201
|
+
title={widget.title}
|
|
202
|
+
description={widget.description}
|
|
203
|
+
data={widget.data}
|
|
204
|
+
loading={widget.loading}
|
|
205
|
+
glassmorphism={glassmorphism}
|
|
206
|
+
onAction={(action, data) => onWidgetAction?.(widget.id, action, data)}
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
case 'activity':
|
|
211
|
+
return (
|
|
212
|
+
<ActivityFeed
|
|
213
|
+
items={widget.data?.items || []}
|
|
214
|
+
title={widget.title}
|
|
215
|
+
loading={widget.loading}
|
|
216
|
+
glassmorphism={glassmorphism}
|
|
217
|
+
realtime={widget.data?.realtime}
|
|
218
|
+
onAction={(action, data) => onWidgetAction?.(widget.id, action, data)}
|
|
219
|
+
/>
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
case 'table':
|
|
223
|
+
case 'map':
|
|
224
|
+
case 'calendar':
|
|
225
|
+
case 'progress':
|
|
226
|
+
case 'comparison':
|
|
227
|
+
// Bu widget tipleri için geçici olarak basit bir görünüm
|
|
228
|
+
return (
|
|
229
|
+
<div className={cn(
|
|
230
|
+
"h-full p-4 rounded-lg border",
|
|
231
|
+
glassmorphism ? "bg-background/60 backdrop-blur-md border-white/10" : "bg-card"
|
|
232
|
+
)}>
|
|
233
|
+
<h3 className="font-semibold mb-2">{widget.title}</h3>
|
|
234
|
+
{widget.description && (
|
|
235
|
+
<p className="text-sm text-muted-foreground mb-4">{widget.description}</p>
|
|
236
|
+
)}
|
|
237
|
+
<div className="flex items-center justify-center h-32 text-muted-foreground">
|
|
238
|
+
<span className="text-sm">{widget.type} widget content</span>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
default:
|
|
244
|
+
return (
|
|
245
|
+
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
246
|
+
Widget type not supported: {widget.type}
|
|
247
|
+
</div>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<motion.div
|
|
254
|
+
key={widget.id}
|
|
255
|
+
className={cn(
|
|
256
|
+
"relative group h-full",
|
|
257
|
+
isFullscreen && "!fixed !inset-4 !z-50 !h-auto !w-auto"
|
|
258
|
+
)}
|
|
259
|
+
layout={!isFullscreen}
|
|
260
|
+
transition={{ duration: 0.2 }}
|
|
261
|
+
>
|
|
262
|
+
{/* Widget içeriği */}
|
|
263
|
+
<div className="h-full">
|
|
264
|
+
{widgetContent()}
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Edit mode kontrolleri */}
|
|
268
|
+
{editMode && !isFullscreen && (
|
|
269
|
+
<motion.div
|
|
270
|
+
initial={{ opacity: 0 }}
|
|
271
|
+
animate={{ opacity: 1 }}
|
|
272
|
+
className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
273
|
+
>
|
|
274
|
+
<div className={cn(
|
|
275
|
+
"flex items-center gap-1 p-1 rounded-lg",
|
|
276
|
+
glassmorphism ? "bg-background/80 backdrop-blur-sm" : "bg-background/95",
|
|
277
|
+
"border shadow-sm"
|
|
278
|
+
)}>
|
|
279
|
+
{/* Drag handle */}
|
|
280
|
+
{!isLocked && (
|
|
281
|
+
<div className="drag-handle cursor-move p-1 hover:bg-muted rounded">
|
|
282
|
+
<Grip className="h-4 w-4" />
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{/* Lock/Unlock */}
|
|
287
|
+
<Button
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="sm"
|
|
290
|
+
className="h-6 w-6 p-0"
|
|
291
|
+
onClick={() => toggleWidgetLock(widget.id)}
|
|
292
|
+
>
|
|
293
|
+
{isLocked ? (
|
|
294
|
+
<Lock className="h-3 w-3" />
|
|
295
|
+
) : (
|
|
296
|
+
<Unlock className="h-3 w-3" />
|
|
297
|
+
)}
|
|
298
|
+
</Button>
|
|
299
|
+
|
|
300
|
+
{/* Fullscreen */}
|
|
301
|
+
<Button
|
|
302
|
+
variant="ghost"
|
|
303
|
+
size="sm"
|
|
304
|
+
className="h-6 w-6 p-0"
|
|
305
|
+
onClick={() => toggleFullscreen(widget.id)}
|
|
306
|
+
>
|
|
307
|
+
<Maximize2 className="h-3 w-3" />
|
|
308
|
+
</Button>
|
|
309
|
+
|
|
310
|
+
{/* Settings */}
|
|
311
|
+
<Button
|
|
312
|
+
variant="ghost"
|
|
313
|
+
size="sm"
|
|
314
|
+
className="h-6 w-6 p-0"
|
|
315
|
+
onClick={() => onWidgetAction?.(widget.id, 'settings')}
|
|
316
|
+
>
|
|
317
|
+
<Settings className="h-3 w-3" />
|
|
318
|
+
</Button>
|
|
319
|
+
|
|
320
|
+
{/* Remove */}
|
|
321
|
+
{widget.permissions?.canDelete !== false && (
|
|
322
|
+
<Button
|
|
323
|
+
variant="ghost"
|
|
324
|
+
size="sm"
|
|
325
|
+
className="h-6 w-6 p-0 text-destructive hover:text-destructive"
|
|
326
|
+
onClick={() => onWidgetRemove?.(widget.id)}
|
|
327
|
+
>
|
|
328
|
+
<X className="h-3 w-3" />
|
|
329
|
+
</Button>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
</motion.div>
|
|
333
|
+
)}
|
|
334
|
+
|
|
335
|
+
{/* Fullscreen kontrolleri */}
|
|
336
|
+
{isFullscreen && (
|
|
337
|
+
<motion.div
|
|
338
|
+
initial={{ opacity: 0, y: -10 }}
|
|
339
|
+
animate={{ opacity: 1, y: 0 }}
|
|
340
|
+
className="absolute top-4 right-4 z-10"
|
|
341
|
+
>
|
|
342
|
+
<Button
|
|
343
|
+
variant="outline"
|
|
344
|
+
size="sm"
|
|
345
|
+
onClick={() => toggleFullscreen(widget.id)}
|
|
346
|
+
>
|
|
347
|
+
<Minimize2 className="h-4 w-4 mr-2" />
|
|
348
|
+
Exit Fullscreen
|
|
349
|
+
</Button>
|
|
350
|
+
</motion.div>
|
|
351
|
+
)}
|
|
352
|
+
</motion.div>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Mobile kontrolü
|
|
357
|
+
const isMobile = currentBreakpoint === 'xs' || currentBreakpoint === 'xxs' || currentBreakpoint === 'sm';
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div ref={containerRef} className={cn("w-full relative", className)}>
|
|
361
|
+
<AnimatePresence>
|
|
362
|
+
{fullscreenWidget ? (
|
|
363
|
+
// Fullscreen widget
|
|
364
|
+
<motion.div
|
|
365
|
+
initial={{ opacity: 0 }}
|
|
366
|
+
animate={{ opacity: 1 }}
|
|
367
|
+
exit={{ opacity: 0 }}
|
|
368
|
+
className="fixed inset-0 z-40 bg-background"
|
|
369
|
+
>
|
|
370
|
+
{widgets
|
|
371
|
+
.filter(w => w.id === fullscreenWidget)
|
|
372
|
+
.map(renderWidget)}
|
|
373
|
+
</motion.div>
|
|
374
|
+
) : isMobile ? (
|
|
375
|
+
// Mobile layout - Grid Layout yerine normal flex kullan
|
|
376
|
+
<div className="space-y-6">
|
|
377
|
+
{widgets.map(widget => (
|
|
378
|
+
<motion.div
|
|
379
|
+
key={widget.id}
|
|
380
|
+
initial={{ opacity: 0, y: 20 }}
|
|
381
|
+
animate={{ opacity: 1, y: 0 }}
|
|
382
|
+
transition={{ duration: 0.3 }}
|
|
383
|
+
className="w-full"
|
|
384
|
+
>
|
|
385
|
+
{renderWidget(widget)}
|
|
386
|
+
</motion.div>
|
|
387
|
+
))}
|
|
388
|
+
</div>
|
|
389
|
+
) : (
|
|
390
|
+
// Desktop Grid layout
|
|
391
|
+
<ResponsiveGridLayout
|
|
392
|
+
className="layout"
|
|
393
|
+
layouts={layouts.lg ? layouts : generateResponsiveLayouts()}
|
|
394
|
+
breakpoints={breakpoints}
|
|
395
|
+
cols={cols}
|
|
396
|
+
rowHeight={currentBreakpoint === 'xs' || currentBreakpoint === 'xxs' ? 60 : currentBreakpoint === 'sm' ? 70 : rowHeight}
|
|
397
|
+
margin={currentBreakpoint === 'xs' || currentBreakpoint === 'xxs' ? [12, 20] : margin}
|
|
398
|
+
containerPadding={currentBreakpoint === 'xs' || currentBreakpoint === 'xxs' ? [8, 8] : containerPadding}
|
|
399
|
+
onLayoutChange={(layout, allLayouts) => {
|
|
400
|
+
setLayouts(allLayouts)
|
|
401
|
+
onLayoutChange?.(layout)
|
|
402
|
+
}}
|
|
403
|
+
onBreakpointChange={(breakpoint) => {
|
|
404
|
+
setCurrentBreakpoint(breakpoint)
|
|
405
|
+
// Dar alanlarda compact type'ı değiştir
|
|
406
|
+
if (breakpoint === 'xs' || breakpoint === 'xxs' || breakpoint === 'sm') {
|
|
407
|
+
setCompactType('vertical')
|
|
408
|
+
} else {
|
|
409
|
+
setCompactType('vertical')
|
|
410
|
+
}
|
|
411
|
+
}}
|
|
412
|
+
draggableHandle=".drag-handle"
|
|
413
|
+
isDraggable={editMode}
|
|
414
|
+
isResizable={editMode}
|
|
415
|
+
compactType={compactType}
|
|
416
|
+
preventCollision={false}
|
|
417
|
+
useCSSTransforms={true}
|
|
418
|
+
transformScale={1}
|
|
419
|
+
isDroppable={true}
|
|
420
|
+
autoSize={true}
|
|
421
|
+
verticalCompact={true}
|
|
422
|
+
>
|
|
423
|
+
{widgets.map(widget => (
|
|
424
|
+
<div key={widget.id}>
|
|
425
|
+
{renderWidget(widget)}
|
|
426
|
+
</div>
|
|
427
|
+
))}
|
|
428
|
+
</ResponsiveGridLayout>
|
|
429
|
+
)}
|
|
430
|
+
</AnimatePresence>
|
|
431
|
+
|
|
432
|
+
{/* Grid çizgileri (edit mode) */}
|
|
433
|
+
{editMode && !fullscreenWidget && (
|
|
434
|
+
<motion.div
|
|
435
|
+
initial={{ opacity: 0 }}
|
|
436
|
+
animate={{ opacity: 0.1 }}
|
|
437
|
+
className="absolute inset-0 pointer-events-none"
|
|
438
|
+
style={{
|
|
439
|
+
backgroundImage: `
|
|
440
|
+
repeating-linear-gradient(
|
|
441
|
+
0deg,
|
|
442
|
+
var(--border) 0px,
|
|
443
|
+
transparent 1px,
|
|
444
|
+
transparent ${rowHeight + margin[1]}px,
|
|
445
|
+
var(--border) ${rowHeight + margin[1]}px
|
|
446
|
+
),
|
|
447
|
+
repeating-linear-gradient(
|
|
448
|
+
90deg,
|
|
449
|
+
var(--border) 0px,
|
|
450
|
+
transparent 1px,
|
|
451
|
+
transparent ${100 / cols[currentBreakpoint as keyof typeof cols]}%,
|
|
452
|
+
var(--border) ${100 / cols[currentBreakpoint as keyof typeof cols]}%
|
|
453
|
+
)
|
|
454
|
+
`
|
|
455
|
+
}}
|
|
456
|
+
/>
|
|
457
|
+
)}
|
|
458
|
+
</div>
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export default DashboardGrid
|