feat: add ThemedChart wrapper component
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { ThemedChart } from './ThemedChart'
|
||||||
|
import { Line } from 'recharts'
|
||||||
|
|
||||||
|
// Recharts uses ResizeObserver internally
|
||||||
|
class ResizeObserverMock {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
globalThis.ResizeObserver = ResizeObserverMock as any
|
||||||
|
|
||||||
|
describe('ThemedChart', () => {
|
||||||
|
it('renders nothing when data is empty', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ThemedChart data={[]}>
|
||||||
|
<Line dataKey="value" />
|
||||||
|
</ThemedChart>,
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders a chart container when data is provided', () => {
|
||||||
|
const data = [
|
||||||
|
{ time: '10:00', value: 10 },
|
||||||
|
{ time: '10:01', value: 20 },
|
||||||
|
]
|
||||||
|
const { container } = render(
|
||||||
|
<ThemedChart data={data} height={160}>
|
||||||
|
<Line dataKey="value" />
|
||||||
|
</ThemedChart>,
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.recharts-responsive-container')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies custom className', () => {
|
||||||
|
const data = [{ time: '10:00', value: 5 }]
|
||||||
|
const { container } = render(
|
||||||
|
<ThemedChart data={data} className="my-chart">
|
||||||
|
<Line dataKey="value" />
|
||||||
|
</ThemedChart>,
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.my-chart')).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
66
src/design-system/composites/ThemedChart/ThemedChart.tsx
Normal file
66
src/design-system/composites/ThemedChart/ThemedChart.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
ResponsiveContainer,
|
||||||
|
ComposedChart,
|
||||||
|
CartesianGrid,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
Tooltip,
|
||||||
|
} from 'recharts'
|
||||||
|
import { rechartsTheme } from '../../utils/rechartsTheme'
|
||||||
|
import { ChartTooltip } from './ChartTooltip'
|
||||||
|
|
||||||
|
interface ThemedChartProps {
|
||||||
|
data: Record<string, any>[]
|
||||||
|
height?: number
|
||||||
|
xDataKey?: string
|
||||||
|
xType?: 'number' | 'category'
|
||||||
|
xTickFormatter?: (value: any) => string
|
||||||
|
yTickFormatter?: (value: any) => string
|
||||||
|
yLabel?: string
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemedChart({
|
||||||
|
data,
|
||||||
|
height = 200,
|
||||||
|
xDataKey = 'time',
|
||||||
|
xType = 'category',
|
||||||
|
xTickFormatter,
|
||||||
|
yTickFormatter,
|
||||||
|
yLabel,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: ThemedChartProps) {
|
||||||
|
if (!data.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} style={{ width: '100%', height }}>
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<ComposedChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>
|
||||||
|
<CartesianGrid {...rechartsTheme.cartesianGrid} />
|
||||||
|
<XAxis
|
||||||
|
dataKey={xDataKey}
|
||||||
|
type={xType}
|
||||||
|
{...rechartsTheme.xAxis}
|
||||||
|
tickFormatter={xTickFormatter}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
{...rechartsTheme.yAxis}
|
||||||
|
tickFormatter={yTickFormatter}
|
||||||
|
label={yLabel ? {
|
||||||
|
value: yLabel,
|
||||||
|
angle: -90,
|
||||||
|
position: 'insideLeft',
|
||||||
|
style: { fontSize: 11, fill: 'var(--text-muted)' },
|
||||||
|
} : undefined}
|
||||||
|
/>
|
||||||
|
<Tooltip content={<ChartTooltip />} cursor={rechartsTheme.tooltip.cursor} />
|
||||||
|
{children}
|
||||||
|
</ComposedChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user