@arcfusionz/arc-analytic-ui 0.0.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 (156) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +13 -0
  2. package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
  3. package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
  4. package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
  5. package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
  6. package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
  7. package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
  8. package/dist/components/AnomalyCard/index.d.ts +4 -0
  9. package/dist/components/AnomalyCard/index.js +4 -0
  10. package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
  11. package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
  12. package/dist/components/ChartCard/ChartCard.d.ts +206 -0
  13. package/dist/components/ChartCard/ChartCard.js +718 -0
  14. package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
  15. package/dist/components/ChartCard/ChartCard.model.js +1223 -0
  16. package/dist/components/ChartCard/index.d.ts +4 -0
  17. package/dist/components/ChartCard/index.js +4 -0
  18. package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
  19. package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
  20. package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
  21. package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
  22. package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
  23. package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
  24. package/dist/components/ComparisonCard/index.d.ts +4 -0
  25. package/dist/components/ComparisonCard/index.js +4 -0
  26. package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
  27. package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
  28. package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
  29. package/dist/components/ForecastCard/ForecastCard.js +1029 -0
  30. package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
  31. package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
  32. package/dist/components/ForecastCard/index.d.ts +4 -0
  33. package/dist/components/ForecastCard/index.js +4 -0
  34. package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
  35. package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
  36. package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
  37. package/dist/components/HighlightCard/HighlightCard.js +1286 -0
  38. package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
  39. package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
  40. package/dist/components/HighlightCard/index.d.ts +4 -0
  41. package/dist/components/HighlightCard/index.js +4 -0
  42. package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
  43. package/dist/components/InsightCard/InsightCard.chart.js +471 -0
  44. package/dist/components/InsightCard/InsightCard.d.ts +161 -0
  45. package/dist/components/InsightCard/InsightCard.js +961 -0
  46. package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
  47. package/dist/components/InsightCard/InsightCard.model.js +421 -0
  48. package/dist/components/InsightCard/index.d.ts +4 -0
  49. package/dist/components/InsightCard/index.js +4 -0
  50. package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
  51. package/dist/components/MetricCard/MetricCard.chart.js +435 -0
  52. package/dist/components/MetricCard/MetricCard.d.ts +227 -0
  53. package/dist/components/MetricCard/MetricCard.js +1293 -0
  54. package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
  55. package/dist/components/MetricCard/MetricCard.model.js +627 -0
  56. package/dist/components/MetricCard/index.d.ts +4 -0
  57. package/dist/components/MetricCard/index.js +4 -0
  58. package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
  59. package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
  60. package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
  61. package/dist/components/ProfileCard/ProfileCard.js +735 -0
  62. package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
  63. package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
  64. package/dist/components/ProfileCard/index.d.ts +4 -0
  65. package/dist/components/ProfileCard/index.js +4 -0
  66. package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
  67. package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
  68. package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
  69. package/dist/components/ProgressCard/ProgressCard.js +908 -0
  70. package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
  71. package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
  72. package/dist/components/ProgressCard/index.d.ts +4 -0
  73. package/dist/components/ProgressCard/index.js +4 -0
  74. package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
  75. package/dist/components/ReportCard/ReportCard.chart.js +231 -0
  76. package/dist/components/ReportCard/ReportCard.d.ts +153 -0
  77. package/dist/components/ReportCard/ReportCard.js +1118 -0
  78. package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
  79. package/dist/components/ReportCard/ReportCard.model.js +399 -0
  80. package/dist/components/ReportCard/index.d.ts +4 -0
  81. package/dist/components/ReportCard/index.js +4 -0
  82. package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
  83. package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
  84. package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
  85. package/dist/components/SankeyCard/SankeyCard.js +788 -0
  86. package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
  87. package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
  88. package/dist/components/SankeyCard/index.d.ts +4 -0
  89. package/dist/components/SankeyCard/index.js +4 -0
  90. package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
  91. package/dist/components/StatusCard/StatusCard.chart.js +188 -0
  92. package/dist/components/StatusCard/StatusCard.d.ts +176 -0
  93. package/dist/components/StatusCard/StatusCard.js +989 -0
  94. package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
  95. package/dist/components/StatusCard/StatusCard.model.js +776 -0
  96. package/dist/components/StatusCard/index.d.ts +4 -0
  97. package/dist/components/StatusCard/index.js +4 -0
  98. package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
  99. package/dist/components/TableCard/TableCard.chart.js +192 -0
  100. package/dist/components/TableCard/TableCard.d.ts +140 -0
  101. package/dist/components/TableCard/TableCard.js +811 -0
  102. package/dist/components/TableCard/TableCard.model.d.ts +381 -0
  103. package/dist/components/TableCard/TableCard.model.js +475 -0
  104. package/dist/components/TableCard/index.d.ts +4 -0
  105. package/dist/components/TableCard/index.js +4 -0
  106. package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
  107. package/dist/components/TargetCard/TargetCard.chart.js +273 -0
  108. package/dist/components/TargetCard/TargetCard.d.ts +143 -0
  109. package/dist/components/TargetCard/TargetCard.js +910 -0
  110. package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
  111. package/dist/components/TargetCard/TargetCard.model.js +581 -0
  112. package/dist/components/TargetCard/index.d.ts +4 -0
  113. package/dist/components/TargetCard/index.js +4 -0
  114. package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
  115. package/dist/components/TreeCard/TreeCard.chart.js +178 -0
  116. package/dist/components/TreeCard/TreeCard.d.ts +182 -0
  117. package/dist/components/TreeCard/TreeCard.js +1187 -0
  118. package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
  119. package/dist/components/TreeCard/TreeCard.model.js +401 -0
  120. package/dist/components/TreeCard/index.d.ts +4 -0
  121. package/dist/components/TreeCard/index.js +4 -0
  122. package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
  123. package/dist/components/TrendCard/TrendCard.chart.js +393 -0
  124. package/dist/components/TrendCard/TrendCard.d.ts +224 -0
  125. package/dist/components/TrendCard/TrendCard.js +1053 -0
  126. package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
  127. package/dist/components/TrendCard/TrendCard.model.js +623 -0
  128. package/dist/components/TrendCard/index.d.ts +4 -0
  129. package/dist/components/TrendCard/index.js +4 -0
  130. package/dist/index.d.ts +76 -0
  131. package/dist/index.js +57 -0
  132. package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
  133. package/dist/lib/vega/ArcVegaChart.js +42 -0
  134. package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
  135. package/dist/lib/vega/ArcVegaSurface.js +38 -0
  136. package/dist/lib/vega/axisTicks.js +156 -0
  137. package/dist/lib/vega/config.d.ts +29 -0
  138. package/dist/lib/vega/config.js +113 -0
  139. package/dist/lib/vega/embed.d.ts +29 -0
  140. package/dist/lib/vega/embed.js +29 -0
  141. package/dist/lib/vega/engine.d.ts +10 -0
  142. package/dist/lib/vega/engine.js +6 -0
  143. package/dist/lib/vega/formatters.d.ts +125 -0
  144. package/dist/lib/vega/formatters.js +277 -0
  145. package/dist/lib/vega/index.d.ts +11 -0
  146. package/dist/lib/vega/load.d.ts +13 -0
  147. package/dist/lib/vega/load.js +21 -0
  148. package/dist/lib/vega/time.d.ts +45 -0
  149. package/dist/lib/vega/time.js +105 -0
  150. package/dist/lib/vega/tokens.d.ts +73 -0
  151. package/dist/lib/vega/tokens.js +137 -0
  152. package/dist/lib/vega/tooltip.d.ts +45 -0
  153. package/dist/lib/vega/tooltip.js +124 -0
  154. package/dist/lib/vega/useArcVega.d.ts +43 -0
  155. package/dist/lib/vega/useArcVega.js +121 -0
  156. package/package.json +60 -0
