@godxjp/ui 33.15.0 → 33.17.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.
Files changed (88) hide show
  1. package/README.md +4 -4
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/AppShell.json +1 -1
  4. package/agent/components/AreaChart.json +6 -0
  5. package/agent/components/BarChart.json +6 -0
  6. package/agent/components/Card.json +1 -1
  7. package/agent/components/DashboardGrid.json +76 -0
  8. package/agent/components/DataTable.json +1 -1
  9. package/agent/components/Heading.json +6 -0
  10. package/agent/components/HeatmapChart.json +6 -0
  11. package/agent/components/LineChart.json +6 -0
  12. package/agent/components/Pagination.json +1 -1
  13. package/agent/components/PieChart.json +6 -0
  14. package/agent/components/Table.json +2 -1
  15. package/agent/components-index.json +9 -3
  16. package/agent/components.json +118 -5
  17. package/agent/index.json +8 -8
  18. package/agent/llms.txt +6 -6
  19. package/agent/rules.json +1 -1
  20. package/agent/tokens.json +74 -2
  21. package/agent/vocabulary.json +12 -0
  22. package/dist/components/charts/chart-cartesian.d.ts +3 -2
  23. package/dist/components/charts/chart-cartesian.js +146 -46
  24. package/dist/components/charts/chart-datum-selection.d.ts +15 -0
  25. package/dist/components/charts/chart-datum-selection.js +97 -0
  26. package/dist/components/charts/chart-frame.d.ts +2 -1
  27. package/dist/components/charts/chart-frame.js +2 -1
  28. package/dist/components/charts/heatmap-chart.d.ts +1 -1
  29. package/dist/components/charts/heatmap-chart.js +20 -0
  30. package/dist/components/charts/index.d.ts +1 -1
  31. package/dist/components/charts/pie-chart.d.ts +2 -1
  32. package/dist/components/charts/pie-chart.js +36 -1
  33. package/dist/components/charts/recharts-peer.d.ts +1 -1
  34. package/dist/components/charts/recharts-peer.js +6 -0
  35. package/dist/components/data-display/index.d.ts +1 -1
  36. package/dist/components/data-display/index.js +10 -1
  37. package/dist/components/data-display/table.d.ts +9 -0
  38. package/dist/components/data-display/table.js +3 -0
  39. package/dist/components/general/typography.d.ts +1 -0
  40. package/dist/components/general/typography.js +3 -0
  41. package/dist/contracts/measurement.json +1 -1
  42. package/dist/i18n/messages/en.json +10 -0
  43. package/dist/i18n/messages/ja.json +10 -0
  44. package/dist/i18n/messages/vi.json +10 -0
  45. package/dist/lab/dashboard-grid-layout.d.ts +4 -0
  46. package/dist/lab/dashboard-grid-layout.js +36 -0
  47. package/dist/lab/dashboard-grid.d.ts +5 -0
  48. package/dist/lab/dashboard-grid.js +258 -0
  49. package/dist/lab/index.d.ts +2 -0
  50. package/dist/lab/index.js +2 -0
  51. package/dist/lib/hooks.d.ts +8 -0
  52. package/dist/lib/hooks.js +20 -21
  53. package/dist/props/components/charts.prop.d.ts +11 -0
  54. package/dist/props/components/dashboard-grid.prop.d.ts +27 -0
  55. package/dist/props/components/dashboard-grid.prop.js +0 -0
  56. package/dist/props/components/general.prop.d.ts +8 -1
  57. package/dist/props/components/index.d.ts +1 -1
  58. package/dist/props/registry.d.ts +89 -6
  59. package/dist/props/registry.js +85 -0
  60. package/dist/props/vocabulary/index.d.ts +1 -1
  61. package/dist/props/vocabulary/interaction.prop.d.ts +6 -0
  62. package/dist/styles/badge-layout.css +1 -0
  63. package/dist/styles/base.css +8 -0
  64. package/dist/styles/card-layout.css +8 -0
  65. package/dist/styles/chart-layout.css +28 -0
  66. package/dist/styles/control.css +82 -0
  67. package/dist/styles/dialog-layout.css +7 -3
  68. package/dist/styles/layers.json +10 -1
  69. package/dist/styles/layout.css +2 -0
  70. package/dist/styles/logo-layout.css +18 -12
  71. package/dist/styles/navigation-layout.css +2 -0
  72. package/dist/styles/shell-layout.css +40 -1
  73. package/dist/styles/table-layout.css +30 -0
  74. package/dist/styles/text-layout.css +5 -0
  75. package/dist/styles.css +1 -1
  76. package/dist/theme/famgia.service.css +10 -8
  77. package/dist/themes/godx-tokens.css +20 -16
  78. package/dist/tokens/base.css +2 -0
  79. package/dist/tokens/components/dashboard-grid.css +14 -0
  80. package/dist/tokens/components/feedback.css +2 -0
  81. package/dist/tokens/components/logo.css +6 -4
  82. package/dist/tokens/components/table.css +5 -1
  83. package/dist/tokens/derived.css +28 -24
  84. package/dist/tokens/foundation.css +62 -56
  85. package/dist/tokens/semantic/layout.css +5 -3
  86. package/docs/COMPOSITION-VS-COMPONENT.md +15 -14
  87. package/docs/TOKENS.md +33 -0
  88. package/package.json +2 -2
