cortena-ui 1.3.0 → 1.4.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/README.md +74 -3
- package/dist/a2ui/catalogue-doc.d.ts +44 -0
- package/dist/a2ui/catalogue-doc.js +169 -0
- package/dist/a2ui/catalogue-doc.js.map +1 -0
- package/dist/a2ui/catalogue.d.ts +44 -0
- package/dist/a2ui/catalogue.js +942 -0
- package/dist/a2ui/catalogue.js.map +1 -0
- package/dist/a2ui/index.d.ts +7 -0
- package/dist/a2ui/message.d.ts +232 -0
- package/dist/a2ui/message.js +365 -0
- package/dist/a2ui/message.js.map +1 -0
- package/dist/a2ui/renderer.d.ts +46 -0
- package/dist/a2ui/renderer.js +252 -0
- package/dist/a2ui/renderer.js.map +1 -0
- package/dist/a2ui/views.d.ts +80 -0
- package/dist/a2ui/views.js +596 -0
- package/dist/a2ui/views.js.map +1 -0
- package/dist/a2ui.d.ts +8 -0
- package/dist/a2ui.js +7 -0
- package/dist/chart.d.ts +9 -0
- package/dist/chart.js +9 -0
- package/dist/components/accordion.d.ts +22 -0
- package/dist/components/accordion.js +63 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert.d.ts +15 -0
- package/dist/components/alert.js +64 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +18 -0
- package/dist/components/avatar.js +40 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +12 -0
- package/dist/components/badge.js +47 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.d.ts +71 -0
- package/dist/components/breadcrumb.js +123 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button-link.d.ts +28 -0
- package/dist/components/button-link.js +24 -0
- package/dist/components/button-link.js.map +1 -0
- package/dist/components/button.d.ts +24 -0
- package/dist/components/button.js +65 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +28 -0
- package/dist/components/calendar.js +123 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +32 -0
- package/dist/components/card.js +57 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/chart/chart.d.ts +71 -0
- package/dist/components/chart/chart.js +167 -0
- package/dist/components/chart/chart.js.map +1 -0
- package/dist/components/chart/container.d.ts +88 -0
- package/dist/components/chart/container.js +189 -0
- package/dist/components/chart/container.js.map +1 -0
- package/dist/components/chart/data.js +128 -0
- package/dist/components/chart/data.js.map +1 -0
- package/dist/components/chart/index.d.ts +9 -0
- package/dist/components/chart/index.js +10 -0
- package/dist/components/chart/nivo-charts.js +225 -0
- package/dist/components/chart/nivo-charts.js.map +1 -0
- package/dist/components/chart/nivo-lazy.js +50 -0
- package/dist/components/chart/nivo-lazy.js.map +1 -0
- package/dist/components/chart/nivo-theme.d.ts +156 -0
- package/dist/components/chart/nivo-theme.js +161 -0
- package/dist/components/chart/nivo-theme.js.map +1 -0
- package/dist/components/chart/recharts-charts.js +276 -0
- package/dist/components/chart/recharts-charts.js.map +1 -0
- package/dist/components/chart/recharts-lazy.js +58 -0
- package/dist/components/chart/recharts-lazy.js.map +1 -0
- package/dist/components/chart/recharts-parts.d.ts +10 -0
- package/dist/components/chart/recharts-parts.js +25 -0
- package/dist/components/chart/recharts-parts.js.map +1 -0
- package/dist/components/chart/tokens.d.ts +62 -0
- package/dist/components/chart/tokens.js +192 -0
- package/dist/components/chart/tokens.js.map +1 -0
- package/dist/components/chart/types.d.ts +114 -0
- package/dist/components/chart/types.js +27 -0
- package/dist/components/chart/types.js.map +1 -0
- package/dist/components/checkbox.d.ts +20 -0
- package/dist/components/checkbox.js +24 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +17 -0
- package/dist/components/chip.js +47 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/combobox.d.ts +76 -0
- package/dist/components/combobox.js +201 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/command.d.ts +50 -0
- package/dist/components/command.js +117 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +65 -0
- package/dist/components/data-table/data-table.js +588 -0
- package/dist/components/data-table/data-table.js.map +1 -0
- package/dist/components/data-table/export.d.ts +23 -0
- package/dist/components/data-table/export.js +95 -0
- package/dist/components/data-table/export.js.map +1 -0
- package/dist/components/data-table/features.d.ts +66 -0
- package/dist/components/data-table/features.js +103 -0
- package/dist/components/data-table/features.js.map +1 -0
- package/dist/components/data-table/index.d.ts +8 -0
- package/dist/components/data-table/parts.d.ts +79 -0
- package/dist/components/data-table/parts.js +400 -0
- package/dist/components/data-table/parts.js.map +1 -0
- package/dist/components/data-table/system-columns.js +84 -0
- package/dist/components/data-table/system-columns.js.map +1 -0
- package/dist/components/data-table/types.d.ts +172 -0
- package/dist/components/data-table/use-data-table.d.ts +19 -0
- package/dist/components/data-table/use-data-table.js +171 -0
- package/dist/components/data-table/use-data-table.js.map +1 -0
- package/dist/components/data-table/use-server-source.js +200 -0
- package/dist/components/data-table/use-server-source.js.map +1 -0
- package/dist/components/date-field.d.ts +54 -0
- package/dist/components/date-field.js +235 -0
- package/dist/components/date-field.js.map +1 -0
- package/dist/components/date-picker.d.ts +42 -0
- package/dist/components/date-picker.js +125 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +36 -0
- package/dist/components/dialog.js +97 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +54 -0
- package/dist/components/dropdown-menu.js +191 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/dropzone.d.ts +48 -0
- package/dist/components/dropzone.js +185 -0
- package/dist/components/dropzone.js.map +1 -0
- package/dist/components/empty-state.d.ts +22 -0
- package/dist/components/empty-state.js +46 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/error-banner.d.ts +17 -0
- package/dist/components/error-banner.js +52 -0
- package/dist/components/error-banner.js.map +1 -0
- package/dist/components/field.d.ts +64 -0
- package/dist/components/field.js +133 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/form.d.ts +37 -0
- package/dist/components/form.js +103 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.js +26 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +11 -0
- package/dist/components/kbd.js +20 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +14 -0
- package/dist/components/label.js +28 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/loading-skeleton.d.ts +22 -0
- package/dist/components/loading-skeleton.js +123 -0
- package/dist/components/loading-skeleton.js.map +1 -0
- package/dist/components/markdown.d.ts +44 -0
- package/dist/components/markdown.js +231 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/page-header.d.ts +26 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/popover.d.ts +29 -0
- package/dist/components/popover.js +74 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +22 -0
- package/dist/components/progress.js +49 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +20 -0
- package/dist/components/radio-group.js +29 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts +25 -0
- package/dist/components/scroll-area.js +39 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/section-card.d.ts +29 -0
- package/dist/components/section-card.js +55 -0
- package/dist/components/section-card.js.map +1 -0
- package/dist/components/segmented.d.ts +27 -0
- package/dist/components/segmented.js +32 -0
- package/dist/components/segmented.js.map +1 -0
- package/dist/components/select.d.ts +62 -0
- package/dist/components/select.js +97 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +19 -0
- package/dist/components/separator.js +23 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +35 -0
- package/dist/components/sheet.js +131 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +12 -0
- package/dist/components/skeleton.js +22 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sortable-list.d.ts +67 -0
- package/dist/components/sortable-list.js +206 -0
- package/dist/components/sortable-list.js.map +1 -0
- package/dist/components/spinner.d.ts +29 -0
- package/dist/components/spinner.js +34 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/status-dot.d.ts +26 -0
- package/dist/components/status-dot.js +47 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/switch.d.ts +21 -0
- package/dist/components/switch.js +21 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +24 -0
- package/dist/components/tabs.js +60 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +19 -0
- package/dist/components/textarea.js +47 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toast.d.ts +47 -0
- package/dist/components/toast.js +106 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toolbar.d.ts +19 -0
- package/dist/components/toolbar.js +45 -0
- package/dist/components/toolbar.js.map +1 -0
- package/dist/components/tooltip.d.ts +28 -0
- package/dist/components/tooltip.js +59 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/core.d.ts +44 -0
- package/dist/core.js +44 -0
- package/dist/data-table.d.ts +9 -0
- package/dist/data-table.js +7 -0
- package/dist/form.d.ts +7 -0
- package/dist/form.js +7 -0
- package/dist/hooks/use-cortena-theme.d.ts +98 -0
- package/dist/hooks/use-cortena-theme.js +145 -0
- package/dist/hooks/use-cortena-theme.js.map +1 -0
- package/dist/index.d.ts +75 -2678
- package/dist/index.js +66 -8598
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.js +15 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/render.d.ts +23 -0
- package/dist/lib/render.js +20 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/lib/shakeable.js +39 -0
- package/dist/lib/shakeable.js.map +1 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +3 -0
- package/dist/sortable-list.d.ts +3 -0
- package/dist/sortable-list.js +3 -0
- package/package.json +37 -14
- package/src/a2ui/catalogue.ts +140 -103
- package/src/a2ui/views.tsx +5 -2
- package/src/components/alert.tsx +1 -1
- package/src/components/badge.tsx +1 -1
- package/src/components/button.tsx +1 -1
- package/src/components/chart/chart.tsx +72 -25
- package/src/components/chart/container.tsx +24 -39
- package/src/components/chart/index.tsx +4 -2
- package/src/components/chart/nivo-charts.tsx +41 -13
- package/src/components/chart/nivo-lazy.tsx +52 -0
- package/src/components/chart/recharts-charts.tsx +98 -3
- package/src/components/chart/recharts-lazy.tsx +72 -0
- package/src/components/chart/recharts-parts.tsx +34 -0
- package/src/components/chart/tokens.ts +1 -1
- package/src/components/chart/types.ts +14 -0
- package/src/components/chip.tsx +1 -1
- package/src/components/combobox.tsx +2 -2
- package/src/components/data-table/export.ts +18 -2
- package/src/components/data-table/features.ts +77 -45
- package/src/components/data-table/parts.tsx +15 -0
- package/src/components/data-table/use-data-table.ts +7 -2
- package/src/components/data-table/use-server-source.ts +17 -4
- package/src/components/date-field.tsx +1 -1
- package/src/components/error-banner.tsx +1 -1
- package/src/components/field.tsx +2 -2
- package/src/components/form.tsx +2 -2
- package/src/components/input.tsx +1 -1
- package/src/components/label.tsx +1 -1
- package/src/components/markdown.tsx +5 -4
- package/src/components/sheet.tsx +2 -2
- package/src/components/sortable-list.tsx +2 -2
- package/src/components/textarea.tsx +1 -1
- package/src/components/toast.tsx +1 -1
- package/src/entries/a2ui.ts +11 -0
- package/src/entries/chart.ts +10 -0
- package/src/entries/core.ts +91 -0
- package/src/entries/data-table.ts +9 -0
- package/src/entries/form.ts +24 -0
- package/src/entries/markdown.ts +9 -0
- package/src/entries/sortable-list.ts +13 -0
- package/src/index.ts +32 -98
- package/src/lib/shakeable.ts +33 -0
- package/dist/index.js.map +0 -1
|
@@ -4,12 +4,15 @@ import * as React from "react";
|
|
|
4
4
|
import { cn } from "@/lib/cn";
|
|
5
5
|
import { ChartContainer, ChartEmpty, ChartError, ChartLoading, type ChartContainerProps } from "./container";
|
|
6
6
|
import { configFor, isEmptyData, isMatrix, isSeriesArray, toRows, toSlices } from "./data";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import type { NivoType } from "./nivo-charts";
|
|
8
|
+
import { NivoChartLazy, resetNivoChunks } from "./nivo-lazy";
|
|
9
|
+
import type { RechartsType } from "./recharts-charts";
|
|
10
|
+
import { RechartsChartLazy, resetRechartsChunks } from "./recharts-lazy";
|
|
9
11
|
import {
|
|
10
12
|
chartEngine,
|
|
11
13
|
type ChartConfig,
|
|
12
14
|
type ChartDataFor,
|
|
15
|
+
type ChartLabels,
|
|
13
16
|
type ChartMatrix,
|
|
14
17
|
type ChartRenderer,
|
|
15
18
|
type ChartRow,
|
|
@@ -44,6 +47,12 @@ export interface ChartProps<T extends ChartType = ChartType>
|
|
|
44
47
|
grid?: boolean;
|
|
45
48
|
/** Show a tooltip on hover. Default true. */
|
|
46
49
|
tooltip?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Per-arc labels for `pie` and `donut`; ignored by every other type. Default
|
|
52
|
+
* `percent`, which is what the reader almost always wants and what cortenaweb's
|
|
53
|
+
* donuts showed before this package existed. See {@link ChartLabels}.
|
|
54
|
+
*/
|
|
55
|
+
labels?: ChartLabels;
|
|
47
56
|
/** Format values in axes, tooltips and labels. */
|
|
48
57
|
valueFormatter?: (value: number) => string;
|
|
49
58
|
/** Calendar range (ISO dates); defaults to the data's extent. */
|
|
@@ -82,25 +91,59 @@ function seriesKeys(type: ChartType, data: unknown, indexBy?: string, keys?: str
|
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
interface ChunkBoundaryProps {
|
|
95
|
+
children: React.ReactNode;
|
|
96
|
+
/** Changing it clears a previous failure and renders the children again. */
|
|
97
|
+
resetKey: string;
|
|
98
|
+
fallback: (retry: () => void) => React.ReactNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Catches a Nivo chunk that failed to load. Recovering takes two things,
|
|
103
|
+
* because `React.lazy` memoises a rejection for the life of the page: fresh
|
|
104
|
+
* lazy components ({@link resetNivoChunks}) and a boundary that leaves its
|
|
105
|
+
* failed state. `resetKey` does the second on its own when the chart changes,
|
|
106
|
+
* and the fallback gets a `retry` that does both.
|
|
107
|
+
*/
|
|
108
|
+
class ChunkBoundary extends React.Component<ChunkBoundaryProps, { failed: boolean; key: string }> {
|
|
109
|
+
state = { failed: false, key: this.props.resetKey };
|
|
90
110
|
static getDerivedStateFromError() {
|
|
91
111
|
return { failed: true };
|
|
92
112
|
}
|
|
113
|
+
static getDerivedStateFromProps(props: ChunkBoundaryProps, state: { failed: boolean; key: string }) {
|
|
114
|
+
return props.resetKey === state.key ? null : { failed: false, key: props.resetKey };
|
|
115
|
+
}
|
|
116
|
+
retry = () => {
|
|
117
|
+
resetNivoChunks();
|
|
118
|
+
resetRechartsChunks();
|
|
119
|
+
this.setState({ failed: false });
|
|
120
|
+
};
|
|
93
121
|
render() {
|
|
94
|
-
return this.state.failed ? this.props.fallback : this.props.children;
|
|
122
|
+
return this.state.failed ? this.props.fallback(this.retry) : this.props.children;
|
|
95
123
|
}
|
|
96
124
|
}
|
|
97
125
|
|
|
126
|
+
/** Shown when an engine chunk could not be fetched, with a way back. */
|
|
127
|
+
function chunkFallback(retry: () => void): React.ReactNode {
|
|
128
|
+
return (
|
|
129
|
+
<ChartError>
|
|
130
|
+
Chart could not be loaded.{" "}
|
|
131
|
+
<button type="button" data-slot="chart-retry" onClick={retry} className="underline underline-offset-2">
|
|
132
|
+
Retry
|
|
133
|
+
</button>
|
|
134
|
+
</ChartError>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
98
138
|
/**
|
|
99
139
|
* Chart — one component for every chart type the fleet draws.
|
|
100
140
|
*
|
|
101
|
-
* `type` picks the engine: Recharts
|
|
102
|
-
*
|
|
103
|
-
*
|
|
141
|
+
* `type` picks the engine: Recharts for bar, line, area, pie, donut, scatter,
|
|
142
|
+
* radar and funnel; Nivo for heatmap, calendar, treemap and sunburst, with
|
|
143
|
+
* `renderer="canvas"` for large series. Both are loaded on the first render of
|
|
144
|
+
* a chart that needs them — Recharts as one chunk, each `@nivo/*` package as
|
|
145
|
+
* its own — so importing `Chart` costs nothing until a chart is drawn, and a
|
|
146
|
+
* screen that draws a bar chart never fetches Nivo.
|
|
104
147
|
* Colours, fonts, axes, grid, tooltip, legend and the empty, loading and
|
|
105
148
|
* error states all come from cortena-design tokens; a consumer passes data
|
|
106
149
|
* and, optionally, a `config` of labels.
|
|
@@ -125,6 +168,7 @@ function Chart<T extends ChartType>({
|
|
|
125
168
|
legend,
|
|
126
169
|
grid = true,
|
|
127
170
|
tooltip = true,
|
|
171
|
+
labels = "percent",
|
|
128
172
|
valueFormatter,
|
|
129
173
|
from,
|
|
130
174
|
to,
|
|
@@ -151,9 +195,9 @@ function Chart<T extends ChartType>({
|
|
|
151
195
|
else if (empty) body = <ChartEmpty>{emptyMessage}</ChartEmpty>;
|
|
152
196
|
else if (engine === "nivo") {
|
|
153
197
|
body = (
|
|
154
|
-
<ChunkBoundary fallback={
|
|
198
|
+
<ChunkBoundary resetKey={`${type}|${renderer}`} fallback={chunkFallback}>
|
|
155
199
|
<React.Suspense fallback={<ChartLoading />}>
|
|
156
|
-
<
|
|
200
|
+
<NivoChartLazy
|
|
157
201
|
type={type as NivoType}
|
|
158
202
|
data={data}
|
|
159
203
|
config={fullConfig}
|
|
@@ -170,18 +214,21 @@ function Chart<T extends ChartType>({
|
|
|
170
214
|
} else {
|
|
171
215
|
responsive = true;
|
|
172
216
|
body = (
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
217
|
+
<ChunkBoundary resetKey={type} fallback={chunkFallback}>
|
|
218
|
+
<RechartsChartLazy
|
|
219
|
+
type={type as RechartsType}
|
|
220
|
+
data={data}
|
|
221
|
+
config={fullConfig}
|
|
222
|
+
indexBy={indexBy}
|
|
223
|
+
keys={keys}
|
|
224
|
+
stacked={stacked}
|
|
225
|
+
legend={showLegend}
|
|
226
|
+
grid={grid}
|
|
227
|
+
tooltip={tooltip}
|
|
228
|
+
labels={labels}
|
|
229
|
+
valueFormatter={valueFormatter}
|
|
230
|
+
/>
|
|
231
|
+
</ChunkBoundary>
|
|
185
232
|
);
|
|
186
233
|
}
|
|
187
234
|
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type LegendProps,
|
|
10
|
-
type TooltipContentProps,
|
|
11
|
-
type TooltipProps,
|
|
12
|
-
} from "recharts";
|
|
4
|
+
// Types only. The two Recharts *values* this file used to reach for — `Tooltip`
|
|
5
|
+
// and `Legend` — are in `recharts-parts.tsx`, so that nothing on the eager path
|
|
6
|
+
// imports the engine; see the note there.
|
|
7
|
+
import type { LegendPayload, TooltipContentProps } from "recharts";
|
|
8
|
+
import { RechartsResponsive } from "./recharts-lazy";
|
|
13
9
|
import { cn } from "@/lib/cn";
|
|
14
10
|
import { Skeleton } from "@/components/skeleton";
|
|
15
11
|
import { vizVar } from "./tokens";
|
|
@@ -23,7 +19,7 @@ interface ChartContextValue {
|
|
|
23
19
|
element: HTMLDivElement | null;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
const ChartContext = React.createContext<ChartContextValue | null>(null);
|
|
22
|
+
const ChartContext = /* @__PURE__ */ React.createContext<ChartContextValue | null>(null);
|
|
27
23
|
|
|
28
24
|
/** The nearest ChartContainer's config and element; null outside one. */
|
|
29
25
|
export function useChart(): ChartContextValue | null {
|
|
@@ -143,14 +139,23 @@ function ChartContainer({
|
|
|
143
139
|
{...props}
|
|
144
140
|
>
|
|
145
141
|
{responsive ? (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
// Recharts' ResponsiveContainer arrives with the engine chunk, so
|
|
143
|
+
// this suspends on the first chart drawn on the page — and so does
|
|
144
|
+
// whatever it wraps, which is why the skeleton lives here rather than
|
|
145
|
+
// in `Chart`: this is the innermost boundary, so it is the one that
|
|
146
|
+
// catches. A `ChartContainer` used directly, to compose a one-off
|
|
147
|
+
// Recharts chart, gets the same treatment and needs no boundary of
|
|
148
|
+
// its own.
|
|
149
|
+
<React.Suspense fallback={<ChartLoading />}>
|
|
150
|
+
<RechartsResponsive
|
|
151
|
+
width="100%"
|
|
152
|
+
height="100%"
|
|
153
|
+
minWidth={testMode ? TEST_WIDTH : undefined}
|
|
154
|
+
minHeight={testMode ? TEST_HEIGHT : undefined}
|
|
155
|
+
>
|
|
156
|
+
{children as React.ReactElement}
|
|
157
|
+
</RechartsResponsive>
|
|
158
|
+
</React.Suspense>
|
|
154
159
|
) : (
|
|
155
160
|
children
|
|
156
161
|
)}
|
|
@@ -286,11 +291,6 @@ function ChartTooltipContent({
|
|
|
286
291
|
);
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
/** Recharts' Tooltip with the token-styled content and a subtle cursor. */
|
|
290
|
-
function ChartTooltip(props: TooltipProps) {
|
|
291
|
-
return <RechartsTooltip content={<ChartTooltipContent />} {...props} />;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
294
|
/* ── legend ──────────────────────────────────────────────────────────────── */
|
|
295
295
|
|
|
296
296
|
export interface ChartLegendContentProps {
|
|
@@ -337,19 +337,4 @@ function ChartLegendContent({ payload, verticalAlign = "bottom", className, hide
|
|
|
337
337
|
);
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
|
|
341
|
-
function ChartLegend(props: LegendProps) {
|
|
342
|
-
return <RechartsLegend content={<ChartLegendContent />} {...props} />;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export {
|
|
346
|
-
ChartContainer,
|
|
347
|
-
ChartContext,
|
|
348
|
-
ChartEmpty,
|
|
349
|
-
ChartError,
|
|
350
|
-
ChartLoading,
|
|
351
|
-
ChartTooltip,
|
|
352
|
-
ChartTooltipContent,
|
|
353
|
-
ChartLegend,
|
|
354
|
-
ChartLegendContent,
|
|
355
|
-
};
|
|
340
|
+
export { ChartContainer, ChartContext, ChartEmpty, ChartError, ChartLoading, ChartTooltipContent, ChartLegendContent };
|
|
@@ -14,14 +14,15 @@ export {
|
|
|
14
14
|
ChartContainer,
|
|
15
15
|
ChartEmpty,
|
|
16
16
|
ChartError,
|
|
17
|
-
ChartLegend,
|
|
18
17
|
ChartLegendContent,
|
|
19
18
|
ChartLoading,
|
|
20
|
-
ChartTooltip,
|
|
21
19
|
ChartTooltipContent,
|
|
22
20
|
seriesColor,
|
|
23
21
|
useChart,
|
|
24
22
|
} from "./container";
|
|
23
|
+
// Recharts elements, so importing either is what makes an app pay for the
|
|
24
|
+
// engine; see the note in recharts-parts.tsx.
|
|
25
|
+
export { ChartLegend, ChartTooltip } from "./recharts-parts";
|
|
25
26
|
export type { ChartContainerProps, ChartLegendContentProps, ChartTooltipContentProps } from "./container";
|
|
26
27
|
export { nivoTheme, useNivoTheme } from "./nivo-theme";
|
|
27
28
|
export type { NivoTheme } from "./nivo-theme";
|
|
@@ -41,6 +42,7 @@ export type {
|
|
|
41
42
|
ChartDataFor,
|
|
42
43
|
ChartDay,
|
|
43
44
|
ChartEngine,
|
|
45
|
+
ChartLabels,
|
|
44
46
|
ChartMatrix,
|
|
45
47
|
ChartPoint,
|
|
46
48
|
ChartRenderer,
|
|
@@ -18,16 +18,42 @@ export type NivoType = Extract<ChartType, "heatmap" | "calendar" | "treemap" | "
|
|
|
18
18
|
*/
|
|
19
19
|
type AnyComponent = React.ComponentType<Record<string, unknown>>;
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const loaders = {
|
|
22
|
+
"heatmap-svg": async () => (await import("@nivo/heatmap")).ResponsiveHeatMap as AnyComponent,
|
|
23
|
+
"heatmap-canvas": async () => (await import("@nivo/heatmap")).ResponsiveHeatMapCanvas as AnyComponent,
|
|
24
|
+
"calendar-svg": async () => (await import("@nivo/calendar")).ResponsiveCalendar as unknown as AnyComponent,
|
|
25
|
+
"calendar-canvas": async () =>
|
|
26
|
+
(await import("@nivo/calendar")).ResponsiveCalendarCanvas as unknown as AnyComponent,
|
|
27
|
+
"treemap-svg": async () => (await import("@nivo/treemap")).ResponsiveTreeMap as AnyComponent,
|
|
28
|
+
"treemap-canvas": async () => (await import("@nivo/treemap")).ResponsiveTreeMapCanvas as AnyComponent,
|
|
29
|
+
"sunburst-svg": async () => (await import("@nivo/sunburst")).ResponsiveSunburst as AnyComponent,
|
|
30
|
+
} satisfies Record<string, () => Promise<AnyComponent>>;
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
type ChunkName = keyof typeof loaders;
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* `React.lazy` memoises the outcome of its loader, a rejection included: a
|
|
36
|
+
* chunk that fails once — an offline moment, a deploy that moved the file —
|
|
37
|
+
* throws the same error for the life of the page, and no re-render retries it.
|
|
38
|
+
* The lazy components are therefore cached here rather than frozen at module
|
|
39
|
+
* scope, and `resetNivoChunks` drops the cache so the next render builds fresh
|
|
40
|
+
* ones and asks for the chunk again. `Chart`'s ChunkBoundary calls it.
|
|
41
|
+
*/
|
|
42
|
+
let chunks = new Map<ChunkName, AnyComponent>();
|
|
43
|
+
|
|
44
|
+
function chunk(name: ChunkName): AnyComponent {
|
|
45
|
+
let component = chunks.get(name);
|
|
46
|
+
if (!component) {
|
|
47
|
+
component = React.lazy(async () => ({ default: await loaders[name]() })) as unknown as AnyComponent;
|
|
48
|
+
chunks.set(name, component);
|
|
49
|
+
}
|
|
50
|
+
return component;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Forget every loaded-or-failed Nivo chunk, so the next render loads it again. */
|
|
54
|
+
export function resetNivoChunks(): void {
|
|
55
|
+
chunks = new Map();
|
|
56
|
+
}
|
|
31
57
|
|
|
32
58
|
export interface NivoRenderProps {
|
|
33
59
|
type: NivoType;
|
|
@@ -93,7 +119,7 @@ export function NivoChart({ type, data, config, renderer, scale, legend, valueFo
|
|
|
93
119
|
|
|
94
120
|
switch (type) {
|
|
95
121
|
case "heatmap": {
|
|
96
|
-
const Component = canvas ?
|
|
122
|
+
const Component = chunk(canvas ? "heatmap-canvas" : "heatmap-svg");
|
|
97
123
|
// Either shape lands on Nivo's `[{ id, data: [{ x, y }] }]`.
|
|
98
124
|
const rows = isMatrix(data) ? matrixToSeries(data as ChartMatrix) : (data as ChartSeries[]);
|
|
99
125
|
// Labels only when cells are big enough to carry them.
|
|
@@ -139,7 +165,7 @@ export function NivoChart({ type, data, config, renderer, scale, legend, valueFo
|
|
|
139
165
|
}
|
|
140
166
|
|
|
141
167
|
case "calendar": {
|
|
142
|
-
const Component = canvas ?
|
|
168
|
+
const Component = chunk(canvas ? "calendar-canvas" : "calendar-svg");
|
|
143
169
|
const days = data as ChartDay[];
|
|
144
170
|
const range = calendarRange(days, from, to);
|
|
145
171
|
return (
|
|
@@ -177,7 +203,7 @@ export function NivoChart({ type, data, config, renderer, scale, legend, valueFo
|
|
|
177
203
|
}
|
|
178
204
|
|
|
179
205
|
case "treemap": {
|
|
180
|
-
const Component = canvas ?
|
|
206
|
+
const Component = chunk(canvas ? "treemap-canvas" : "treemap-svg");
|
|
181
207
|
const tree = data as ChartTree;
|
|
182
208
|
return (
|
|
183
209
|
<Component
|
|
@@ -202,9 +228,11 @@ export function NivoChart({ type, data, config, renderer, scale, legend, valueFo
|
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
case "sunburst": {
|
|
231
|
+
// Sunburst has no canvas renderer; SVG either way.
|
|
232
|
+
const Component = chunk("sunburst-svg");
|
|
205
233
|
const tree = data as ChartTree;
|
|
206
234
|
return (
|
|
207
|
-
<
|
|
235
|
+
<Component
|
|
208
236
|
data={tree}
|
|
209
237
|
id={treeIdentity(tree)}
|
|
210
238
|
value="value"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nivo, loaded on first render of a Nivo chart type.
|
|
5
|
+
*
|
|
6
|
+
* `nivo-charts.tsx` already loads each `@nivo/*` engine dynamically, but it was
|
|
7
|
+
* itself a static import, and it pulls in `nivo-theme.ts` — which reads
|
|
8
|
+
* `@nivo/core`, `@nivo/legends`, `@nivo/theming` and friends for real, about
|
|
9
|
+
* 63 kB of them. So a bundle that could reach `Chart` shipped Nivo's theming
|
|
10
|
+
* layer whether or not it ever drew a Nivo chart. One more dynamic boundary
|
|
11
|
+
* here puts the theme on the same footing as the engines it themes.
|
|
12
|
+
*
|
|
13
|
+
* Two levels of laziness, deliberately: this chunk, then the per-package
|
|
14
|
+
* loaders inside it, so a screen that draws a heatmap fetches the heatmap
|
|
15
|
+
* package and not the calendar, treemap and sunburst ones as well.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import type { NivoRenderProps } from "./nivo-charts";
|
|
20
|
+
|
|
21
|
+
type AnyComponent = React.ComponentType<Record<string, unknown>>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The loaded module, kept so {@link resetNivoChunks} can reach the inner
|
|
25
|
+
* per-package cache. Before the chunk loads there is nothing inside it to
|
|
26
|
+
* reset, and resetting the outer lazy component is enough to make the next
|
|
27
|
+
* render ask for it again.
|
|
28
|
+
*/
|
|
29
|
+
let loaded: typeof import("./nivo-charts") | null = null;
|
|
30
|
+
let outer: AnyComponent | null = null;
|
|
31
|
+
|
|
32
|
+
function chunk(): AnyComponent {
|
|
33
|
+
outer ??= React.lazy(async () => {
|
|
34
|
+
const module = await import("./nivo-charts");
|
|
35
|
+
loaded = module;
|
|
36
|
+
return { default: module.NivoChart as unknown as React.ComponentType };
|
|
37
|
+
}) as unknown as AnyComponent;
|
|
38
|
+
return outer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Forget every loaded-or-failed Nivo chunk, outer and per-package, so the next render loads it again. */
|
|
42
|
+
export function resetNivoChunks(): void {
|
|
43
|
+
outer = null;
|
|
44
|
+
loaded?.resetNivoChunks();
|
|
45
|
+
loaded = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `NivoChart` behind its chunk. Suspends on first render of a Nivo type. */
|
|
49
|
+
export function NivoChartLazy(props: NivoRenderProps): React.ReactElement {
|
|
50
|
+
const Component = chunk();
|
|
51
|
+
return <Component {...(props as unknown as Record<string, unknown>)} />;
|
|
52
|
+
}
|
|
@@ -24,9 +24,11 @@ import {
|
|
|
24
24
|
XAxis,
|
|
25
25
|
YAxis,
|
|
26
26
|
} from "recharts";
|
|
27
|
-
import {
|
|
27
|
+
import { seriesColor, useChart } from "./container";
|
|
28
|
+
import { ChartLegend, ChartTooltip } from "./recharts-parts";
|
|
28
29
|
import { toRows, toSlices } from "./data";
|
|
29
|
-
import
|
|
30
|
+
import { contrastLabel, useThemeVersion } from "./tokens";
|
|
31
|
+
import type { ChartConfig, ChartLabels, ChartRow, ChartSeries, ChartSlice, ChartType } from "./types";
|
|
30
32
|
|
|
31
33
|
export type RechartsType = Extract<
|
|
32
34
|
ChartType,
|
|
@@ -43,11 +45,91 @@ export interface RechartsRenderProps {
|
|
|
43
45
|
legend: boolean;
|
|
44
46
|
grid: boolean;
|
|
45
47
|
tooltip: boolean;
|
|
48
|
+
/** Per-arc labels for pie and donut. Ignored by every other type. */
|
|
49
|
+
labels: ChartLabels;
|
|
46
50
|
valueFormatter?: (value: number) => string;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const MARGIN = { top: 8, right: 8, bottom: 0, left: 0 };
|
|
50
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Where an arc label sits, as a fraction of the way from the inner radius to
|
|
57
|
+
* the outer one. Just inside the middle of the band: the arc is widest towards
|
|
58
|
+
* its outer edge, so a label pushed slightly out has more room than a centred
|
|
59
|
+
* one, without ever touching the rim.
|
|
60
|
+
*/
|
|
61
|
+
const ARC_LABEL_RADIUS = 0.58;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Arcs smaller than this share of the whole are left unlabelled.
|
|
65
|
+
*
|
|
66
|
+
* Below roughly a twentieth the arc is narrower than the text it would carry,
|
|
67
|
+
* so the label either overflows into its neighbours or has to be dragged
|
|
68
|
+
* outside on a leader line. Leader lines on a small chart are worse than no
|
|
69
|
+
* label — they collide with each other and with the legend, and the reader
|
|
70
|
+
* still has to trace them. The tooltip covers the slices that fall out.
|
|
71
|
+
*/
|
|
72
|
+
const ARC_LABEL_MIN_FRACTION = 0.05;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What Recharts hands a `<Pie label={…}>` renderer. Every geometry field is
|
|
76
|
+
* optional on its side — the same callback types the label of a chart that has
|
|
77
|
+
* not measured itself yet — so each is checked before it is used rather than
|
|
78
|
+
* asserted away.
|
|
79
|
+
*/
|
|
80
|
+
interface ArcLabelProps {
|
|
81
|
+
cx?: number;
|
|
82
|
+
cy?: number;
|
|
83
|
+
midAngle?: number;
|
|
84
|
+
innerRadius?: number;
|
|
85
|
+
outerRadius?: number;
|
|
86
|
+
percent?: number;
|
|
87
|
+
value?: number;
|
|
88
|
+
index?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A per-arc label, drawn inside the arc it belongs to.
|
|
93
|
+
*
|
|
94
|
+
* Inside rather than outside so there is nothing to collide: each label is
|
|
95
|
+
* bounded by its own sector, and a sector too thin to hold one simply does not
|
|
96
|
+
* get one. The text colour comes from `contrastLabel`, the same measured
|
|
97
|
+
* ink-or-white choice the Nivo charts make, so a label stays legible on a pale
|
|
98
|
+
* series colour and on a dark one, in either theme.
|
|
99
|
+
*/
|
|
100
|
+
function arcLabel(
|
|
101
|
+
labels: Exclude<ChartLabels, "none">,
|
|
102
|
+
colourOn: (fill: string) => string,
|
|
103
|
+
fillOf: (index: number) => string,
|
|
104
|
+
valueFormatter?: (value: number) => string,
|
|
105
|
+
) {
|
|
106
|
+
return function ArcLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, index }: ArcLabelProps) {
|
|
107
|
+
if (cx == null || cy == null || midAngle == null || innerRadius == null || outerRadius == null) return null;
|
|
108
|
+
if (!(percent != null && percent >= ARC_LABEL_MIN_FRACTION)) return null;
|
|
109
|
+
const radius = innerRadius + (outerRadius - innerRadius) * ARC_LABEL_RADIUS;
|
|
110
|
+
// Recharts measures angles anticlockwise from the positive x-axis, SVG's y
|
|
111
|
+
// grows downward, so the sine is negated to land on the drawn arc.
|
|
112
|
+
const radians = (-midAngle * Math.PI) / 180;
|
|
113
|
+
const text =
|
|
114
|
+
labels === "percent"
|
|
115
|
+
? `${Math.round(percent * 100)}%`
|
|
116
|
+
: (valueFormatter?.(Number(value)) ?? String(value ?? ""));
|
|
117
|
+
return (
|
|
118
|
+
<text
|
|
119
|
+
data-slot="chart-arc-label"
|
|
120
|
+
x={cx + radius * Math.cos(radians)}
|
|
121
|
+
y={cy + radius * Math.sin(radians)}
|
|
122
|
+
fill={colourOn(fillOf(index ?? 0))}
|
|
123
|
+
textAnchor="middle"
|
|
124
|
+
dominantBaseline="central"
|
|
125
|
+
className="pointer-events-none text-[length:var(--ds-text-caption)] font-medium tabular-nums"
|
|
126
|
+
>
|
|
127
|
+
{text}
|
|
128
|
+
</text>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
51
133
|
/**
|
|
52
134
|
* The Recharts side of `Chart`. Returns a chart element for the
|
|
53
135
|
* ResponsiveContainer inside ChartContainer; colours come from `seriesColor`
|
|
@@ -64,8 +146,17 @@ export function RechartsChart({
|
|
|
64
146
|
legend,
|
|
65
147
|
grid,
|
|
66
148
|
tooltip,
|
|
149
|
+
labels,
|
|
67
150
|
valueFormatter,
|
|
68
151
|
}: RechartsRenderProps): React.ReactElement {
|
|
152
|
+
// Arc labels sit on top of a series colour, so their ink depends on which
|
|
153
|
+
// colour and on the theme. `useThemeVersion` is the cache key for that DOM
|
|
154
|
+
// read; `element` scopes it to a nested theme rather than the document.
|
|
155
|
+
const themeVersion = useThemeVersion();
|
|
156
|
+
const element = useChart()?.element ?? null;
|
|
157
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read
|
|
158
|
+
const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);
|
|
159
|
+
|
|
69
160
|
const tip = tooltip ? (
|
|
70
161
|
<ChartTooltip
|
|
71
162
|
formatter={valueFormatter ? (value: unknown) => valueFormatter(Number(value)) : undefined}
|
|
@@ -152,6 +243,7 @@ export function RechartsChart({
|
|
|
152
243
|
case "pie":
|
|
153
244
|
case "donut": {
|
|
154
245
|
const slices = toSlices(data as ChartSlice[]);
|
|
246
|
+
const fillOf = (i: number) => seriesColor(config, slices[i]?.id ?? String(i), i);
|
|
155
247
|
return (
|
|
156
248
|
<PieChart margin={MARGIN}>
|
|
157
249
|
{tip}
|
|
@@ -164,9 +256,12 @@ export function RechartsChart({
|
|
|
164
256
|
outerRadius="80%"
|
|
165
257
|
paddingAngle={type === "donut" ? 2 : 0}
|
|
166
258
|
strokeWidth={1}
|
|
259
|
+
label={labels === "none" ? undefined : arcLabel(labels, colourOn, fillOf, valueFormatter)}
|
|
260
|
+
// The labels are inside their arcs, so there is nothing to lead to.
|
|
261
|
+
labelLine={false}
|
|
167
262
|
>
|
|
168
263
|
{slices.map((slice, i) => (
|
|
169
|
-
<Cell key={slice.id} fill={
|
|
264
|
+
<Cell key={slice.id} fill={fillOf(i)} />
|
|
170
265
|
))}
|
|
171
266
|
</Pie>
|
|
172
267
|
</PieChart>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recharts, loaded on first render of a chart.
|
|
5
|
+
*
|
|
6
|
+
* Recharts is 774 kB of the package's dependency weight — more than everything
|
|
7
|
+
* else in `cortena-ui` combined — and until this existed it was in the static
|
|
8
|
+
* graph of anything that could reach `Chart`, the A2UI catalogue included. An
|
|
9
|
+
* app that imported the catalogue to render a text card paid for a charting
|
|
10
|
+
* engine it never drew with.
|
|
11
|
+
*
|
|
12
|
+
* Both engines are now on the same footing: `@nivo/*` in `nivo-charts.tsx` and
|
|
13
|
+
* Recharts here are dynamic imports, so a chart type costs its engine when it
|
|
14
|
+
* is first drawn and nothing before. `ChartContainer`'s responsive wrapper is
|
|
15
|
+
* here for the same reason — it is Recharts' `ResponsiveContainer`, and
|
|
16
|
+
* `ChartContainer` is on the eager path because `Chart` renders it before it
|
|
17
|
+
* knows which engine will draw.
|
|
18
|
+
*
|
|
19
|
+
* The chunk cache repeats the shape in `nivo-charts.tsx` for the same reason:
|
|
20
|
+
* `React.lazy` memoises a rejection for the life of the page, so the lazy
|
|
21
|
+
* components are cached here rather than frozen at module scope and
|
|
22
|
+
* {@link resetRechartsChunks} drops them, letting `Chart`'s ChunkBoundary
|
|
23
|
+
* retry a chunk that failed once.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import * as React from "react";
|
|
27
|
+
import type { RechartsRenderProps } from "./recharts-charts";
|
|
28
|
+
|
|
29
|
+
type AnyComponent = React.ComponentType<Record<string, unknown>>;
|
|
30
|
+
|
|
31
|
+
const loaders = {
|
|
32
|
+
chart: async () => (await import("./recharts-charts")).RechartsChart as unknown as AnyComponent,
|
|
33
|
+
responsive: async () => (await import("recharts")).ResponsiveContainer as unknown as AnyComponent,
|
|
34
|
+
} satisfies Record<string, () => Promise<AnyComponent>>;
|
|
35
|
+
|
|
36
|
+
type ChunkName = keyof typeof loaders;
|
|
37
|
+
|
|
38
|
+
let chunks = new Map<ChunkName, AnyComponent>();
|
|
39
|
+
|
|
40
|
+
function chunk(name: ChunkName): AnyComponent {
|
|
41
|
+
let component = chunks.get(name);
|
|
42
|
+
if (!component) {
|
|
43
|
+
component = React.lazy(async () => ({ default: await loaders[name]() })) as unknown as AnyComponent;
|
|
44
|
+
chunks.set(name, component);
|
|
45
|
+
}
|
|
46
|
+
return component;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Forget every loaded-or-failed Recharts chunk, so the next render loads it again. */
|
|
50
|
+
export function resetRechartsChunks(): void {
|
|
51
|
+
chunks = new Map();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** `RechartsChart` behind its chunk. Suspends on first render of a Recharts type. */
|
|
55
|
+
export function RechartsChartLazy(props: RechartsRenderProps): React.ReactElement {
|
|
56
|
+
const Component = chunk("chart");
|
|
57
|
+
return <Component {...(props as unknown as Record<string, unknown>)} />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RechartsResponsiveProps {
|
|
61
|
+
width: string | number;
|
|
62
|
+
height: string | number;
|
|
63
|
+
minWidth?: number;
|
|
64
|
+
minHeight?: number;
|
|
65
|
+
children: React.ReactElement;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Recharts' `ResponsiveContainer` behind its chunk. */
|
|
69
|
+
export function RechartsResponsive(props: RechartsResponsiveProps): React.ReactElement {
|
|
70
|
+
const Component = chunk("responsive");
|
|
71
|
+
return <Component {...(props as unknown as Record<string, unknown>)} />;
|
|
72
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The two parts of the chart surface that are Recharts elements rather than
|
|
5
|
+
* plain markup: its `Tooltip` and its `Legend`, each given the token-styled
|
|
6
|
+
* content from `container.tsx`.
|
|
7
|
+
*
|
|
8
|
+
* They live here and not in `container.tsx` for one reason. `container.tsx` is
|
|
9
|
+
* on the eager path — `Chart` renders `ChartContainer` before it knows which
|
|
10
|
+
* engine will draw — and `recharts-charts.tsx`, the lazy engine chunk, needs
|
|
11
|
+
* both of these. A binding the lazy chunk imports from an eager module is
|
|
12
|
+
* pulled into the eager chunk, and these two carry `recharts` with them: with
|
|
13
|
+
* them in `container.tsx`, importing `Chart` shipped all 774 kB of Recharts up
|
|
14
|
+
* front however lazy the rest of the chunking was.
|
|
15
|
+
*
|
|
16
|
+
* With them here, the only static importer is the lazy chunk, so they travel
|
|
17
|
+
* with it. `index.tsx` still re-exports them, so a consumer composing a one-off
|
|
18
|
+
* chart imports them from `cortena-ui/chart` exactly as before — and pays for
|
|
19
|
+
* Recharts, which is the point of asking for them.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as React from "react";
|
|
23
|
+
import { Legend as RechartsLegend, Tooltip as RechartsTooltip, type LegendProps, type TooltipProps } from "recharts";
|
|
24
|
+
import { ChartLegendContent, ChartTooltipContent } from "./container";
|
|
25
|
+
|
|
26
|
+
/** Recharts' Tooltip with the token-styled content and a subtle cursor. */
|
|
27
|
+
export function ChartTooltip(props: TooltipProps): React.ReactElement {
|
|
28
|
+
return <RechartsTooltip content={<ChartTooltipContent />} {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Recharts' Legend with the token-styled content. */
|
|
32
|
+
export function ChartLegend(props: LegendProps): React.ReactElement {
|
|
33
|
+
return <RechartsLegend content={<ChartLegendContent />} {...props} />;
|
|
34
|
+
}
|