@@ -0,0 +1,811 @@
1
+ import { ArcVegaChart } from "../../lib/vega/ArcVegaChart.js";
2
+ import { arcEmbedOptions } from "../../lib/vega/embed.js";
3
+ import { formatDate, formatNumber } from "../../lib/vega/formatters.js";
4
+ import { useChartTokens } from "../../lib/vega/tokens.js";
5
+ import { ArcTooltipRows, useArcTooltipHandler } from "../../lib/vega/tooltip.js";
6
+ import { TABLE_TREND_DATASET, TABLE_TREND_LATEST_DATASET, buildTableTrendSpec } from "./TableCard.chart.js";
7
+ import { buildTableDescription, buildTableInsight, columnId, columnValueFormat, deriveTableState, nextSort, normalizeTable, resolveTableLabels, trendTimeFormat } from "./TableCard.model.js";
8
+ import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { Badge, Box, Button, Skeleton, SkeletonGroup, SystemState, Table, TableBody, TableCaption, TableCell, TableContainer, TableFooter, TableHead, TableHeader, TableRow, Tooltip, TooltipPopup, TooltipTrigger, cn, meterIndicatorVariants, meterTrackVariants } from "@arcfusionz/arc-primitive-ui";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/components/TableCard/TableCard.tsx
12
+ const sizeStyles = {
13
+ sm: {
14
+ padding: "sm",
15
+ gap: "gap-2",
16
+ gapPx: 8,
17
+ label: "text-xs",
18
+ body: "text-xs",
19
+ table: "sm",
20
+ trend: 18,
21
+ meter: "sm",
22
+ badge: "sm",
23
+ rowPx: 36,
24
+ headerPx: 36
25
+ },
26
+ md: {
27
+ padding: "md",
28
+ gap: "gap-3",
29
+ gapPx: 12,
30
+ label: "text-sm",
31
+ body: "text-sm",
32
+ table: "sm",
33
+ trend: 22,
34
+ meter: "sm",
35
+ badge: "sm",
36
+ rowPx: 40,
37
+ headerPx: 36
38
+ },
39
+ lg: {
40
+ padding: "lg",
41
+ gap: "gap-3",
42
+ gapPx: 12,
43
+ label: "text-base",
44
+ body: "text-sm",
45
+ table: "md",
46
+ trend: 26,
47
+ meter: "md",
48
+ badge: "md",
49
+ rowPx: 52,
50
+ headerPx: 44
51
+ }
52
+ };
53
+ const labelClasses = "min-w-0 font-heading font-semibold tracking-tight text-foreground";
54
+ const captionClasses = "font-sans text-xs text-muted-foreground";
55
+ const insightClasses = "font-sans text-xs text-muted-foreground";
56
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
57
+ const panelStateClasses = "w-full max-w-none gap-3 px-3 py-6 [--artwork-box:4rem]";
58
+ const cellButtonClasses = "h-auto min-w-0 rounded border-0 shadow-none text-inherit [font:inherit]";
59
+ const sortButtonClasses = `${cellButtonClasses} -mx-2 w-full justify-start gap-1 px-2 py-1 hover:bg-muted`;
60
+ const deltaTextClasses = {
61
+ positive: "text-chart-positive",
62
+ negative: "text-chart-negative",
63
+ neutral: "text-foreground"
64
+ };
65
+ const toneBadgeVariant = {
66
+ neutral: "neutral",
67
+ positive: "success",
68
+ negative: "destructive",
69
+ warning: "warning",
70
+ info: "primary"
71
+ };
72
+ const MISSING_NAME = "Table";
73
+ const EMPTY_ROWS = [];
74
+ const EMPTY_POINTS = [];
75
+ function InfoIcon(props) {
76
+ return /* @__PURE__ */ jsxs("svg", {
77
+ viewBox: "0 0 16 16",
78
+ fill: "none",
79
+ stroke: "currentColor",
80
+ strokeWidth: "1.5",
81
+ "aria-hidden": "true",
82
+ ...props,
83
+ children: [
84
+ /* @__PURE__ */ jsx("circle", {
85
+ cx: "8",
86
+ cy: "8",
87
+ r: "6.25"
88
+ }),
89
+ /* @__PURE__ */ jsx("path", {
90
+ d: "M8 7.25v4",
91
+ strokeLinecap: "round"
92
+ }),
93
+ /* @__PURE__ */ jsx("path", {
94
+ d: "M8 4.9v.1",
95
+ strokeLinecap: "round"
96
+ })
97
+ ]
98
+ });
99
+ }
100
+ function SortIcon({ direction, ...props }) {
101
+ return /* @__PURE__ */ jsx("svg", {
102
+ viewBox: "0 0 16 16",
103
+ fill: "none",
104
+ stroke: "currentColor",
105
+ strokeWidth: "1.5",
106
+ "aria-hidden": "true",
107
+ ...props,
108
+ children: direction === "asc" ? /* @__PURE__ */ jsx("path", {
109
+ d: "M4.5 9.75 8 6.25l3.5 3.5",
110
+ strokeLinecap: "round",
111
+ strokeLinejoin: "round"
112
+ }) : direction === "desc" ? /* @__PURE__ */ jsx("path", {
113
+ d: "M4.5 6.25 8 9.75l3.5-3.5",
114
+ strokeLinecap: "round",
115
+ strokeLinejoin: "round"
116
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("path", {
117
+ d: "M5.5 7 8 4.5 10.5 7",
118
+ strokeLinecap: "round",
119
+ strokeLinejoin: "round",
120
+ opacity: "0.5"
121
+ }), /* @__PURE__ */ jsx("path", {
122
+ d: "M5.5 9 8 11.5 10.5 9",
123
+ strokeLinecap: "round",
124
+ strokeLinejoin: "round",
125
+ opacity: "0.5"
126
+ })] })
127
+ });
128
+ }
129
+ function DeltaGlyph({ direction, ...props }) {
130
+ return /* @__PURE__ */ jsx("svg", {
131
+ viewBox: "0 0 12 12",
132
+ fill: "currentColor",
133
+ "aria-hidden": "true",
134
+ ...props,
135
+ children: direction === "up" ? /* @__PURE__ */ jsx("path", { d: "M6 2.5 10 8H2z" }) : direction === "down" ? /* @__PURE__ */ jsx("path", { d: "M6 9.5 2 4h8z" }) : /* @__PURE__ */ jsx("rect", {
136
+ x: "2",
137
+ y: "5.25",
138
+ width: "8",
139
+ height: "1.5",
140
+ rx: "0.75"
141
+ })
142
+ });
143
+ }
144
+ function useMergedRef(forwarded) {
145
+ const inner = useRef(null);
146
+ return [inner, useCallback((node) => {
147
+ inner.current = node;
148
+ if (typeof forwarded === "function") forwarded(node);
149
+ else if (forwarded) forwarded.current = node;
150
+ }, [forwarded])];
151
+ }
152
+ /**
153
+ * The card's own width, so columns drop at their `hideBelow` and the table
154
+ * decides when to scroll — container-driven, never viewport-driven, because a
155
+ * dashboard puts the same card in a full-width row and in a third of one.
156
+ * `0` until the first measurement, which happens before paint.
157
+ */
158
+ function useContainerWidth(ref) {
159
+ const [width, setWidth] = useState(0);
160
+ useLayoutEffect(() => {
161
+ const el = ref.current;
162
+ if (!el) return;
163
+ let frame = 0;
164
+ const measure = () => {
165
+ const next = Math.round(el.getBoundingClientRect().width);
166
+ setWidth((prev) => prev === next ? prev : next);
167
+ };
168
+ measure();
169
+ if (typeof ResizeObserver === "undefined") return;
170
+ const observer = new ResizeObserver(() => {
171
+ measure();
172
+ cancelAnimationFrame(frame);
173
+ frame = requestAnimationFrame(measure);
174
+ });
175
+ observer.observe(el);
176
+ return () => {
177
+ cancelAnimationFrame(frame);
178
+ observer.disconnect();
179
+ };
180
+ }, [ref]);
181
+ return width;
182
+ }
183
+ /**
184
+ * One row's sparkline. Its own element, its own `ResizeObserver` and its own
185
+ * View — the spec and the embed options are the column's, shared by identity
186
+ * across every row, so a re-render of the table never re-embeds a cell.
187
+ *
188
+ * `aria-hidden`: the reading is text in this cell and its neighbours, and the
189
+ * series' shape is spelled out in the cell's own sentence. There is nothing
190
+ * focusable inside, so hiding the subtree hides nothing reachable.
191
+ */
192
+ function TrendCell({ cell, spec, options, height, latest, onEmbed }) {
193
+ const ref = useRef(null);
194
+ const width = useContainerWidth(ref);
195
+ const datasets = useMemo(() => {
196
+ const points = cell.points ?? EMPTY_POINTS;
197
+ const withValue = points.filter((point) => point.y != null);
198
+ return {
199
+ [TABLE_TREND_DATASET]: points,
200
+ [TABLE_TREND_LATEST_DATASET]: latest && withValue.length > 0 ? [withValue[withValue.length - 1]] : []
201
+ };
202
+ }, [cell.points, latest]);
203
+ const drawable = (cell.trend?.count ?? 0) > 1;
204
+ return /* @__PURE__ */ jsx("div", {
205
+ ref,
206
+ "aria-hidden": "true",
207
+ style: { height },
208
+ className: "min-w-0 flex-1",
209
+ children: drawable && width > 0 && /* @__PURE__ */ jsx(ArcVegaChart, {
210
+ spec,
211
+ options,
212
+ datasets,
213
+ width,
214
+ height,
215
+ onEmbed,
216
+ "data-part": "sparkline",
217
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
218
+ variant: "rounded",
219
+ className: "w-full",
220
+ style: { height }
221
+ })
222
+ })
223
+ });
224
+ }
225
+ function BarCell({ cell, size, showValue }) {
226
+ const sizes = sizeStyles[size];
227
+ return /* @__PURE__ */ jsxs("div", {
228
+ className: "flex min-w-0 items-center justify-end gap-2",
229
+ children: [
230
+ /* @__PURE__ */ jsx("div", {
231
+ "aria-hidden": "true",
232
+ className: meterTrackVariants({
233
+ size: sizes.meter,
234
+ className: "min-w-8 flex-1"
235
+ }),
236
+ children: cell.share != null && /* @__PURE__ */ jsx("div", {
237
+ className: meterIndicatorVariants({ className: "bg-chart-emphasis" }),
238
+ style: { width: `${cell.share * 100}%` }
239
+ })
240
+ }),
241
+ showValue ? /* @__PURE__ */ jsx("span", {
242
+ className: "shrink-0 tabular-nums",
243
+ children: cell.text
244
+ }) : /* @__PURE__ */ jsx("span", {
245
+ className: "sr-only",
246
+ children: cell.text
247
+ }),
248
+ cell.detail && /* @__PURE__ */ jsx("span", {
249
+ className: "sr-only",
250
+ children: cell.detail
251
+ })
252
+ ]
253
+ });
254
+ }
255
+ function DeltaCell({ cell, labels }) {
256
+ if (cell.delta == null) return /* @__PURE__ */ jsx("span", {
257
+ className: "tabular-nums text-muted-foreground",
258
+ children: "—"
259
+ });
260
+ const { direction, polarity } = cell.delta;
261
+ const word = direction === "up" ? labels.increase : direction === "down" ? labels.decrease : labels.unchanged;
262
+ return /* @__PURE__ */ jsxs("span", {
263
+ className: cn("inline-flex items-center gap-1 font-medium", deltaTextClasses[polarity]),
264
+ children: [
265
+ /* @__PURE__ */ jsx(DeltaGlyph, {
266
+ direction,
267
+ className: "size-2.5 shrink-0"
268
+ }),
269
+ /* @__PURE__ */ jsx("span", {
270
+ className: "tabular-nums",
271
+ children: cell.delta.text
272
+ }),
273
+ /* @__PURE__ */ jsx("span", {
274
+ className: "sr-only",
275
+ children: word
276
+ })
277
+ ]
278
+ });
279
+ }
280
+ /**
281
+ * An analytical table for dashboard grids and analysis pages: many entities
282
+ * down the page, several measures across it, each column encoding what its
283
+ * kind calls for — exact numbers, magnitude bars on the column's own
284
+ * zero-based scale, signed deltas, categories, and a sparkline per row — with
285
+ * sortable headings, a rank, an honest cap, opt-in footer totals, every
286
+ * loading, empty, no-match, error and stale state, and a generated
287
+ * description and insight sentence.
288
+ *
289
+ * It is the family's form for the question a plot cannot take: more measures
290
+ * per entity than a chart can encode, exact values, and long names. Only the
291
+ * trend column is a chart; every other cell is an Arc primitive.
292
+ */
293
+ const TableCard = forwardRef(({ label, name: nameProp, hint, columns, rows = EMPTY_ROWS, rowKey, status = "ready", expectedRows = 5, filtered = false, onClearFilters, onRetry, sort: sortProp, defaultSort, onSortChange, sortable = true, showRank = false, maxRows = 10, totalRows, onShowAll, selectedRow: selectedRowProp, defaultSelectedRow, onRowSelect, size = "md", maxHeight, stickyColumn = true, zebra = false, trendMark = "line", trendLatest = true, insight, description, caption, updatedAt, updatedAtFormat = "day", stale = false, locale, timeZone, labels: labelsProp, action, titleLevel = 4, theme, patchSpec, onEmbed, className, render, ...rest }, forwardedRef) => {
294
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
295
+ const headingId = useId();
296
+ const descriptionId = useId();
297
+ const hintId = useId();
298
+ const captionId = useId();
299
+ const labels = resolveTableLabels(labelsProp);
300
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
301
+ const sizes = sizeStyles[size];
302
+ const tokens = useChartTokens(rootRef, theme);
303
+ const cardWidth = useContainerWidth(rootRef);
304
+ const isSortControlled = sortProp !== void 0;
305
+ const [sortState, setSortState] = useState(defaultSort ?? null);
306
+ const sort = isSortControlled ? sortProp ?? null : sortState;
307
+ const sortInteractive = sortable && (!isSortControlled || onSortChange != null);
308
+ const [announcement, setAnnouncement] = useState("");
309
+ const applySort = useCallback((column) => {
310
+ const next = nextSort(sort, column);
311
+ if (!isSortControlled) setSortState(next);
312
+ onSortChange?.(next);
313
+ setAnnouncement(labels.sortedBy(column.header, next.direction));
314
+ }, [
315
+ sort,
316
+ isSortControlled,
317
+ onSortChange,
318
+ labels
319
+ ]);
320
+ const isSelectionControlled = selectedRowProp !== void 0;
321
+ const [selectionState, setSelectionState] = useState(defaultSelectedRow ?? null);
322
+ const selectedRow = isSelectionControlled ? selectedRowProp ?? null : selectionState;
323
+ const selectRow = useCallback((key) => {
324
+ if (!isSelectionControlled) setSelectionState(key);
325
+ onRowSelect?.(key);
326
+ }, [isSelectionControlled, onRowSelect]);
327
+ const state = deriveTableState({
328
+ status,
329
+ rowCount: rows.length,
330
+ filtered
331
+ });
332
+ const showContent = state === "ready" || state === "refreshing";
333
+ const summary = useMemo(() => normalizeTable({
334
+ columns,
335
+ rows,
336
+ rowKey,
337
+ sort,
338
+ maxRows,
339
+ totalRows,
340
+ showRank,
341
+ locale,
342
+ timeZone,
343
+ labels
344
+ }), [
345
+ columns,
346
+ rows,
347
+ rowKey,
348
+ sort,
349
+ maxRows,
350
+ totalRows,
351
+ showRank,
352
+ locale,
353
+ timeZone,
354
+ labels
355
+ ]);
356
+ const visibleColumns = useMemo(() => summary.columns.filter((column) => {
357
+ const below = column.column.hideBelow;
358
+ return below == null || cardWidth === 0 || cardWidth >= below;
359
+ }), [summary.columns, cardWidth]);
360
+ const rankVisible = showRank && sort != null && summary.sortColumn != null;
361
+ const hasTotals = visibleColumns.some((column) => column.totalText != null);
362
+ const trendColumns = visibleColumns.filter((column) => column.kind === "trend");
363
+ const hasTrend = trendColumns.length > 0;
364
+ const rowHeaderId = visibleColumns.find((column) => column.kind === "text")?.id;
365
+ const cardDescription = description ?? buildTableDescription({
366
+ name,
367
+ summary,
368
+ labels,
369
+ sort,
370
+ updatedAt,
371
+ updatedAtFormat,
372
+ locale,
373
+ timeZone
374
+ });
375
+ const generatedInsight = useMemo(() => insight === void 0 ? buildTableInsight({
376
+ summary,
377
+ labels,
378
+ locale
379
+ }) : null, [
380
+ insight,
381
+ summary,
382
+ labels,
383
+ locale
384
+ ]);
385
+ const insightNode = insight === false ? null : insight === void 0 ? generatedInsight : insight;
386
+ const trendColumn = trendColumns[0]?.column;
387
+ const trendId = trendColumn ? columnId(trendColumn) : "";
388
+ const xType = useMemo(() => {
389
+ const sample = summary.rows.find((row) => (row.byField[trendId]?.points?.length ?? 0) > 0)?.byField[trendId]?.points?.[0];
390
+ if (sample == null) return "quantitative";
391
+ if (typeof sample.x === "number") return "quantitative";
392
+ return trendColumn?.granularity != null ? "temporal" : "ordinal";
393
+ }, [
394
+ summary.rows,
395
+ trendColumn,
396
+ trendId
397
+ ]);
398
+ const tooltipFormat = trendColumn != null ? columnValueFormat(trendColumn, locale) : "compact";
399
+ const tooltip = useArcTooltipHandler({ content: ({ item }) => {
400
+ const datum = item.datum?.datum ?? item.datum;
401
+ if (datum == null || datum.y == null) return null;
402
+ const xText = xType === "temporal" ? formatDate(datum.x, {
403
+ granularity: trendColumn?.granularity ?? "day",
404
+ locale,
405
+ timeZone
406
+ }) : String(datum.x);
407
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows: [[trendColumn?.header ?? "", formatNumber(datum.y, tooltipFormat, locale)], ["", xText]] });
408
+ } });
409
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltip.handler }), [tooltip.handler]);
410
+ const trendSpec = useMemo(() => {
411
+ if (tokens == null || trendColumn == null) return null;
412
+ const spec = buildTableTrendSpec({
413
+ mark: trendColumn.mark ?? trendMark,
414
+ height: sizes.trend,
415
+ latest: trendLatest,
416
+ tooltip: true,
417
+ xType,
418
+ xTitle: trendColumn.header,
419
+ yTitle: trendColumn.header,
420
+ valueFormat: tooltipFormat,
421
+ timeFormat: trendTimeFormat(trendColumn.granularity, locale, timeZone),
422
+ locale,
423
+ timeZone
424
+ }, tokens);
425
+ return patchSpec ? patchSpec(spec) : spec;
426
+ }, [
427
+ tokens,
428
+ trendColumn,
429
+ trendMark,
430
+ sizes.trend,
431
+ trendLatest,
432
+ xType,
433
+ tooltipFormat,
434
+ locale,
435
+ timeZone,
436
+ patchSpec
437
+ ]);
438
+ const bodyRows = showContent ? summary.count : Math.max(1, expectedRows);
439
+ const bodyMinHeight = sizes.headerPx + bodyRows * sizes.rowPx + (hasTotals ? sizes.rowPx : 0);
440
+ const busy = state === "loading" || state === "refreshing";
441
+ const HeadingTag = `h${titleLevel}`;
442
+ const columnCount = visibleColumns.length;
443
+ useEffect(() => {
444
+ if (announcement === "") return;
445
+ const timer = setTimeout(() => setAnnouncement(""), 1e3);
446
+ return () => clearTimeout(timer);
447
+ }, [announcement]);
448
+ const isRowHeaderColumn = (column) => column.id === rowHeaderId;
449
+ const renderCell = (cell, column, row) => {
450
+ switch (column.kind) {
451
+ case "text": {
452
+ const drillable = onRowSelect != null;
453
+ const ordinal = rankVisible && isRowHeaderColumn(column) && row.rank != null ? row.rank : null;
454
+ const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
455
+ className: "block truncate",
456
+ children: [ordinal != null && /* @__PURE__ */ jsxs("span", {
457
+ className: "mr-1.5 font-normal tabular-nums text-muted-foreground",
458
+ "data-part": "rank",
459
+ children: [/* @__PURE__ */ jsxs("span", {
460
+ className: "sr-only",
461
+ children: [labels.rank(ordinal), ", "]
462
+ }), /* @__PURE__ */ jsxs("span", {
463
+ "aria-hidden": "true",
464
+ children: [ordinal, "."]
465
+ })]
466
+ }), cell.text]
467
+ }), cell.caption && /* @__PURE__ */ jsx("span", {
468
+ className: "block truncate text-xs font-normal text-muted-foreground",
469
+ children: cell.caption
470
+ })] });
471
+ if (!drillable) return /* @__PURE__ */ jsx("span", {
472
+ className: "block min-w-0",
473
+ children: body
474
+ });
475
+ return /* @__PURE__ */ jsx(Button, {
476
+ variant: "ghost",
477
+ size: "sm",
478
+ onClick: () => selectRow(row.key),
479
+ "aria-label": labels.openRow(row.label),
480
+ className: cn(cellButtonClasses, "-mx-2 w-full justify-start px-2 py-1 text-left hover:underline"),
481
+ children: body
482
+ });
483
+ }
484
+ case "number": return /* @__PURE__ */ jsx("span", {
485
+ className: "tabular-nums",
486
+ children: cell.text
487
+ });
488
+ case "bar": return /* @__PURE__ */ jsx(BarCell, {
489
+ cell,
490
+ size,
491
+ showValue: column.column.showValue ?? true
492
+ });
493
+ case "delta": return /* @__PURE__ */ jsx(DeltaCell, {
494
+ cell,
495
+ labels
496
+ });
497
+ case "badge": return cell.sortValue == null ? /* @__PURE__ */ jsx("span", {
498
+ className: "text-muted-foreground",
499
+ children: "—"
500
+ }) : /* @__PURE__ */ jsx(Badge, {
501
+ variant: toneBadgeVariant[cell.tone ?? "neutral"],
502
+ appearance: "soft",
503
+ size: sizes.badge,
504
+ className: "max-w-full",
505
+ title: cell.text,
506
+ children: /* @__PURE__ */ jsx("span", {
507
+ className: "truncate",
508
+ children: cell.text
509
+ })
510
+ });
511
+ case "trend": return /* @__PURE__ */ jsxs("div", {
512
+ className: "flex min-w-0 items-center gap-2",
513
+ children: [trendSpec != null ? /* @__PURE__ */ jsx(TrendCell, {
514
+ cell,
515
+ spec: trendSpec,
516
+ options: embedOptions,
517
+ height: sizes.trend,
518
+ latest: trendLatest,
519
+ onEmbed
520
+ }) : /* @__PURE__ */ jsx("div", {
521
+ "aria-hidden": "true",
522
+ style: { height: sizes.trend },
523
+ className: "min-w-0 flex-1"
524
+ }), /* @__PURE__ */ jsx("span", {
525
+ className: "sr-only",
526
+ children: cell.detail
527
+ })]
528
+ });
529
+ }
530
+ };
531
+ return /* @__PURE__ */ jsxs(Box, {
532
+ ref: setRootRef,
533
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
534
+ padding: sizes.padding,
535
+ "aria-labelledby": headingId,
536
+ "aria-describedby": descriptionId,
537
+ "aria-busy": busy || void 0,
538
+ "data-status": status,
539
+ "data-state": state,
540
+ "data-size": size,
541
+ "data-sort": sort ? `${sort.field}:${sort.direction}` : void 0,
542
+ "data-rows": showContent ? summary.count : void 0,
543
+ "data-truncated": showContent && summary.truncated > 0 ? summary.truncated : void 0,
544
+ "data-selected": showContent ? selectedRow ?? void 0 : void 0,
545
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
546
+ "data-stale": stale ? "" : void 0,
547
+ "data-insufficient": showContent && summary.insufficient ? "" : void 0,
548
+ "data-trend": hasTrend ? "" : void 0,
549
+ ...rest,
550
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
551
+ children: [
552
+ /* @__PURE__ */ jsxs("div", {
553
+ className: "flex items-center justify-between gap-2",
554
+ children: [/* @__PURE__ */ jsxs("div", {
555
+ className: "flex min-w-0 items-center gap-1",
556
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
557
+ id: headingId,
558
+ className: cn(labelClasses, sizes.label, "line-clamp-2"),
559
+ children: label
560
+ }), hint && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
561
+ render: /* @__PURE__ */ jsx(Button, {
562
+ variant: "ghost",
563
+ size: "sm",
564
+ iconOnly: true,
565
+ circular: true,
566
+ "aria-label": labels.about(name),
567
+ "aria-describedby": hintId,
568
+ className: headerControlClasses
569
+ }),
570
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
571
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: hint })] }), /* @__PURE__ */ jsx("span", {
572
+ id: hintId,
573
+ className: "sr-only",
574
+ children: hint
575
+ })] })]
576
+ }), /* @__PURE__ */ jsxs("div", {
577
+ className: "flex shrink-0 items-center gap-1",
578
+ children: [stale && /* @__PURE__ */ jsx(Badge, {
579
+ variant: "warning",
580
+ appearance: "soft",
581
+ size: "sm",
582
+ children: labels.stale
583
+ }), action]
584
+ })]
585
+ }),
586
+ /* @__PURE__ */ jsx("p", {
587
+ id: descriptionId,
588
+ className: "sr-only",
589
+ children: cardDescription
590
+ }),
591
+ /* @__PURE__ */ jsx("p", {
592
+ role: "status",
593
+ "aria-live": "polite",
594
+ className: "sr-only",
595
+ "data-part": "announcement",
596
+ children: announcement
597
+ }),
598
+ /* @__PURE__ */ jsxs("div", {
599
+ className: "relative flex min-w-0 flex-col",
600
+ style: { minHeight: bodyMinHeight },
601
+ children: [
602
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
603
+ className: "flex flex-col gap-2",
604
+ "data-part": "rows-skeleton",
605
+ children: [/* @__PURE__ */ jsx("div", {
606
+ className: "flex items-center gap-3",
607
+ style: { height: sizes.headerPx },
608
+ children: Array.from({ length: Math.max(2, columnCount) }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
609
+ variant: "rounded",
610
+ className: cn("h-3", index === 0 ? "flex-[2]" : "flex-1")
611
+ }, index))
612
+ }), Array.from({ length: Math.max(1, expectedRows) }, (_, row) => /* @__PURE__ */ jsx("div", {
613
+ className: "flex items-center gap-3",
614
+ style: { height: sizes.rowPx },
615
+ children: Array.from({ length: Math.max(2, columnCount) }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
616
+ variant: "rounded",
617
+ className: cn("h-3", index === 0 ? "flex-[2]" : "flex-1")
618
+ }, index))
619
+ }, row))]
620
+ }),
621
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
622
+ variant: "server-error",
623
+ scope: "inline",
624
+ title: labels.error,
625
+ description: false,
626
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
627
+ size: "sm",
628
+ onClick: onRetry,
629
+ children: labels.retry
630
+ }) : void 0,
631
+ className: cn(panelStateClasses, "my-auto")
632
+ }),
633
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
634
+ variant: "empty",
635
+ scope: "inline",
636
+ title: labels.noData,
637
+ description: false,
638
+ className: cn(panelStateClasses, "my-auto")
639
+ }),
640
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
641
+ variant: "no-results",
642
+ scope: "inline",
643
+ title: labels.noMatch,
644
+ description: false,
645
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
646
+ variant: "outline",
647
+ size: "sm",
648
+ onClick: onClearFilters,
649
+ children: labels.clearFilters
650
+ }) : void 0,
651
+ className: cn(panelStateClasses, "my-auto")
652
+ }),
653
+ showContent && /* @__PURE__ */ jsxs("div", {
654
+ className: cn("flex min-w-0 flex-col", sizes.gap),
655
+ children: [
656
+ /* @__PURE__ */ jsx(TableContainer, {
657
+ variant: "plain",
658
+ style: maxHeight != null ? { maxHeight } : void 0,
659
+ className: "min-w-0",
660
+ children: /* @__PURE__ */ jsxs(Table, {
661
+ size: sizes.table,
662
+ layout: "fixed",
663
+ stickyHeader: maxHeight != null,
664
+ stickyFirstColumn: stickyColumn,
665
+ striped: zebra,
666
+ hoverable: true,
667
+ className: cn("w-full", sizes.body),
668
+ "data-part": "table",
669
+ children: [
670
+ /* @__PURE__ */ jsx(TableCaption, {
671
+ side: "bottom",
672
+ id: captionId,
673
+ className: "sr-only",
674
+ children: caption ?? labels.caption(name)
675
+ }),
676
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, { children: visibleColumns.map((column) => {
677
+ const active = sort?.field === column.id;
678
+ const direction = active ? sort?.direction ?? null : null;
679
+ const canSort = sortInteractive && column.sortable;
680
+ const heading = /* @__PURE__ */ jsxs("span", {
681
+ className: "flex min-w-0 flex-col items-start",
682
+ children: [/* @__PURE__ */ jsx("span", {
683
+ className: "truncate",
684
+ children: column.header
685
+ }), column.column.unit && /* @__PURE__ */ jsx("span", {
686
+ className: "truncate text-xs font-normal text-muted-foreground",
687
+ children: column.column.unit
688
+ })]
689
+ });
690
+ return /* @__PURE__ */ jsx(TableHead, {
691
+ scope: "col",
692
+ "aria-sort": active ? direction === "asc" ? "ascending" : "descending" : canSort ? "none" : void 0,
693
+ "data-column": column.id,
694
+ "data-field": column.field,
695
+ "data-kind": column.kind,
696
+ style: column.column.width != null ? { width: column.column.width } : void 0,
697
+ className: cn("min-w-0", column.align === "end" && "text-right"),
698
+ children: /* @__PURE__ */ jsxs("span", {
699
+ className: cn("flex min-w-0 items-center gap-1", column.align === "end" && "justify-end"),
700
+ children: [canSort ? /* @__PURE__ */ jsxs(Button, {
701
+ variant: "ghost",
702
+ size: "sm",
703
+ onClick: () => applySort(column),
704
+ "aria-label": active ? direction === "asc" ? labels.sortDescending(column.header) : labels.sortAscending(column.header) : labels.sortBy(column.header),
705
+ className: cn(sortButtonClasses, column.align === "end" && "justify-end"),
706
+ children: [heading, /* @__PURE__ */ jsx(SortIcon, {
707
+ direction,
708
+ className: cn("size-3 shrink-0", active ? "text-foreground" : "text-muted-foreground")
709
+ })]
710
+ }) : heading, column.column.hint && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
711
+ render: /* @__PURE__ */ jsx(Button, {
712
+ variant: "ghost",
713
+ size: "sm",
714
+ iconOnly: true,
715
+ circular: true,
716
+ "aria-label": labels.about(column.header),
717
+ className: cn(headerControlClasses, "size-5 shrink-0")
718
+ }),
719
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3" })
720
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: column.column.hint })] })]
721
+ })
722
+ }, column.id);
723
+ }) }) }),
724
+ /* @__PURE__ */ jsx(TableBody, { children: summary.rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
725
+ selected: selectedRow === row.key,
726
+ "data-row": row.key,
727
+ style: { height: sizes.rowPx },
728
+ children: visibleColumns.map((column) => {
729
+ const cell = row.byField[column.id];
730
+ const isRowHeader = isRowHeaderColumn(column);
731
+ const content = cell != null ? renderCell(cell, column, row) : /* @__PURE__ */ jsx("span", {
732
+ className: "text-muted-foreground",
733
+ children: "—"
734
+ });
735
+ return /* @__PURE__ */ jsx(TableCell, {
736
+ render: isRowHeader ? /* @__PURE__ */ jsx("th", { scope: "row" }) : void 0,
737
+ "data-column": column.id,
738
+ "data-field": column.field,
739
+ className: cn("min-w-0", column.align === "end" && "text-right", isRowHeader && "font-medium text-foreground"),
740
+ children: content
741
+ }, column.id);
742
+ })
743
+ }, row.key)) }),
744
+ hasTotals && /* @__PURE__ */ jsx(TableFooter, { children: /* @__PURE__ */ jsx(TableRow, {
745
+ style: { height: sizes.rowPx },
746
+ children: visibleColumns.map((column, index) => {
747
+ const first = index === 0;
748
+ return /* @__PURE__ */ jsx(TableCell, {
749
+ render: first ? /* @__PURE__ */ jsx("th", { scope: "row" }) : void 0,
750
+ "data-column": column.id,
751
+ "data-field": column.field,
752
+ className: cn("min-w-0 font-medium", column.align === "end" && "text-right", column.totalText != null && "tabular-nums"),
753
+ children: first ? labels.total : column.totalText ?? null
754
+ }, column.id);
755
+ })
756
+ }) })
757
+ ]
758
+ })
759
+ }),
760
+ insightNode && /* @__PURE__ */ jsx("p", {
761
+ className: insightClasses,
762
+ "data-part": "insight",
763
+ children: insightNode
764
+ }),
765
+ (summary.truncated > 0 || summary.insufficient || updatedAt != null) && /* @__PURE__ */ jsxs("div", {
766
+ className: cn("flex flex-wrap items-center gap-x-3 gap-y-1", captionClasses),
767
+ children: [
768
+ summary.truncated > 0 && /* @__PURE__ */ jsxs("span", {
769
+ "data-part": "truncation",
770
+ children: [labels.showingTopOf(summary.count, summary.total), onShowAll && /* @__PURE__ */ jsx(Button, {
771
+ variant: "ghost",
772
+ size: "sm",
773
+ onClick: onShowAll,
774
+ className: "ml-1 h-auto px-1 py-0 text-xs underline",
775
+ children: labels.showAll
776
+ })]
777
+ }),
778
+ summary.insufficient && /* @__PURE__ */ jsx("span", {
779
+ "data-part": "insufficient",
780
+ children: labels.insufficient
781
+ }),
782
+ updatedAt != null && /* @__PURE__ */ jsxs("span", {
783
+ "data-part": "as-of",
784
+ children: [
785
+ labels.asOf,
786
+ " ",
787
+ formatDate(updatedAt, {
788
+ granularity: updatedAtFormat,
789
+ locale,
790
+ timeZone
791
+ })
792
+ ]
793
+ })
794
+ ]
795
+ })
796
+ ]
797
+ }),
798
+ state === "refreshing" && /* @__PURE__ */ jsx("div", {
799
+ className: "absolute inset-0 z-10 rounded-md bg-veil",
800
+ "aria-hidden": "true",
801
+ "data-part": "veil"
802
+ })
803
+ ]
804
+ }),
805
+ tooltip.tooltip
806
+ ]
807
+ });
808
+ });
809
+ TableCard.displayName = "TableCard";
810
+ //#endregion
811
+ export { TableCard };