@@ -797,6 +797,7 @@
797
797
  }
798
798
  },
799
799
  "chart": {
800
+ "datumLabel": "{label}, {series}: {value}",
800
801
  "empty": "No data to display",
801
802
  "summaryCartesian": "Chart with {series} series across {points} data points",
802
803
  "summaryPie": "Pie chart with {slices} slices",
@@ -868,5 +869,14 @@
868
869
  "error": "failed"
869
870
  }
870
871
  }
872
+ },
873
+ "dashboardGrid": {
874
+ "move": "Move {label}",
875
+ "resize": "Resize {label}",
876
+ "hint": "Enter or Space grabs and drops. Arrow keys move; Shift and arrows resize. Escape cancels.",
877
+ "grabbed": "Grabbed {label}. Column {x}, row {y}, width {w}, height {h}.",
878
+ "moved": "{label}. Column {x}, row {y}, width {w}, height {h}.",
879
+ "dropped": "Saved {label}. Column {x}, row {y}, width {w}, height {h}.",
880
+ "cancelled": "Cancelled changes to {label}."
871
881
  }
872
882
  }
@@ -771,6 +771,7 @@
771
771
  }
772
772
  },
773
773
  "chart": {
774
+ "datumLabel": "{label}, {series}: {value}",
774
775
  "empty": "データがありません",
775
776
  "summaryCartesian": "{series}系列・{points}データ点のグラフ",
776
777
  "summaryPie": "{slices}区分の円グラフ",
@@ -841,5 +842,14 @@
841
842
  "error": "失敗"
842
843
  }
843
844
  }
845
+ },
846
+ "dashboardGrid": {
847
+ "move": "{label}を移動",
848
+ "resize": "{label}のサイズ変更",
849
+ "hint": "Enter またはスペースでつかむ・確定。矢印キーで移動、Shift と矢印でサイズ変更。Escape で取り消します。",
850
+ "grabbed": "{label}をつかみました。列{x}、行{y}、幅{w}、高さ{h}。",
851
+ "moved": "{label}。列{x}、行{y}、幅{w}、高さ{h}。",
852
+ "dropped": "{label}を保存しました。列{x}、行{y}、幅{w}、高さ{h}。",
853
+ "cancelled": "{label}の変更を取り消しました。"
844
854
  }
845
855
  }
@@ -772,6 +772,7 @@
772
772
  }
773
773
  },
774
774
  "chart": {
775
+ "datumLabel": "{label}, {series}: {value}",
775
776
  "empty": "Không có dữ liệu",
776
777
  "summaryCartesian": "Biểu đồ gồm {series} chuỗi trên {points} điểm dữ liệu",
777
778
  "summaryPie": "Biểu đồ tròn gồm {slices} phần",
@@ -842,5 +843,14 @@
842
843
  "error": "thất bại"
843
844
  }
844
845
  }
846
+ },
847
+ "dashboardGrid": {
848
+ "move": "Di chuyển {label}",
849
+ "resize": "Đổi kích thước {label}",
850
+ "hint": "Enter hoặc Space để nắm và thả. Phím mũi tên để di chuyển; Shift và mũi tên để đổi kích thước. Escape để hủy.",
851
+ "grabbed": "Đã nắm {label}. Cột {x}, hàng {y}, rộng {w}, cao {h}.",
852
+ "moved": "{label}. Cột {x}, hàng {y}, rộng {w}, cao {h}.",
853
+ "dropped": "Đã lưu {label}. Cột {x}, hàng {y}, rộng {w}, cao {h}.",
854
+ "cancelled": "Đã hủy thay đổi {label}."
845
855
  }
