@nadicodeai/ui 6.6.2 → 7.1.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 (79) hide show
  1. package/AGENTS.md +4 -4
  2. package/LICENSES/LOBEHUB-ICONS-MIT.txt +21 -0
  3. package/NOTICE +14 -0
  4. package/dist/components/activity-orb.d.ts +1 -1
  5. package/dist/components/activity-orb.d.ts.map +1 -1
  6. package/dist/components/brand-icons/claude.d.ts +18 -0
  7. package/dist/components/brand-icons/claude.d.ts.map +1 -0
  8. package/dist/components/brand-icons/claude.js +22 -0
  9. package/dist/components/brand-icons/deepseek.d.ts +18 -0
  10. package/dist/components/brand-icons/deepseek.d.ts.map +1 -0
  11. package/dist/components/brand-icons/deepseek.js +22 -0
  12. package/dist/components/brand-icons/gemini.d.ts +18 -0
  13. package/dist/components/brand-icons/gemini.d.ts.map +1 -0
  14. package/dist/components/brand-icons/gemini.js +25 -0
  15. package/dist/components/brand-icons/grok.d.ts +18 -0
  16. package/dist/components/brand-icons/grok.d.ts.map +1 -0
  17. package/dist/components/brand-icons/grok.js +22 -0
  18. package/dist/components/brand-icons/meta.d.ts +18 -0
  19. package/dist/components/brand-icons/meta.d.ts.map +1 -0
  20. package/dist/components/brand-icons/meta.js +25 -0
  21. package/dist/components/brand-icons/mistral.d.ts +18 -0
  22. package/dist/components/brand-icons/mistral.d.ts.map +1 -0
  23. package/dist/components/brand-icons/mistral.js +22 -0
  24. package/dist/components/brand-icons/moonshot.d.ts +18 -0
  25. package/dist/components/brand-icons/moonshot.d.ts.map +1 -0
  26. package/dist/components/brand-icons/moonshot.js +22 -0
  27. package/dist/components/brand-icons/openai.d.ts +18 -0
  28. package/dist/components/brand-icons/openai.d.ts.map +1 -0
  29. package/dist/components/brand-icons/openai.js +22 -0
  30. package/dist/components/brand-icons/qwen.d.ts +18 -0
  31. package/dist/components/brand-icons/qwen.d.ts.map +1 -0
  32. package/dist/components/brand-icons/qwen.js +25 -0
  33. package/dist/components/brand-icons.d.ts +9 -0
  34. package/dist/components/brand-icons.d.ts.map +1 -1
  35. package/dist/components/brand-icons.js +9 -0
  36. package/dist/components/chart-breakdown-table.d.ts +26 -6
  37. package/dist/components/chart-breakdown-table.d.ts.map +1 -1
  38. package/dist/components/chart-breakdown-table.js +4 -4
  39. package/dist/components/chart-time-series.d.ts +68 -2
  40. package/dist/components/chart-time-series.d.ts.map +1 -1
  41. package/dist/components/chart-time-series.js +112 -11
  42. package/dist/components/chart.d.ts +2 -1
  43. package/dist/components/chart.d.ts.map +1 -1
  44. package/dist/components/chart.js +11 -2
  45. package/dist/components/model-mark.d.ts +19 -0
  46. package/dist/components/model-mark.d.ts.map +1 -0
  47. package/dist/components/model-mark.js +66 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -0
  51. package/docs/activity.md +4 -5
  52. package/docs/contract.md +48 -5
  53. package/package.json +3 -7
  54. package/dist/components/threshold/canvas-budget.d.ts +0 -10
  55. package/dist/components/threshold/canvas-budget.d.ts.map +0 -1
  56. package/dist/components/threshold/canvas-budget.js +0 -54
  57. package/dist/components/threshold/index.d.ts +0 -6
  58. package/dist/components/threshold/index.d.ts.map +0 -1
  59. package/dist/components/threshold/index.js +0 -5
  60. package/dist/components/threshold/orb.d.ts +0 -14
  61. package/dist/components/threshold/orb.d.ts.map +0 -1
  62. package/dist/components/threshold/orb.js +0 -17
  63. package/dist/components/threshold/presence.d.ts +0 -27
  64. package/dist/components/threshold/presence.d.ts.map +0 -1
  65. package/dist/components/threshold/presence.js +0 -159
  66. package/dist/components/threshold/room.d.ts +0 -33
  67. package/dist/components/threshold/room.d.ts.map +0 -1
  68. package/dist/components/threshold/room.js +0 -168
  69. package/dist/components/threshold/threshold.css +0 -228
  70. package/dist/components/threshold/typed-line.d.ts +0 -8
  71. package/dist/components/threshold/typed-line.d.ts.map +0 -1
  72. package/dist/components/threshold/typed-line.js +0 -43
  73. package/dist/components/threshold/use-reduced-motion.d.ts +0 -10
  74. package/dist/components/threshold/use-reduced-motion.d.ts.map +0 -1
  75. package/dist/components/threshold/use-reduced-motion.js +0 -32
  76. package/dist/threshold.d.ts +0 -2
  77. package/dist/threshold.d.ts.map +0 -1
  78. package/dist/threshold.js +0 -13
  79. package/docs/threshold.md +0 -221
@@ -7,19 +7,35 @@ import { type EntityColorMap } from "./chart-entity-colors.js";
7
7
  *
8
8
  * Real `<table>` semantics: each entity row reads name + value + proportion
9
9
  * bar + share of total, with the entity-color dot resolved from the shared
