@godxjp/ui 33.15.0 → 33.16.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 +4 -4
- package/agent/START-HERE.md +5 -5
- package/agent/components/AreaChart.json +6 -0
- package/agent/components/BarChart.json +6 -0
- package/agent/components/DashboardGrid.json +76 -0
- package/agent/components/DataTable.json +1 -1
- package/agent/components/HeatmapChart.json +6 -0
- package/agent/components/LineChart.json +6 -0
- package/agent/components/Pagination.json +1 -1
- package/agent/components/PieChart.json +6 -0
- package/agent/components/Table.json +1 -1
- package/agent/components-index.json +7 -1
- package/agent/components.json +109 -3
- package/agent/index.json +6 -6
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +38 -2
- package/dist/components/charts/chart-cartesian.d.ts +3 -2
- package/dist/components/charts/chart-cartesian.js +146 -46
- package/dist/components/charts/chart-datum-selection.d.ts +15 -0
- package/dist/components/charts/chart-datum-selection.js +97 -0
- package/dist/components/charts/chart-frame.d.ts +2 -1
- package/dist/components/charts/chart-frame.js +2 -1
- package/dist/components/charts/heatmap-chart.d.ts +1 -1
- package/dist/components/charts/heatmap-chart.js +20 -0
- package/dist/components/charts/index.d.ts +1 -1
- package/dist/components/charts/pie-chart.d.ts +2 -1
- package/dist/components/charts/pie-chart.js +36 -1
- package/dist/components/charts/recharts-peer.d.ts +1 -1
- package/dist/components/charts/recharts-peer.js +6 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +10 -0
- package/dist/i18n/messages/ja.json +10 -0
- package/dist/i18n/messages/vi.json +10 -0
- package/dist/lab/dashboard-grid-layout.d.ts +4 -0
- package/dist/lab/dashboard-grid-layout.js +36 -0
- package/dist/lab/dashboard-grid.d.ts +5 -0
- package/dist/lab/dashboard-grid.js +258 -0
- package/dist/lab/index.d.ts +2 -0
- package/dist/lab/index.js +2 -0
- package/dist/props/components/charts.prop.d.ts +11 -0
- package/dist/props/components/dashboard-grid.prop.d.ts +27 -0
- package/dist/props/components/dashboard-grid.prop.js +0 -0
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/registry.d.ts +83 -5
- package/dist/props/registry.js +79 -0
- package/dist/styles/chart-layout.css +28 -0
- package/dist/styles/control.css +82 -0
- package/dist/styles/layers.json +10 -1
- package/dist/styles/navigation-layout.css +2 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/base.css +2 -0
- package/dist/tokens/components/dashboard-grid.css +14 -0
- package/dist/tokens/components/table.css +1 -1
- package/docs/COMPOSITION-VS-COMPONENT.md +15 -14
- package/package.json +2 -2
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system:
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 171 components, 2257 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.16.0.
|
|
5
5
|
|
|
6
6
|
If your client can run a process, do not read these files — run the MCP server instead
|
|
7
|
-
(`npx @godxjp/ui-mcp@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.16.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -16,10 +16,10 @@ that can only fetch URLs.
|
|
|
16
16
|
## Catalog
|
|
17
17
|
|
|
18
18
|
- [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
|
|
19
|
-
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json):
|
|
19
|
+
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 54 KB — all 171 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
20
20
|
- [components/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–38 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
|
|
21
21
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.3 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
22
|
-
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles,
|
|
22
|
+
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1926 `component` knobs.
|
|
23
23
|
- [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
|
|
24
24
|
- [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
|
|
25
25
|
- [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
|
|
@@ -27,7 +27,7 @@ that can only fetch URLs.
|
|
|
27
27
|
## Pinning
|
|
28
28
|
|
|
29
29
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
30
|
-
the tag: `.../godx-jp/godxjp-ui/v33.
|
|
30
|
+
the tag: `.../godx-jp/godxjp-ui/v33.16.0/agent/...`. A catalog that does not match the installed
|
|
31
31
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
32
32
|
|
|
33
33
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
package/agent/tokens.json
CHANGED
|
@@ -5291,6 +5291,42 @@
|
|
|
5291
5291
|
"tier": "component",
|
|
5292
5292
|
"value": "var(--space-1)"
|
|
5293
5293
|
},
|
|
5294
|
+
{
|
|
5295
|
+
"description": "Gap between DashboardGrid tiles, both axes (#1397).",
|
|
5296
|
+
"name": "--dashboard-grid-gap",
|
|
5297
|
+
"tier": "component",
|
|
5298
|
+
"value": "var(--space-4)"
|
|
5299
|
+
},
|
|
5300
|
+
{
|
|
5301
|
+
"description": "Height of one grid row; a tile's `h` is a multiple of it.",
|
|
5302
|
+
"name": "--dashboard-grid-row-height",
|
|
5303
|
+
"tier": "component",
|
|
5304
|
+
"value": "var(--space-20)"
|
|
5305
|
+
},
|
|
5306
|
+
{
|
|
5307
|
+
"description": "Size of the drag and resize handles in edit mode (≥24px target).",
|
|
5308
|
+
"name": "--dashboard-grid-handle-size",
|
|
5309
|
+
"tier": "component",
|
|
5310
|
+
"value": "var(--control-height)"
|
|
5311
|
+
},
|
|
5312
|
+
{
|
|
5313
|
+
"description": "Inset of the resize handle from the tile's bottom-end corner.",
|
|
5314
|
+
"name": "--dashboard-grid-handle-inset",
|
|
5315
|
+
"tier": "component",
|
|
5316
|
+
"value": "var(--space-1)"
|
|
5317
|
+
},
|
|
5318
|
+
{
|
|
5319
|
+
"description": "Corner radius of a tile; defaults to the Card radius.",
|
|
5320
|
+
"name": "--dashboard-grid-item-radius",
|
|
5321
|
+
"tier": "component",
|
|
5322
|
+
"value": "var(--card-radius)"
|
|
5323
|
+
},
|
|
5324
|
+
{
|
|
5325
|
+
"description": "Colour of the drop-preview ring while moving or resizing (role-mirror knob; defaults to the focus ring colour).",
|
|
5326
|
+
"name": "--dashboard-grid-preview-ring-color",
|
|
5327
|
+
"tier": "component",
|
|
5328
|
+
"value": "initial"
|
|
5329
|
+
},
|
|
5294
5330
|
{
|
|
5295
5331
|
"description": "Accordion chevron and Carousel arrow. Both were bare 1rem literals in data-display-layout.css — glyphs with no tier-2 route, invisible to the icon ratchet because neither selector contains the word \"icon\".",
|
|
5296
5332
|
"name": "--accordion-chevron-size",
|
|
@@ -12588,7 +12624,7 @@
|
|
|
12588
12624
|
"value": "0px"
|
|
12589
12625
|
},
|
|
12590
12626
|
{
|
|
12591
|
-
"description": "Table · stacked-record-collection preset (gh#293 restore — SCR-215) — the canonical WIDE, heterogeneous record collection. Below the collapse step every `<tr>` becomes a
|
|
12627
|
+
"description": "Table · stacked-record-collection preset (gh#293 restore — SCR-215) — the canonical WIDE, heterogeneous record collection. Below the collapse step every `<tr>` becomes a continuous key-value record; these tokens own the record's geometry (unlike action-collection, there is no column-priority measure to retune — the whole row becomes one block).",
|
|
12592
12628
|
"name": "--table-stacked-collection-card-padding-y",
|
|
12593
12629
|
"tier": "component",
|
|
12594
12630
|
"value": "var(--space-stack-sm)"
|
|
@@ -12603,7 +12639,7 @@
|
|
|
12603
12639
|
"description": "Table component tokens: row height, cell padding.",
|
|
12604
12640
|
"name": "--table-stacked-collection-card-gap",
|
|
12605
12641
|
"tier": "component",
|
|
12606
|
-
"value": "
|
|
12642
|
+
"value": "0"
|
|
12607
12643
|
},
|
|
12608
12644
|
{
|
|
12609
12645
|
"description": "Table component tokens: row height, cell padding.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { ChartDatum, ChartSeriesProp } from "../../props/components/charts.prop.js";
|
|
2
|
+
import type { ChartDatum, ChartSeriesProp, ChartDatumSelect } from "../../props/components/charts.prop.js";
|
|
3
3
|
/**
|
|
4
4
|
* Shared cartesian renderer for Line / Bar / Area. Internal — the filename pascal
|
|
5
5
|
* (`ChartCartesian`) is intentionally NOT exported, so this is not a catalog entry.
|
|
@@ -21,6 +21,7 @@ type CartesianChartProps = {
|
|
|
21
21
|
valueTicks?: number[];
|
|
22
22
|
numberFormat?: Intl.NumberFormatOptions;
|
|
23
23
|
emptyMessage?: string;
|
|
24
|
+
onDatumSelect?: ChartDatumSelect;
|
|
24
25
|
onCategoryClick?: (datum: ChartDatum, index: number) => void;
|
|
25
26
|
className?: string;
|
|
26
27
|
id?: string;
|
|
@@ -33,5 +34,5 @@ type CartesianChartProps = {
|
|
|
33
34
|
/** bar */
|
|
34
35
|
horizontal?: boolean;
|
|
35
36
|
};
|
|
36
|
-
export declare function CartesianChart({ kind, data, series, categoryKey, label, showCaption, description, size, height, showLegend, showGrid, valueDomain, valueTicks, numberFormat, emptyMessage, onCategoryClick, className, id, curved, showDots, stacked, horizontal, }: CartesianChartProps): React.JSX.Element;
|
|
37
|
+
export declare function CartesianChart({ kind, data, series, categoryKey, label, showCaption, description, size, height, showLegend, showGrid, valueDomain, valueTicks, numberFormat, emptyMessage, onCategoryClick, onDatumSelect, className, id, curved, showDots, stacked, horizontal, }: CartesianChartProps): React.JSX.Element;
|
|
37
38
|
export {};
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import {
|
|
5
|
+
Dot,
|
|
6
|
+
Rectangle,
|
|
5
7
|
Area,
|
|
6
8
|
AreaChart as RAreaChart,
|
|
7
9
|
Bar,
|
|
@@ -22,6 +24,7 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
22
24
|
import { ChartFrame, chartColor, chartHeight, useChartNumberFormat } from "./chart-frame.js";
|
|
23
25
|
import { buildCartesianSummary } from "./chart-summary.js";
|
|
24
26
|
import { useCategoryAxisMetrics } from "./chart-category-axis.js";
|
|
27
|
+
import { useChartDatumSelection } from "./chart-datum-selection.js";
|
|
25
28
|
import { listFormat } from "../../lib/intl-cache.js";
|
|
26
29
|
function CartesianChart({
|
|
27
30
|
kind,
|
|
@@ -40,6 +43,7 @@ function CartesianChart({
|
|
|
40
43
|
numberFormat,
|
|
41
44
|
emptyMessage,
|
|
42
45
|
onCategoryClick,
|
|
46
|
+
onDatumSelect,
|
|
43
47
|
className,
|
|
44
48
|
id,
|
|
45
49
|
curved = false,
|
|
@@ -51,6 +55,71 @@ function CartesianChart({
|
|
|
51
55
|
const { t, locale } = useTranslation();
|
|
52
56
|
const fmt = useChartNumberFormat(numberFormat);
|
|
53
57
|
const list = React.useMemo(() => listFormat(locale, { style: "narrow", type: "unit" }), [locale]);
|
|
58
|
+
const points = series.flatMap(
|
|
59
|
+
(s, seriesIndex) => data.flatMap(
|
|
60
|
+
(datum, index) => typeof datum[s.dataKey] === "number" && Number.isFinite(datum[s.dataKey]) ? [{ index, series: seriesIndex }] : []
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
const markProps = useChartDatumSelection(points);
|
|
64
|
+
const mark = (seriesIndex, shape, raw) => {
|
|
65
|
+
const s = series[seriesIndex];
|
|
66
|
+
const props = raw;
|
|
67
|
+
const { x = 0, y = 0, width = 0, height: height2 = 0 } = props;
|
|
68
|
+
const index = props.index;
|
|
69
|
+
const datum = data[index];
|
|
70
|
+
if (!datum || typeof datum[s.dataKey] !== "number" || !Number.isFinite(datum[s.dataKey]))
|
|
71
|
+
return /* @__PURE__ */ jsx("g", {});
|
|
72
|
+
const action = markProps(
|
|
73
|
+
index,
|
|
74
|
+
seriesIndex,
|
|
75
|
+
t("chart.datumLabel", {
|
|
76
|
+
label: String(datum[categoryKey] ?? ""),
|
|
77
|
+
series: s.label ?? s.dataKey,
|
|
78
|
+
value: fmt.format(Number(datum[s.dataKey]))
|
|
79
|
+
}),
|
|
80
|
+
() => onDatumSelect?.(datum, { seriesKey: s.dataKey, index })
|
|
81
|
+
);
|
|
82
|
+
return shape === "bar" ? /* @__PURE__ */ jsxs("g", { ...action, children: [
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
"rect",
|
|
85
|
+
{
|
|
86
|
+
x: Math.min(x, x + width),
|
|
87
|
+
y: Math.min(y, y + height2),
|
|
88
|
+
width: Math.abs(width),
|
|
89
|
+
height: Math.abs(height2),
|
|
90
|
+
className: "ui-chart-bar-hit",
|
|
91
|
+
fill: "transparent",
|
|
92
|
+
style: {
|
|
93
|
+
"--chart-mark-width": `${Math.abs(width)}px`,
|
|
94
|
+
"--chart-mark-height": `${Math.abs(height2)}px`
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
Rectangle,
|
|
100
|
+
{
|
|
101
|
+
x: props.x,
|
|
102
|
+
y: props.y,
|
|
103
|
+
width: props.width,
|
|
104
|
+
height: props.height,
|
|
105
|
+
fill: props.fill,
|
|
106
|
+
radius: 2
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
] }) : /* @__PURE__ */ jsxs("g", { ...action, children: [
|
|
110
|
+
/* @__PURE__ */ jsx("circle", { cx: props.cx, cy: props.cy, className: "ui-chart-point-hit", fill: "transparent" }),
|
|
111
|
+
/* @__PURE__ */ jsx(Dot, { cx: props.cx, cy: props.cy, fill: chartColor(seriesIndex, s.color) })
|
|
112
|
+
] });
|
|
113
|
+
};
|
|
114
|
+
const markRef = React.useRef(mark);
|
|
115
|
+
markRef.current = mark;
|
|
116
|
+
const shapes = React.useMemo(
|
|
117
|
+
() => Array.from({ length: series.length }, (_, i) => ({
|
|
118
|
+
bar: (raw) => markRef.current(i, "bar", raw),
|
|
119
|
+
dot: (raw) => markRef.current(i, "dot", raw)
|
|
120
|
+
})),
|
|
121
|
+
[series.length]
|
|
122
|
+
);
|
|
54
123
|
const hasData = data.length > 0 && series.length > 0;
|
|
55
124
|
const summary = buildCartesianSummary(
|
|
56
125
|
data,
|
|
@@ -161,59 +230,90 @@ function CartesianChart({
|
|
|
161
230
|
const onBarClick = onCategoryClick ? (_entry, index) => pickCategory(index) : void 0;
|
|
162
231
|
const chart = () => {
|
|
163
232
|
if (kind === "line") {
|
|
164
|
-
return /* @__PURE__ */ jsxs(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
233
|
+
return /* @__PURE__ */ jsxs(
|
|
234
|
+
RLineChart,
|
|
235
|
+
{
|
|
236
|
+
accessibilityLayer: onDatumSelect ? false : void 0,
|
|
237
|
+
data,
|
|
238
|
+
onClick: onChartClick,
|
|
239
|
+
children: [
|
|
240
|
+
axes(true),
|
|
241
|
+
series.map((s, i) => /* @__PURE__ */ jsx(
|
|
242
|
+
Line,
|
|
243
|
+
{
|
|
244
|
+
type: curve,
|
|
245
|
+
dataKey: s.dataKey,
|
|
246
|
+
name: s.label ?? s.dataKey,
|
|
247
|
+
stroke: chartColor(i, s.color),
|
|
248
|
+
dot: onDatumSelect ? shapes[i].dot : showDots,
|
|
249
|
+
activeDot: onDatumSelect ? false : void 0,
|
|
250
|
+
isAnimationActive: onDatumSelect ? false : void 0
|
|
251
|
+
},
|
|
252
|
+
s.dataKey
|
|
253
|
+
))
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
);
|
|
178
257
|
}
|
|
179
258
|
if (kind === "area") {
|
|
180
|
-
return /* @__PURE__ */ jsxs(
|
|
181
|
-
|
|
182
|
-
series.map((s, i) => /* @__PURE__ */ jsx(
|
|
183
|
-
Area,
|
|
184
|
-
{
|
|
185
|
-
type: curve,
|
|
186
|
-
dataKey: s.dataKey,
|
|
187
|
-
name: s.label ?? s.dataKey,
|
|
188
|
-
stackId: stacked ? "stack" : void 0,
|
|
189
|
-
stroke: chartColor(i, s.color),
|
|
190
|
-
fill: s.fillColor ?? chartColor(i, s.color),
|
|
191
|
-
dot: showDots
|
|
192
|
-
},
|
|
193
|
-
s.dataKey
|
|
194
|
-
))
|
|
195
|
-
] });
|
|
196
|
-
}
|
|
197
|
-
return /* @__PURE__ */ jsxs(RBarChart, { data, layout: horizontal ? "vertical" : "horizontal", children: [
|
|
198
|
-
axes(!horizontal),
|
|
199
|
-
series.map((s, i) => /* @__PURE__ */ jsx(
|
|
200
|
-
Bar,
|
|
259
|
+
return /* @__PURE__ */ jsxs(
|
|
260
|
+
RAreaChart,
|
|
201
261
|
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
262
|
+
accessibilityLayer: onDatumSelect ? false : void 0,
|
|
263
|
+
data,
|
|
264
|
+
onClick: onChartClick,
|
|
265
|
+
children: [
|
|
266
|
+
axes(true),
|
|
267
|
+
series.map((s, i) => /* @__PURE__ */ jsx(
|
|
268
|
+
Area,
|
|
269
|
+
{
|
|
270
|
+
type: curve,
|
|
271
|
+
dataKey: s.dataKey,
|
|
272
|
+
name: s.label ?? s.dataKey,
|
|
273
|
+
stackId: stacked ? "stack" : void 0,
|
|
274
|
+
stroke: chartColor(i, s.color),
|
|
275
|
+
fill: s.fillColor ?? chartColor(i, s.color),
|
|
276
|
+
dot: onDatumSelect ? shapes[i].dot : showDots,
|
|
277
|
+
activeDot: onDatumSelect ? false : void 0,
|
|
278
|
+
isAnimationActive: onDatumSelect ? false : void 0
|
|
279
|
+
},
|
|
280
|
+
s.dataKey
|
|
281
|
+
))
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return /* @__PURE__ */ jsxs(
|
|
287
|
+
RBarChart,
|
|
288
|
+
{
|
|
289
|
+
accessibilityLayer: onDatumSelect ? false : void 0,
|
|
290
|
+
data,
|
|
291
|
+
layout: horizontal ? "vertical" : "horizontal",
|
|
292
|
+
children: [
|
|
293
|
+
axes(!horizontal),
|
|
294
|
+
series.map((s, i) => /* @__PURE__ */ jsx(
|
|
295
|
+
Bar,
|
|
296
|
+
{
|
|
297
|
+
dataKey: s.dataKey,
|
|
298
|
+
name: s.label ?? s.dataKey,
|
|
299
|
+
stackId: stacked ? "stack" : void 0,
|
|
300
|
+
fill: chartColor(i, s.color),
|
|
301
|
+
radius: 2,
|
|
302
|
+
shape: onDatumSelect ? shapes[i].bar : void 0,
|
|
303
|
+
isAnimationActive: onDatumSelect ? false : void 0,
|
|
304
|
+
onClick: onBarClick,
|
|
305
|
+
background: onCategoryClick ? { fill: "transparent" } : void 0
|
|
306
|
+
},
|
|
307
|
+
s.dataKey
|
|
308
|
+
))
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
);
|
|
213
312
|
};
|
|
214
313
|
return /* @__PURE__ */ jsx(
|
|
215
314
|
ChartFrame,
|
|
216
315
|
{
|
|
316
|
+
interactive: !!onDatumSelect,
|
|
217
317
|
label,
|
|
218
318
|
showCaption,
|
|
219
319
|
description,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Roving mark focus. Geometry stays in recharts; data identity stays in the original array. */
|
|
3
|
+
export declare function useChartDatumSelection(points: {
|
|
4
|
+
index: number;
|
|
5
|
+
series: number;
|
|
6
|
+
}[]): (index: number, series: number, name: string, select: () => void) => {
|
|
7
|
+
role: string;
|
|
8
|
+
"aria-label": string;
|
|
9
|
+
"data-chart-datum": string;
|
|
10
|
+
className: string;
|
|
11
|
+
tabIndex: number;
|
|
12
|
+
onFocus: (event: React.FocusEvent<SVGElement>) => void;
|
|
13
|
+
onClick: (event: React.MouseEvent<SVGElement>) => void;
|
|
14
|
+
onKeyDown: (event: React.KeyboardEvent<SVGElement>) => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
function useChartDatumSelection(points) {
|
|
4
|
+
const active = React.useRef("");
|
|
5
|
+
const recovery = React.useRef(null);
|
|
6
|
+
React.useEffect(() => () => recovery.current?.(), []);
|
|
7
|
+
const key = (index, series) => `${series}:${index}`;
|
|
8
|
+
const keys = points.map((p) => key(p.index, p.series));
|
|
9
|
+
const current = keys.includes(active.current) ? active.current : keys[0];
|
|
10
|
+
const activate = (node, select) => {
|
|
11
|
+
const canvas = node.closest(".ui-chart-canvas");
|
|
12
|
+
const identity = node.dataset.chartDatum;
|
|
13
|
+
recovery.current?.();
|
|
14
|
+
let focused = node;
|
|
15
|
+
const stop = () => {
|
|
16
|
+
observer.disconnect();
|
|
17
|
+
document.removeEventListener("keydown", stop, true);
|
|
18
|
+
document.removeEventListener("pointerdown", stop, true);
|
|
19
|
+
document.removeEventListener("focusin", onFocusIn, true);
|
|
20
|
+
};
|
|
21
|
+
const onFocusIn = (event) => {
|
|
22
|
+
if (event.target !== focused) stop();
|
|
23
|
+
};
|
|
24
|
+
const observer = new MutationObserver(() => {
|
|
25
|
+
if (focused.isConnected || !canvas?.isConnected || document.activeElement !== document.body)
|
|
26
|
+
return;
|
|
27
|
+
const replacement = Array.from(
|
|
28
|
+
canvas.querySelectorAll("[data-chart-datum]")
|
|
29
|
+
).find((mark) => mark.dataset.chartDatum === identity);
|
|
30
|
+
if (replacement) {
|
|
31
|
+
focused = replacement;
|
|
32
|
+
replacement.focus();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
recovery.current = stop;
|
|
36
|
+
if (canvas) observer.observe(canvas, { childList: true, subtree: true });
|
|
37
|
+
document.addEventListener("keydown", stop, true);
|
|
38
|
+
document.addEventListener("pointerdown", stop, true);
|
|
39
|
+
document.addEventListener("focusin", onFocusIn, true);
|
|
40
|
+
select();
|
|
41
|
+
};
|
|
42
|
+
return (index, series, name, select) => ({
|
|
43
|
+
role: "button",
|
|
44
|
+
"aria-label": name,
|
|
45
|
+
"data-chart-datum": key(index, series),
|
|
46
|
+
className: "ui-chart-datum ui-focus-ring",
|
|
47
|
+
tabIndex: current === key(index, series) ? 0 : -1,
|
|
48
|
+
onFocus: (event) => {
|
|
49
|
+
event.stopPropagation();
|
|
50
|
+
active.current = key(index, series);
|
|
51
|
+
event.currentTarget.closest(".ui-chart-canvas")?.querySelectorAll("[data-chart-datum]").forEach((node) => {
|
|
52
|
+
node.tabIndex = node === event.currentTarget ? 0 : -1;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
onClick: (event) => {
|
|
56
|
+
event.stopPropagation();
|
|
57
|
+
event.currentTarget.focus();
|
|
58
|
+
activate(event.currentTarget, select);
|
|
59
|
+
},
|
|
60
|
+
onKeyDown: (event) => {
|
|
61
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
if (!event.repeat) activate(event.currentTarget, select);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
68
|
+
let target;
|
|
69
|
+
const along = event.key === "ArrowRight" ? rtl ? -1 : 1 : event.key === "ArrowLeft" ? rtl ? 1 : -1 : 0;
|
|
70
|
+
const across = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
|
|
71
|
+
if (along) {
|
|
72
|
+
const row = points.filter((p) => p.series === series);
|
|
73
|
+
const at = row.findIndex((p) => p.index === index);
|
|
74
|
+
target = row[Math.max(0, Math.min(row.length - 1, at + along))];
|
|
75
|
+
} else if (across) {
|
|
76
|
+
const column = points.filter((p) => p.index === index);
|
|
77
|
+
const at = column.findIndex((p) => p.series === series);
|
|
78
|
+
target = column[Math.max(0, Math.min(column.length - 1, at + across))];
|
|
79
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
80
|
+
const row = event.ctrlKey || event.metaKey ? points : points.filter((p) => p.series === series);
|
|
81
|
+
target = event.key === "Home" ? row[0] : row.at(-1);
|
|
82
|
+
} else return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
if (!target) return;
|
|
86
|
+
const next = key(target.index, target.series);
|
|
87
|
+
const canvas = event.currentTarget.closest(".ui-chart-canvas");
|
|
88
|
+
const mark = Array.from(
|
|
89
|
+
canvas?.querySelectorAll("[data-chart-datum]") ?? []
|
|
90
|
+
).find((node) => node.dataset.chartDatum === next);
|
|
91
|
+
mark?.focus();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
useChartDatumSelection
|
|
97
|
+
};
|
|
@@ -16,6 +16,7 @@ export declare function chartHeight(size?: "xs" | "sm" | "md" | "lg", height?: n
|
|
|
16
16
|
/** A locale-bound number formatter for the active app locale. */
|
|
17
17
|
export declare function useChartNumberFormat(options?: Intl.NumberFormatOptions): Intl.NumberFormat;
|
|
18
18
|
type ChartFrameProps = {
|
|
19
|
+
interactive?: boolean;
|
|
19
20
|
/** Visible caption; also the accessible name via `aria-labelledby` → `<figcaption>`. */
|
|
20
21
|
label: React.ReactNode;
|
|
21
22
|
/**
|
|
@@ -54,5 +55,5 @@ type ChartFrameProps = {
|
|
|
54
55
|
* in a `role="img"` element with a short `aria-label`; the full data is exposed as
|
|
55
56
|
* a sibling visually-hidden list referenced by the figure's `aria-describedby`.
|
|
56
57
|
*/
|
|
57
|
-
export declare function ChartFrame({ label, showCaption, description, summaryRows, imgSummary, hasData, height, size, footer, className, id, children, ref, canvasRef, canvasExtra, }: ChartFrameProps): React.JSX.Element;
|
|
58
|
+
export declare function ChartFrame({ interactive, label, showCaption, description, summaryRows, imgSummary, hasData, height, size, footer, className, id, children, ref, canvasRef, canvasExtra, }: ChartFrameProps): React.JSX.Element;
|
|
58
59
|
export {};
|
|
@@ -29,6 +29,7 @@ function useChartNumberFormat(options) {
|
|
|
29
29
|
return React.useMemo(() => numberFormat(locale, options), [locale, options]);
|
|
30
30
|
}
|
|
31
31
|
function ChartFrame({
|
|
32
|
+
interactive = false,
|
|
32
33
|
label,
|
|
33
34
|
showCaption = true,
|
|
34
35
|
description,
|
|
@@ -66,7 +67,7 @@ function ChartFrame({
|
|
|
66
67
|
ref: canvasRef,
|
|
67
68
|
className: "ui-chart-canvas",
|
|
68
69
|
style: { height },
|
|
69
|
-
role: "img",
|
|
70
|
+
role: interactive ? "group" : "img",
|
|
70
71
|
"aria-label": imgSummary,
|
|
71
72
|
children: [
|
|
72
73
|
children,
|
|
@@ -16,4 +16,4 @@ export type { HeatmapChartProp, HeatmapChartProp as HeatmapChartProps, HeatmapCa
|
|
|
16
16
|
* - **Narrow screens** — the matrix never shrinks a cell below its `size` tier; it scrolls inside
|
|
17
17
|
* its own box with the row header pinned, so the page never scrolls sideways.
|
|
18
18
|
*/
|
|
19
|
-
export declare function HeatmapChart({ rows, columns, data, label, showCaption, description, size, numberFormat, topN, showLegend, emptyMessage, className, id, ref, }: HeatmapChartProp): React.JSX.Element;
|
|
19
|
+
export declare function HeatmapChart({ onDatumSelect, rows, columns, data, label, showCaption, description, size, numberFormat, topN, showLegend, emptyMessage, className, id, ref, }: HeatmapChartProp): React.JSX.Element;
|
|
@@ -11,6 +11,7 @@ const OPEN_DELAY = 200;
|
|
|
11
11
|
const CLOSE_GRACE = 100;
|
|
12
12
|
const RAMP = Array.from({ length: HEAT_STEPS }, (_, index) => index + 1);
|
|
13
13
|
function HeatmapChart({
|
|
14
|
+
onDatumSelect,
|
|
14
15
|
rows,
|
|
15
16
|
columns,
|
|
16
17
|
data,
|
|
@@ -91,6 +92,12 @@ function HeatmapChart({
|
|
|
91
92
|
reveal(cell);
|
|
92
93
|
}
|
|
93
94
|
};
|
|
95
|
+
const selectCell = (r, c) => {
|
|
96
|
+
const index = data.findIndex(
|
|
97
|
+
(datum) => String(datum.row) === String(rows[r]?.key) && String(datum.column) === String(columns[c]?.key) && typeof datum.value === "number" && Number.isFinite(datum.value)
|
|
98
|
+
);
|
|
99
|
+
if (index >= 0) onDatumSelect?.(data[index], { seriesKey: String(columns[c].key), index });
|
|
100
|
+
};
|
|
94
101
|
const onGridKeyDown = (event) => {
|
|
95
102
|
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
96
103
|
const lastRow = rows.length - 1;
|
|
@@ -193,6 +200,19 @@ function HeatmapChart({
|
|
|
193
200
|
},
|
|
194
201
|
tabIndex: r === activeRow && c === activeColumn ? 0 : -1,
|
|
195
202
|
className: "ui-chart-heatmap-cell ui-focus-ring",
|
|
203
|
+
"data-interactive": onDatumSelect && !empty ? "" : void 0,
|
|
204
|
+
"aria-label": onDatumSelect ? t("chart.heatmapCell", {
|
|
205
|
+
row: row.label,
|
|
206
|
+
column: column.label,
|
|
207
|
+
value: text.value
|
|
208
|
+
}) : void 0,
|
|
209
|
+
onKeyDown: onDatumSelect ? (event) => {
|
|
210
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
event.stopPropagation();
|
|
213
|
+
if (!event.repeat && !empty) selectCell(r, c);
|
|
214
|
+
} : void 0,
|
|
215
|
+
onClick: onDatumSelect && !empty ? () => selectCell(r, c) : void 0,
|
|
196
216
|
"data-step": empty ? void 0 : model.steps.get(key),
|
|
197
217
|
"data-empty": empty ? "" : void 0,
|
|
198
218
|
"data-emphasized": model.ranks.has(key) ? "" : void 0,
|
|
@@ -16,5 +16,5 @@ export { AreaChart } from "./area-chart.js";
|
|
|
16
16
|
export type { AreaChartProp, AreaChartProps } from "./area-chart.js";
|
|
17
17
|
export { PieChart } from "./pie-chart.js";
|
|
18
18
|
export type { PieChartProp, PieChartProps } from "./pie-chart.js";
|
|
19
|
-
export type { ChartSeriesProp, ChartDatum } from "../../props/components/charts.prop.js";
|
|
19
|
+
export type { ChartSeriesProp, ChartDatum, ChartDatumSelect } from "../../props/components/charts.prop.js";
|
|
20
20
|
export { CHART_COLORS } from "./chart-frame.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import type { PieChartProp } from "../../props/components/charts.prop.js";
|
|
2
3
|
export type { PieChartProp, PieChartProp as PieChartProps, } from "../../props/components/charts.prop.js";
|
|
3
4
|
/**
|
|
@@ -5,4 +6,4 @@ export type { PieChartProp, PieChartProp as PieChartProps, } from "../../props/c
|
|
|
5
6
|
* Import from the tree-shaken `@godxjp/ui/charts` entry; requires the `recharts`
|
|
6
7
|
* optional peer dependency.
|
|
7
8
|
*/
|
|
8
|
-
export declare function PieChart({ data, dataKey, nameKey, colors, label, showCaption, description, size, height, showLegend, numberFormat, donut, emptyMessage, className, id, }: PieChartProp):
|
|
9
|
+
export declare function PieChart({ onDatumSelect, data, dataKey, nameKey, colors, label, showCaption, description, size, height, showLegend, numberFormat, donut, emptyMessage, className, id, }: PieChartProp): React.JSX.Element;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import {
|
|
4
5
|
Cell,
|
|
6
|
+
Sector,
|
|
5
7
|
Legend,
|
|
6
8
|
Pie,
|
|
7
9
|
PieChart as RPieChart,
|
|
@@ -12,8 +14,10 @@ import {
|
|
|
12
14
|
import { EmptyState } from "../data-display/empty-state.js";
|
|
13
15
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
14
16
|
import { ChartFrame, chartColor, chartHeight, useChartNumberFormat } from "./chart-frame.js";
|
|
17
|
+
import { useChartDatumSelection } from "./chart-datum-selection.js";
|
|
15
18
|
import { buildPieSummary } from "./chart-summary.js";
|
|
16
19
|
function PieChart({
|
|
20
|
+
onDatumSelect,
|
|
17
21
|
data,
|
|
18
22
|
dataKey,
|
|
19
23
|
nameKey,
|
|
@@ -33,6 +37,34 @@ function PieChart({
|
|
|
33
37
|
assertRechartsPeer();
|
|
34
38
|
const { t } = useTranslation();
|
|
35
39
|
const fmt = useChartNumberFormat(numberFormat);
|
|
40
|
+
const points = data.flatMap(
|
|
41
|
+
(datum, index) => typeof datum[dataKey] === "number" && Number.isFinite(datum[dataKey]) && Number(datum[dataKey]) > 0 ? [{ index, series: 0 }] : []
|
|
42
|
+
);
|
|
43
|
+
const markProps = useChartDatumSelection(points);
|
|
44
|
+
const shape = (raw) => {
|
|
45
|
+
const props = raw;
|
|
46
|
+
const datum = data[props.index];
|
|
47
|
+
if (!datum || !points.some((p) => p.index === props.index)) return /* @__PURE__ */ jsx("g", {});
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
Sector,
|
|
50
|
+
{
|
|
51
|
+
...props,
|
|
52
|
+
...markProps(
|
|
53
|
+
props.index,
|
|
54
|
+
0,
|
|
55
|
+
t("chart.datumLabel", {
|
|
56
|
+
label: String(datum[nameKey] ?? ""),
|
|
57
|
+
series: dataKey,
|
|
58
|
+
value: fmt.format(Number(datum[dataKey]))
|
|
59
|
+
}),
|
|
60
|
+
() => onDatumSelect?.(datum, { seriesKey: dataKey, index: props.index })
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
const shapeRef = React.useRef(shape);
|
|
66
|
+
shapeRef.current = shape;
|
|
67
|
+
const stableShape = React.useCallback((raw) => shapeRef.current(raw), []);
|
|
36
68
|
const hasData = data.length > 0;
|
|
37
69
|
const summary = buildPieSummary(
|
|
38
70
|
data,
|
|
@@ -45,6 +77,7 @@ function PieChart({
|
|
|
45
77
|
return /* @__PURE__ */ jsx(
|
|
46
78
|
ChartFrame,
|
|
47
79
|
{
|
|
80
|
+
interactive: !!onDatumSelect,
|
|
48
81
|
label,
|
|
49
82
|
showCaption,
|
|
50
83
|
description,
|
|
@@ -54,10 +87,12 @@ function PieChart({
|
|
|
54
87
|
height: resolvedHeight,
|
|
55
88
|
className,
|
|
56
89
|
id,
|
|
57
|
-
children: hasData ? /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, children: /* @__PURE__ */ jsxs(RPieChart, { children: [
|
|
90
|
+
children: hasData ? /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, children: /* @__PURE__ */ jsxs(RPieChart, { accessibilityLayer: onDatumSelect ? false : void 0, children: [
|
|
58
91
|
/* @__PURE__ */ jsx(
|
|
59
92
|
Pie,
|
|
60
93
|
{
|
|
94
|
+
shape: onDatumSelect ? stableShape : void 0,
|
|
95
|
+
isAnimationActive: onDatumSelect ? false : void 0,
|
|
61
96
|
data,
|
|
62
97
|
dataKey,
|
|
63
98
|
nameKey,
|
|
@@ -32,7 +32,7 @@ export declare const RECHARTS_PEER_MESSAGE: string;
|
|
|
32
32
|
* recharts-backed chart before it renders anything.
|
|
33
33
|
*/
|
|
34
34
|
export declare function assertRechartsPeer(): void;
|
|
35
|
-
export declare const Area: <DataPointType = any, ValueAxisType = any>(props: peer.AreaProps<DataPointType, ValueAxisType>) => import("react").ReactElement, AreaChart: <DataPointType = any>(props: import("recharts/types/util/types").CartesianChartProps<DataPointType> & {
|
|
35
|
+
export declare const Dot: import("react").FC<peer.DotProps>, Rectangle: import("react").FC<peer.RectangleProps>, Sector: import("react").FC<peer.SectorProps>, Area: <DataPointType = any, ValueAxisType = any>(props: peer.AreaProps<DataPointType, ValueAxisType>) => import("react").ReactElement, AreaChart: <DataPointType = any>(props: import("recharts/types/util/types").CartesianChartProps<DataPointType> & {
|
|
36
36
|
ref?: React.Ref<SVGSVGElement>;
|
|
37
37
|
}) => React.ReactElement, Bar: <DataPointType = any, ValueAxisType = any>(props: peer.BarProps<DataPointType, ValueAxisType>) => import("react").ReactElement, BarChart: <DataPointType = any>(props: import("recharts/types/util/types").CartesianChartProps<DataPointType> & {
|
|
38
38
|
ref?: React.Ref<SVGSVGElement>;
|