@moontra/moonui-pro 2.0.22 → 2.1.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.
Files changed (99) hide show
  1. package/dist/index.mjs +215 -214
  2. package/package.json +4 -2
  3. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  4. package/src/__tests__/use-local-storage.test.tsx +174 -0
  5. package/src/__tests__/use-pro-access.test.tsx +183 -0
  6. package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
  7. package/src/components/advanced-chart/index.tsx +412 -0
  8. package/src/components/advanced-forms/index.tsx +431 -0
  9. package/src/components/animated-button/index.tsx +202 -0
  10. package/src/components/calendar/event-dialog.tsx +372 -0
  11. package/src/components/calendar/index.tsx +557 -0
  12. package/src/components/color-picker/index.tsx +434 -0
  13. package/src/components/dashboard/index.tsx +334 -0
  14. package/src/components/data-table/data-table.test.tsx +187 -0
  15. package/src/components/data-table/index.tsx +368 -0
  16. package/src/components/draggable-list/index.tsx +100 -0
  17. package/src/components/enhanced/button.tsx +360 -0
  18. package/src/components/enhanced/card.tsx +272 -0
  19. package/src/components/enhanced/dialog.tsx +248 -0
  20. package/src/components/enhanced/index.ts +3 -0
  21. package/src/components/error-boundary/index.tsx +111 -0
  22. package/src/components/file-upload/file-upload.test.tsx +242 -0
  23. package/src/components/file-upload/index.tsx +362 -0
  24. package/src/components/floating-action-button/index.tsx +209 -0
  25. package/src/components/github-stars/index.tsx +414 -0
  26. package/src/components/health-check/index.tsx +441 -0
  27. package/src/components/hover-card-3d/index.tsx +170 -0
  28. package/src/components/index.ts +76 -0
  29. package/src/components/kanban/index.tsx +436 -0
  30. package/src/components/lazy-component/index.tsx +342 -0
  31. package/src/components/magnetic-button/index.tsx +170 -0
  32. package/src/components/memory-efficient-data/index.tsx +352 -0
  33. package/src/components/optimized-image/index.tsx +427 -0
  34. package/src/components/performance-debugger/index.tsx +591 -0
  35. package/src/components/performance-monitor/index.tsx +775 -0
  36. package/src/components/pinch-zoom/index.tsx +172 -0
  37. package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
  38. package/src/components/rich-text-editor/index.tsx +1537 -0
  39. package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
  40. package/src/components/rich-text-editor/slash-commands.css +35 -0
  41. package/src/components/rich-text-editor/table-styles.css +65 -0
  42. package/src/components/spotlight-card/index.tsx +194 -0
  43. package/src/components/swipeable-card/index.tsx +100 -0
  44. package/src/components/timeline/index.tsx +333 -0
  45. package/src/components/ui/animated-button.tsx +185 -0
  46. package/src/components/ui/avatar.tsx +135 -0
  47. package/src/components/ui/badge.tsx +225 -0
  48. package/src/components/ui/button.tsx +221 -0
  49. package/src/components/ui/card.tsx +141 -0
  50. package/src/components/ui/checkbox.tsx +256 -0
  51. package/src/components/ui/color-picker.tsx +95 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/hover-card-3d.tsx +103 -0
  55. package/src/components/ui/index.ts +33 -0
  56. package/src/components/ui/input.tsx +219 -0
  57. package/src/components/ui/label.tsx +26 -0
  58. package/src/components/ui/magnetic-button.tsx +129 -0
  59. package/src/components/ui/popover.tsx +183 -0
  60. package/src/components/ui/select.tsx +273 -0
  61. package/src/components/ui/separator.tsx +140 -0
  62. package/src/components/ui/slider.tsx +351 -0
  63. package/src/components/ui/spotlight-card.tsx +119 -0
  64. package/src/components/ui/switch.tsx +83 -0
  65. package/src/components/ui/tabs.tsx +195 -0
  66. package/src/components/ui/textarea.tsx +25 -0
  67. package/src/components/ui/toast.tsx +313 -0
  68. package/src/components/ui/tooltip.tsx +152 -0
  69. package/src/components/virtual-list/index.tsx +369 -0
  70. package/src/hooks/use-chart.ts +205 -0
  71. package/src/hooks/use-data-table.ts +182 -0
  72. package/src/hooks/use-docs-pro-access.ts +13 -0
  73. package/src/hooks/use-license-check.ts +65 -0
  74. package/src/hooks/use-subscription.ts +19 -0
  75. package/src/index.ts +14 -0
  76. package/src/lib/micro-interactions.ts +255 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/patterns/login-form/index.tsx +276 -0
  79. package/src/patterns/login-form/types.ts +67 -0
  80. package/src/setupTests.ts +41 -0
  81. package/src/styles/design-system.css +365 -0
  82. package/src/styles/index.css +4 -0
  83. package/src/styles/tailwind.css +6 -0
  84. package/src/styles/tokens.css +453 -0
  85. package/src/types/moonui.d.ts +22 -0
  86. package/src/use-intersection-observer.tsx +154 -0
  87. package/src/use-local-storage.tsx +71 -0
  88. package/src/use-paddle.ts +138 -0
  89. package/src/use-performance-optimizer.ts +379 -0
  90. package/src/use-pro-access.ts +141 -0
  91. package/src/use-scroll-animation.ts +221 -0
  92. package/src/use-subscription.ts +37 -0
  93. package/src/use-toast.ts +32 -0
  94. package/src/utils/chart-helpers.ts +257 -0
  95. package/src/utils/cn.ts +69 -0
  96. package/src/utils/data-processing.ts +151 -0
  97. package/src/utils/license-guard.tsx +177 -0
  98. package/src/utils/license-validator.tsx +183 -0
  99. package/src/utils/package-guard.ts +60 -0