10
- * page-context assignment map. `topN` folds the tail into a single
11
- * "everything else" rollup row on the neutral register — never an extra hue.
12
- * When the caller has already bounded the read store-side, `otherValue` passes
13
- * the precomputed tail sum instead: the given entities render as-is (no local
14
- * fold) and the rollup row and shares account for that tail.
10
+ * page-context assignment map. An entity may also carry a `mark`, printed
11
+ * between its dot and its name — a model's maker's mark, so color never
12
+ * identifies it alone (rule `model-shown-with-logo`). `topN` folds the tail
13
+ * into a single "everything else" rollup row on the neutral register — never an
14
+ * extra hue. When the caller has already bounded the read store-side,
15
+ * `otherValue` passes the precomputed tail sum instead: the given entities
16
+ * render as-is (no local fold) and the rollup row and shares account for that
17
+ * tail.
18
+ *
19
+ * `value` is a float and is for geometry only: sorting, the fold and the share
20
+ * bars. A printed figure that must not cross a float (money) comes in as
21
+ * `display`, `otherDisplay` and `totalDisplay`, text the caller formatted from
22
+ * the exact amount and the table prints as given.
15
23
  */
16
24
  type BreakdownEntity = {
17
25
  /** Stable entity id — the key the entity-color map assigns by. */
18
26
  id: string;
19
27
  label: React.ReactNode;
20
28
  value: number;
29
+ /** The value's printed text; without it the table formats `value`. */
30
+ display?: string;
31
+ /**
32
+ * The entity's mark, printed beside its label. A model row carries its
33
+ * maker's mark here; the mark is decorative by contract, because the label
34
+ * is the entity's accessible name.
35
+ */
36
+ mark?: React.ReactNode;
21
37
  };
22
- declare function BreakdownTable({ entities, entityColorMap, label, topN, otherValue, valueFormatter, columnLabels, otherLabel, totalLabel, }: {
38
+ declare function BreakdownTable({ entities, entityColorMap, label, topN, otherValue, otherDisplay, totalDisplay, valueFormatter, columnLabels, otherLabel, totalLabel, }: {
23
39
  entities: readonly BreakdownEntity[];
24
40
  /** The shared page-context entity-color assignment map. */
25
41
  entityColorMap: EntityColorMap;
@@ -33,6 +49,10 @@ declare function BreakdownTable({ entities, entityColorMap, label, topN, otherVa
33
49
  * the neutral rollup row and the share denominator.
34
50
  */
35
51
  otherValue?: number;
52
+ /** The printed text of `otherValue`, as an entity's `display` is for its value. */
53
+ otherDisplay?: string;
54
+ /** The printed text of the footer total, which the table otherwise sums as floats. */
55
+ totalDisplay?: string;
36
56
  valueFormatter?: (value: number) => string;
37
57
  columnLabels?: {
38
58
  entity?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"chart-breakdown-table.d.ts","sourceRoot":"","sources":["../../src/components/chart-breakdown-table.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;;;;;GAYG;AAEH,KAAK,eAAe,GAAG;IACrB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA+EF,iBAAS,cAAc,CAAC,EACtB,QAAQ,EACR,cAAc,EACd,KAAK,EACL,IAAI,EACJ,UAAU,EACV,cAA8B,EAC9B,YAAY,EACZ,UAA8B,EAC9B,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,YAAY,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,wCAAwC;IACxC,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B,qBAmEA;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"chart-breakdown-table.d.ts","sourceRoot":"","sources":["../../src/components/chart-breakdown-table.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,KAAK,eAAe,GAAG;IACrB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB,CAAC;AAsFF,iBAAS,cAAc,CAAC,EACtB,QAAQ,EACR,cAAc,EACd,KAAK,EACL,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,cAA8B,EAC9B,YAAY,EACZ,UAA8B,EAC9B,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,YAAY,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,wCAAwC;IACxC,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B,qBAuEA;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,eAAe,EAAE,CAAC"}
@@ -15,12 +15,12 @@ function defaultFormat(value) {
15
15
  function ShareBar({ share, color }) {
16
16
  return (_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { "data-slot": "breakdown-table-bar", "aria-hidden": "true", className: "h-1.5 min-w-0 flex-1 overflow-hidden rounded-xs bg-muted", children: _jsx("span", { className: "block h-full rounded-r-xs", style: { width: `${Number(share.toFixed(3))}%`, backgroundColor: color } }) }), _jsx("span", { className: "shrink-0 text-right font-mono text-xs text-muted-foreground tabular-nums", children: formatShare(share) })] }));
17
17
  }
18
- function BreakdownRow({ label, value, share, color, shareLabel, valueFormatter, ...props }) {
19
- return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { className: tableCompactLayouts.standard.row, ...props, children: [_jsx(TableCell, { className: "wrap-anywhere font-medium whitespace-normal", children: _jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { "data-slot": "breakdown-table-dot", "aria-hidden": "true", className: "size-2 shrink-0 rounded-xs", style: { backgroundColor: color } }), _jsx("span", { className: "min-w-0", children: label })] }) }), _jsx(TableCell, { className: "text-right font-mono tabular-nums", children: valueFormatter(value) }), _jsx(TableCell, { className: tableCompactLayouts.standard.column, children: _jsx(ShareBar, { share: share, color: color }) })] }), _jsx(TableDetailRow, { colSpan: 2, facts: [
18
+ function BreakdownRow({ label, value, display, share, color, mark, shareLabel, valueFormatter, ...props }) {
19
+ return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { className: tableCompactLayouts.standard.row, ...props, children: [_jsx(TableCell, { className: "wrap-anywhere font-medium whitespace-normal", children: _jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { "data-slot": "breakdown-table-dot", "aria-hidden": "true", className: "size-2 shrink-0 rounded-xs", style: { backgroundColor: color } }), mark, _jsx("span", { className: "min-w-0", children: label })] }) }), _jsx(TableCell, { className: "text-right font-mono tabular-nums", children: display ?? valueFormatter(value) }), _jsx(TableCell, { className: tableCompactLayouts.standard.column, children: _jsx(ShareBar, { share: share, color: color }) })] }), _jsx(TableDetailRow, { colSpan: 2, facts: [
20
20
  { id: "share", label: shareLabel, value: _jsx(ShareBar, { share: share, color: color }) },
21
21
  ] })] }));
