@turtleclub/ui 0.7.0-beta.3 → 0.7.0-beta.30
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/.turbo/turbo-build.log +143 -132
- package/CHANGELOG.md +152 -0
- package/dist/index.cjs +76 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36068 -17674
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/components/charts/area-chart.d.ts +108 -0
- package/dist/types/components/charts/area-chart.d.ts.map +1 -0
- package/dist/types/components/charts/bar-chart.d.ts +110 -0
- package/dist/types/components/charts/bar-chart.d.ts.map +1 -0
- package/dist/types/components/charts/index.d.ts +5 -0
- package/dist/types/components/charts/index.d.ts.map +1 -0
- package/dist/types/components/charts/pie-chart.d.ts +94 -0
- package/dist/types/components/charts/pie-chart.d.ts.map +1 -0
- package/dist/types/components/charts/radial-chart.d.ts +151 -0
- package/dist/types/components/charts/radial-chart.d.ts.map +1 -0
- package/dist/types/components/features/data-table/data-table.d.ts +7 -4
- package/dist/types/components/features/data-table/data-table.d.ts.map +1 -1
- package/dist/types/components/features/data-table/sort-dropdown.d.ts.map +1 -1
- package/dist/types/components/features/search-bar.d.ts +1 -0
- package/dist/types/components/features/search-bar.d.ts.map +1 -1
- package/dist/types/components/molecules/swap-input.d.ts +3 -0
- package/dist/types/components/molecules/swap-input.d.ts.map +1 -1
- package/dist/types/components/molecules/token-selector.d.ts +2 -1
- package/dist/types/components/molecules/token-selector.d.ts.map +1 -1
- package/dist/types/components/ui/avatar.d.ts +2 -2
- package/dist/types/components/ui/avatar.d.ts.map +1 -1
- package/dist/types/components/ui/chart.d.ts +18 -4
- package/dist/types/components/ui/chart.d.ts.map +1 -1
- package/dist/types/components/ui/combobox.d.ts +21 -0
- package/dist/types/components/ui/combobox.d.ts.map +1 -1
- package/dist/types/components/ui/dialog.d.ts.map +1 -1
- package/dist/types/components/ui/dropdown.d.ts +2 -1
- package/dist/types/components/ui/dropdown.d.ts.map +1 -1
- package/dist/types/components/ui/index.d.ts +1 -0
- package/dist/types/components/ui/index.d.ts.map +1 -1
- package/dist/types/components/ui/multi-select.d.ts.map +1 -1
- package/dist/types/components/ui/segment-control.d.ts +1 -0
- package/dist/types/components/ui/segment-control.d.ts.map +1 -1
- package/dist/types/components/ui/slider.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/charts/QUICK_REFERENCE.md +323 -0
- package/src/components/charts/README.md +658 -0
- package/src/components/charts/RECHARTS_FEATURES.md +458 -0
- package/src/components/charts/area-chart.tsx +248 -0
- package/src/components/charts/bar-chart.tsx +362 -0
- package/src/components/charts/index.ts +4 -0
- package/src/components/charts/pie-chart.tsx +277 -0
- package/src/components/charts/radial-chart.tsx +312 -0
- package/src/components/features/data-table/data-table.tsx +136 -125
- package/src/components/features/data-table/sort-dropdown.tsx +8 -11
- package/src/components/features/search-bar.tsx +6 -1
- package/src/components/molecules/swap-input.tsx +44 -30
- package/src/components/molecules/token-selector.tsx +10 -1
- package/src/components/ui/avatar.tsx +8 -15
- package/src/components/ui/chart.tsx +100 -109
- package/src/components/ui/combobox.tsx +150 -137
- package/src/components/ui/dialog.tsx +9 -23
- package/src/components/ui/dropdown.tsx +3 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/multi-select.tsx +325 -307
- package/src/components/ui/segment-control.tsx +7 -2
- package/src/components/ui/slider.tsx +6 -11
- package/src/index.ts +1 -0
- package/src/styles/globals.css +4 -0
- package/src/styles/themes/semantic.css +26 -56
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Recharts Native Features Usage
|
|
2
|
+
|
|
3
|
+
This document details how our chart components leverage Recharts' built-in features for optimal performance and functionality.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Our chart components are designed as **thin wrappers** around Recharts, utilizing its native capabilities wherever possible rather than implementing custom solutions. This approach ensures:
|
|
8
|
+
|
|
9
|
+
- ✅ Better performance (native rendering)
|
|
10
|
+
- ✅ Consistent behavior with Recharts ecosystem
|
|
11
|
+
- ✅ Easier maintenance and updates
|
|
12
|
+
- ✅ Access to full Recharts API when needed
|
|
13
|
+
|
|
14
|
+
## Core Native Features
|
|
15
|
+
|
|
16
|
+
### 1. Cell Component
|
|
17
|
+
|
|
18
|
+
**What it is:** Recharts' built-in component for styling individual chart elements (bars, pie segments, etc.)
|
|
19
|
+
|
|
20
|
+
**How we use it:**
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// In BarChart component
|
|
24
|
+
<Bar dataKey="sales" fill="var(--color-sales)" radius={5}>
|
|
25
|
+
{data.map((entry, index) => (
|
|
26
|
+
<Cell
|
|
27
|
+
key={`cell-${index}`}
|
|
28
|
+
fill={customColor || defaultColor}
|
|
29
|
+
/>
|
|
30
|
+
))}
|
|
31
|
+
</Bar>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Benefits:**
|
|
35
|
+
- Per-item customization without custom rendering
|
|
36
|
+
- Conditional coloring based on data values
|
|
37
|
+
- Native performance optimization
|
|
38
|
+
|
|
39
|
+
**User-facing API:**
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<BarChart
|
|
43
|
+
data={data}
|
|
44
|
+
config={config}
|
|
45
|
+
categoryKey="month"
|
|
46
|
+
dataKeys={["sales"]}
|
|
47
|
+
getBarColor={(entry, index, dataKey) => {
|
|
48
|
+
// Return custom color or undefined for default
|
|
49
|
+
return entry.sales > 200 ? "hsl(var(--destructive))" : undefined;
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Linear Gradients (SVG Defs)
|
|
55
|
+
|
|
56
|
+
**What it is:** Recharts supports SVG `<defs>` for defining reusable graphics like gradients
|
|
57
|
+
|
|
58
|
+
**How we use it:**
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// In BarChart component
|
|
62
|
+
<defs>
|
|
63
|
+
{gradient && dataKeys.map((key) => (
|
|
64
|
+
<linearGradient
|
|
65
|
+
key={key}
|
|
66
|
+
id={`gradient-bar-${key}`}
|
|
67
|
+
x1={isVertical ? "1" : "0"}
|
|
68
|
+
y1={isVertical ? "0" : "0"}
|
|
69
|
+
x2={isVertical ? "0" : "0"}
|
|
70
|
+
y2={isVertical ? "0" : "1"}
|
|
71
|
+
>
|
|
72
|
+
<stop offset="0%" stopColor={`var(--color-${key})`} stopOpacity={1} />
|
|
73
|
+
<stop offset="100%" stopColor={`var(--color-${key})`} stopOpacity={0.1} />
|
|
74
|
+
</linearGradient>
|
|
75
|
+
))}
|
|
76
|
+
</defs>
|
|
77
|
+
|
|
78
|
+
<Bar fill={gradient ? `url(#gradient-bar-${key})` : `var(--color-${key})`} />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Benefits:**
|
|
82
|
+
- Native SVG rendering
|
|
83
|
+
- Automatic orientation handling
|
|
84
|
+
- Hardware-accelerated graphics
|
|
85
|
+
- Proper gradient direction per chart orientation
|
|
86
|
+
|
|
87
|
+
**Gradient directions per chart type:**
|
|
88
|
+
- **BarChart (vertical bars):** Top to bottom (`y1="0" y2="1"`)
|
|
89
|
+
- **BarChart (horizontal bars):** Right to left (`x1="1" x2="0"`)
|
|
90
|
+
- **AreaChart:** Top to bottom (`y1="0" y2="1"`)
|
|
91
|
+
- **PieChart:** Radial from center (`radialGradient`)
|
|
92
|
+
- **RadialBarChart:** Horizontal along bar (`x1="0" x2="1"`)
|
|
93
|
+
|
|
94
|
+
### 3. Active Shape Props
|
|
95
|
+
|
|
96
|
+
**What it is:** Recharts' built-in props for handling hover/active states
|
|
97
|
+
|
|
98
|
+
**Available props:**
|
|
99
|
+
- `activeShape` - Custom renderer for active element
|
|
100
|
+
- `activeDot` - Active dot styling for line/area charts
|
|
101
|
+
- `activeBar` - Active bar styling for bar charts
|
|
102
|
+
|
|
103
|
+
**How we use it:**
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// In AreaChart component
|
|
107
|
+
<Area
|
|
108
|
+
dataKey="revenue"
|
|
109
|
+
activeDot={
|
|
110
|
+
activeDot !== undefined
|
|
111
|
+
? activeDot
|
|
112
|
+
: showDots ? { r: 4, strokeWidth: 2 } : undefined
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**User-facing API:**
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<AreaChart
|
|
121
|
+
data={data}
|
|
122
|
+
config={config}
|
|
123
|
+
categoryKey="month"
|
|
124
|
+
dataKeys={["revenue"]}
|
|
125
|
+
showDots={true}
|
|
126
|
+
activeDot={{ r: 6, strokeWidth: 2, fill: "hsl(var(--primary))" }}
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 4. PolarGrid Component
|
|
131
|
+
|
|
132
|
+
**What it is:** Recharts' component for rendering grids in polar coordinate systems
|
|
133
|
+
|
|
134
|
+
**How we use it:**
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// In PieChart component
|
|
138
|
+
<PolarGrid
|
|
139
|
+
gridType="circle"
|
|
140
|
+
radialLines={true}
|
|
141
|
+
polarAngles={Array.from(
|
|
142
|
+
{ length: radialGridLines },
|
|
143
|
+
(_, i) => (360 / radialGridLines) * i
|
|
144
|
+
)}
|
|
145
|
+
stroke="hsl(var(--border))"
|
|
146
|
+
strokeOpacity={0.3}
|
|
147
|
+
strokeWidth={1}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**User-facing API:**
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<PieChart
|
|
155
|
+
data={data}
|
|
156
|
+
config={config}
|
|
157
|
+
nameKey="category"
|
|
158
|
+
dataKey="value"
|
|
159
|
+
showRadialGrid={true}
|
|
160
|
+
radialGridLines={12}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 5. CartesianGrid Component
|
|
165
|
+
|
|
166
|
+
**What it is:** Recharts' component for rendering grid lines in Cartesian charts
|
|
167
|
+
|
|
168
|
+
**How we use it:**
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// In BarChart, AreaChart components
|
|
172
|
+
<CartesianGrid
|
|
173
|
+
strokeDasharray="3 3"
|
|
174
|
+
horizontal={gridConfig.horizontal}
|
|
175
|
+
vertical={gridConfig.vertical}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**User-facing API:**
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
// Boolean (both axes)
|
|
183
|
+
<BarChart showGrid={true} />
|
|
184
|
+
|
|
185
|
+
// Granular control
|
|
186
|
+
<BarChart
|
|
187
|
+
showGrid={{
|
|
188
|
+
horizontal: true,
|
|
189
|
+
vertical: false
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 6. Shape Prop (Custom Rendering)
|
|
195
|
+
|
|
196
|
+
**What it is:** Recharts allows custom shape renderers for complete control
|
|
197
|
+
|
|
198
|
+
**How we use it:**
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// In PieChart component for scaled radius
|
|
202
|
+
const renderCustomShape = (props) => {
|
|
203
|
+
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill, value } = props;
|
|
204
|
+
|
|
205
|
+
// Calculate scaled radius
|
|
206
|
+
let scaledOuterRadius = outerRadius;
|
|
207
|
+
if (scaledRadius && maxValue > 0) {
|
|
208
|
+
const percentage = Number(value) / maxValue;
|
|
209
|
+
scaledOuterRadius = outerRadius * (0.4 + percentage * 0.6);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<Sector
|
|
214
|
+
cx={cx}
|
|
215
|
+
cy={cy}
|
|
216
|
+
innerRadius={innerRadius}
|
|
217
|
+
outerRadius={scaledOuterRadius}
|
|
218
|
+
startAngle={startAngle}
|
|
219
|
+
endAngle={endAngle}
|
|
220
|
+
fill={fill}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
<Pie shape={renderCustomShape} />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**User-facing API:**
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
<PieChart
|
|
232
|
+
data={data}
|
|
233
|
+
config={config}
|
|
234
|
+
nameKey="category"
|
|
235
|
+
dataKey="value"
|
|
236
|
+
scaledRadius={true} // Segments scale based on value
|
|
237
|
+
/>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Implementation Patterns
|
|
241
|
+
|
|
242
|
+
### Pattern 1: Conditional Rendering with Cell
|
|
243
|
+
|
|
244
|
+
**Use case:** Color individual bars/segments based on data values
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
{dataKeys.map((key) => (
|
|
248
|
+
<Bar key={key} dataKey={key} fill={defaultFill}>
|
|
249
|
+
{getBarColor && data.map((entry, index) => {
|
|
250
|
+
const customColor = getBarColor(entry, index, key);
|
|
251
|
+
return (
|
|
252
|
+
<Cell
|
|
253
|
+
key={`cell-${key}-${index}`}
|
|
254
|
+
fill={customColor || defaultFill}
|
|
255
|
+
/>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</Bar>
|
|
259
|
+
))}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Pattern 2: Dynamic Gradient Definitions
|
|
263
|
+
|
|
264
|
+
**Use case:** Create gradients that adapt to chart configuration
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<defs>
|
|
268
|
+
{gradient && dataKeys.map((key) => (
|
|
269
|
+
<linearGradient
|
|
270
|
+
key={key}
|
|
271
|
+
id={`gradient-${chartType}-${key}`}
|
|
272
|
+
x1={orientation === "horizontal" ? "1" : "0"}
|
|
273
|
+
y1={orientation === "horizontal" ? "0" : "0"}
|
|
274
|
+
x2={orientation === "horizontal" ? "0" : "0"}
|
|
275
|
+
y2={orientation === "horizontal" ? "0" : "1"}
|
|
276
|
+
>
|
|
277
|
+
<stop offset="0%" stopColor={`var(--color-${key})`} stopOpacity={1} />
|
|
278
|
+
<stop offset="100%" stopColor={`var(--color-${key})`} stopOpacity={0.1} />
|
|
279
|
+
</linearGradient>
|
|
280
|
+
))}
|
|
281
|
+
</defs>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Pattern 3: Responsive Container
|
|
285
|
+
|
|
286
|
+
**Use case:** Charts that adapt to parent container size
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
// We use ChartContainer which wraps ResponsiveContainer
|
|
290
|
+
<ChartContainer config={config} className={grow ? "h-full w-full" : aspectRatio}>
|
|
291
|
+
<RechartsBarChart data={data}>
|
|
292
|
+
{/* chart elements */}
|
|
293
|
+
</RechartsBarChart>
|
|
294
|
+
</ChartContainer>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Why Native Features Matter
|
|
298
|
+
|
|
299
|
+
### Performance
|
|
300
|
+
|
|
301
|
+
Native Recharts features are optimized for:
|
|
302
|
+
- Efficient SVG rendering
|
|
303
|
+
- Minimal re-renders
|
|
304
|
+
- Hardware acceleration where possible
|
|
305
|
+
- React reconciliation optimization
|
|
306
|
+
|
|
307
|
+
### Maintainability
|
|
308
|
+
|
|
309
|
+
Using native features means:
|
|
310
|
+
- Fewer custom implementations to maintain
|
|
311
|
+
- Easier to update when Recharts updates
|
|
312
|
+
- Better TypeScript support
|
|
313
|
+
- Consistent with Recharts documentation
|
|
314
|
+
|
|
315
|
+
### Extensibility
|
|
316
|
+
|
|
317
|
+
Users can:
|
|
318
|
+
- Pass additional Recharts props through
|
|
319
|
+
- Extend components without rewriting
|
|
320
|
+
- Use Recharts plugins and extensions
|
|
321
|
+
- Access full Recharts ecosystem
|
|
322
|
+
|
|
323
|
+
## Examples
|
|
324
|
+
|
|
325
|
+
### Example 1: Traffic Light Colors
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
const data = [
|
|
329
|
+
{ status: "Critical", count: 12 },
|
|
330
|
+
{ status: "Warning", count: 45 },
|
|
331
|
+
{ status: "Good", count: 89 },
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
<BarChart
|
|
335
|
+
data={data}
|
|
336
|
+
config={config}
|
|
337
|
+
categoryKey="status"
|
|
338
|
+
dataKeys={["count"]}
|
|
339
|
+
getBarColor={(entry) => {
|
|
340
|
+
switch (entry.status) {
|
|
341
|
+
case "Critical": return "hsl(0 84% 60%)"; // Red
|
|
342
|
+
case "Warning": return "hsl(48 96% 53%)"; // Yellow
|
|
343
|
+
case "Good": return "hsl(142 76% 36%)"; // Green
|
|
344
|
+
default: return undefined;
|
|
345
|
+
}
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Example 2: Threshold-Based Coloring
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
const data = [
|
|
354
|
+
{ month: "Jan", revenue: 1200, target: 1000 },
|
|
355
|
+
{ month: "Feb", revenue: 800, target: 1000 },
|
|
356
|
+
{ month: "Mar", revenue: 1500, target: 1000 },
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
<BarChart
|
|
360
|
+
data={data}
|
|
361
|
+
config={config}
|
|
362
|
+
categoryKey="month"
|
|
363
|
+
dataKeys={["revenue"]}
|
|
364
|
+
getBarColor={(entry) => {
|
|
365
|
+
const percentage = (entry.revenue / entry.target) * 100;
|
|
366
|
+
if (percentage >= 100) return "hsl(142 76% 36%)"; // Green: Met target
|
|
367
|
+
if (percentage >= 80) return "hsl(48 96% 53%)"; // Yellow: Close
|
|
368
|
+
return "hsl(0 84% 60%)"; // Red: Below target
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Example 3: Gradient with Conditional Override
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
<BarChart
|
|
377
|
+
data={data}
|
|
378
|
+
config={config}
|
|
379
|
+
categoryKey="month"
|
|
380
|
+
dataKeys={["sales"]}
|
|
381
|
+
gradient={true}
|
|
382
|
+
getBarColor={(entry) => {
|
|
383
|
+
// Highlight special cases with solid color, let others use gradient
|
|
384
|
+
if (entry.month === "Dec") {
|
|
385
|
+
return "hsl(var(--primary))"; // Solid color for December
|
|
386
|
+
}
|
|
387
|
+
return undefined; // Use gradient for others
|
|
388
|
+
}}
|
|
389
|
+
/>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Example 4: Interactive Area Chart
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
<AreaChart
|
|
396
|
+
data={data}
|
|
397
|
+
config={config}
|
|
398
|
+
categoryKey="date"
|
|
399
|
+
dataKeys={["value"]}
|
|
400
|
+
gradient={true}
|
|
401
|
+
showDots={true}
|
|
402
|
+
activeDot={{
|
|
403
|
+
r: 6,
|
|
404
|
+
strokeWidth: 2,
|
|
405
|
+
fill: "hsl(var(--primary))",
|
|
406
|
+
stroke: "hsl(var(--background))",
|
|
407
|
+
}}
|
|
408
|
+
curveType="monotone"
|
|
409
|
+
/>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Best Practices
|
|
413
|
+
|
|
414
|
+
### ✅ Do
|
|
415
|
+
|
|
416
|
+
- Use `Cell` component for per-item customization
|
|
417
|
+
- Leverage native gradient support via `<defs>`
|
|
418
|
+
- Use Recharts' active state props for hover effects
|
|
419
|
+
- Keep gradient IDs unique per chart instance
|
|
420
|
+
- Return `undefined` from color functions to use defaults
|
|
421
|
+
|
|
422
|
+
### ❌ Don't
|
|
423
|
+
|
|
424
|
+
- Implement custom rendering when native features suffice
|
|
425
|
+
- Hardcode gradient definitions outside `<defs>`
|
|
426
|
+
- Override all Recharts styles (let native theming work)
|
|
427
|
+
- Ignore orientation when defining gradients
|
|
428
|
+
- Create duplicate gradient definitions
|
|
429
|
+
|
|
430
|
+
## Future Enhancements
|
|
431
|
+
|
|
432
|
+
Potential native features to explore:
|
|
433
|
+
|
|
434
|
+
1. **Brush component** - For data zooming/panning
|
|
435
|
+
2. **ReferenceLines** - For threshold indicators
|
|
436
|
+
3. **ReferenceDots** - For specific data point highlighting
|
|
437
|
+
4. **Customized Label** - For advanced label positioning
|
|
438
|
+
5. **Legend customization** - Using native Legend props
|
|
439
|
+
6. **Animation props** - Recharts' native animation system
|
|
440
|
+
|
|
441
|
+
## Resources
|
|
442
|
+
|
|
443
|
+
- [Recharts Documentation](https://recharts.org/)
|
|
444
|
+
- [Recharts API Reference](https://recharts.org/en-US/api)
|
|
445
|
+
- [Recharts GitHub](https://github.com/recharts/recharts)
|
|
446
|
+
- [SVG Gradients MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient)
|
|
447
|
+
|
|
448
|
+
## Summary
|
|
449
|
+
|
|
450
|
+
Our chart components are **Recharts-first** implementations that:
|
|
451
|
+
|
|
452
|
+
1. **Expose native Recharts features** through clean, typed APIs
|
|
453
|
+
2. **Enhance with convenience props** while maintaining Recharts compatibility
|
|
454
|
+
3. **Use Cell, gradients, and native props** for styling and interactivity
|
|
455
|
+
4. **Avoid custom rendering** unless absolutely necessary
|
|
456
|
+
5. **Stay compatible** with Recharts ecosystem and updates
|
|
457
|
+
|
|
458
|
+
This approach gives users the full power of Recharts while providing sensible defaults and TypeScript safety.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Area, AreaChart as RechartsAreaChart, CartesianGrid, XAxis, YAxis, Dot } from "recharts";
|
|
4
|
+
import {
|
|
5
|
+
ChartConfig,
|
|
6
|
+
ChartContainer,
|
|
7
|
+
ChartLegend,
|
|
8
|
+
ChartLegendContent,
|
|
9
|
+
ChartTooltip,
|
|
10
|
+
ChartTooltipContent,
|
|
11
|
+
} from "../ui/chart";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
|
|
14
|
+
export interface AreaChartData {
|
|
15
|
+
[key: string]: string | number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AreaChartProps {
|
|
19
|
+
/**
|
|
20
|
+
* Array of data objects to be displayed in the chart
|
|
21
|
+
*/
|
|
22
|
+
data: AreaChartData[];
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for chart styling and labels
|
|
25
|
+
*/
|
|
26
|
+
config: ChartConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Key from data objects to use for X-axis categories
|
|
29
|
+
*/
|
|
30
|
+
categoryKey: string;
|
|
31
|
+
/**
|
|
32
|
+
* Array of keys from data objects to display as areas
|
|
33
|
+
*/
|
|
34
|
+
dataKeys: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Show grid lines. Can be boolean for both axes or object for granular control
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
showGrid?: boolean | { horizontal?: boolean; vertical?: boolean };
|
|
40
|
+
/**
|
|
41
|
+
* Show legend
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
showLegend?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Show tooltip on hover
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
showTooltip?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Stack areas on top of each other
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
stacked?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Area curve type
|
|
57
|
+
* @default "monotone"
|
|
58
|
+
*/
|
|
59
|
+
curveType?:
|
|
60
|
+
| "basis"
|
|
61
|
+
| "basisClosed"
|
|
62
|
+
| "basisOpen"
|
|
63
|
+
| "linear"
|
|
64
|
+
| "linearClosed"
|
|
65
|
+
| "natural"
|
|
66
|
+
| "monotoneX"
|
|
67
|
+
| "monotoneY"
|
|
68
|
+
| "monotone"
|
|
69
|
+
| "step"
|
|
70
|
+
| "stepBefore"
|
|
71
|
+
| "stepAfter";
|
|
72
|
+
/**
|
|
73
|
+
* Fill opacity for areas
|
|
74
|
+
* @default 0.6
|
|
75
|
+
*/
|
|
76
|
+
fillOpacity?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Stroke width for area lines
|
|
79
|
+
* @default 2
|
|
80
|
+
*/
|
|
81
|
+
strokeWidth?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Enable gradient fill for areas (uses Recharts native gradient support)
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
gradient?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Active dot props for highlighting data points on hover
|
|
89
|
+
* @default undefined
|
|
90
|
+
*/
|
|
91
|
+
activeDot?: boolean | object;
|
|
92
|
+
/**
|
|
93
|
+
* Custom className for the container
|
|
94
|
+
*/
|
|
95
|
+
className?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Chart height aspect ratio
|
|
98
|
+
* @default "aspect-video"
|
|
99
|
+
*/
|
|
100
|
+
aspectRatio?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Make chart grow to fill parent container (ignores aspectRatio if true)
|
|
103
|
+
* @default false
|
|
104
|
+
*/
|
|
105
|
+
grow?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Show dots on data points
|
|
108
|
+
* @default false
|
|
109
|
+
*/
|
|
110
|
+
showDots?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Custom formatter function for Y-axis tick labels
|
|
113
|
+
* @example (value) => `$${value.toLocaleString()}`
|
|
114
|
+
* @example (value) => formatCompactNumber(value)
|
|
115
|
+
*/
|
|
116
|
+
yAxisFormatter?: (value: number) => string;
|
|
117
|
+
/**
|
|
118
|
+
* Custom formatter function for X-axis tick labels
|
|
119
|
+
* @example (value) => new Date(value).toLocaleDateString()
|
|
120
|
+
* @example (value) => formatCompactNumber(value)
|
|
121
|
+
*/
|
|
122
|
+
xAxisFormatter?: (value: any) => string;
|
|
123
|
+
/**
|
|
124
|
+
* Custom formatter function for tooltip values
|
|
125
|
+
* @example (value, name, item, index, payload) => `$${value.toLocaleString()}`
|
|
126
|
+
*/
|
|
127
|
+
tooltipFormatter?: (
|
|
128
|
+
value: any,
|
|
129
|
+
name: any,
|
|
130
|
+
item: any,
|
|
131
|
+
index: number,
|
|
132
|
+
payload: any
|
|
133
|
+
) => React.ReactNode;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function AreaChart({
|
|
137
|
+
data,
|
|
138
|
+
config,
|
|
139
|
+
categoryKey,
|
|
140
|
+
dataKeys,
|
|
141
|
+
showGrid = true,
|
|
142
|
+
showLegend = false,
|
|
143
|
+
showTooltip = true,
|
|
144
|
+
stacked = false,
|
|
145
|
+
curveType = "monotone",
|
|
146
|
+
fillOpacity = 0.6,
|
|
147
|
+
strokeWidth = 2,
|
|
148
|
+
gradient = true,
|
|
149
|
+
className,
|
|
150
|
+
aspectRatio = "aspect-video",
|
|
151
|
+
showDots = false,
|
|
152
|
+
grow = false,
|
|
153
|
+
activeDot,
|
|
154
|
+
yAxisFormatter,
|
|
155
|
+
xAxisFormatter,
|
|
156
|
+
tooltipFormatter,
|
|
157
|
+
}: AreaChartProps) {
|
|
158
|
+
const stackId = stacked ? "stack" : undefined;
|
|
159
|
+
|
|
160
|
+
// Parse grid configuration
|
|
161
|
+
const gridConfig =
|
|
162
|
+
typeof showGrid === "boolean"
|
|
163
|
+
? { horizontal: showGrid, vertical: showGrid }
|
|
164
|
+
: { horizontal: showGrid?.horizontal ?? false, vertical: showGrid?.vertical ?? false };
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ChartContainer config={config} className={cn(grow ? "h-full w-full" : aspectRatio, className)}>
|
|
168
|
+
<RechartsAreaChart
|
|
169
|
+
accessibilityLayer
|
|
170
|
+
data={data}
|
|
171
|
+
margin={{
|
|
172
|
+
top: 10,
|
|
173
|
+
right: 10,
|
|
174
|
+
bottom: 10,
|
|
175
|
+
left: 10,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
{(gridConfig.horizontal || gridConfig.vertical) && (
|
|
179
|
+
<CartesianGrid
|
|
180
|
+
strokeDasharray="3 3"
|
|
181
|
+
horizontal={gridConfig.horizontal}
|
|
182
|
+
vertical={gridConfig.vertical}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<XAxis
|
|
187
|
+
dataKey={categoryKey}
|
|
188
|
+
tickLine={false}
|
|
189
|
+
tickMargin={10}
|
|
190
|
+
axisLine={false}
|
|
191
|
+
tickFormatter={(value) => {
|
|
192
|
+
if (xAxisFormatter) {
|
|
193
|
+
return xAxisFormatter(value);
|
|
194
|
+
}
|
|
195
|
+
return config[value as keyof typeof config]?.label?.toString() || value;
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
<YAxis
|
|
199
|
+
tickLine={false}
|
|
200
|
+
axisLine={false}
|
|
201
|
+
tickFormatter={(value) => (yAxisFormatter ? yAxisFormatter(value) : value)}
|
|
202
|
+
width={yAxisFormatter ? 50 : undefined}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
{showTooltip && (
|
|
206
|
+
<ChartTooltip
|
|
207
|
+
cursor={false}
|
|
208
|
+
content={
|
|
209
|
+
<ChartTooltipContent
|
|
210
|
+
indicator={stacked ? "line" : "dot"}
|
|
211
|
+
formatter={tooltipFormatter}
|
|
212
|
+
/>
|
|
213
|
+
}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
|
218
|
+
|
|
219
|
+
<defs>
|
|
220
|
+
{gradient &&
|
|
221
|
+
dataKeys.map((key) => (
|
|
222
|
+
<linearGradient key={key} id={`gradient-area-${key}`} x1="0" y1="0" x2="0" y2="1">
|
|
223
|
+
<stop offset="0%" stopColor={`var(--color-${key})`} stopOpacity={0.6} />
|
|
224
|
+
<stop offset="100%" stopColor={`var(--color-${key})`} stopOpacity={0.05} />
|
|
225
|
+
</linearGradient>
|
|
226
|
+
))}
|
|
227
|
+
</defs>
|
|
228
|
+
|
|
229
|
+
{dataKeys.map((key) => (
|
|
230
|
+
<Area
|
|
231
|
+
key={key}
|
|
232
|
+
dataKey={key}
|
|
233
|
+
type={curveType}
|
|
234
|
+
fill={gradient ? `url(#gradient-area-${key})` : `var(--color-${key})`}
|
|
235
|
+
stroke={`var(--color-${key})`}
|
|
236
|
+
fillOpacity={gradient ? 1 : fillOpacity}
|
|
237
|
+
strokeWidth={strokeWidth}
|
|
238
|
+
stackId={stackId}
|
|
239
|
+
dot={showDots}
|
|
240
|
+
activeDot={
|
|
241
|
+
activeDot !== undefined ? activeDot : showDots ? { r: 4, strokeWidth: 2 } : undefined
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
244
|
+
))}
|
|
245
|
+
</RechartsAreaChart>
|
|
246
|
+
</ChartContainer>
|
|
247
|
+
);
|
|
248
|
+
}
|