@@ -0,0 +1,281 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import '@testing-library/jest-dom'
3
+ import { AdvancedChart } from './index'
4
+
5
+ // Mock recharts to avoid canvas issues in tests
6
+ jest.mock('recharts', () => ({
7
+ ResponsiveContainer: ({ children }: any) => <div data-testid="responsive-container">{children}</div>,
8
+ LineChart: ({ children }: any) => <div data-testid="line-chart">{children}</div>,
9
+ BarChart: ({ children }: any) => <div data-testid="bar-chart">{children}</div>,
10
+ AreaChart: ({ children }: any) => <div data-testid="area-chart">{children}</div>,
11
+ PieChart: ({ children }: any) => <div data-testid="pie-chart">{children}</div>,
12
+ ScatterChart: ({ children }: any) => <div data-testid="scatter-chart">{children}</div>,
13
+ Line: () => <div data-testid="line" />,
14
+ Bar: () => <div data-testid="bar" />,
15
+ Area: () => <div data-testid="area" />,
16
+ Pie: () => <div data-testid="pie" />,
17
+ Scatter: () => <div data-testid="scatter" />,
18
+ Cell: () => <div data-testid="cell" />,
19
+ XAxis: () => <div data-testid="x-axis" />,
20
+ YAxis: () => <div data-testid="y-axis" />,
21
+ CartesianGrid: () => <div data-testid="grid" />,
22
+ Tooltip: () => <div data-testid="tooltip" />,
23
+ Legend: () => <div data-testid="legend" />,
24
+ ReferenceLine: () => <div data-testid="reference-line" />,
25
+ ReferenceArea: () => <div data-testid="reference-area" />,
26
+ Brush: () => <div data-testid="brush" />,
27
+ }))
28
+
29
+ // Mock data for testing
30
+ const mockData = [
31
+ { name: 'Jan', value: 400, revenue: 2400 },
32
+ { name: 'Feb', value: 300, revenue: 1398 },
33
+ { name: 'Mar', value: 200, revenue: 9800 },
34
+ { name: 'Apr', value: 278, revenue: 3908 },
35
+ { name: 'May', value: 189, revenue: 4800 },
36
+ { name: 'Jun', value: 239, revenue: 3800 },
37
+ ]
38
+
39
+ const mockSeries = [
40
+ {
41
+ dataKey: 'value',
42
+ name: 'Users',
43
+ color: '#8884d8',
44
+ },
45
+ {
46
+ dataKey: 'revenue',
47
+ name: 'Revenue',
48
+ color: '#82ca9d',
49
+ },
50
+ ]
51
+
52
+ describe('AdvancedChart', () => {
53
+ it('renders without crashing', () => {
54
+ render(
55
+ <AdvancedChart
56
+ data={mockData}
57
+ series={mockSeries}
58
+ type="line"
59
+ />
60
+ )
61
+
62
+ expect(screen.getByText('Advanced Chart')).toBeInTheDocument()
63
+ })
64
+
65
+ it('renders with custom title', () => {
66
+ render(
67
+ <AdvancedChart
68
+ data={mockData}
69
+ series={mockSeries}
70
+ type="line"
71
+ title="Custom Chart Title"
72
+ />
73
+ )
74
+
75
+ expect(screen.getByText('Custom Chart Title')).toBeInTheDocument()
76
+ })
77
+
78
+ it('renders with subtitle', () => {
79
+ render(
80
+ <AdvancedChart
81
+ data={mockData}
82
+ series={mockSeries}
83
+ type="line"
84
+ title="Chart Title"
85
+ subtitle="Chart Subtitle"
86
+ />
87
+ )
88
+
89
+ expect(screen.getByText('Chart Subtitle')).toBeInTheDocument()
90
+ })
91
+
92
+ it('renders line chart type', () => {
93
+ render(
94
+ <AdvancedChart
95
+ data={mockData}
96
+ series={mockSeries}
97
+ type="line"
98
+ />
99
+ )
100
+
101
+ expect(screen.getByTestId('line-chart')).toBeInTheDocument()
102
+ })
103
+
104
+ it('renders bar chart type', () => {
105
+ render(
106
+ <AdvancedChart
107
+ data={mockData}
108
+ series={mockSeries}
109
+ type="bar"
110
+ />
111
+ )
112
+
113
+ expect(screen.getByTestId('bar-chart')).toBeInTheDocument()
114
+ })
115
+
116
+ it('renders area chart type', () => {
117
+ render(
118
+ <AdvancedChart
119
+ data={mockData}
120
+ series={mockSeries}
121
+ type="area"
122
+ />
123
+ )
124
+
125
+ expect(screen.getByTestId('area-chart')).toBeInTheDocument()
126
+ })
127
+
128
+ it('renders pie chart type', () => {
129
+ render(
130
+ <AdvancedChart
131
+ data={mockData}
132
+ series={mockSeries}
133
+ type="pie"
134
+ />
135
+ )
136
+
137
+ expect(screen.getByTestId('pie-chart')).toBeInTheDocument()
138
+ })
139
+
140
+ it('renders scatter chart type', () => {
141
+ render(
142
+ <AdvancedChart
143
+ data={mockData}
144
+ series={mockSeries}
145
+ type="scatter"
146
+ />
147
+ )
148
+
149
+ expect(screen.getByTestId('scatter-chart')).toBeInTheDocument()
150
+ })
151
+
152
+ it('shows loading state', () => {
153
+ render(
154
+ <AdvancedChart
155
+ data={mockData}
156
+ series={mockSeries}
157
+ type="line"
158
+ loading={true}
159
+ />
160
+ )
161
+
162
+ expect(screen.getByText('Loading chart...')).toBeInTheDocument()
163
+ })
164
+
165
+ it('shows error state', () => {
166
+ render(
167
+ <AdvancedChart
168
+ data={mockData}
169
+ series={mockSeries}
170
+ type="line"
171
+ error="Something went wrong"
172
+ />
173
+ )
174
+
175
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument()
176
+ expect(screen.getByText('Retry')).toBeInTheDocument()
177
+ })
178
+
179
+ it('calls onRefresh when refresh button is clicked', () => {
180
+ const mockOnRefresh = jest.fn()
181
+
182
+ render(
183
+ <AdvancedChart
184
+ data={mockData}
185
+ series={mockSeries}
186
+ type="line"
187
+ onRefresh={mockOnRefresh}
188
+ />
189
+ )
190
+
191
+ const refreshButton = screen.getByRole('button', { name: /refresh/i })
192
+ fireEvent.click(refreshButton)
193
+
194
+ expect(mockOnRefresh).toHaveBeenCalled()
195
+ })
196
+
197
+ it('calls onExport when export button is clicked', () => {
198
+ const mockOnExport = jest.fn()
199
+
200
+ render(
201
+ <AdvancedChart
202
+ data={mockData}
203
+ series={mockSeries}
204
+ type="line"
205
+ onExport={mockOnExport}
206
+ />
207
+ )
208
+
209
+ const exportButton = screen.getByRole('button', { name: /download/i })
210
+ fireEvent.click(exportButton)
211
+
212
+ expect(mockOnExport).toHaveBeenCalledWith('png')
213
+ })
214
+
215
+ it('displays trend indicator', () => {
216
+ render(
217
+ <AdvancedChart
218
+ data={mockData}
219
+ series={mockSeries}
220
+ type="line"
221
+ title="Revenue Chart"
222
+ />
223
+ )
224
+
225
+ // Should display trend percentage
226
+ expect(screen.getByText(/%/)).toBeInTheDocument()
227
+ })
228
+
229
+ it('renders with custom height', () => {
230
+ render(
231
+ <AdvancedChart
232
+ data={mockData}
233
+ series={mockSeries}
234
+ type="line"
235
+ height={600}
236
+ />
237
+ )
238
+
239
+ expect(screen.getByTestId('responsive-container')).toBeInTheDocument()
240
+ })
241
+
242
+ it('handles responsive mode', () => {
243
+ render(
244
+ <AdvancedChart
245
+ data={mockData}
246
+ series={mockSeries}
247
+ type="line"
248
+ responsive={true}
249
+ />
250
+ )
251
+
252
+ expect(screen.getByTestId('responsive-container')).toBeInTheDocument()
253
+ })
254
+
255
+ it('handles non-responsive mode', () => {
256
+ render(
257
+ <AdvancedChart
258
+ data={mockData}
259
+ series={mockSeries}
260
+ type="line"
261
+ responsive={false}
262
+ />
263
+ )
264
+
265
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument()
266
+ })
267
+
268
+ it('applies custom className', () => {
269
+ render(
270
+ <AdvancedChart
271
+ data={mockData}
272
+ series={mockSeries}
273
+ type="line"
274
+ className="custom-chart"
275
+ />
276
+ )
277
+
278
+ const chartContainer = screen.getByText('Advanced Chart').closest('div')
279
+ expect(chartContainer).toHaveClass('custom-chart')
280
+ })
281
+ })
@@ -0,0 +1,412 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import {
5
+ ResponsiveContainer,
6
+ LineChart,
7
+ BarChart,
8
+ AreaChart,
9
+ PieChart,
10
+ ScatterChart,
11
+ Line,
12
+ Bar,
13
+ Area,
14
+ Pie,
15
+ Cell,
16
+ Scatter,
17
+ XAxis,
18
+ YAxis,
19
+ CartesianGrid,
20
+ Tooltip,
21
+ Legend,
22
+ ReferenceLine,
23
+ ReferenceArea,
24
+ Brush,
25
+ } from 'recharts'
26
+ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
27
+ import { Button } from '../ui/button'
28
+ import {
29
+ Download,
30
+ Maximize2,
31
+ Settings,
32
+ RefreshCw,
33
+ TrendingUp,
34
+ TrendingDown,
35
+ Minus,
36
+ Lock,
37
+ Sparkles
38
+ } from 'lucide-react'
39
+ import { cn } from '@moontra/moonui'
40
+
41
+ export type ChartType = 'line' | 'bar' | 'area' | 'pie' | 'scatter'
42
+
43
+ export interface ChartDataPoint {
44
+ [key: string]: string | number | null
45
+ }
46
+
47
+ export interface ChartSeries {
48
+ dataKey: string
49
+ name: string
50
+ color: string
51
+ type?: 'monotone' | 'linear' | 'step'
52
+ strokeWidth?: number
53
+ fillOpacity?: number
54
+ hide?: boolean
55
+ }
56
+
57
+ export interface AdvancedChartProps {
58
+ data: ChartDataPoint[]
59
+ type: ChartType
60
+ series: ChartSeries[]
61
+ title?: string
62
+ subtitle?: string
63
+ height?: number
64
+ width?: number | string
65
+ xAxisKey?: string
66
+ yAxisKey?: string
67
+ showGrid?: boolean
68
+ showTooltip?: boolean
69
+ showLegend?: boolean
70
+ showBrush?: boolean
71
+ showReference?: boolean
72
+ referenceLines?: Array<{
73
+ value: number
74
+ label?: string
75
+ color?: string
76
+ }>
77
+ referenceAreas?: Array<{
78
+ x1: string | number
79
+ x2: string | number
80
+ label?: string
81
+ color?: string
82
+ }>
83
+ colors?: string[]
84
+ className?: string
85
+ onDataPointClick?: (data: ChartDataPoint) => void
86
+ onExport?: (format: 'png' | 'svg' | 'pdf') => void
87
+ onRefresh?: () => void
88
+ customTooltip?: React.ComponentType<any>
89
+ customLegend?: React.ComponentType<any>
90
+ loading?: boolean
91
+ error?: string | null
92
+ animated?: boolean
93
+ responsive?: boolean
94
+ }
95
+
96
+ const DEFAULT_COLORS = [
97
+ '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
98
+ '#06b6d4', '#f97316', '#84cc16', '#ec4899', '#6366f1'
99
+ ]
100
+
101
+ export function AdvancedChart({
102
+ data,
103
+ type,
104
+ series,
105
+ title,
106
+ subtitle,
107
+ height = 400,
108
+ width = '100%',
109
+ xAxisKey = 'name',
110
+ yAxisKey,
111
+ showGrid = true,
112
+ showTooltip = true,
113
+ showLegend = true,
114
+ showBrush = false,
115
+ showReference = false,
116
+ referenceLines = [],
117
+ referenceAreas = [],
118
+ colors = DEFAULT_COLORS,
119
+ className,
120
+ onDataPointClick,
121
+ onExport,
122
+ onRefresh,
123
+ customTooltip,
124
+ customLegend,
125
+ loading = false,
126
+ error = null,
127
+ animated = true,
128
+ responsive = true,
129
+ }: AdvancedChartProps) {
130
+ const [isFullscreen, setIsFullscreen] = React.useState(false)
131
+ const chartRef = React.useRef<HTMLDivElement>(null)
132
+
133
+ // Calculate trend for the first series
134
+ const trend = React.useMemo(() => {
135
+ if (!data.length || !series.length) return null
136
+
137
+ const firstSeries = series[0]
138
+ const values = data.map(d => Number(d[firstSeries.dataKey])).filter(v => !isNaN(v))
139
+
140
+ if (values.length < 2) return null
141
+
142
+ const first = values[0]
143
+ const last = values[values.length - 1]
144
+ const change = ((last - first) / first) * 100
145
+
146
+ return {
147
+ direction: change > 0 ? 'up' : change < 0 ? 'down' : 'neutral',
148
+ percentage: Math.abs(change).toFixed(1)
149
+ }
150
+ }, [data, series])
151
+
152
+ const handleExport = (format: 'png' | 'svg' | 'pdf') => {
153
+ if (onExport) {
154
+ onExport(format)
155
+ }
156
+ }
157
+
158
+ const renderChart = () => {
159
+ const commonProps = {
160
+ data,
161
+ width: typeof width === 'string' ? undefined : width,
162
+ height,
163
+ margin: { top: 20, right: 30, left: 20, bottom: 20 },
164
+ // onClick: onDataPointClick,
165
+ }
166
+
167
+ const visibleSeries = series.filter(s => !s.hide)
168
+
169
+ switch (type) {
170
+ case 'line':
171
+ return (
172
+ <LineChart {...commonProps}>
173
+ {showGrid && <CartesianGrid strokeDasharray="3 3" />}
174
+ <XAxis dataKey={xAxisKey} />
175
+ <YAxis />
176
+ {showTooltip && <Tooltip />}
177
+ {showLegend && <Legend />}
178
+ {visibleSeries.map((s, index) => (
179
+ <Line
180
+ key={s.dataKey}
181
+ type={s.type || 'monotone'}
182
+ dataKey={s.dataKey}
183
+ stroke={s.color || colors[index % colors.length]}
184
+ strokeWidth={s.strokeWidth || 2}
185
+ name={s.name}
186
+ animationDuration={animated ? 1000 : 0}
187
+ />
188
+ ))}
189
+ {showReference && referenceLines.map((ref, index) => (
190
+ <ReferenceLine
191
+ key={index}
192
+ y={ref.value}
193
+ stroke={ref.color || '#666'}
194
+ strokeDasharray="5 5"
195
+ label={ref.label}
196
+ />
197
+ ))}
198
+ {showReference && referenceAreas.map((ref, index) => (
199
+ <ReferenceArea
200
+ key={index}
201
+ x1={ref.x1}
202
+ x2={ref.x2}
203
+ fill={ref.color || '#f0f0f0'}
204
+ fillOpacity={0.3}
205
+ label={ref.label}
206
+ />
207
+ ))}
208
+ {showBrush && <Brush />}
209
+ </LineChart>
210
+ )
211
+
212
+ case 'bar':
213
+ return (
214
+ <BarChart {...commonProps}>
215
+ {showGrid && <CartesianGrid strokeDasharray="3 3" />}
216
+ <XAxis dataKey={xAxisKey} />
217
+ <YAxis />
218
+ {showTooltip && <Tooltip />}
219
+ {showLegend && <Legend />}
220
+ {visibleSeries.map((s, index) => (
221
+ <Bar
222
+ key={s.dataKey}
223
+ dataKey={s.dataKey}
224
+ fill={s.color || colors[index % colors.length]}
225
+ name={s.name}
226
+ animationDuration={animated ? 1000 : 0}
227
+ />
228
+ ))}
229
+ {showReference && referenceLines.map((ref, index) => (
230
+ <ReferenceLine
231
+ key={index}
232
+ y={ref.value}
233
+ stroke={ref.color || '#666'}
234
+ strokeDasharray="5 5"
235
+ label={ref.label}
236
+ />
237
+ ))}
238
+ {showBrush && <Brush />}
239
+ </BarChart>
240
+ )
241
+
242
+ case 'area':
243
+ return (
244
+ <AreaChart {...commonProps}>
245
+ {showGrid && <CartesianGrid strokeDasharray="3 3" />}
246
+ <XAxis dataKey={xAxisKey} />
247
+ <YAxis />
248
+ {showTooltip && <Tooltip />}
249
+ {showLegend && <Legend />}
250
+ {visibleSeries.map((s, index) => (
251
+ <Area
252
+ key={s.dataKey}
253
+ type={s.type || 'monotone'}
254
+ dataKey={s.dataKey}
255
+ stroke={s.color || colors[index % colors.length]}
256
+ fill={s.color || colors[index % colors.length]}
257
+ fillOpacity={s.fillOpacity || 0.3}
258
+ name={s.name}
259
+ animationDuration={animated ? 1000 : 0}
260
+ />
261
+ ))}
262
+ {showReference && referenceLines.map((ref, index) => (
263
+ <ReferenceLine
264
+ key={index}
265
+ y={ref.value}
266
+ stroke={ref.color || '#666'}
267
+ strokeDasharray="5 5"
268
+ label={ref.label}
269
+ />
270
+ ))}
271
+ {showBrush && <Brush />}
272
+ </AreaChart>
273
+ )
274
+
275
+ case 'pie':
276
+ return (
277
+ <PieChart {...commonProps}>
278
+ {showTooltip && <Tooltip />}
279
+ {showLegend && <Legend />}
280
+ <Pie
281
+ data={data}
282
+ dataKey={series[0]?.dataKey || 'value'}
283
+ nameKey={xAxisKey}
284
+ cx="50%"
285
+ cy="50%"
286
+ outerRadius={Math.min(height, typeof width === 'number' ? width : 400) / 3}
287
+ animationDuration={animated ? 1000 : 0}
288
+ >
289
+ {data.map((entry, index) => (
290
+ <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
291
+ ))}
292
+ </Pie>
293
+ </PieChart>
294
+ )
295
+
296
+ case 'scatter':
297
+ return (
298
+ <ScatterChart {...commonProps}>
299
+ {showGrid && <CartesianGrid strokeDasharray="3 3" />}
300
+ <XAxis dataKey={xAxisKey} />
301
+ <YAxis dataKey={yAxisKey} />
302
+ {showTooltip && <Tooltip />}
303
+ {showLegend && <Legend />}
304
+ {visibleSeries.map((s, index) => (
305
+ <Scatter
306
+ key={s.dataKey}
307
+ dataKey={s.dataKey}
308
+ fill={s.color || colors[index % colors.length]}
309
+ name={s.name}
310
+ animationDuration={animated ? 1000 : 0}
311
+ />
312
+ ))}
313
+ </ScatterChart>
314
+ )
315
+
316
+ default:
317
+ return <div>Unsupported chart type</div>
318
+ }
319
+ }
320
+
321
+ if (loading) {
322
+ return (
323
+ <Card className={cn("w-full", className)}>
324
+ <CardContent className="flex items-center justify-center" style={{ height }}>
325
+ <div className="flex items-center space-x-2">
326
+ <RefreshCw className="h-4 w-4 animate-spin" />
327
+ <span>Loading chart...</span>
328
+ </div>
329
+ </CardContent>
330
+ </Card>
331
+ )
332
+ }
333
+
334
+ if (error) {
335
+ return (
336
+ <Card className={cn("w-full", className)}>
337
+ <CardContent className="flex items-center justify-center" style={{ height }}>
338
+ <div className="text-center">
339
+ <p className="text-destructive mb-2">{error}</p>
340
+ {onRefresh && (
341
+ <Button variant="outline" onClick={onRefresh}>
342
+ <RefreshCw className="mr-2 h-4 w-4" />
343
+ Retry
344
+ </Button>
345
+ )}
346
+ </div>
347
+ </CardContent>
348
+ </Card>
349
+ )
350
+ }
351
+
352
+ return (
353
+ <Card className={cn("w-full", className)} ref={chartRef}>
354
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
355
+ <div className="space-y-1">
356
+ <CardTitle className="text-base font-medium">
357
+ {title}
358
+ {trend && (
359
+ <span className="ml-2 inline-flex items-center">
360
+ {trend.direction === 'up' && <TrendingUp className="h-4 w-4 text-green-500" />}
361
+ {trend.direction === 'down' && <TrendingDown className="h-4 w-4 text-red-500" />}
362
+ {trend.direction === 'neutral' && <Minus className="h-4 w-4 text-gray-500" />}
363
+ <span className={cn(
364
+ "ml-1 text-sm",
365
+ trend.direction === 'up' && "text-green-500",
366
+ trend.direction === 'down' && "text-red-500",
367
+ trend.direction === 'neutral' && "text-gray-500"
368
+ )}>
369
+ {trend.percentage}%
370
+ </span>
371
+ </span>
372
+ )}
373
+ </CardTitle>
374
+ {subtitle && (
375
+ <p className="text-xs text-muted-foreground">{subtitle}</p>
376
+ )}
377
+ </div>
378
+ <div className="flex items-center space-x-1">
379
+ {onRefresh && (
380
+ <Button variant="ghost" size="sm" onClick={onRefresh}>
381
+ <RefreshCw className="h-4 w-4" />
382
+ </Button>
383
+ )}
384
+ <Button variant="ghost" size="sm">
385
+ <Settings className="h-4 w-4" />
386
+ </Button>
387
+ {onExport && (
388
+ <Button variant="ghost" size="sm" onClick={() => handleExport('png')}>
389
+ <Download className="h-4 w-4" />
390
+ </Button>
391
+ )}
392
+ <Button variant="ghost" size="sm" onClick={() => setIsFullscreen(!isFullscreen)}>
393
+ <Maximize2 className="h-4 w-4" />
394
+ </Button>
395
+ </div>
396
+ </CardHeader>
397
+ <CardContent>
398
+ {responsive ? (
399
+ <ResponsiveContainer width="100%" height={height}>
400
+ {renderChart()}
401
+ </ResponsiveContainer>
402
+ ) : (
403
+ <div style={{ width: '100%', height: `${height}px` }}>
404
+ {renderChart()}
405
+ </div>
406
+ )}
407
+ </CardContent>
408
+ </Card>
409
+ )
410
+ }
411
+
412
+ export default AdvancedChart