22
22
  }
23
- function BreakdownTable({ entities, entityColorMap, label, topN, otherValue, valueFormatter = defaultFormat, columnLabels, otherLabel = "Everything else", totalLabel, }) {
23
+ function BreakdownTable({ entities, entityColorMap, label, topN, otherValue, otherDisplay, totalDisplay, valueFormatter = defaultFormat, columnLabels, otherLabel = "Everything else", totalLabel, }) {
24
24
  const providedOther = otherValue !== undefined;
25
25
  const sorted = [...entities].sort((a, b) => b.value - a.value);
26
26
  const visible = !providedOther && topN !== undefined && topN < sorted.length ? sorted.slice(0, topN) : sorted;
@@ -30,6 +30,6 @@ function BreakdownTable({ entities, entityColorMap, label, topN, otherValue, val
30
30
  const total = sorted.reduce((sum, entity) => sum + entity.value, 0) + (providedOther ? otherValue : 0);
31
31
  const showOther = providedOther ? otherValue > 0 : visible.length < sorted.length;
32
32
  const shareLabel = columnLabels?.share ?? "Share of total";
33
- return (_jsxs(Table, { "data-slot": "breakdown-table", className: "table-fixed text-sm", compactDetails: "standard", children: [_jsx(TableCaption, { className: "sr-only", children: label }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-1/2 whitespace-normal", children: columnLabels?.entity ?? "Entity" }), _jsx(TableHead, { className: "w-1/4 text-right whitespace-normal", children: columnLabels?.value ?? "Value" }), _jsx(TableHead, { className: cn("w-1/4 whitespace-normal", tableCompactLayouts.standard.column), children: shareLabel })] }) }), _jsxs(TableBody, { children: [visible.map((entity) => (_jsx(BreakdownRow, { label: entity.label, value: entity.value, share: percentOf(entity.value, total), color: entityColor(entityColorMap, entity.id), shareLabel: shareLabel, valueFormatter: valueFormatter }, entity.id))), showOther ? (_jsx(BreakdownRow, { "data-slot": "breakdown-table-other", label: otherLabel, value: foldedValue, share: percentOf(foldedValue, total), color: CHART_ENTITY_OTHER_COLOR, shareLabel: shareLabel, valueFormatter: valueFormatter })) : null] }), totalLabel ? (_jsx(TableFooter, { children: _jsx(BreakdownRow, { label: totalLabel, value: total, share: percentOf(total, total), color: CHART_ENTITY_OTHER_COLOR, shareLabel: shareLabel, valueFormatter: valueFormatter }) })) : null] }));
33
+ return (_jsxs(Table, { "data-slot": "breakdown-table", className: "table-fixed text-sm", compactDetails: "standard", children: [_jsx(TableCaption, { className: "sr-only", children: label }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-1/2 whitespace-normal", children: columnLabels?.entity ?? "Entity" }), _jsx(TableHead, { className: "w-1/4 text-right whitespace-normal", children: columnLabels?.value ?? "Value" }), _jsx(TableHead, { className: cn("w-1/4 whitespace-normal", tableCompactLayouts.standard.column), children: shareLabel })] }) }), _jsxs(TableBody, { children: [visible.map((entity) => (_jsx(BreakdownRow, { label: entity.label, value: entity.value, ...(entity.display !== undefined ? { display: entity.display } : {}), share: percentOf(entity.value, total), color: entityColor(entityColorMap, entity.id), mark: entity.mark, shareLabel: shareLabel, valueFormatter: valueFormatter }, entity.id))), showOther ? (_jsx(BreakdownRow, { "data-slot": "breakdown-table-other", label: otherLabel, value: foldedValue, ...(otherDisplay !== undefined ? { display: otherDisplay } : {}), share: percentOf(foldedValue, total), color: CHART_ENTITY_OTHER_COLOR, shareLabel: shareLabel, valueFormatter: valueFormatter })) : null] }), totalLabel ? (_jsx(TableFooter, { children: _jsx(BreakdownRow, { label: totalLabel, value: total, ...(totalDisplay !== undefined ? { display: totalDisplay } : {}), share: percentOf(total, total), color: CHART_ENTITY_OTHER_COLOR, shareLabel: shareLabel, valueFormatter: valueFormatter }) })) : null] }));
34
34
  }
35
35
  export { BreakdownTable };
@@ -5,6 +5,14 @@ type TimeSeriesSeries = {
5
5
  /** Stable entity id — the data key in each datum and the color-map key. */
6
6
  id: string;
7
7
  label: string;
8
+ /**
9
+ * The entity's mark, printed beside its name in the legend and its tooltip
10
+ * row. A model series carries its maker's mark here so colour never
11
+ * identifies it alone (rule `model-shown-with-logo`); the chart only places
12
+ * the node, and the mark stays decorative because the name beside it is the
13
+ * series' text.
14
+ */
15
+ mark?: React.ReactNode;
8
16
  };
9
17
  type TimeSeriesDatum = Record<string, number | string>;
10
18
  type TimeSeriesProps = Omit<React.ComponentProps<"figure">, "children"> & {
@@ -30,8 +38,66 @@ type TimeSeriesProps = Omit<React.ComponentProps<"figure">, "children"> & {
30
38
  /** Label for the folded neutral "Other" series (entities past the sixth). */
31
39
  otherLabel?: string;
32
40
  };
41
+ type FormattedSeries = {
42
+ id: string;
43
+ label: string;
44
+ /** Formats this series' values where it needs its own reading, e.g. a rate beside a sum. */
45
+ valueFormatter?: (value: number) => string;
46
+ };
33
47
  declare function TimeSeriesBars({ data, xKey, series, entityColorMap, label, xLabel, valueFormatter, yAxisWidth, otherLabel, barCategoryKey, size, className, ...props }: TimeSeriesProps): React.JSX.Element;
34
48
  declare function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel, valueFormatter, yAxisWidth, otherLabel, size, className, ...props }: TimeSeriesProps): React.JSX.Element;
35
- export { TimeSeriesArea, TimeSeriesBars };
36
- export type { TimeSeriesDatum, TimeSeriesProps, TimeSeriesSeries };
49
+ /**
50
+ * One series of a composed plot. A composed chart mixes measures that do not
51
+ * share a unit — sums as bars on the left axis, a rate as a line on the right —
52
+ * so each series states how it is drawn, which axis it reads, and how its own
53
+ * values print.
54
+ */
55
+ type TimeSeriesComposedSeries = FormattedSeries & {
56
+ /**
57
+ * The series' mark, printed beside its name in the legend and its tooltip
58
+ * row (rule `model-shown-with-logo`); see `TimeSeriesSeries.mark`.
59
+ */
60
+ mark?: React.ReactNode;
61
+ /** Drawn as a column or as a line. */
62
+ kind: "bar" | "line";
63
+ /** The value axis this series reads. Defaults to the left one. */
64
+ axis?: "left" | "right";
65
+ /**
66
+ * A bar's fill as a share of its hue, so a line sharing the plot still reads
67
+ * over it. Full strength when omitted; a wash stays a readability choice of
68
+ * the surface that composes the chart.
69
+ */
70
+ fillOpacity?: number;
71
+ /** Shows the line's point markers at the DESIGN.md marker geometry. Off by default. */
72
+ dot?: boolean;
73
+ };
74
+ type TimeSeriesComposedProps = Omit<React.ComponentProps<"figure">, "children"> & {
75
+ size?: TimeSeriesSize;
76
+ data: readonly TimeSeriesDatum[];
77
+ /** The datum key carrying the period (e.g. the month). */
78
+ xKey: string;
79
+ series: readonly TimeSeriesComposedSeries[];
80
+ /** The shared page-context entity-color assignment map. */
81
+ entityColorMap: EntityColorMap;
82
+ /** REQUIRED accessible name for the chart and its table alternative. */
83
+ label: string;
84
+ /** Column header for the period column in the table alternative. */
85
+ xLabel?: string;
86
+ /** Formats every series that declares none of its own. */
87
+ valueFormatter?: (value: number) => string;
88
+ /** Width of both y-axis gutters. Defaults to `"auto"` (recharts sizes them to the ticks). */
89
+ yAxisWidth?: number;
90
+ };
91
+ /**
92
+ * Bars for the measures, a line over them for the rate, and a second axis for
93
+ * the rate's own unit. The shadcn registry holds no composed chart item (see the
94
+ * file header), so this is Recharts' `ComposedChart` through the same
95
+ * foundation, and it keeps the kit's whole contract: a named figure, a
96
+ * `accessibilityLayer` graphic with its own title, a legend at two or more
97
+ * series, an entity-colored pair of marks, and a table twin carrying each
98
+ * series in its own terms.
99
+ */
100
+ declare function TimeSeriesComposed({ data, xKey, series, entityColorMap, label, xLabel, valueFormatter, yAxisWidth, size, className, ...props }: TimeSeriesComposedProps): React.JSX.Element;
101
+ export { TimeSeriesArea, TimeSeriesBars, TimeSeriesComposed };
102
+ export type { TimeSeriesComposedProps, TimeSeriesComposedSeries, TimeSeriesDatum, TimeSeriesProps, TimeSeriesSeries, };
37
103
  //# sourceMappingURL=chart-time-series.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-time-series.d.ts","sourceRoot":"","sources":["../../src/components/chart-time-series.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAS/F,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAkH1C,KAAK,gBAAgB,GAAG;IACtB,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,KAAK,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACxE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpC,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsJF,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,cAAc,EACd,IAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAqKjB;AAED,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,IAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBA6FjB;AAED,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"chart-time-series.d.ts","sourceRoot":"","sources":["../../src/components/chart-time-series.tsx"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Area, AreaChart, Bar, BarChart, BarStack, CartesianGrid, Rectangle, Text, XAxis, YAxis, } from "recharts";
3
+ import { Area, AreaChart, Bar, BarChart, BarStack, CartesianGrid, ComposedChart, Line, Rectangle, Text, XAxis, YAxis, } from "recharts";
4
4
  import { cn } from "../lib/utils.js";
5
5
  import { timeSeriesLayouts } from "./chart-time-series-layout.js";
6
6
  import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "./chart.js";
7
7
  import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js";
8
8
  /**
9
- * chart-time-series — stacked time-series and categorical bars for the console chart
10
- * kit (issue #166), built on the Recharts foundation
11
- * (ChartContainer / ChartTooltip / ChartLegend; chart.tsx untouched).
9
+ * chart-time-series — stacked time-series, categorical bars, and a
10
+ * bars-plus-line composition for the console chart kit (issue #166), built on
11
+ * the Recharts foundation (ChartContainer / ChartTooltip / ChartLegend; the
12
+ * legend's own marks path is the one seam chart.tsx carries for this kit).
12
13
  *
13
14
  * Contract highlights (DESIGN.md "Mark specs", "Legends and labels"):
14
15
  * - A y-axis is ALWAYS present — never axisless stacks.
@@ -29,6 +30,15 @@ import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js
29
30
  * accessible name. Recharts' `accessibilityLayer` (ARIA + arrow-key data
30
31
  * navigation) stays enabled, and the focusable chart svg carries its own
31
32
  * `title` so the accessibility tree names the graphic, not just the figure.
33
+ * - `TimeSeriesComposed` draws measures with a different unit against a second
34
+ * axis: bars for the sums, a line for the rate over them. The shadcn registry
35
+ * was searched first (`npx shadcn@latest search @shadcn -q chart`, and
36
+ * `view @shadcn/chart-example`) and holds no composed item, so the composition
37
+ * is Recharts' `ComposedChart` through this same foundation.
38
+ * - A series may carry a `mark`, printed beside its name in the legend. A model
39
+ * series uses it for its maker's mark, so color never identifies a model
40
+ * alone (rule `model-shown-with-logo`); the mark is decorative there, because
41
+ * the legend's own text names the series.
32
42
  */
33
43
  /**
34
44
  * The stock `ChartContainer` suppresses the Recharts svg outline
@@ -100,6 +110,31 @@ function niceYDomainMax(data, series) {
100
110
  const step = niceStep(max / (Y_AXIS_TICK_COUNT - 1));
101
111
  return step * (Y_AXIS_TICK_COUNT - 1);
102
112
  }
113
+ /**
114
+ * The largest single value across `series`, rounded up to a whole number of
115
+ * nice steps. A composed plot groups its bars instead of stacking them, so its
116
+ * axis bounds one value rather than a column's sum. `undefined` when there is
117
+ * nothing to bound, or when any value is negative: a diverging read fits its
118
+ * own domain instead of being clipped to a zero-based one.
119
+ */
120
+ function niceSeriesDomainMax(data, series) {
121
+ let max = 0;
122
+ for (const datum of data) {
123
+ for (const entry of series) {
124
+ const value = datum[entry.id];
125
+ if (typeof value !== "number" || !Number.isFinite(value))
126
+ continue;
127
+ if (value < 0)
128
+ return undefined;
129
+ if (value > max)
130
+ max = value;
131
+ }
132
+ }
133
+ if (max <= 0)
134
+ return undefined;
135
+ const step = niceStep(max / (Y_AXIS_TICK_COUNT - 1));
136
+ return step * (Y_AXIS_TICK_COUNT - 1);
137
+ }
103
138
  /**
104
139
  * Synthetic id/key for the single aggregated neutral series. Entities past the
105
140
  * sixth resolve to the neutral register, and rendering each as its own
@@ -116,18 +151,43 @@ function defaultFormat(value) {
116
151
  * Route the public `valueFormatter` through the visible tooltip, not only the
117
152
  * table twin. The stock shadcn tooltip takes a full-row `formatter`, so this
118
153
  * re-renders the stock row shape — entity swatch, series label, formatted
119
- * value — with the formatter applied to the number.
154
+ * value — with each series' own formatter when it declares one and the chart's
155
+ * otherwise, so a rate and a sum in one tooltip each read in their own terms. A
156
+ * marked series prints its mark here too: rule `model-shown-with-logo` names
157
+ * tooltips among the places a model appears, so the row reads swatch, mark,
158
+ * name, value — the legend's order.
120
159
  */
121
160
  function tooltipRowFormatter(series, valueFormatter) {
122
- const labelById = new Map(series.map((entry) => [entry.id, entry.label]));
123
- return (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "time-series-tooltip-swatch", className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: item.color ?? item.payload?.fill } }), _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: labelById.get(String(name)) ?? name }), _jsx("span", { className: "font-mono font-medium text-foreground tabular-nums", children: typeof value === "number" ? valueFormatter(value) : String(value) })] })] }));
161
+ const byId = new Map(series.map((entry) => [entry.id, entry]));
162
+ return (value, name, item) => {
163
+ const entry = byId.get(String(name));
164
+ const format = entry?.valueFormatter ?? valueFormatter;
165
+ return (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "time-series-tooltip-swatch", className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: item.color ?? item.payload?.fill } }), entry?.mark, _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: entry?.label ?? name }), _jsx("span", { className: "font-mono font-medium text-foreground tabular-nums", children: typeof value === "number" ? format(value) : String(value) })] })] }));
166
+ };
124
167
  }