846
856
  }
@@ -0,0 +1,4 @@
1
+ import type { DashboardGridItemProp } from "../props/components/dashboard-grid.prop.js";
2
+ export declare const gridColumns: (value: number) => number;
3
+ /** Active tile wins. Others push down in stable visual order, then compact upward without overlap. */
4
+ export declare function resolveDashboardLayout(layout: readonly DashboardGridItemProp[], columns: number, active?: string): DashboardGridItemProp[];
@@ -0,0 +1,36 @@
1
+ const integer = (value, fallback) => Number.isFinite(value) ? Math.trunc(value) : fallback;
2
+ const gridColumns = (value) => Math.max(1, integer(value, 1));
3
+ const overlaps = (a, b) => a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
4
+ function resolveDashboardLayout(layout, columns, active) {
5
+ const count = gridColumns(columns);
6
+ const normalized = layout.map((item) => {
7
+ const w = Math.min(count, Math.max(1, integer(item.w, 1)));
8
+ return {
9
+ ...item,
10
+ w,
11
+ h: Math.max(1, integer(item.h, 1)),
12
+ x: Math.max(0, Math.min(count - w, integer(item.x, 0))),
13
+ y: Math.max(0, integer(item.y, 0))
14
+ };
15
+ });
16
+ const sorted = normalized.slice().sort((a, b) => a.y - b.y || a.x - b.x);
17
+ const held = normalized.find((item) => item.id === active);
18
+ const placed = held ? [held] : [];
19
+ for (const item of sorted) {
20
+ if (item === held) continue;
21
+ let collision;
22
+ while (collision = placed.find((other) => overlaps(item, other)))
23
+ item.y = collision.y + collision.h;
24
+ while (item.y > 0 && !placed.some((other) => overlaps({ ...item, y: item.y - 1 }, other)))
25
+ item.y--;
26
+ placed.push(item);
27
+ }
28
+ if (held)
29
+ while (held.y > 0 && !placed.some((other) => other !== held && overlaps({ ...held, y: held.y - 1 }, other)))
30
+ held.y--;
31
+ return normalized;
32
+ }
33
+ export {
34
+ gridColumns,
35
+ resolveDashboardLayout
36
+ };
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ import type { DashboardGridProp } from "../props/components/dashboard-grid.prop.js";
3
+ export type { DashboardGridItemProp, DashboardGridProp, DashboardGridProp as DashboardGridProps, } from "../props/components/dashboard-grid.prop.js";
4
+ /** Optional BI layout editor. DOM reading order remains items order; x starts at inline-start. */
5
+ export declare const DashboardGrid: React.ForwardRefExoticComponent<DashboardGridProp & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,258 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { GripVertical } from "lucide-react";
5
+ import { Card, CardHeader, CardTitle } from "../components/data-display/card.js";
6
+ import { Button } from "../components/general/button.js";
7
+ import { useTranslation } from "../i18n/use-translation.js";
8
+ import { useMediaQuery } from "../lib/hooks.js";
9
+ import { cn } from "../lib/utils.js";
10
+ import { gridColumns, resolveDashboardLayout } from "./dashboard-grid-layout.js";
11
+ const DashboardGrid = React.forwardRef(
12
+ function DashboardGrid2({
13
+ items,
14
+ layout,
15
+ defaultLayout,
16
+ onLayoutChange,
17
+ columns = 12,
18
+ rowHeight,
19
+ editable = false,
20
+ className,
21
+ ...props
22
+ }, ref) {
23
+ const { t, locale } = useTranslation();
24
+ const sm = useMediaQuery("(min-width: 40rem)");
25
+ const md = useMediaQuery("(min-width: 48rem)");
26
+ const lg = useMediaQuery("(min-width: 64rem)");
27
+ const xl = useMediaQuery("(min-width: 80rem)");
28
+ const count = gridColumns(
29
+ typeof columns === "number" ? columns : xl && columns.xl || lg && columns.lg || md && columns.md || sm && columns.sm || columns.base
30
+ );
31
+ const [inner, setInner] = React.useState(defaultLayout ?? []);
32
+ const source = layout ?? inner;
33
+ const committed = resolveDashboardLayout(
34
+ items.map(
35
+ (item, index) => source.find((entry) => entry.id === item.id) ?? {
36
+ id: item.id,
37
+ x: 0,
38
+ y: index,
39
+ w: 1,
40
+ h: 1
41
+ }
42
+ ),
43
+ count
44
+ );
45
+ const root = React.useRef(null);
46
+ const session = React.useRef(null);
47
+ const [preview, setPreview] = React.useState(null);
48
+ const [announcement, announce] = React.useState("");
49
+ const hint = React.useId();
50
+ const format = new Intl.NumberFormat(locale);
51
+ const speak = (action, entries, id) => {
52
+ const item = entries.find((entry) => entry.id === id);
53
+ if (!item) return;
54
+ announce(
55
+ t(`dashboardGrid.${action}`, {
56
+ label: items.find((entry) => entry.id === id)?.label ?? id,
57
+ x: format.format(item.x + 1),
58
+ y: format.format(item.y + 1),
59
+ w: format.format(item.w),
60
+ h: format.format(item.h)
61
+ })
62
+ );
63
+ };
64
+ const begin = (id) => {
65
+ const next = committed.map((item) => ({ ...item }));
66
+ session.current = { id, base: next, next };
67
+ setPreview(next);
68
+ speak("grabbed", next, id);
69
+ };
70
+ const finish = (cancel) => {
71
+ const current = session.current;
72
+ if (!current) return;
73
+ session.current = null;
74
+ setPreview(null);
75
+ speak(cancel ? "cancelled" : "dropped", cancel ? current.base : current.next, current.id);
76
+ if (!cancel && JSON.stringify(current.base) !== JSON.stringify(current.next)) {
77
+ if (layout === void 0) setInner(current.next);
78
+ onLayoutChange?.(current.next);
79
+ }
80
+ };
81
+ const update = (dx, dy, resize, fromBase = false) => {
82
+ const current = session.current;
83
+ if (!current) return;
84
+ current.next = resolveDashboardLayout(
85
+ (fromBase ? current.base : current.next).map(
86
+ (item) => item.id !== current.id ? item : resize ? { ...item, w: item.w + dx, h: item.h + dy } : { ...item, x: item.x + dx, y: item.y + dy }
87
+ ),
88
+ count,
89
+ current.id
90
+ );
91
+ setPreview(current.next);
92
+ speak("moved", current.next, current.id);
93
+ };
94
+ React.useEffect(() => {
95
+ session.current = null;
96
+ setPreview(null);
97
+ }, [layout, columns, count, editable, items]);
98
+ const latest = React.useRef({ finish, update });
99
+ latest.current = { finish, update };
100
+ React.useEffect(() => {
101
+ const move = (event) => {
102
+ const pointer = session.current?.pointer;
103
+ if (!pointer || event.pointerId !== pointer.id) return;
104
+ event.preventDefault();
105
+ latest.current.update(
106
+ Math.round((event.clientX - pointer.x) / pointer.stepX) * (pointer.rtl ? -1 : 1),
107
+ Math.round((event.clientY - pointer.y) / pointer.stepY),
108
+ pointer.resize,
109
+ true
110
+ );
111
+ };
112
+ const end = (event) => {
113
+ if (session.current?.pointer?.id === event.pointerId)
114
+ latest.current.finish(event.type === "pointercancel");
115
+ };
116
+ const escape = (event) => {
117
+ if (event.key === "Escape" && session.current) {
118
+ event.preventDefault();
119
+ latest.current.finish(true);
120
+ }
121
+ };
122
+ window.addEventListener("pointermove", move, { passive: false });
123
+ window.addEventListener("pointerup", end);
124
+ window.addEventListener("pointercancel", end);
125
+ window.addEventListener("keydown", escape);
126
+ return () => {
127
+ window.removeEventListener("pointermove", move);
128
+ window.removeEventListener("pointerup", end);
129
+ window.removeEventListener("pointercancel", end);
130
+ window.removeEventListener("keydown", escape);
131
+ };
132
+ }, []);
133
+ const pointerDown = (id, resize) => (event) => {
134
+ if (!editable || event.button !== 0 || session.current || !root.current) return;
135
+ event.preventDefault();
136
+ event.currentTarget.focus();
137
+ const css = getComputedStyle(root.current);
138
+ const gap = parseFloat(css.columnGap) || 0;
139
+ begin(id);
140
+ session.current.pointer = {
141
+ id: event.pointerId,
142
+ x: event.clientX,
143
+ y: event.clientY,
144
+ stepX: (root.current.clientWidth + gap) / count,
145
+ stepY: (parseFloat(css.gridAutoRows) || rowHeight || 80) + (parseFloat(css.rowGap) || 0),
146
+ rtl: css.direction === "rtl",
147
+ resize
148
+ };
149
+ };
150
+ const keyDown = (id) => (event) => {
151
+ if (event.key === "Enter" || event.key === " ") {
152
+ event.preventDefault();
153
+ if (!session.current) begin(id);
154
+ else if (session.current.id === id) finish(false);
155
+ return;
156
+ }
157
+ if (session.current?.id !== id || !event.key.startsWith("Arrow")) return;
158
+ event.preventDefault();
159
+ const rtl = root.current && getComputedStyle(root.current).direction === "rtl";
160
+ update(
161
+ (event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0) * (rtl ? -1 : 1),
162
+ event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0,
163
+ event.shiftKey
164
+ );
165
+ };
166
+ const shown = preview ?? committed;
167
+ return /* @__PURE__ */ jsxs("div", { ...props, ref, className: cn("ui-dashboard-grid", className), children: [
168
+ /* @__PURE__ */ jsx(
169
+ "div",
170
+ {
171
+ ref: root,
172
+ className: "ui-dashboard-grid-items",
173
+ "data-stacked": count === 1 || void 0,
174
+ style: {
175
+ "--dashboard-grid-columns": count,
176
+ ...rowHeight && Number.isFinite(rowHeight) && rowHeight > 0 ? { "--dashboard-grid-row-height": `${rowHeight}px` } : {}
177
+ },
178
+ children: items.map((item) => {
179
+ const box = shown.find((entry) => entry.id === item.id) ?? committed.find((entry) => entry.id === item.id);
180
+ const held = session.current?.id === item.id;
181
+ return /* @__PURE__ */ jsx(
182
+ "section",
183
+ {
184
+ "aria-label": item.label,
185
+ className: "ui-dashboard-grid-item",
186
+ "data-dashboard-id": item.id,
187
+ "data-x": box.x,
188
+ "data-y": box.y,
189
+ "data-w": box.w,
190
+ "data-h": box.h,
191
+ "data-editable": editable || void 0,
192
+ "data-held": held || void 0,
193
+ style: {
194
+ gridColumn: `${box.x + 1} / span ${box.w}`,
195
+ gridRow: count === 1 ? "auto" : `${box.y + 1} / span ${box.h}`
196
+ },
197
+ children: /* @__PURE__ */ jsxs(Card, { className: "ui-dashboard-grid-surface", children: [
198
+ /* @__PURE__ */ jsx(CardHeader, { className: "ui-dashboard-grid-header", children: /* @__PURE__ */ jsx(CardTitle, { children: item.label }) }),
199
+ editable && /* @__PURE__ */ jsx(
200
+ Button,
201
+ {
202
+ variant: "ghost",
203
+ size: "icon-sm",
204
+ className: "ui-dashboard-grid-move",
205
+ "aria-label": t("dashboardGrid.move", { label: item.label }),
206
+ "aria-describedby": hint,
207
+ "aria-pressed": held,
208
+ onPointerDown: pointerDown(item.id, false),
209
+ onKeyDown: keyDown(item.id),
210
+ onBlur: () => {
211
+ if (!session.current?.pointer) finish(true);
212
+ },
213
+ children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
214
+ }
215
+ ),
216
+ /* @__PURE__ */ jsx("div", { className: "ui-dashboard-grid-content", children: item.content }),
217
+ editable && /* @__PURE__ */ jsx(
218
+ Button,
219
+ {
220
+ variant: "ghost",
221
+ size: "icon-sm",
222
+ className: "ui-dashboard-grid-resize",
223
+ "aria-label": t("dashboardGrid.resize", { label: item.label }),
224
+ "aria-describedby": hint,
225
+ "aria-pressed": held,
226
+ onPointerDown: pointerDown(item.id, true),
227
+ onKeyDown: keyDown(item.id),
228
+ onBlur: () => {
229
+ if (!session.current?.pointer) finish(true);
230
+ },
231
+ children: /* @__PURE__ */ jsx(
232
+ "svg",
233
+ {
234
+ viewBox: "0 0 24 24",
235
+ fill: "none",
236
+ stroke: "currentColor",
237
+ strokeWidth: "1.5",
238
+ "aria-hidden": "true",
239
+ children: /* @__PURE__ */ jsx("path", { d: "M8 20 20 8M14 20 20 14" })
240
+ }
241
+ )
242
+ }
243
+ )
244
+ ] })
245
+ },
246
+ item.id
247
+ );
248
+ })
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx("span", { id: hint, className: "sr-only", children: t("dashboardGrid.hint") }),
252
+ /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement })
253
+ ] });
254
+ }
255
+ );
256
+ export {
257
+ DashboardGrid
258
+ };
@@ -37,3 +37,5 @@ export { MegaMenu } from "./mega-menu.js";
37
37
  export type { MegaMenuProp, MegaMenuProps, MegaMenuItemProp, MegaMenuItemProps, MegaMenuPanelProp, MegaMenuPanelProps, MegaMenuGroupProp, MegaMenuGroupProps, MegaMenuLinkProp, MegaMenuLinkProps, MegaMenuLinkComponentProp, MegaMenuLinkComponentProps, MegaMenuTriggerActionProp, MegaMenuTriggerActionProps, } from "./mega-menu.js";
