@wallarm-org/design-system 0.35.1-rc-feature-AS-1005.1 → 0.36.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.
- package/dist/components/SimpleCharts/LineChart/LineChart.d.ts +70 -0
- package/dist/components/SimpleCharts/LineChart/LineChart.figma.d.ts +1 -0
- package/dist/components/SimpleCharts/LineChart/LineChart.figma.js +163 -0
- package/dist/components/SimpleCharts/LineChart/LineChart.js +136 -0
- package/dist/components/SimpleCharts/LineChart/LineChartBody.d.ts +12 -0
- package/dist/components/SimpleCharts/LineChart/LineChartBody.js +66 -0
- package/dist/components/SimpleCharts/LineChart/LineChartContext.d.ts +151 -0
- package/dist/components/SimpleCharts/LineChart/LineChartContext.js +16 -0
- package/dist/components/SimpleCharts/LineChart/LineChartEmpty.d.ts +15 -0
- package/dist/components/SimpleCharts/LineChart/LineChartEmpty.js +71 -0
- package/dist/components/SimpleCharts/LineChart/LineChartGrid.d.ts +19 -0
- package/dist/components/SimpleCharts/LineChart/LineChartGrid.js +17 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopover.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopover.js +14 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverDot.d.ts +14 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverDot.js +20 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverRow.d.ts +12 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverRow.js +33 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverTimestamp.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChartHoverPopoverTimestamp.js +12 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLegend.d.ts +15 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLegend.js +19 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLegendItem.d.ts +14 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLegendItem.js +112 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLine.d.ts +17 -0
- package/dist/components/SimpleCharts/LineChart/LineChartLine.js +57 -0
- package/dist/components/SimpleCharts/LineChart/LineChartTooltip.d.ts +31 -0
- package/dist/components/SimpleCharts/LineChart/LineChartTooltip.js +75 -0
- package/dist/components/SimpleCharts/LineChart/LineChartXAxis.d.ts +51 -0
- package/dist/components/SimpleCharts/LineChart/LineChartXAxis.js +34 -0
- package/dist/components/SimpleCharts/LineChart/LineChartYAxis.d.ts +24 -0
- package/dist/components/SimpleCharts/LineChart/LineChartYAxis.js +30 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomBrush.d.ts +32 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomBrush.js +104 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopover.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopover.js +14 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopoverConfirm.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopoverConfirm.js +14 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopoverRange.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/LineChartZoomPopoverRange.js +12 -0
- package/dist/components/SimpleCharts/LineChart/classes.d.ts +28 -0
- package/dist/components/SimpleCharts/LineChart/classes.js +95 -0
- package/dist/components/SimpleCharts/LineChart/constants.d.ts +25 -0
- package/dist/components/SimpleCharts/LineChart/constants.js +26 -0
- package/dist/components/SimpleCharts/LineChart/hooks/index.d.ts +5 -0
- package/dist/components/SimpleCharts/LineChart/hooks/index.js +6 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartActiveKey.d.ts +33 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartActiveKey.js +33 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartDataWarnings.d.ts +18 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartDataWarnings.js +47 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.d.ts +37 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useLineChartZoomState.js +111 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useZoomDragListeners.d.ts +16 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useZoomDragListeners.js +27 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useZoomPendingListeners.d.ts +21 -0
- package/dist/components/SimpleCharts/LineChart/hooks/useZoomPendingListeners.js +35 -0
- package/dist/components/SimpleCharts/LineChart/index.d.ts +19 -0
- package/dist/components/SimpleCharts/LineChart/index.js +19 -0
- package/dist/components/SimpleCharts/LineChart/lib/dropEdgeGridLines.d.ts +30 -0
- package/dist/components/SimpleCharts/LineChart/lib/dropEdgeGridLines.js +48 -0
- package/dist/components/SimpleCharts/LineChart/lib/formatRange.d.ts +9 -0
- package/dist/components/SimpleCharts/LineChart/lib/formatRange.js +7 -0
- package/dist/components/SimpleCharts/LineChart/lib/sampleData.d.ts +15 -0
- package/dist/components/SimpleCharts/LineChart/lib/sampleData.js +109 -0
- package/dist/components/SimpleCharts/LineChart/lib/tickHorizontalCoordinates.d.ts +15 -0
- package/dist/components/SimpleCharts/LineChart/lib/tickHorizontalCoordinates.js +13 -0
- package/dist/components/SimpleCharts/LineChart/lib/warn.d.ts +4 -0
- package/dist/components/SimpleCharts/LineChart/lib/warn.js +6 -0
- package/dist/components/SimpleCharts/PieChart/PieChartContext.js +5 -2
- package/dist/components/SimpleCharts/PieChart/constants.d.ts +1 -2
- package/dist/components/SimpleCharts/PieChart/constants.js +2 -15
- package/dist/components/SimpleCharts/hooks/index.d.ts +1 -0
- package/dist/components/SimpleCharts/hooks/index.js +2 -0
- package/dist/components/SimpleCharts/hooks/useChartTimeFormatters.d.ts +21 -0
- package/dist/components/SimpleCharts/hooks/useChartTimeFormatters.js +33 -0
- package/dist/components/SimpleCharts/index.d.ts +3 -0
- package/dist/components/SimpleCharts/index.js +4 -1
- package/dist/components/SimpleCharts/lib/chartPalette.d.ts +10 -0
- package/dist/components/SimpleCharts/lib/chartPalette.js +20 -0
- package/dist/components/SimpleCharts/lib/hoverSync.d.ts +9 -0
- package/dist/components/SimpleCharts/lib/hoverSync.js +5 -0
- package/dist/components/SimpleCharts/lib/index.d.ts +2 -0
- package/dist/components/SimpleCharts/lib/index.js +3 -0
- package/dist/components/SimpleCharts/lib/timeFormatters.d.ts +25 -0
- package/dist/components/SimpleCharts/lib/timeFormatters.js +57 -0
- package/dist/metadata/components.json +3664 -1
- package/dist/utils/formatDateTime.d.ts +4 -0
- package/dist/utils/formatDateTime.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactElement, type Ref } from 'react';
|
|
2
|
+
import type { SyncMethod } from 'recharts/types/synchronisation/types';
|
|
3
|
+
import { type TestableProps } from '../../../utils/testId';
|
|
4
|
+
import { type LineChartDatum, type LineChartSeries, type LineChartZoomRange } from './LineChartContext';
|
|
5
|
+
/**
|
|
6
|
+
* Generic over the datum shape so `xKey` and `series[].key` are checked at
|
|
7
|
+
* compile time against the actual data interface. When `T` is left at the
|
|
8
|
+
* default `LineChartDatum`, the constraints collapse to `string` and the API
|
|
9
|
+
* keeps its untyped form — the safety only kicks in when callers pass a
|
|
10
|
+
* concrete datum interface.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* interface RequestRow { timestamp: number; requests: number; errors: number }
|
|
14
|
+
*
|
|
15
|
+
* <LineChart<RequestRow>
|
|
16
|
+
* data={rows}
|
|
17
|
+
* xKey='timestamp' // ✅ valid keyof T
|
|
18
|
+
* series={[{ key: 'errors', label: 'Errors' }]} // ✅ valid keyof T
|
|
19
|
+
* />
|
|
20
|
+
* <LineChart<RequestRow> data={rows} xKey='timetamp' /> // ❌ Type error
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface LineChartProps<T extends LineChartDatum = LineChartDatum> extends HTMLAttributes<HTMLDivElement>, TestableProps {
|
|
24
|
+
ref?: Ref<HTMLDivElement>;
|
|
25
|
+
/** Long-form data array, one entry per X position. */
|
|
26
|
+
data: T[];
|
|
27
|
+
/** Schema describing each line drawn from `data`. */
|
|
28
|
+
series: LineChartSeries<Extract<keyof T, string>>[];
|
|
29
|
+
/**
|
|
30
|
+
* Key on each datum used as the X-axis value. Required — a silent fallback
|
|
31
|
+
* would hide key-name typos until the chart renders empty.
|
|
32
|
+
*/
|
|
33
|
+
xKey: Extract<keyof T, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Controlled hover key — pass alongside `onActiveKeyChange` for cross-chart
|
|
36
|
+
* sync.
|
|
37
|
+
*
|
|
38
|
+
* **Heads-up:** if the controlled value points at a series key that no
|
|
39
|
+
* longer exists in `series` (filter, schema change, refresh), the chart
|
|
40
|
+
* pushes `null` back through `onActiveKeyChange` so sibling charts stop
|
|
41
|
+
* highlighting a stale key. If you wire `activeKey` to external state, the
|
|
42
|
+
* snap-to-null write will arrive as a normal state change — do not be
|
|
43
|
+
* surprised when your value resets after a series disappears.
|
|
44
|
+
*/
|
|
45
|
+
activeKey?: string | null;
|
|
46
|
+
onActiveKeyChange?: (key: string | null) => void;
|
|
47
|
+
/** Controlled set of `series.key` values to hide from the plot. */
|
|
48
|
+
filteredKeys?: string[];
|
|
49
|
+
/** Fired after the user confirms a brush selection. */
|
|
50
|
+
onZoomChange?: (range: LineChartZoomRange | null) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Charts that share a `syncId` synchronise their tooltip cursor and brush
|
|
53
|
+
* via recharts' built-in middleware — hovering one chart highlights the
|
|
54
|
+
* matching X on every sibling chart with the same id. Pair with `activeKey`
|
|
55
|
+
* + `onActiveKeyChange` if you also want the *series* highlight to sync.
|
|
56
|
+
*/
|
|
57
|
+
syncId?: string | number;
|
|
58
|
+
/**
|
|
59
|
+
* How sibling `syncId` charts match X positions. Defaults to `'index'`
|
|
60
|
+
* (Recharts' default). Use `'value'` when the synced charts have different
|
|
61
|
+
* dataset lengths but share categorical X values.
|
|
62
|
+
*
|
|
63
|
+
* @see {@link https://recharts.github.io/en-US/examples/SynchronizedAreaChart/ Recharts synchronisation example}
|
|
64
|
+
*/
|
|
65
|
+
syncMethod?: SyncMethod;
|
|
66
|
+
}
|
|
67
|
+
export declare function LineChart<T extends LineChartDatum = LineChartDatum>({ data, series, xKey, activeKey: controlledActiveKey, onActiveKeyChange, filteredKeys, onZoomChange, syncId, syncMethod, className, children, ref, 'data-testid': testId, ...props }: LineChartProps<T>): ReactElement;
|
|
68
|
+
export declare namespace LineChart {
|
|
69
|
+
var displayName: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import code_connect from "@figma/code-connect";
|
|
3
|
+
import { Chart } from "../Chart/Chart.js";
|
|
4
|
+
import { ChartHeader } from "../Chart/ChartHeader.js";
|
|
5
|
+
import { ChartTitle } from "../Chart/ChartTitle.js";
|
|
6
|
+
import { formatChartHour } from "../lib/timeFormatters.js";
|
|
7
|
+
import { LineChart } from "./LineChart.js";
|
|
8
|
+
import { LineChartBody } from "./LineChartBody.js";
|
|
9
|
+
import { LineChartGrid } from "./LineChartGrid.js";
|
|
10
|
+
import { LineChartHoverPopoverDot } from "./LineChartHoverPopoverDot.js";
|
|
11
|
+
import { LineChartLegend } from "./LineChartLegend.js";
|
|
12
|
+
import { LineChartLegendItem } from "./LineChartLegendItem.js";
|
|
13
|
+
import { LineChartLine } from "./LineChartLine.js";
|
|
14
|
+
import { LineChartTooltip } from "./LineChartTooltip.js";
|
|
15
|
+
import { LineChartXAxis } from "./LineChartXAxis.js";
|
|
16
|
+
import { LineChartYAxis } from "./LineChartYAxis.js";
|
|
17
|
+
import { LineChartZoomBrush } from "./LineChartZoomBrush.js";
|
|
18
|
+
const figmaNodeUrl = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/WADS-Components?node-id=7490-123142&m=dev';
|
|
19
|
+
const figmaMultiNodeUrl = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/WADS-Components?node-id=7509-2354&m=dev';
|
|
20
|
+
const sampleData = Array.from({
|
|
21
|
+
length: 24
|
|
22
|
+
}, (_, i)=>{
|
|
23
|
+
const t = Date.UTC(2025, 0, 1, i, 0, 0);
|
|
24
|
+
const requests = Math.round(120 + 60 * Math.sin(i / 3));
|
|
25
|
+
const errors = Math.round(20 + 12 * Math.cos(i / 4));
|
|
26
|
+
const latency = Math.round(80 + 20 * Math.sin(i / 5));
|
|
27
|
+
return {
|
|
28
|
+
timestamp: t,
|
|
29
|
+
requests,
|
|
30
|
+
errors,
|
|
31
|
+
latency
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
const singleSeries = [
|
|
35
|
+
{
|
|
36
|
+
key: 'requests',
|
|
37
|
+
label: 'Requests',
|
|
38
|
+
color: 'brand'
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
const multiSeries = [
|
|
42
|
+
{
|
|
43
|
+
key: 'requests',
|
|
44
|
+
label: 'Requests',
|
|
45
|
+
color: 'brand'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'errors',
|
|
49
|
+
label: 'Errors',
|
|
50
|
+
color: 'red'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: 'latency',
|
|
54
|
+
label: 'Latency',
|
|
55
|
+
color: 'blue'
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
const formatXTick = (value)=>formatChartHour(value);
|
|
59
|
+
const formatYTick = (value)=>Number(value).toLocaleString('en-US');
|
|
60
|
+
code_connect.connect(LineChart, figmaNodeUrl, {
|
|
61
|
+
props: {
|
|
62
|
+
title: code_connect.string('Title'),
|
|
63
|
+
state: code_connect["enum"]('State', {
|
|
64
|
+
Default: 'default',
|
|
65
|
+
Hovered: 'hovered',
|
|
66
|
+
Zooming: 'zooming'
|
|
67
|
+
})
|
|
68
|
+
},
|
|
69
|
+
example: ({ title, state })=>/*#__PURE__*/ jsxs(Chart, {
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ jsx(ChartHeader, {
|
|
72
|
+
children: /*#__PURE__*/ jsx(ChartTitle, {
|
|
73
|
+
children: title
|
|
74
|
+
})
|
|
75
|
+
}),
|
|
76
|
+
/*#__PURE__*/ jsx(LineChart, {
|
|
77
|
+
data: sampleData,
|
|
78
|
+
series: singleSeries,
|
|
79
|
+
xKey: "timestamp",
|
|
80
|
+
children: /*#__PURE__*/ jsxs(LineChartBody, {
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ jsx(LineChartGrid, {}),
|
|
83
|
+
/*#__PURE__*/ jsx(LineChartXAxis, {
|
|
84
|
+
tickFormatter: formatXTick,
|
|
85
|
+
minTickGap: 32
|
|
86
|
+
}),
|
|
87
|
+
/*#__PURE__*/ jsx(LineChartYAxis, {
|
|
88
|
+
tickFormatter: formatYTick
|
|
89
|
+
}),
|
|
90
|
+
/*#__PURE__*/ jsx(LineChartLine, {
|
|
91
|
+
seriesKey: "requests"
|
|
92
|
+
}),
|
|
93
|
+
/*#__PURE__*/ jsx(LineChartTooltip, {
|
|
94
|
+
xTickFormatter: formatChartHour
|
|
95
|
+
}),
|
|
96
|
+
'zooming' === state && /*#__PURE__*/ jsx(LineChartZoomBrush, {})
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
code_connect.connect(LineChart, figmaMultiNodeUrl, {
|
|
104
|
+
props: {
|
|
105
|
+
title: code_connect.string('Title'),
|
|
106
|
+
state: code_connect["enum"]('State', {
|
|
107
|
+
Default: 'default',
|
|
108
|
+
Hovered: 'hovered',
|
|
109
|
+
Filtered: 'filtered'
|
|
110
|
+
})
|
|
111
|
+
},
|
|
112
|
+
example: ({ title, state })=>/*#__PURE__*/ jsxs(Chart, {
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ jsx(ChartHeader, {
|
|
115
|
+
children: /*#__PURE__*/ jsx(ChartTitle, {
|
|
116
|
+
children: title
|
|
117
|
+
})
|
|
118
|
+
}),
|
|
119
|
+
/*#__PURE__*/ jsxs(LineChart, {
|
|
120
|
+
data: sampleData,
|
|
121
|
+
series: multiSeries,
|
|
122
|
+
xKey: "timestamp",
|
|
123
|
+
filteredKeys: 'filtered' === state ? [
|
|
124
|
+
'latency'
|
|
125
|
+
] : [],
|
|
126
|
+
children: [
|
|
127
|
+
/*#__PURE__*/ jsx(LineChartLegend, {
|
|
128
|
+
children: multiSeries.map((s)=>/*#__PURE__*/ jsxs(LineChartLegendItem, {
|
|
129
|
+
seriesKey: s.key,
|
|
130
|
+
children: [
|
|
131
|
+
/*#__PURE__*/ jsx(LineChartHoverPopoverDot, {
|
|
132
|
+
color: s.color
|
|
133
|
+
}),
|
|
134
|
+
/*#__PURE__*/ jsx("span", {
|
|
135
|
+
className: "text-xs font-mono text-text-primary",
|
|
136
|
+
children: s.label
|
|
137
|
+
})
|
|
138
|
+
]
|
|
139
|
+
}, s.key))
|
|
140
|
+
}),
|
|
141
|
+
/*#__PURE__*/ jsxs(LineChartBody, {
|
|
142
|
+
children: [
|
|
143
|
+
/*#__PURE__*/ jsx(LineChartGrid, {}),
|
|
144
|
+
/*#__PURE__*/ jsx(LineChartXAxis, {
|
|
145
|
+
tickFormatter: formatXTick,
|
|
146
|
+
minTickGap: 32
|
|
147
|
+
}),
|
|
148
|
+
/*#__PURE__*/ jsx(LineChartYAxis, {
|
|
149
|
+
tickFormatter: formatYTick
|
|
150
|
+
}),
|
|
151
|
+
multiSeries.map((s)=>/*#__PURE__*/ jsx(LineChartLine, {
|
|
152
|
+
seriesKey: s.key
|
|
153
|
+
}, s.key)),
|
|
154
|
+
/*#__PURE__*/ jsx(LineChartTooltip, {
|
|
155
|
+
xTickFormatter: formatChartHour
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
})
|
|
159
|
+
]
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
})
|
|
163
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
3
|
+
import { cn } from "../../../utils/cn.js";
|
|
4
|
+
import { TestIdProvider } from "../../../utils/testId.js";
|
|
5
|
+
import { lineChartRootClasses } from "./classes.js";
|
|
6
|
+
import { useLineChartActiveKey } from "./hooks/useLineChartActiveKey.js";
|
|
7
|
+
import { useLineChartDataWarnings } from "./hooks/useLineChartDataWarnings.js";
|
|
8
|
+
import { useLineChartZoomState } from "./hooks/useLineChartZoomState.js";
|
|
9
|
+
import { EMPTY_HIDDEN_SET, LineChartActiveContext, LineChartDataContext, LineChartSelectionContext, LineChartZoomContext } from "./LineChartContext.js";
|
|
10
|
+
function LineChart({ data, series, xKey, activeKey: controlledActiveKey, onActiveKeyChange, filteredKeys, onZoomChange, syncId, syncMethod, className, children, ref, 'data-testid': testId, ...props }) {
|
|
11
|
+
const seriesByKey = useMemo(()=>{
|
|
12
|
+
const map = new Map();
|
|
13
|
+
for (const s of series)map.set(s.key, s);
|
|
14
|
+
return map;
|
|
15
|
+
}, [
|
|
16
|
+
series
|
|
17
|
+
]);
|
|
18
|
+
useLineChartDataWarnings({
|
|
19
|
+
data,
|
|
20
|
+
series,
|
|
21
|
+
xKey
|
|
22
|
+
});
|
|
23
|
+
const { activeKey, setActiveKey } = useLineChartActiveKey({
|
|
24
|
+
controlledActiveKey,
|
|
25
|
+
onActiveKeyChange,
|
|
26
|
+
seriesByKey
|
|
27
|
+
});
|
|
28
|
+
const emitZoom = useCallback((range)=>{
|
|
29
|
+
onZoomChange?.(range);
|
|
30
|
+
}, [
|
|
31
|
+
onZoomChange
|
|
32
|
+
]);
|
|
33
|
+
const zoom = useLineChartZoomState({
|
|
34
|
+
data,
|
|
35
|
+
xKey,
|
|
36
|
+
onZoomChange
|
|
37
|
+
});
|
|
38
|
+
const hiddenSet = useMemo(()=>{
|
|
39
|
+
if (!filteredKeys?.length) return EMPTY_HIDDEN_SET;
|
|
40
|
+
const set = new Set();
|
|
41
|
+
for (const key of filteredKeys)if (seriesByKey.has(key)) set.add(key);
|
|
42
|
+
return set.size > 0 ? set : EMPTY_HIDDEN_SET;
|
|
43
|
+
}, [
|
|
44
|
+
filteredKeys,
|
|
45
|
+
seriesByKey
|
|
46
|
+
]);
|
|
47
|
+
const dataValue = useMemo(()=>({
|
|
48
|
+
data,
|
|
49
|
+
series,
|
|
50
|
+
seriesByKey,
|
|
51
|
+
xKey,
|
|
52
|
+
hiddenSet,
|
|
53
|
+
setActiveKey,
|
|
54
|
+
emitZoom,
|
|
55
|
+
syncId,
|
|
56
|
+
syncMethod
|
|
57
|
+
}), [
|
|
58
|
+
data,
|
|
59
|
+
series,
|
|
60
|
+
seriesByKey,
|
|
61
|
+
xKey,
|
|
62
|
+
hiddenSet,
|
|
63
|
+
setActiveKey,
|
|
64
|
+
emitZoom,
|
|
65
|
+
syncId,
|
|
66
|
+
syncMethod
|
|
67
|
+
]);
|
|
68
|
+
const activeKeyContextValue = useMemo(()=>({
|
|
69
|
+
activeKey
|
|
70
|
+
}), [
|
|
71
|
+
activeKey
|
|
72
|
+
]);
|
|
73
|
+
const selectionValue = useMemo(()=>({
|
|
74
|
+
hiddenSet
|
|
75
|
+
}), [
|
|
76
|
+
hiddenSet
|
|
77
|
+
]);
|
|
78
|
+
const rootRef = useRef(null);
|
|
79
|
+
const setRootRef = useCallback((node)=>{
|
|
80
|
+
rootRef.current = node;
|
|
81
|
+
if ('function' == typeof ref) ref(node);
|
|
82
|
+
else if (ref) ref.current = node;
|
|
83
|
+
}, [
|
|
84
|
+
ref
|
|
85
|
+
]);
|
|
86
|
+
const { enabled: zoomEnabled, drag: zoomDrag, pending: zoomPending, registerEnabled, startDrag, updateDrag, endDrag, cancelDrag, confirmZoom, cancelPending } = zoom;
|
|
87
|
+
const zoomContextValue = useMemo(()=>({
|
|
88
|
+
enabled: zoomEnabled,
|
|
89
|
+
drag: zoomDrag,
|
|
90
|
+
pending: zoomPending,
|
|
91
|
+
rootRef,
|
|
92
|
+
registerEnabled,
|
|
93
|
+
startDrag,
|
|
94
|
+
updateDrag,
|
|
95
|
+
endDrag,
|
|
96
|
+
cancelDrag,
|
|
97
|
+
confirmZoom,
|
|
98
|
+
cancelPending
|
|
99
|
+
}), [
|
|
100
|
+
zoomEnabled,
|
|
101
|
+
zoomDrag,
|
|
102
|
+
zoomPending,
|
|
103
|
+
registerEnabled,
|
|
104
|
+
startDrag,
|
|
105
|
+
updateDrag,
|
|
106
|
+
endDrag,
|
|
107
|
+
cancelDrag,
|
|
108
|
+
confirmZoom,
|
|
109
|
+
cancelPending
|
|
110
|
+
]);
|
|
111
|
+
return /*#__PURE__*/ jsx(LineChartDataContext.Provider, {
|
|
112
|
+
value: dataValue,
|
|
113
|
+
children: /*#__PURE__*/ jsx(LineChartActiveContext.Provider, {
|
|
114
|
+
value: activeKeyContextValue,
|
|
115
|
+
children: /*#__PURE__*/ jsx(LineChartSelectionContext.Provider, {
|
|
116
|
+
value: selectionValue,
|
|
117
|
+
children: /*#__PURE__*/ jsx(LineChartZoomContext.Provider, {
|
|
118
|
+
value: zoomContextValue,
|
|
119
|
+
children: /*#__PURE__*/ jsx(TestIdProvider, {
|
|
120
|
+
value: testId,
|
|
121
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
122
|
+
...props,
|
|
123
|
+
ref: setRootRef,
|
|
124
|
+
"data-slot": "line-chart",
|
|
125
|
+
"data-testid": testId,
|
|
126
|
+
className: cn(lineChartRootClasses, className),
|
|
127
|
+
children: children
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
LineChart.displayName = 'LineChart';
|
|
136
|
+
export { LineChart };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type FC, type HTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export interface LineChartBodyProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
ref?: Ref<HTMLDivElement>;
|
|
4
|
+
/** Recharts subcomponents — `<XAxis>`, `<YAxis>`, `<CartesianGrid>`, `<Line>`, `<Tooltip>`, `<Brush>`. */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Body height passed to recharts' `<ResponsiveContainer>`. Defaults to the
|
|
8
|
+
* Figma single-line plot height (`LINE_CARD_HEIGHT - LINE_HEADER_HEIGHT`).
|
|
9
|
+
*/
|
|
10
|
+
height?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const LineChartBody: FC<LineChartBodyProps>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useContext } from "react";
|
|
3
|
+
import { LineChart, ResponsiveContainer } from "recharts";
|
|
4
|
+
import { cn } from "../../../utils/cn.js";
|
|
5
|
+
import { useTestId } from "../../../utils/testId.js";
|
|
6
|
+
import { lineChartBodyClasses, lineChartBodyZoomEnabledClasses } from "./classes.js";
|
|
7
|
+
import { LINE_CARD_HEIGHT, LINE_DEFAULT_BODY_MARGIN, LINE_HEADER_HEIGHT } from "./constants.js";
|
|
8
|
+
import { LineChartDataContext, LineChartZoomContext } from "./LineChartContext.js";
|
|
9
|
+
const DEFAULT_BODY_HEIGHT = LINE_CARD_HEIGHT - LINE_HEADER_HEIGHT;
|
|
10
|
+
const toTooltipIndex = (value)=>{
|
|
11
|
+
if ('number' == typeof value) return Number.isFinite(value) ? value : null;
|
|
12
|
+
if ('string' == typeof value) {
|
|
13
|
+
const parsed = Number(value);
|
|
14
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
const LineChartBody = ({ height = DEFAULT_BODY_HEIGHT, className, ref, children, ...props })=>{
|
|
19
|
+
const testId = useTestId('body');
|
|
20
|
+
const dataCtx = useContext(LineChartDataContext);
|
|
21
|
+
const zoomCtx = useContext(LineChartZoomContext);
|
|
22
|
+
const isZoomEnabled = zoomCtx?.enabled ?? false;
|
|
23
|
+
const isZoomDragging = zoomCtx?.drag != null;
|
|
24
|
+
const handleMouseDown = useCallback((state, event)=>{
|
|
25
|
+
if (!zoomCtx?.enabled) return;
|
|
26
|
+
const index = toTooltipIndex(state.activeTooltipIndex);
|
|
27
|
+
if (null === index) return;
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
zoomCtx.startDrag(index, event.clientX, event.clientY);
|
|
30
|
+
}, [
|
|
31
|
+
zoomCtx
|
|
32
|
+
]);
|
|
33
|
+
const handleMouseMove = useCallback((state, event)=>{
|
|
34
|
+
if (!zoomCtx?.drag) return;
|
|
35
|
+
const next = toTooltipIndex(state.activeTooltipIndex);
|
|
36
|
+
if (null === next) return;
|
|
37
|
+
zoomCtx.updateDrag(next, event.clientX, event.clientY);
|
|
38
|
+
}, [
|
|
39
|
+
zoomCtx
|
|
40
|
+
]);
|
|
41
|
+
return /*#__PURE__*/ jsx("div", {
|
|
42
|
+
...props,
|
|
43
|
+
ref: ref,
|
|
44
|
+
"data-slot": "line-chart-body",
|
|
45
|
+
"data-testid": testId,
|
|
46
|
+
"aria-hidden": "true",
|
|
47
|
+
"data-zoom-active": isZoomDragging ? 'true' : void 0,
|
|
48
|
+
className: cn(lineChartBodyClasses, isZoomEnabled && lineChartBodyZoomEnabledClasses, className),
|
|
49
|
+
children: /*#__PURE__*/ jsx(ResponsiveContainer, {
|
|
50
|
+
width: "100%",
|
|
51
|
+
height: height,
|
|
52
|
+
children: /*#__PURE__*/ jsx(LineChart, {
|
|
53
|
+
data: dataCtx?.data ?? [],
|
|
54
|
+
margin: LINE_DEFAULT_BODY_MARGIN,
|
|
55
|
+
accessibilityLayer: false,
|
|
56
|
+
syncId: dataCtx?.syncId,
|
|
57
|
+
syncMethod: dataCtx?.syncMethod,
|
|
58
|
+
onMouseDown: handleMouseDown,
|
|
59
|
+
onMouseMove: handleMouseMove,
|
|
60
|
+
children: children
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
LineChartBody.displayName = 'LineChartBody';
|
|
66
|
+
export { LineChartBody };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { SyncMethod } from 'recharts/types/synchronisation/types';
|
|
3
|
+
import type { ChartColor } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* A single point on the X axis. Extra keys are series values.
|
|
6
|
+
*
|
|
7
|
+
* The loose default form is `Record<string, …>` — pass a concrete interface
|
|
8
|
+
* (e.g. `interface MyDatum { timestamp: number; requests: number }`) to gain
|
|
9
|
+
* compile-time safety on `xKey` and `series[].key`. See `LineChartProps<T>`.
|
|
10
|
+
*/
|
|
11
|
+
export type LineChartDatum = Record<string, number | string | null | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* Series schema. `K` is the literal-string union of valid keys; defaults to
|
|
14
|
+
* `string` so untyped callers keep the loose form. When `LineChartProps<T>`
|
|
15
|
+
* is parameterised with a concrete `T`, `K` collapses to `Extract<keyof T,
|
|
16
|
+
* string>` and a typo in `key` becomes a compile error rather than a
|
|
17
|
+
* dev-mode `console.warn`.
|
|
18
|
+
*/
|
|
19
|
+
export interface LineChartSeries<K extends string = string> {
|
|
20
|
+
/**
|
|
21
|
+
* Stable identity. Used as the lookup key on each `LineChartDatum`, the join key
|
|
22
|
+
* for legend/line/tooltip sync, and the React reconciliation key. Must be unique
|
|
23
|
+
* within `series`.
|
|
24
|
+
*/
|
|
25
|
+
key: K;
|
|
26
|
+
/** Visible label rendered by the legend and the tooltip rows. */
|
|
27
|
+
label: string;
|
|
28
|
+
/**
|
|
29
|
+
* Series colour. Either a built-in palette token (`'brand'`, `'red'`, …) or
|
|
30
|
+
* any CSS colour string (`'var(--color-violet-500)'`, `'#8b5cf6'`,
|
|
31
|
+
* `'oklch(…)'`). Drives both the line stroke and the legend/tooltip dot — a
|
|
32
|
+
* single value keeps the two surfaces in sync. Defaults to `'slate'`.
|
|
33
|
+
*/
|
|
34
|
+
color?: ChartColor | (string & {});
|
|
35
|
+
/** Visual line style. Defaults to `'solid'`. */
|
|
36
|
+
variant?: 'solid' | 'dashed';
|
|
37
|
+
}
|
|
38
|
+
export interface LineChartZoomRange {
|
|
39
|
+
/** Inclusive index into `data`. */
|
|
40
|
+
fromIndex: number;
|
|
41
|
+
/** Inclusive index into `data`. */
|
|
42
|
+
toIndex: number;
|
|
43
|
+
/** The `xKey` value at `fromIndex`. */
|
|
44
|
+
from: number | string;
|
|
45
|
+
/** The `xKey` value at `toIndex`. */
|
|
46
|
+
to: number | string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Legend's internal row vs column layout. Pair with the wrapping layout the
|
|
50
|
+
* consumer chooses: `'horizontal'` for top/bottom JSX-order placement,
|
|
51
|
+
* `'vertical'` when wrapping body+legend in an `<HStack>` for left/right.
|
|
52
|
+
*/
|
|
53
|
+
export type LineChartLegendOrientation = 'horizontal' | 'vertical';
|
|
54
|
+
/** Static chart shape — recomputes only when data/series/filter changes. */
|
|
55
|
+
export interface LineChartDataContextValue {
|
|
56
|
+
data: LineChartDatum[];
|
|
57
|
+
series: LineChartSeries[];
|
|
58
|
+
seriesByKey: Map<string, LineChartSeries>;
|
|
59
|
+
xKey: string;
|
|
60
|
+
hiddenSet: ReadonlySet<string>;
|
|
61
|
+
setActiveKey: (key: string | null) => void;
|
|
62
|
+
emitZoom: (range: LineChartZoomRange | null) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Pair of charts that share a `syncId` get their tooltip cursor + brush
|
|
65
|
+
* synchronised by recharts' own redux middleware. `LineChartBody` forwards
|
|
66
|
+
* both values to `<RechartsLineChart>`; the chart itself does not read them.
|
|
67
|
+
*/
|
|
68
|
+
syncId: string | number | undefined;
|
|
69
|
+
syncMethod: SyncMethod | undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Active *series* — set by legend hover/focus. Drives line dimming and the
|
|
73
|
+
* legend item's `aria-current`/`data-active`. Cross-chart sync of the *cursor
|
|
74
|
+
* X* (tooltip + brush) is handled by recharts' built-in `syncId` mechanism on
|
|
75
|
+
* `<LineChart>` — only the series highlight needs custom controlled state, so
|
|
76
|
+
* the index has no equivalent context.
|
|
77
|
+
*/
|
|
78
|
+
export interface LineChartActiveContextValue {
|
|
79
|
+
activeKey: string | null;
|
|
80
|
+
}
|
|
81
|
+
/** Set of `series.key` values that should be hidden from the plot and tooltip. */
|
|
82
|
+
export interface LineChartSelectionContextValue {
|
|
83
|
+
hiddenSet: ReadonlySet<string>;
|
|
84
|
+
}
|
|
85
|
+
/** In-progress zoom selection. Indices are inclusive into `data`. */
|
|
86
|
+
export interface LineChartZoomDragState {
|
|
87
|
+
startIndex: number;
|
|
88
|
+
endIndex: number;
|
|
89
|
+
/** Viewport coords of the latest pointer position — drives the floating popover. */
|
|
90
|
+
clientX: number;
|
|
91
|
+
clientY: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Released-but-unconfirmed zoom selection. The drag has ended, the gray
|
|
95
|
+
* selection rectangle stays, and the popover surfaces a "Zoom in" button for
|
|
96
|
+
* the user to confirm or dismiss. Position is locked at the cursor release.
|
|
97
|
+
*/
|
|
98
|
+
export interface LineChartZoomPendingState {
|
|
99
|
+
range: LineChartZoomRange;
|
|
100
|
+
clientX: number;
|
|
101
|
+
clientY: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Zoom-drag state lives in its own context so the body re-renders on every
|
|
105
|
+
* mousemove frame without dragging the whole tree (static contexts like
|
|
106
|
+
* `LineChartDataContext` stay quiet). `enabled` flips when `LineChartZoomBrush`
|
|
107
|
+
* is mounted — the body uses it to decide whether to capture mousedown.
|
|
108
|
+
*
|
|
109
|
+
* The flow is two-phase: `drag` while the mouse button is held, then `pending`
|
|
110
|
+
* once it releases. `pending` is what the Figma confirm popover renders against
|
|
111
|
+
* — committing fires `onZoomChange`, dismissing just clears the state.
|
|
112
|
+
*/
|
|
113
|
+
export interface LineChartZoomContextValue {
|
|
114
|
+
enabled: boolean;
|
|
115
|
+
drag: LineChartZoomDragState | null;
|
|
116
|
+
pending: LineChartZoomPendingState | null;
|
|
117
|
+
/**
|
|
118
|
+
* Ref to the owning chart root. Scopes the pending-state Enter/Escape
|
|
119
|
+
* handler so it only reacts to keystrokes from within *this* chart, even
|
|
120
|
+
* when several charts share the page (e.g. cross-chart hover sync).
|
|
121
|
+
*/
|
|
122
|
+
rootRef: RefObject<HTMLDivElement | null>;
|
|
123
|
+
/** Called by `LineChartZoomBrush` on mount to opt the chart into zoom mode. */
|
|
124
|
+
registerEnabled: () => () => void;
|
|
125
|
+
startDrag: (index: number, clientX: number, clientY: number) => void;
|
|
126
|
+
updateDrag: (index: number, clientX: number, clientY: number) => void;
|
|
127
|
+
/** Releases the drag — moves it into `pending` for the user to confirm. */
|
|
128
|
+
endDrag: () => void;
|
|
129
|
+
/** Discards the in-progress drag without emitting. */
|
|
130
|
+
cancelDrag: () => void;
|
|
131
|
+
/** Commits the pending selection as a zoom range and clears it. */
|
|
132
|
+
confirmZoom: () => void;
|
|
133
|
+
/** Dismisses the pending selection without emitting. */
|
|
134
|
+
cancelPending: () => void;
|
|
135
|
+
}
|
|
136
|
+
/** Per-legend-row context — published by `LineChartLegendItem` to its children. */
|
|
137
|
+
export interface LineChartItemContextValue {
|
|
138
|
+
seriesKey: string;
|
|
139
|
+
selected: boolean;
|
|
140
|
+
interactive: boolean;
|
|
141
|
+
active: boolean;
|
|
142
|
+
color: ChartColor | string | undefined;
|
|
143
|
+
label: string | undefined;
|
|
144
|
+
}
|
|
145
|
+
export declare const EMPTY_HIDDEN_SET: ReadonlySet<string>;
|
|
146
|
+
export declare const isHoverSyncTarget: (target: EventTarget | null | undefined) => boolean;
|
|
147
|
+
export declare const LineChartDataContext: import("react").Context<LineChartDataContextValue | null>;
|
|
148
|
+
export declare const LineChartActiveContext: import("react").Context<LineChartActiveContextValue>;
|
|
149
|
+
export declare const LineChartSelectionContext: import("react").Context<LineChartSelectionContextValue>;
|
|
150
|
+
export declare const LineChartZoomContext: import("react").Context<LineChartZoomContextValue | null>;
|
|
151
|
+
export declare const LineChartItemContext: import("react").Context<LineChartItemContextValue | null>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { makeIsHoverSyncTarget } from "../lib/hoverSync.js";
|
|
3
|
+
const EMPTY_HIDDEN_SET = new Set();
|
|
4
|
+
const isHoverSyncTarget = makeIsHoverSyncTarget([
|
|
5
|
+
'line-chart-legend-item'
|
|
6
|
+
]);
|
|
7
|
+
const LineChartDataContext = createContext(null);
|
|
8
|
+
const LineChartActiveContext = createContext({
|
|
9
|
+
activeKey: null
|
|
10
|
+
});
|
|
11
|
+
const LineChartSelectionContext = createContext({
|
|
12
|
+
hiddenSet: EMPTY_HIDDEN_SET
|
|
13
|
+
});
|
|
14
|
+
const LineChartZoomContext = createContext(null);
|
|
15
|
+
const LineChartItemContext = createContext(null);
|
|
16
|
+
export { EMPTY_HIDDEN_SET, LineChartActiveContext, LineChartDataContext, LineChartItemContext, LineChartSelectionContext, LineChartZoomContext, isHoverSyncTarget };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
import { type TestableProps } from '../../../utils/testId';
|
|
3
|
+
export interface LineChartEmptyProps extends HTMLAttributes<HTMLDivElement>, TestableProps {
|
|
4
|
+
ref?: Ref<HTMLDivElement>;
|
|
5
|
+
/** Body height override. Defaults to the standard plot height. */
|
|
6
|
+
height?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Empty-state message. Pass the copy you want to render (`"No data"`, a
|
|
9
|
+
* localised string, or richer JSX). Omit (or pass `null`) to render only
|
|
10
|
+
* the dashed grid frame — the idiom for loading skeletons. There is no
|
|
11
|
+
* default text.
|
|
12
|
+
*/
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare const LineChartEmpty: FC<LineChartEmptyProps>;
|