168
+ /**
169
+ * The chart config the legend and tooltip read: a series' label and its color.
170
+ * A series' mark is identity, not part of this record — both surfaces read the
171
+ * config's label, so an identity placed here would reach both unasked. The
172
+ * legend takes marks through `ChartLegendContent`'s own path instead.
173
+ */
125
174
  function seriesChartConfig(series, entityColorMap) {
126
175
  return Object.fromEntries(series.map((entry) => [
127
176
  entry.id,
128
177
  { label: entry.label, color: entityColor(entityColorMap, entry.id) },
129
178
  ]));
130
179
  }
180
+ /**
181
+ * Each series' mark by data key, for the legend's marks path. `undefined` when
182
+ * no series carries one, which leaves a chart of customers or accounts on the
183
+ * stock legend.
184
+ */
185
+ function seriesMarks(series) {
186
+ const marked = series.filter((entry) => entry.mark);
187
+ return marked.length === 0
188
+ ? undefined
189
+ : Object.fromEntries(marked.map((entry) => [entry.id, entry.mark]));
190
+ }
131
191
  /**
132
192
  * Fold every series whose entity color resolves to the neutral "other"
133
193
  * register into ONE aggregated "Other" series (values summed per bucket).
@@ -162,7 +222,9 @@ function foldNeutralSeries(series, entityColorMap, data, otherLabel) {
162
222
  function TimeSeriesTableAlternative({ data, xKey, xLabel, series, label, valueFormatter, }) {
163
223
  return (_jsxs("table", { className: "sr-only", children: [_jsx("caption", { className: "sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: xLabel }), series.map((entry) => (_jsx("th", { scope: "col", children: entry.label }, entry.id)))] }) }), _jsx("tbody", { children: data.map((datum, index) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: String(datum[xKey] ?? "") }), series.map((entry) => {
164
224
  const value = datum[entry.id];
165
- return (_jsx("td", { children: typeof value === "number" ? valueFormatter(value) : String(value ?? "") }, entry.id));
225
+ return (_jsx("td", { children: typeof value === "number"
226
+ ? (entry.valueFormatter ?? valueFormatter)(value)
227
+ : String(value ?? "") }, entry.id));
166
228
  })] }, index))) })] }));
167
229
  }
168
230
  function TimeSeriesFrame({ label, className, children, ...props }) {
@@ -183,14 +245,16 @@ function TimeSeriesBars({ data, xKey, series, entityColorMap, label, xLabel = "P
183
245
  }
184
246
  const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
185
247
  const config = seriesChartConfig(folded.series, entityColorMap);
248
+ const marks = seriesMarks(folded.series);
186
249
  const yDomainMax = niceYDomainMax(folded.data, folded.series);
187
- return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-bars", label: label, className: cn(timeSeriesLayouts[size].frame, className), ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full aspect-auto", timeSeriesLayouts[size].chart, CHART_SURFACE_FOCUS_CLASS), children: _jsxs(BarChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, _jsx(BarStack, { radius: [4, 4, 0, 0], children: folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, isAnimationActive: false, maxBarSize: 24, fill: entityColor(entityColorMap, entry.id), stroke: "var(--background)", strokeWidth: 2 }, entry.id))) })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
250
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-bars", label: label, className: cn(timeSeriesLayouts[size].frame, className), ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full aspect-auto", timeSeriesLayouts[size].chart, CHART_SURFACE_FOCUS_CLASS), children: _jsxs(BarChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? (_jsx(ChartLegend, { content: _jsx(ChartLegendContent, { marks: marks }) })) : null, _jsx(BarStack, { radius: [4, 4, 0, 0], children: folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, isAnimationActive: false, maxBarSize: 24, fill: entityColor(entityColorMap, entry.id), stroke: "var(--background)", strokeWidth: 2 }, entry.id))) })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
188
251
  }
189
252
  function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, otherLabel = "Other", size = "default", className, ...props }) {
190
253
  const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
191
254
  const config = seriesChartConfig(folded.series, entityColorMap);
255
+ const marks = seriesMarks(folded.series);
192
256
  const yDomainMax = niceYDomainMax(folded.data, folded.series);
193
- return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-area", label: label, className: cn(timeSeriesLayouts[size].frame, className), ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full aspect-auto", timeSeriesLayouts[size].chart, CHART_SURFACE_FOCUS_CLASS), children: _jsxs(AreaChart, { accessibilityLayer: true, title: label, data: folded.data, margin: { top: 5, right: 20, bottom: 5, left: 20 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, folded.series.map((entry) => {
257
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-area", label: label, className: cn(timeSeriesLayouts[size].frame, className), ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full aspect-auto", timeSeriesLayouts[size].chart, CHART_SURFACE_FOCUS_CLASS), children: _jsxs(AreaChart, { accessibilityLayer: true, title: label, data: folded.data, margin: { top: 5, right: 20, bottom: 5, left: 20 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? (_jsx(ChartLegend, { content: _jsx(ChartLegendContent, { marks: marks }) })) : null, folded.series.map((entry) => {
194
258
  const color = entityColor(entityColorMap, entry.id);
195
259
  return (_jsx(Area, { type: "monotone", dataKey: entry.id, isAnimationActive: false, stackId: "stacked", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", fill: color,
196
260
  // DESIGN.md mark specs: an area fill reads as a light wash
@@ -206,4 +270,41 @@ function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel = "P
206
270
  } }, entry.id));
207
271
  })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
208
272
  }
209
- export { TimeSeriesArea, TimeSeriesBars };
273
+ /**
274
+ * Bars for the measures, a line over them for the rate, and a second axis for
275
+ * the rate's own unit. The shadcn registry holds no composed chart item (see the
276
+ * file header), so this is Recharts' `ComposedChart` through the same
277
+ * foundation, and it keeps the kit's whole contract: a named figure, a
278
+ * `accessibilityLayer` graphic with its own title, a legend at two or more
279
+ * series, an entity-colored pair of marks, and a table twin carrying each
280
+ * series in its own terms.
281
+ */
282
+ function TimeSeriesComposed({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, size = "default", className, ...props }) {
283
+ const config = seriesChartConfig(series, entityColorMap);
284
+ const marks = seriesMarks(series);
285
+ const leftSeries = series.filter((entry) => entry.axis !== "right");
286
+ const rightSeries = series.filter((entry) => entry.axis === "right");
287
+ const leftDomainMax = niceSeriesDomainMax(data, leftSeries);
288
+ const rightDomainMax = niceSeriesDomainMax(data, rightSeries);
289
+ const leftFormatter = leftSeries[0]?.valueFormatter ?? valueFormatter;
290
+ const rightFormatter = rightSeries[0]?.valueFormatter ?? valueFormatter;
291
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-composed", label: label, className: cn(timeSeriesLayouts[size].frame, className), ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full aspect-auto", timeSeriesLayouts[size].chart, CHART_SURFACE_FOCUS_CLASS), children: _jsxs(ComposedChart, { accessibilityLayer: true, title: label, data: data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { yAxisId: "left", tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(leftDomainMax === undefined
292
+ ? {}
293
+ : { domain: [0, leftDomainMax] }), tickFormatter: (value) => leftFormatter(Number(value)) }), rightSeries.length > 0 ? (_jsx(YAxis, { yAxisId: "right", orientation: "right", tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(rightDomainMax === undefined
294
+ ? {}
295
+ : { domain: [0, rightDomainMax] }), tickFormatter: (value) => rightFormatter(Number(value)) })) : null, _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(series, valueFormatter) }) }), series.length > 1 ? (_jsx(ChartLegend, { content: _jsx(ChartLegendContent, { marks: marks }) })) : null, series.map((entry) => {
296
+ const color = entityColor(entityColorMap, entry.id);
297
+ const yAxisId = entry.axis === "right" ? "right" : "left";
298
+ return entry.kind === "bar" ? (_jsx(Bar, { yAxisId: yAxisId, dataKey: entry.id, isAnimationActive: false, maxBarSize: 24, radius: [4, 4, 0, 0], fill: color, fillOpacity: entry.fillOpacity }, entry.id)) : (_jsx(Line, { yAxisId: yAxisId, type: "monotone", dataKey: entry.id, isAnimationActive: false, stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round",
299
+ // DESIGN.md mark specs: a point marker runs at least 8px with a
300
+ // 2px surface ring, and the marker itself carries the series'
301
+ // hue — Recharts fills a dot white, which reads as a hole on the
302
+ // light surface and stays white in dark mode. Both markers are
303
+ // configured: the active one arrives ringless, because the chart
304
+ // foundation maps Recharts' white active stroke to transparent.
305
+ dot: entry.dot
306
+ ? { r: 4, fill: color, stroke: "var(--background)", strokeWidth: 2 }
307
+ : false, activeDot: { r: 4, stroke: "var(--background)", strokeWidth: 2 } }, entry.id));
308
+ })] }) }), _jsx(TimeSeriesTableAlternative, { data: data, xKey: xKey, xLabel: xLabel, series: series, label: label, valueFormatter: valueFormatter })] }));
309
+ }
310
+ export { TimeSeriesArea, TimeSeriesBars, TimeSeriesComposed };
@@ -40,10 +40,11 @@ declare function ChartTooltipContent({ active, payload, className, indicator, hi
40
40
  labelKey?: string;
41
41
  } & Omit<RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>, "accessibilityLayer">): React.JSX.Element | null;