38
38
  export { Anchor } from "./anchor.js";
39
39
  export type { AnchorContainerProp, AnchorDirectionProp, AnchorItemProp, AnchorProp, AnchorProps, } from "./anchor.js";
40
+ export { DashboardGrid } from "./dashboard-grid.js";
41
+ export type { DashboardGridProp, DashboardGridProps, DashboardGridItemProp } from "./dashboard-grid.js";
package/dist/lab/index.js CHANGED
@@ -21,6 +21,7 @@ import { EmojiPicker } from "./emoji-picker.js";
21
21
  import { SortableList } from "./sortable-list.js";
22
22
  import { MegaMenu } from "./mega-menu.js";
23
23
  import { Anchor } from "./anchor.js";
24
+ import { DashboardGrid } from "./dashboard-grid.js";
24
25
  export {
25
26
  Anchor,
26
27
  Carousel,
@@ -29,6 +30,7 @@ export {
29
30
  CarouselItem,
30
31
  CarouselNext,
31
32
  CarouselPrevious,
33
+ DashboardGrid,
32
34
  DraggablePanel,
33
35
  EmojiPicker,
34
36
  FloatButton,
@@ -21,6 +21,14 @@ export declare function useTimeoutFlag(signal: unknown, ms?: number): boolean;
21
21
  * first defined value arrives, then PROMOTES to controlled for good.
22
22
  */
23
23
  export declare function useControlledLatch(valueIsDefined: boolean): boolean;
24
+ /**
25
+ * Live `matchMedia(query).matches`, hydration-safe. Built on `useSyncExternalStore` so that during
26
+ * hydration React renders with the SERVER snapshot (`false`, what SSR sent) and switches to the live
27
+ * match immediately after — the first client render never differs from the server HTML. Reading
28
+ * `matchMedia` in the initial `useState` made a phone hydrate a different tree than the server sent
29
+ * (AppShell `data-navigation` "docked" → "drawer", an attribute mismatch React does not patch up).
30
+ * A client-only mount still gets the live value on its first render.
31
+ */
24
32
  export declare function useMediaQuery(query: string): boolean;
25
33
  export declare function useIsMobile(): boolean;
26
34
  /**
package/dist/lib/hooks.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useLayoutEffect } from "@react-aria/utils";
3
- import { useEffect, useState } from "react";
3
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
4
4
  import { flushSync } from "react-dom";
5
5
  function useDebouncedValue(value, delay = 250) {
6
6
  const [debounced, setDebounced] = useState(value);
@@ -43,29 +43,28 @@ function useControlledLatch(valueIsDefined) {
43
43
  if (valueIsDefined && !latched) setLatched(true);
44
44
  return valueIsDefined || latched;
45
45
  }
46
+ const getServerMatch = () => false;
46
47
  function useMediaQuery(query) {
47
- const isBrowser = typeof window !== "undefined";
48
- const getMatch = () => isBrowser ? window.matchMedia(query).matches : false;
49
- const [matches, setMatches] = useState(getMatch());
50
- useEffect(() => {
51
- if (!isBrowser) return void 0;
52
- const mediaQuery = window.matchMedia(query);
53
- const updateMatch = () => {
54
- setMatches(mediaQuery.matches);
55
- };
56
- updateMatch();
57
- if (typeof mediaQuery.addEventListener === "function") {
58
- mediaQuery.addEventListener("change", updateMatch);
48
+ const subscribe = useCallback(
49
+ (onChange) => {
50
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
51
+ };
52
+ const mediaQuery = window.matchMedia(query);
53
+ if (typeof mediaQuery.addEventListener === "function") {
54
+ mediaQuery.addEventListener("change", onChange);
55
+ return () => {
56
+ mediaQuery.removeEventListener("change", onChange);
57
+ };
58
+ }
59
+ mediaQuery.addListener(onChange);
59
60
  return () => {
60
- mediaQuery.removeEventListener("change", updateMatch);
61
+ mediaQuery.removeListener(onChange);
61
62
  };
62
- }
63
- mediaQuery.addListener(updateMatch);
64
- return () => {
65
- mediaQuery.removeListener(updateMatch);
66
- };
67
- }, [isBrowser, query]);
68
- return matches;
63
+ },
64
+ [query]
65
+ );
66
+ const getMatch = () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false;
67
+ return useSyncExternalStore(subscribe, getMatch, getServerMatch);
69
68
  }
70
69
  function useIsMobile() {
71
70
  return useMediaQuery("(max-width: 767px)");
@@ -21,6 +21,11 @@ export type ChartSeriesProp = {
21
21
  */
22
22
  fillColor?: string;
23
23
  };
24
+ /** Selection is an action: the dashboard owns filter state. Index refers to the original data array. */
25
+ export type ChartDatumSelect<T = ChartDatum> = (datum: T, context: {
26
+ seriesKey: string;
27
+ index: number;
28
+ }) => void;
24
29
  /** Shared base for the cartesian charts (Line / Bar / Area). */
25
30
  type ChartCartesianBase = {
26
31
  /** Row data. Each row is one category with a value per series. */
@@ -69,6 +74,8 @@ type ChartCartesianBase = {
69
74
  numberFormat?: Intl.NumberFormatOptions;
70
75
  /** Message shown when `data` is empty. Defaults to a localized "no data". */
71
76
  emptyMessage?: EmptyMessageProp;
77
+ /** Select a finite datum by pointer or Enter/Space. Arrows navigate categories and series. */
78
+ onDatumSelect?: ChartDatumSelect;
72
79
  /**
73
80
  * Called with the clicked category's datum and its index — drill-down, zoom-to-bucket. The whole
74
81
  * category column is the target, not only the mark, so a zero-height bar is still clickable.
@@ -161,6 +168,8 @@ export type HeatmapDatum = {
161
168
  * steps through the `--chart-heat-1..5` ramp; an empty cell is outlined, a zero cell is filled.
162
169
  */
163
170
  export type HeatmapChartProp = {
171
+ /** Select a present cell. seriesKey is String(column); index is the original data index. */
172
+ onDatumSelect?: ChartDatumSelect<HeatmapDatum>;
164
173
  /** Row categories, top to bottom (the y axis — weekdays). */
165
174
  rows: HeatmapCategory[];
166
175
  /** Column categories, start to end (the x axis — hours). */
@@ -207,6 +216,8 @@ export type AreaChartProp = ChartCartesianBase & {
207
216
  };
208
217
  /** @see PieChart */
209
218
  export type PieChartProp = {
219
+ /** Select a slice by pointer or Enter/Space; seriesKey is dataKey. Arrows navigate slices. */
220
+ onDatumSelect?: ChartDatumSelect;
210
221
  /** Row data — one slice per row. */
211
222
  data: ChartDatum[];
212
223
  /** Key into each datum holding the slice's numeric value. */
@@ -0,0 +1,27 @@
1
+ import type * as React from "react";
2
+ import type { BreakpointProp } from "../vocabulary/index.js";
3
+ /** Integer grid coordinates, zero-based from inline-start. Stable id owns persistence. */
4
+ export interface DashboardGridItemProp {
5
+ id: string;
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ }
11
+ export interface DashboardGridProp extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
12
+ /** Each tile owns its Card and visible label header; content is the body (e.g. CardContent). */
13
+ items: {
14
+ id: string;
15
+ label: string;
16
+ content: React.ReactNode;
17
+ }[];
18
+ layout?: DashboardGridItemProp[];
19
+ defaultLayout?: DashboardGridItemProp[];
20
+ onLayoutChange?: (layout: DashboardGridItemProp[]) => void;
21
+ columns?: number | ({
22
+ base: number;
23
+ } & Partial<Record<BreakpointProp, number>>);
24
+ /** Row track size in pixels; omitted reads --dashboard-grid-row-height. */
25
+ rowHeight?: number;
26
+ editable?: boolean;
27
+ }
File without changes
@@ -1,6 +1,6 @@
1
1
  /** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
2
2
  import type * as React from "react";
3
- import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DescriptionProp, DisabledProp, FontWeightProp, HeadingLevelProp, IconSizeProp, IdProp, InViewAmountProp, LabelProp, OnClickProp, OnOpenChangeProp, OpenProp, PendingProp, RevealDelayProp, RevealTriggerProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp, TextBreakProp, TypographyActionsConfigProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyTypeProp } from "../vocabulary/index.js";
3
+ import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DescriptionProp, DisabledProp, FontWeightProp, HeadingFamilyProp, HeadingLevelProp, IconSizeProp, IdProp, InViewAmountProp, LabelProp, OnClickProp, OnOpenChangeProp, OpenProp, PendingProp, RevealDelayProp, RevealTriggerProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp, TextBreakProp, TypographyActionsConfigProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyTypeProp } from "../vocabulary/index.js";
4
4
  /**
5
5
  * @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`.
6
6
  *
@@ -131,6 +131,13 @@ export type HeadingProp = Omit<React.HTMLAttributes<HTMLHeadingElement>, "color"
131
131
  truncate?: boolean;
132
132
  /** Weight (system canon: 400 · 500 · 700). Default `medium` — set `bold` for an emphasised title. */
133
133
  weight?: FontWeightProp;
134
+ /**
135
+ * Heading face. `editorial` sets this heading in --font-family-editorial — the opt-in serif a
136
+ * public or editorial page uses for its hero and section headings while the UI stays sans. Until a
137
+ * theme names that face it falls back to --font-family-display, so the prop is inert by default.
138
+ * Omit it (or `display`) for every UI heading.
139
+ */
140
+ family?: HeadingFamilyProp;
134
141
  };
135
142
  /**
136
143
  * The antd `BlockProps` behaviour shared by `Text` / `Link`.
@@ -2,7 +2,7 @@ export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherPr
2
2
  export type { ButtonProp } from "./general.prop.js";
3
3
  export type { CommandProp, CommandSeparatorProp, InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, TimePickerProp, ColorPickerProp, ColorPickerPresetProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
4
4
  export type { AvatarProp, EmptyStateProp, DescriptionsProp, DescriptionsItemProp, BadgeProp, DataTableProp, QrCodeProp, } from "./data-display.prop.js";
5
- export type { ChartDatum, ChartSeriesProp, LineChartProp, BarChartProp, AreaChartProp, PieChartProp, } from "./charts.prop.js";
5
+ export type { ChartDatumSelect, ChartDatum, ChartSeriesProp, LineChartProp, BarChartProp, AreaChartProp, PieChartProp, } from "./charts.prop.js";
6
6
  export type { AlertQueryErrorProp, AuthExpiryProviderProp, AlertProp, DialogProp, DialogContentProp, AlertTitleProp, AlertContentProp, AlertDescriptionProp, AlertActionsProp, SheetResponsiveProp, SkeletonRowsProp, } from "./feedback.prop.js";
7
7
  export type { DataStateProp, InfiniteQueryStateProp, InfiniteQueryHelpers, PrefetchLinkProp, } from "./query.prop.js";
8
8
  export type { DropdownMenuPlacementProp, DropdownMenuTriggerActionProp, PaginationProp, PaginationSizeProp, PaginationAlignProp, StepsProp, StepItemProp, StepStatusProp, StepsTypeProp, TabsProp, TabItemProp, TabsVariantProp, TabsPlacementProp, TabsExtraProp, TabsOnEditProp, } from "./navigation.prop.js";