42
42
  declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
43
- declare function ChartLegendContent({ className, hideIcon, payload, placement, nameKey, }: React.ComponentProps<"div"> & {
43
+ declare function ChartLegendContent({ className, hideIcon, payload, placement, nameKey, marks, }: React.ComponentProps<"div"> & {
44
44
  hideIcon?: boolean;
45
45
  nameKey?: string;
46
46
  placement?: "top" | "bottom";
47
+ marks?: Record<string, React.ReactNode>;
47
48
  } & Omit<RechartsPrimitive.DefaultLegendContentProps, "verticalAlign">): React.JSX.Element | null;
48
49
  export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, };
49
50
  //# sourceMappingURL=chart.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../src/components/chart.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../src/components/chart.tsx"],"names":[],"mappings":"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"}
@@ -105,7 +105,16 @@ function ChartTooltipContent({ active, payload, className, indicator = "dot", hi
105
105
  }) })] }));
106
106
  }
107
107
  const ChartLegend = RechartsPrimitive.Legend;
108
- function ChartLegendContent({ className, hideIcon = false, payload, placement = "bottom", nameKey, }) {
108
+ /*
109
+ * A series' identity mark by its legend key, for the `marks` path below.
110
+ * `hasOwn` and not a bare index: the key is a series id from an app, so one
111
+ * named `constructor` or `toString` must read as no mark rather than as an
112
+ * inherited member of the record.
113
+ */
114
+ function legendMark(marks, key) {
115
+ return marks && Object.hasOwn(marks, key) ? marks[key] : null;
116
+ }
117
+ function ChartLegendContent({ className, hideIcon = false, payload, placement = "bottom", nameKey, marks, }) {
109
118
  const { config } = useChart();
110
119
  if (!payload?.length) {
111
120
  return null;
@@ -117,7 +126,7 @@ function ChartLegendContent({ className, hideIcon = false, payload, placement =
117
126
  const itemConfig = getPayloadConfigFromPayload(config, item, key);
118
127
  return (_jsxs("div", { className: cn("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"), children: [itemConfig?.icon && !hideIcon ? (_jsx(itemConfig.icon, {})) : (_jsx("div", { className: "h-2 w-2 shrink-0 rounded-xs", style: {
119
128
  backgroundColor: item.color,
120
- } })), itemConfig?.label] }, index));
129
+ } })), legendMark(marks, key), itemConfig?.label] }, index));
121
130
  }) }));
122
131
  }
123
132
  function getPayloadConfigFromPayload(config, payload, key) {
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ type ModelMarkProps = Omit<React.ComponentProps<"span">, "children"> & {
3
+ /** The maker's author slug as the usage read reports it, e.g. `"openai"`. */
4
+ author: string;
5
+ /** The model's name. Omitted, the mark renders the maker's logo alone. */
6
+ name?: string;
7
+ /** The tile's box in CSS pixels; the glyph scales with it. Defaults to 28. */
8
+ size?: number;
9
+ /**
10
+ * The mark's accessible name. A model named beside it needs none — the text
11
+ * is the name — and a bare mark stays decorative until one arrives, whether
12
+ * it comes from here or as the caller's own `aria-label`.
13
+ */
14
+ label?: string;
15
+ };
16
+ declare function ModelMark({ author, name, size, label, className, ...props }: ModelMarkProps): React.JSX.Element;
17
+ export { ModelMark };
18
+ export type { ModelMarkProps };
19
+ //# sourceMappingURL=model-mark.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-mark.d.ts","sourceRoot":"","sources":["../../src/components/model-mark.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAmE/B,KAAK,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IACrE,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,iBAAS,SAAS,CAAC,EACjB,MAAM,EACN,IAAI,EACJ,IAAsB,EACtB,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,qBA2ChB;AAED,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { ClaudeIcon, DeepseekIcon, GeminiIcon, GrokIcon, MetaIcon, MistralIcon, MoonshotIcon, OpenaiIcon, QwenIcon, } from "./brand-icons.js";
4
+ /**
5
+ * model-mark — a model's maker and name as one identity (rule
6
+ * `model-shown-with-logo`; DESIGN.md "Entity-color assignment" for the color a
7
+ * caller may pair with it).
8
+ *
9
+ * The maker's mark is the artwork the generator emitted into
10
+ * `src/components/brand-icons/` from the LobeHub icon set (MIT), keyed here by
11
+ * the author slug a usage read reports. An author off this allow-list still gets
12
+ * a tile — the neutral register — so a model never falls back to a bare color
13
+ * swatch. Colour alone never identifies a model.
14
+ *
15
+ * Presentation is one component with two readings: a `name` prints the model
16
+ * beside its maker, as a table cell or ranked list reads it; without one the
17
+ * bare mark stands in a chart legend beside that legend's own label. The tile,
18
+ * not the artwork, sets the box: the glyph scales with it because a 24px mark
19
+ * inside an 18px tile would overflow.
20
+ */
21
+ /**
22
+ * The maker allow-list by author slug, drawn from the icons the Analytics
23
+ * boards used (`nadicodeai/2026-09-18-live-usage`, `mockups/source/icons`).
24
+ * Adding an author is two edits: its mark to the generator's allow-list in
25
+ * `scripts/build-brand-icons.mjs`, then its slug here.
26
+ */
27
+ const MODEL_AUTHOR_MARKS = {
28
+ anthropic: ClaudeIcon,
29
+ openai: OpenaiIcon,
30
+ google: GeminiIcon,
31
+ deepseek: DeepseekIcon,
32
+ "x-ai": GrokIcon,
33
+ mistralai: MistralIcon,
34
+ "meta-llama": MetaIcon,
35
+ qwen: QwenIcon,
36
+ moonshotai: MoonshotIcon,
37
+ };
38
+ const MODEL_MARK_SIZE = 28;
39
+ /** The glyph's share of the tile, the ratio the boards' generator derived. */
40
+ const MODEL_MARK_GLYPH_RATIO = 0.62;
41
+ /**
42
+ * Whether a usage read's author slug names a maker this package ships. `hasOwn`
43
+ * and not a bare index: the slug is external input, so an author named
44
+ * `constructor` or `toString` must reach the neutral tile rather than an
45
+ * inherited method.
46
+ */
47
+ function isModelAuthor(slug) {
48
+ return Object.hasOwn(MODEL_AUTHOR_MARKS, slug);
49
+ }
50
+ function ModelMark({ author, name, size = MODEL_MARK_SIZE, label, className, ...props }) {
51
+ const slug = author.trim().toLowerCase();
52
+ const Mark = isModelAuthor(slug) ? MODEL_AUTHOR_MARKS[slug] : undefined;
53
+ // A name reaches the root from `label` or from the caller's own aria
54
+ // attributes; only the `label` prop also states the role, because a name
55
+ // alone on a generic element is not an accessible name. A mark with no name
56
+ // of its own and nothing to sit beside stays hidden, so a bare `role="img"`
57
+ // cannot expose an unnamed image.
58
+ const named = Boolean(label?.trim() || props["aria-label"]?.trim() || props["aria-labelledby"]?.trim());
59
+ const labelling = !named && !name
60
+ ? { "aria-hidden": true }
61
+ : label?.trim()
62
+ ? { role: "img", "aria-label": label }
63
+ : {};
64
+ return (_jsxs("span", { "data-slot": "model-mark", "data-author": author, className: cn("inline-flex min-w-0 items-center gap-1.5", className), ...props, ...labelling, children: [_jsx("span", { "data-slot": "model-mark-tile", "data-mark": Mark ? "brand" : "neutral", "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center border border-border bg-background text-foreground", style: { width: size, height: size, borderRadius: Math.round(size / 4) }, children: Mark ? _jsx(Mark, { size: Math.round(size * MODEL_MARK_GLYPH_RATIO) }) : null }), name ? (_jsxs("span", { className: "min-w-0 truncate", children: [_jsx("span", { className: "font-medium", children: name }), " ", _jsx("span", { className: "text-xs text-muted-foreground", children: author })] })) : null] }));
65
+ }
66
+ export { ModelMark };
package/dist/index.d.ts CHANGED
@@ -86,6 +86,7 @@ export * from "./components/media-mount.js";
86
86
  export * from "./components/memory-item.js";
87
87
  export * from "./components/menu-choice-row.js";
88
88
  export * from "./components/menubar.js";
89
+ export * from "./components/model-mark.js";
89
90
  export * from "./components/native-select.js";
90
91
  export * from "./components/nav-bar.js";
91
92
  export * from "./components/navigation-menu.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -86,6 +86,7 @@ export * from "./components/media-mount.js";
86
86
  export * from "./components/memory-item.js";
87
87
  export * from "./components/menu-choice-row.js";
88
88
  export * from "./components/menubar.js";
89
+ export * from "./components/model-mark.js";
89
90
  export * from "./components/native-select.js";
90
91
  export * from "./components/nav-bar.js";
91
92
  export * from "./components/navigation-menu.js";
package/docs/activity.md CHANGED
@@ -61,7 +61,7 @@ movement as well as its travelling particles. The implementation owns these
61
61
  values together; consumers cannot tune speed, density or particle colours.
62
62
 
63
63
  Use 20 CSS pixels inline, including buttons and rows with room for that size;
64
- 64 for a contained region; 88 for an application threshold. The 20 preset is
64
+ 64 for a contained region; 88 on an application's entry screen. The 20 preset is
65
65
  separately tuned, and must not be obtained by shrinking the 64 preset. The 88
66
66
  extent uses the 64 preset. A 12–16 pixel badge slot keeps its existing compact
67
67
  spinner until its surrounding component explicitly accommodates this family.
@@ -103,10 +103,9 @@ the operation fails or React replaces the startup surface.
103
103
 
104
104
  Import `@nadicodeai/ui/globals.css` as described in the
105
105
  [consumer guide](consuming-cross-repo.md). The renderer inherits actual CSS
106
- ink and reads canonical colour tokens. The existing `Orb` threshold adapter
107
- keeps its public API: `working` maps to `processing`, `connecting` maps to
108
- `connecting`, and `absent` mounts nothing. Its fixed dark appearance stays at
109
- the threshold seam, outside the shared renderer.
106
+ ink and reads canonical colour tokens. Nadia's entry screen wraps this renderer
107
+ in its own `Orb`, which fixes a dark appearance the shared renderer knows
108
+ nothing about; that wrapper lives in the desktop, not here.
110
109
 
111
110
  ## Accessibility and runtime
112
111