@nadicodeai/ui 0.5.2 → 0.5.3

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 (35) hide show
  1. package/AGENTS.md +10 -1
  2. package/README.md +18 -1
  3. package/dist/components/chart-breakdown-table.d.ts +39 -0
  4. package/dist/components/chart-breakdown-table.d.ts.map +1 -0
  5. package/dist/components/chart-breakdown-table.js +29 -0
  6. package/dist/components/chart-bullet-bar.d.ts +36 -0
  7. package/dist/components/chart-bullet-bar.d.ts.map +1 -0
  8. package/dist/components/chart-bullet-bar.js +30 -0
  9. package/dist/components/chart-date-range-control.d.ts +12 -0
  10. package/dist/components/chart-date-range-control.d.ts.map +1 -0
  11. package/dist/components/chart-date-range-control.js +59 -0
  12. package/dist/components/chart-date-range.d.ts +38 -0
  13. package/dist/components/chart-date-range.d.ts.map +1 -0
  14. package/dist/components/chart-date-range.js +63 -0
  15. package/dist/components/chart-delta.d.ts +28 -0
  16. package/dist/components/chart-delta.d.ts.map +1 -0
  17. package/dist/components/chart-delta.js +32 -0
  18. package/dist/components/chart-entity-colors.d.ts +37 -0
  19. package/dist/components/chart-entity-colors.d.ts.map +1 -0
  20. package/dist/components/chart-entity-colors.js +68 -0
  21. package/dist/components/chart-kpi-stat.d.ts +33 -0
  22. package/dist/components/chart-kpi-stat.d.ts.map +1 -0
  23. package/dist/components/chart-kpi-stat.js +7 -0
  24. package/dist/components/chart-sparkline.d.ts +45 -0
  25. package/dist/components/chart-sparkline.d.ts.map +1 -0
  26. package/dist/components/chart-sparkline.js +44 -0
  27. package/dist/components/chart-time-series.d.ts +33 -0
  28. package/dist/components/chart-time-series.d.ts.map +1 -0
  29. package/dist/components/chart-time-series.js +136 -0
  30. package/dist/index.d.ts +9 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +9 -0
  33. package/docs/agents/nadicodeai-ui.md +24 -1
  34. package/llms.txt +11 -1
  35. package/package.json +2 -2
package/AGENTS.md CHANGED
@@ -26,7 +26,7 @@ skills.
26
26
  - **Named shadcn blocks stay named.** When a user, plan, or issue selects a shadcn block/example such as `login-02`, `signup-02`, `dashboard-01`, or `sidebar-*`, use that registry item as the source of truth through shadcn CLI/MCP. Do not replace it with a package-authored abstraction unless the plan explicitly records the substitution, the reason, and the true consumer route that proves it.
27
27
  - **Package surface is source-backed.** `src/components/` is intentionally flat shadcn-style source. Do not document folders or package namespaces that do not exist. Do not add top-level exports for recipes, blocks, agent, or terminal groupings unless a later package API decision adds concrete source, built files, docs, and tests in the same change.
28
28
  - **Auth routes are consumer-owned shadcn block work.** Login, signup, OTP, two-factor, magic-link, and provider-button flows start from the selected shadcn block/example in the consuming app, with app-owned Better Auth behavior. Do not ship generic auth, signup, OTP, federated-login, or provider abstractions from this package unless a later explicit boundary decision replaces the selected shadcn block.
29
- - **Standard components, not app-owned product features.** This package ships primitives, standard UI components, AI Elements, brand adapters, React adapters, reusable sections, and presentational product components such as settings, shells, plugin boundaries, installer states, agent chat, artifacts, files, diffs, and redacted terminal summaries. It must not ship app-specific feature/use-case screens or names such as operator-login, portal activation, customer credential control, customer management, or other product-domain use cases. Generic data tables, command palettes, and chart cards are app-owned shadcn registry adaptations, not NadicodeAI package abstractions. Those screens live in the consuming app and are built from `@nadicodeai/ui` components and first-party shadcn blocks/examples.
29
+ - **Standard components, not app-owned product features.** This package ships primitives, standard UI components, AI Elements, brand adapters, React adapters, reusable sections, and presentational product components such as settings, shells, plugin boundaries, installer states, agent chat, artifacts, files, diffs, and redacted terminal summaries. It must not ship app-specific feature/use-case screens or names such as operator-login, portal activation, customer credential control, customer management, or other product-domain use cases. Generic data tables, command palettes, and chart cards are app-owned shadcn registry adaptations, not NadicodeAI package abstractions. Those screens live in the consuming app and are built from `@nadicodeai/ui` components and first-party shadcn blocks/examples. The console chart kit (`chart-kpi-stat`, `chart-sparkline`, `chart-time-series`, `chart-bullet-bar`, `chart-breakdown-table`, `chart-delta`, `chart-entity-colors`, `chart-date-range`, `chart-date-range-control`) is plan-approved package surface per issue #166 and ADR 0010; chart *cards* — the app screens that arrange these widgets — remain app-owned. Every data-shaped chart names a data alternative (a visually hidden `<table>` twin for the time-series charts, a real `<table>` for `chart-breakdown-table`, an sr-only value list for a standalone `chart-sparkline`); `chart-bullet-bar` (data in `aria-valuetext` plus the visible value) and `chart-kpi-stat` (data in visible text) carry no separate table twin by design.
30
30
  - **Design-system geometry remains the foundation.** React components compose the kept `nc-*` page grammar and display CSS components; they do not redefine frame math, marker placement, token values, or raw CSS geometry. Use parent-paints-plus-gap for bordered cell grids and keep every visible seam single-owner.
31
31
  - **Reusable sections and compositions live here after proof.** Bento grids, media mounts, demo surfaces, page frames, section intros, CTA bands, proof slots, benefits grids, generic shells, and homepage/use-case sections belong in `@nadicodeai/ui` when a second concrete consumer, a plan-approved shared component, or a named validation slice proves the package boundary. Website, portal, and sibling Nadia routes should pass data and media children into existing compositions instead of rebuilding layout locally.
32
32
  - **Brand renderers live here for React consumers.** `BrandMark` is the logo mark, and `BrandWordmark` is the text-only wordmark. A full lockup is exactly one `BrandMark` followed by one `BrandWordmark`; compact mark-only usage uses `BrandMark` alone. These adapters are the only place React code imports the raw `@nadicodeai/design-system/assets/*` logo SVG exports. Website, portal, and sibling routes use these adapters instead of importing logo assets directly, drawing brand SVG inline, masking/recoloring assets, or embedding the mark inside the wordmark.
@@ -94,6 +94,15 @@ Use public component subpaths for product UI:
94
94
  - `@nadicodeai/ui/components/agent-tool-approval`
95
95
  - `@nadicodeai/ui/components/artifact-panel`
96
96
  - `@nadicodeai/ui/components/automation-section`
97
+ - `@nadicodeai/ui/components/chart-breakdown-table`
98
+ - `@nadicodeai/ui/components/chart-bullet-bar`
99
+ - `@nadicodeai/ui/components/chart-date-range`
100
+ - `@nadicodeai/ui/components/chart-date-range-control`
101
+ - `@nadicodeai/ui/components/chart-delta`
102
+ - `@nadicodeai/ui/components/chart-entity-colors`
103
+ - `@nadicodeai/ui/components/chart-kpi-stat`
104
+ - `@nadicodeai/ui/components/chart-sparkline`
105
+ - `@nadicodeai/ui/components/chart-time-series`
97
106
  - `@nadicodeai/ui/components/dashboard-shell`
98
107
  - `@nadicodeai/ui/components/desktop-shell`
99
108
  - `@nadicodeai/ui/components/feedback-state`
package/README.md CHANGED
@@ -114,7 +114,15 @@ runtime adapters, analytics, and product-specific copy.
114
114
  Generic product widgets that shadcn already ships as first-party registry
115
115
  source, such as data tables, command palettes, and chart cards, are app-owned
116
116
  shadcn block/example adaptations. Do not add a second package abstraction over
117
- those registry items.
117
+ those registry items. The console chart kit subpaths (`chart-*`) are the
118
+ plan-approved exception (issue #166, ADR 0010): they mechanize the
119
+ design-system dataviz grammar on the Recharts chart foundation, while chart
120
+ cards — the app screens that arrange those widgets — stay app-owned. Every
121
+ data-shaped chart names a data alternative (a visually hidden `<table>` twin for
122
+ the time-series charts, a real `<table>` for `BreakdownTable`, an sr-only value
123
+ list for a standalone `Sparkline`); `BulletBar` and `KpiStat` carry their data
124
+ fully in `aria-valuetext`/visible text and so have no separate table twin by
125
+ design.
118
126
 
119
127
  Current product subpaths:
120
128
 
@@ -125,6 +133,15 @@ Current product subpaths:
125
133
  - `@nadicodeai/ui/components/agent-tool-approval`
126
134
  - `@nadicodeai/ui/components/artifact-panel`
127
135
  - `@nadicodeai/ui/components/automation-section`
136
+ - `@nadicodeai/ui/components/chart-breakdown-table`
137
+ - `@nadicodeai/ui/components/chart-bullet-bar`
138
+ - `@nadicodeai/ui/components/chart-date-range`
139
+ - `@nadicodeai/ui/components/chart-date-range-control`
140
+ - `@nadicodeai/ui/components/chart-delta`
141
+ - `@nadicodeai/ui/components/chart-entity-colors`
142
+ - `@nadicodeai/ui/components/chart-kpi-stat`
143
+ - `@nadicodeai/ui/components/chart-sparkline`
144
+ - `@nadicodeai/ui/components/chart-time-series`
128
145
  - `@nadicodeai/ui/components/dashboard-shell`
129
146
  - `@nadicodeai/ui/components/desktop-shell`
130
147
  - `@nadicodeai/ui/components/feedback-state`
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { Table } from "./table";
3
+ import { type EntityColorMap } from "./chart-entity-colors";
4
+ /**
5
+ * chart-breakdown-table — entity breakdown with proportion bars for the
6
+ * console chart kit (issue #166; DESIGN.md "Entity-color assignment",
7
+ * proportion-bar carve-out per ADR 0010 D1).
8
+ *
9
+ * Real `<table>` semantics: each entity row reads name + value + proportion
10
+ * bar + share of total, with the entity-color dot resolved from the shared
11
+ * page-context assignment map. `topN` folds the tail into a single
12
+ * "everything else" rollup row on the neutral register — never an extra hue.
13
+ */
14
+ type BreakdownEntity = {
15
+ /** Stable entity id — the key the entity-color map assigns by. */
16
+ id: string;
17
+ label: React.ReactNode;
18
+ value: number;
19
+ };
20
+ declare function BreakdownTable({ entities, entityColorMap, label, topN, valueFormatter, columnLabels, otherLabel, className, ...props }: Omit<React.ComponentProps<typeof Table>, "children"> & {
21
+ entities: readonly BreakdownEntity[];
22
+ /** The shared page-context entity-color assignment map. */
23
+ entityColorMap: EntityColorMap;
24
+ /** REQUIRED accessible name, rendered as a visually hidden caption. */
25
+ label: string;
26
+ /** Keep the top N entities by value; the rest fold into one rollup row. */
27
+ topN?: number;
28
+ valueFormatter?: (value: number) => string;
29
+ columnLabels?: {
30
+ entity?: string;
31
+ value?: string;
32
+ share?: string;
33
+ };
34
+ /** Label for the neutral rollup row. */
35
+ otherLabel?: React.ReactNode;
36
+ }): React.JSX.Element;
37
+ export { BreakdownTable };
38
+ export type { BreakdownEntity };
39
+ //# sourceMappingURL=chart-breakdown-table.d.ts.map
@@ -0,0 +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,CAAA;AAG9B,OAAO,EACL,KAAK,EAON,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAA;AAEzC;;;;;;;;;GASG;AAEH,KAAK,eAAe,GAAG;IACrB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAoED,iBAAS,cAAc,CAAC,EACtB,QAAQ,EACR,cAAc,EACd,KAAK,EACL,IAAI,EACJ,cAA8B,EAC9B,YAAY,EACZ,UAA8B,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACxD,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IACpC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAA;IAC9B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C,YAAY,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,wCAAwC;IACxC,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B,qBAiDA;AAED,OAAO,EAAE,cAAc,EAAE,CAAA;AACzB,YAAY,EAAE,eAAe,EAAE,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "./table.js";
4
+ import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js";
5
+ function percentOf(value, total) {
6
+ return total > 0 ? (value / total) * 100 : 0;
7
+ }
8
+ function formatShare(share) {
9
+ return `${share.toFixed(1)}%`;
10
+ }
11
+ function defaultFormat(value) {
12
+ return value.toLocaleString("en-US");
13
+ }
14
+ function BreakdownRow({ label, value, share, color, valueFormatter, ...props }) {
15
+ return (_jsxs(TableRow, { ...props, children: [_jsx(TableCell, { className: "font-medium", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "data-slot": "breakdown-table-dot", "aria-hidden": "true", className: "size-2 shrink-0 rounded-[2px]", style: { backgroundColor: color } }), label] }) }), _jsx(TableCell, { className: "text-right font-mono tabular-nums", children: valueFormatter(value) }), _jsx(TableCell, { children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "data-slot": "breakdown-table-bar", "aria-hidden": "true", className: "h-1.5 flex-1 overflow-hidden rounded-[2px] bg-muted", children: _jsx("span", { className: "block h-full rounded-r-[2px]", style: {
16
+ width: `${Number(share.toFixed(3))}%`,
17
+ backgroundColor: color,
18
+ } }) }), _jsx("span", { className: "w-12 text-right font-mono text-xs text-muted-foreground tabular-nums", children: formatShare(share) })] }) })] }));
19
+ }
20
+ function BreakdownTable({ entities, entityColorMap, label, topN, valueFormatter = defaultFormat, columnLabels, otherLabel = "Everything else", className, ...props }) {
21
+ const total = entities.reduce((sum, entity) => sum + entity.value, 0);
22
+ const sorted = [...entities].sort((a, b) => b.value - a.value);
23
+ const visible = topN !== undefined && topN < sorted.length ? sorted.slice(0, topN) : sorted;
24
+ const foldedValue = sorted
25
+ .slice(visible.length)
26
+ .reduce((sum, entity) => sum + entity.value, 0);
27
+ return (_jsxs(Table, { "data-slot": "breakdown-table", className: cn("text-sm", className), ...props, children: [_jsx(TableCaption, { className: "sr-only", children: label }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: columnLabels?.entity ?? "Entity" }), _jsx(TableHead, { className: "text-right", children: columnLabels?.value ?? "Value" }), _jsx(TableHead, { children: columnLabels?.share ?? "Share of total" })] }) }), _jsxs(TableBody, { children: [visible.map((entity) => (_jsx(BreakdownRow, { label: entity.label, value: entity.value, share: percentOf(entity.value, total), color: entityColor(entityColorMap, entity.id), valueFormatter: valueFormatter }, entity.id))), visible.length < sorted.length ? (_jsx(BreakdownRow, { "data-slot": "breakdown-table-other", label: otherLabel, value: foldedValue, share: percentOf(foldedValue, total), color: CHART_ENTITY_OTHER_COLOR, valueFormatter: valueFormatter })) : null] })] }));
28
+ }
29
+ export { BreakdownTable };
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ /**
3
+ * chart-bullet-bar — the cap / included-usage meter for the console chart kit
4
+ * (issue #166; DESIGN.md "Meter and bullet grammar").
5
+ *
6
+ * The bar carries a positional threshold tick at the product-configured
7
+ * warning fraction (`thresholdFraction` is a REQUIRED consumer input, never a
8
+ * constant of this component — 0.8 is the common product default) and a cap
9
+ * tick at 100%. Overflow renders as a distinct segment continuing past the
10
+ * cap tick, never clamped away. Severity state tokens reinforce position,
11
+ * never act as the sole signal. The current value reads at the bar tip.
12
+ *
13
+ * Accessibility: `role="meter"` with a REQUIRED accessible name (`label`);
14
+ * `aria-valuenow` clamps to the meter maximum while `aria-valuetext` conveys
15
+ * the true value including overflow.
16
+ */
17
+ type BulletBarSeverity = "ok" | "warn" | "over";
18
+ declare function BulletBar({ value, max, thresholdFraction, label, valueFormatter, valueText, className, ...props }: Omit<React.ComponentProps<"div">, "children"> & {
19
+ value: number;
20
+ /** The cap (included usage / limit) the meter measures against. */
21
+ max: number;
22
+ /**
23
+ * REQUIRED product-configured warning fraction of the cap (0–1) for the
24
+ * positional threshold tick. 0.8 is the common product default, but the
25
+ * fraction is a consumer input, never a constant of this contract.
26
+ */
27
+ thresholdFraction: number;
28
+ /** REQUIRED accessible name for the meter. */
29
+ label: string;
30
+ valueFormatter?: (value: number) => string;
31
+ /** Optional override for the generated `aria-valuetext`. */
32
+ valueText?: string;
33
+ }): React.JSX.Element;
34
+ export { BulletBar };
35
+ export type { BulletBarSeverity };
36
+ //# sourceMappingURL=chart-bullet-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-bullet-bar.d.ts","sourceRoot":"","sources":["../../src/components/chart-bullet-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;GAcG;AAEH,KAAK,iBAAiB,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;AAgB/C,iBAAS,SAAS,CAAC,EACjB,KAAK,EACL,GAAG,EACH,iBAAiB,EACjB,KAAK,EACL,cAA8B,EAC9B,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBAsFA;AAED,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ const BULLET_BAR_VALUE_TONE = {
4
+ ok: "text-foreground",
5
+ warn: "text-(--nc-state-review)",
6
+ over: "text-(--nc-state-blocked)",
7
+ };
8
+ function percent(fraction) {
9
+ return `${Number((fraction * 100).toFixed(3))}%`;
10
+ }
11
+ function defaultFormat(value) {
12
+ return value.toLocaleString("en-US");
13
+ }
14
+ function BulletBar({ value, max, thresholdFraction, label, valueFormatter = defaultFormat, valueText, className, ...props }) {
15
+ const overflow = Math.max(value - max, 0);
16
+ const total = Math.max(value, max);
17
+ const fillFraction = total > 0 ? Math.min(value, max) / total : 0;
18
+ const overflowFraction = total > 0 ? overflow / total : 0;
19
+ const capFraction = total > 0 ? max / total : 1;
20
+ const thresholdTickFraction = capFraction * thresholdFraction;
21
+ const tipFraction = fillFraction + overflowFraction;
22
+ const severity = overflow > 0 ? "over" : value >= thresholdFraction * max ? "warn" : "ok";
23
+ const meterValueText = valueText ??
24
+ `${valueFormatter(value)} of ${valueFormatter(max)}${overflow > 0 ? ` (${valueFormatter(overflow)} over the cap)` : ""}`;
25
+ return (_jsxs("div", { "data-slot": "bullet-bar", "data-severity": severity, className: cn("flex flex-col gap-1", className), ...props, children: [_jsxs("div", { role: "meter", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": max, "aria-valuenow": Math.min(value, max), "aria-valuetext": meterValueText, className: "relative h-3 w-full", children: [_jsx("div", { "data-slot": "bullet-bar-track", className: "absolute inset-y-0 rounded-r-[4px] bg-(--nc-chart-seq-1)", style: { left: 0, width: percent(capFraction) } }), _jsx("div", { "data-slot": "bullet-bar-fill", className: cn("absolute inset-y-0 bg-(--nc-chart-seq-3)", overflow === 0 && "rounded-r-[4px]"), style: { left: 0, width: percent(fillFraction) } }), overflow > 0 ? (_jsx("div", { "data-slot": "bullet-bar-overflow", className: "absolute inset-y-0 rounded-r-[4px] bg-(--nc-state-blocked)", style: {
26
+ left: percent(fillFraction),
27
+ width: percent(overflowFraction),
28
+ } })) : null, _jsx("div", { "data-slot": "bullet-bar-threshold-tick", "aria-hidden": "true", className: "absolute -inset-y-0.5 w-px bg-foreground", style: { left: percent(thresholdTickFraction) } }), _jsx("div", { "data-slot": "bullet-bar-cap-tick", "aria-hidden": "true", className: "absolute -inset-y-0.5 w-px bg-foreground", style: { left: percent(capFraction) } })] }), _jsx("div", { className: "relative h-4 w-full text-xs", "aria-hidden": "true", children: _jsx("span", { "data-slot": "bullet-bar-value", className: cn("absolute font-mono whitespace-nowrap tabular-nums", tipFraction > 0.6 && "-translate-x-full", BULLET_BAR_VALUE_TONE[severity]), style: { left: percent(tipFraction) }, children: valueFormatter(value) }) })] }));
29
+ }
30
+ export { BulletBar };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { type DateRangePreset, type DateRangeValue } from "./chart-date-range";
3
+ declare function DateRangeControl({ value, onChange, label, presetLabels, customLabel, className, ...props }: Omit<React.ComponentProps<"div">, "children" | "onChange"> & {
4
+ value: DateRangeValue;
5
+ onChange: (value: DateRangeValue) => void;
6
+ /** Accessible name for the control group. */
7
+ label?: string;
8
+ presetLabels?: Partial<Record<DateRangePreset, string>>;
9
+ customLabel?: string;
10
+ }): React.JSX.Element;
11
+ export { DateRangeControl };
12
+ //# sourceMappingURL=chart-date-range-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-date-range-control.d.ts","sourceRoot":"","sources":["../../src/components/chart-date-range-control.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,+BAA+B,CAAA;AAqBtC,iBAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,KAAoB,EACpB,YAAY,EACZ,WAAsB,EACtB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG;IAC9D,KAAK,EAAE,cAAc,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAA;IACvD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBAmFA;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { CalendarIcon } from "lucide-react";
5
+ import { cn } from "../lib/utils.js";
6
+ import { Button } from "./button.js";
7
+ import { Calendar } from "./calendar.js";
8
+ import { Popover, PopoverContent, PopoverTrigger, } from "./popover.js";
9
+ import { DATE_RANGE_PRESETS, formatIsoDay, parseIsoDay, } from "./chart-date-range.js";
10
+ /**
11
+ * chart-date-range-control — the console date-range picker for the chart kit
12
+ * (issue #166): relative presets (7d / 14d / 30d / this-cycle) plus a custom
13
+ * calendar range.
14
+ *
15
+ * Fully controlled: the consumer owns the value and typically round-trips it
16
+ * through URL state with `encodeDateRange` / `decodeDateRange` from
17
+ * `chart-date-range` (kept in a separate server-safe module because the
18
+ * package owns no routing). Preset buttons expose their pressed state via
19
+ * `aria-pressed`.
20
+ */
21
+ const DEFAULT_PRESET_LABELS = {
22
+ "7d": "7d",
23
+ "14d": "14d",
24
+ "30d": "30d",
25
+ cycle: "This cycle",
26
+ };
27
+ function DateRangeControl({ value, onChange, label = "Date range", presetLabels, customLabel = "Custom", className, ...props }) {
28
+ const [open, setOpen] = React.useState(false);
29
+ const [pending, setPending] = React.useState(undefined);
30
+ const isCustom = value.kind === "custom";
31
+ const selected = pending ??
32
+ (isCustom
33
+ ? { from: parseIsoDay(value.from), to: parseIsoDay(value.to) }
34
+ : undefined);
35
+ function handleOpenChange(nextOpen) {
36
+ setOpen(nextOpen);
37
+ if (!nextOpen) {
38
+ setPending(undefined);
39
+ }
40
+ }
41
+ function handleSelect(range) {
42
+ if (range?.from && range?.to) {
43
+ setPending(undefined);
44
+ setOpen(false);
45
+ onChange({
46
+ kind: "custom",
47
+ from: formatIsoDay(range.from),
48
+ to: formatIsoDay(range.to),
49
+ });
50
+ return;
51
+ }
52
+ setPending(range);
53
+ }
54
+ return (_jsxs("div", { "data-slot": "date-range-control", role: "group", "aria-label": label, className: cn("flex flex-wrap items-center gap-1", className), ...props, children: [DATE_RANGE_PRESETS.map((preset) => {
55
+ const active = value.kind === "preset" && value.preset === preset;
56
+ return (_jsx(Button, { size: "sm", variant: active ? "secondary" : "ghost", "aria-pressed": active, onClick: () => onChange({ kind: "preset", preset }), children: presetLabels?.[preset] ?? DEFAULT_PRESET_LABELS[preset] }, preset));
57
+ }), _jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { size: "sm", variant: isCustom ? "secondary" : "ghost", "aria-pressed": isCustom, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), isCustom ? `${value.from} – ${value.to}` : customLabel] }) }), _jsx(PopoverContent, { align: "start", className: "w-auto p-0", children: _jsx(Calendar, { mode: "range", numberOfMonths: 2, defaultMonth: selected?.from, selected: selected, onSelect: handleSelect }) })] })] }));
58
+ }
59
+ export { DateRangeControl };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * chart-date-range — the date-range value model and URL-state codec for the
3
+ * console chart kit (issue #166).
4
+ *
5
+ * This module is intentionally server-safe (no "use client" directive): the
6
+ * package owns no routing, so consumers wire `encodeDateRange` /
7
+ * `decodeDateRange` to their own searchParams — often in a Server Component,
8
+ * where importing a pure function from a client module would break. The
9
+ * interactive picker lives in `chart-date-range-control`.
10
+ */
11
+ declare const DATE_RANGE_PRESETS: readonly ["7d", "14d", "30d", "cycle"];
12
+ type DateRangePreset = (typeof DATE_RANGE_PRESETS)[number];
13
+ type DateRangeValue = {
14
+ kind: "preset";
15
+ preset: DateRangePreset;
16
+ } | {
17
+ kind: "custom";
18
+ from: string;
19
+ to: string;
20
+ };
21
+ /**
22
+ * Encodes a date-range value for URL state: a preset encodes as its token
23
+ * ("7d", "cycle", …) and a custom range as "yyyy-mm-dd..yyyy-mm-dd".
24
+ */
25
+ declare function encodeDateRange(value: DateRangeValue): string;
26
+ /**
27
+ * Decodes a URL-state string back into a date-range value. Returns null for
28
+ * anything malformed (unknown preset, bad date shape, inverted range) so
29
+ * consumers fall back to their own default.
30
+ */
31
+ declare function decodeDateRange(raw: string | null | undefined): DateRangeValue | null;
32
+ /** Parses a "yyyy-mm-dd" day as a LOCAL date (no timezone shifting). */
33
+ declare function parseIsoDay(iso: string): Date;
34
+ /** Formats a local date as "yyyy-mm-dd". */
35
+ declare function formatIsoDay(date: Date): string;
36
+ export { DATE_RANGE_PRESETS, decodeDateRange, encodeDateRange, formatIsoDay, parseIsoDay, };
37
+ export type { DateRangePreset, DateRangeValue };
38
+ //# sourceMappingURL=chart-date-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-date-range.d.ts","sourceRoot":"","sources":["../../src/components/chart-date-range.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,QAAA,MAAM,kBAAkB,wCAAyC,CAAA;AAEjE,KAAK,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,KAAK,cAAc,GACf;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAKhD;;;GAGG;AACH,iBAAS,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAItD;AAED;;;;GAIG;AACH,iBAAS,eAAe,CACtB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,cAAc,GAAG,IAAI,CAwBvB;AAED,wEAAwE;AACxE,iBAAS,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGtC;AAED,4CAA4C;AAC5C,iBAAS,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAKxC;AAMD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,WAAW,GACZ,CAAA;AACD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * chart-date-range — the date-range value model and URL-state codec for the
3
+ * console chart kit (issue #166).
4
+ *
5
+ * This module is intentionally server-safe (no "use client" directive): the
6
+ * package owns no routing, so consumers wire `encodeDateRange` /
7
+ * `decodeDateRange` to their own searchParams — often in a Server Component,
8
+ * where importing a pure function from a client module would break. The
9
+ * interactive picker lives in `chart-date-range-control`.
10
+ */
11
+ const DATE_RANGE_PRESETS = ["7d", "14d", "30d", "cycle"];
12
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
13
+ const CUSTOM_SEPARATOR = "..";
14
+ /**
15
+ * Encodes a date-range value for URL state: a preset encodes as its token
16
+ * ("7d", "cycle", …) and a custom range as "yyyy-mm-dd..yyyy-mm-dd".
17
+ */
18
+ function encodeDateRange(value) {
19
+ return value.kind === "preset"
20
+ ? value.preset
21
+ : `${value.from}${CUSTOM_SEPARATOR}${value.to}`;
22
+ }
23
+ /**
24
+ * Decodes a URL-state string back into a date-range value. Returns null for
25
+ * anything malformed (unknown preset, bad date shape, inverted range) so
26
+ * consumers fall back to their own default.
27
+ */
28
+ function decodeDateRange(raw) {
29
+ if (!raw) {
30
+ return null;
31
+ }
32
+ if (DATE_RANGE_PRESETS.includes(raw)) {
33
+ return { kind: "preset", preset: raw };
34
+ }
35
+ const parts = raw.split(CUSTOM_SEPARATOR);
36
+ if (parts.length !== 2) {
37
+ return null;
38
+ }
39
+ const [from, to] = parts;
40
+ if (!ISO_DAY.test(from) || !ISO_DAY.test(to) || from > to) {
41
+ return null;
42
+ }
43
+ if (!isRealIsoDay(from) || !isRealIsoDay(to)) {
44
+ return null;
45
+ }
46
+ return { kind: "custom", from, to };
47
+ }
48
+ /** Parses a "yyyy-mm-dd" day as a LOCAL date (no timezone shifting). */
49
+ function parseIsoDay(iso) {
50
+ const [year, month, day] = iso.split("-").map(Number);
51
+ return new Date(year, month - 1, day);
52
+ }
53
+ /** Formats a local date as "yyyy-mm-dd". */
54
+ function formatIsoDay(date) {
55
+ const year = String(date.getFullYear()).padStart(4, "0");
56
+ const month = String(date.getMonth() + 1).padStart(2, "0");
57
+ const day = String(date.getDate()).padStart(2, "0");
58
+ return `${year}-${month}-${day}`;
59
+ }
60
+ function isRealIsoDay(iso) {
61
+ return formatIsoDay(parseIsoDay(iso)) === iso;
62
+ }
63
+ export { DATE_RANGE_PRESETS, decodeDateRange, encodeDateRange, formatIsoDay, parseIsoDay, };
@@ -0,0 +1,28 @@
1
+ import * as React from "react";
2
+ /**
3
+ * chart-delta — shared delta/trend encoding for the console chart kit
4
+ * (issue #166; DESIGN.md "Delta and trend encoding").
5
+ *
6
+ * A change reads as a direction glyph (▲ ▼) plus a tone token, never bare
7
+ * signed text and never color alone. Tone follows direction crossed with
8
+ * whether up is good in the surrounding context (`upIsGood`).
9
+ */
10
+ type DeltaDirection = "up" | "down" | "flat";
11
+ type DeltaTone = "positive" | "negative" | "neutral";
12
+ /**
13
+ * Maps a delta direction onto its tone: direction crossed with whether up is
14
+ * good in this context. A flat delta is always neutral.
15
+ */
16
+ declare function deltaTone(direction: DeltaDirection, upIsGood?: boolean): DeltaTone;
17
+ declare function Delta({ value, direction, upIsGood, vsLabel, className, ...props }: Omit<React.ComponentProps<"span">, "children"> & {
18
+ /** The formatted change, e.g. "+12%" or "3.4k". */
19
+ value: React.ReactNode;
20
+ direction: DeltaDirection;
21
+ /** Whether an upward move is good in this context. Defaults to true. */
22
+ upIsGood?: boolean;
23
+ /** The named comparison period, rendered as "vs {vsLabel}". */
24
+ vsLabel?: string;
25
+ }): React.JSX.Element;
26
+ export { Delta, deltaTone };
27
+ export type { DeltaDirection, DeltaTone };
28
+ //# sourceMappingURL=chart-delta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-delta.d.ts","sourceRoot":"","sources":["../../src/components/chart-delta.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;GAOG;AAEH,KAAK,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;AAC5C,KAAK,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;AAoBpD;;;GAGG;AACH,iBAAS,SAAS,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,UAAO,GAAG,SAAS,CAMxE;AAED,iBAAS,KAAK,CAAC,EACb,KAAK,EACL,SAAS,EACT,QAAe,EACf,OAAO,EACP,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,mDAAmD;IACnD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,SAAS,EAAE,cAAc,CAAA;IACzB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,qBAuBA;AAED,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AAC3B,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ const DELTA_GLYPHS = {
4
+ up: "▲",
5
+ down: "▼",
6
+ flat: "–",
7
+ };
8
+ const DELTA_DIRECTION_TEXT = {
9
+ up: "up",
10
+ down: "down",
11
+ flat: "unchanged",
12
+ };
13
+ const DELTA_TONE_CLASSES = {
14
+ positive: "text-(--nc-state-complete)",
15
+ negative: "text-(--nc-state-blocked)",
16
+ neutral: "text-muted-foreground",
17
+ };
18
+ /**
19
+ * Maps a delta direction onto its tone: direction crossed with whether up is
20
+ * good in this context. A flat delta is always neutral.
21
+ */
22
+ function deltaTone(direction, upIsGood = true) {
23
+ if (direction === "flat") {
24
+ return "neutral";
25
+ }
26
+ return (direction === "up") === upIsGood ? "positive" : "negative";
27
+ }
28
+ function Delta({ value, direction, upIsGood = true, vsLabel, className, ...props }) {
29
+ const tone = deltaTone(direction, upIsGood);
30
+ return (_jsxs("span", { "data-slot": "delta", "data-direction": direction, "data-tone": tone, className: cn("inline-flex items-baseline gap-1 text-sm font-medium", DELTA_TONE_CLASSES[tone], className), ...props, children: [_jsx("span", { "aria-hidden": "true", children: DELTA_GLYPHS[direction] }), _jsx("span", { className: "sr-only", children: DELTA_DIRECTION_TEXT[direction] }), _jsx("span", { children: value }), vsLabel ? (_jsxs("span", { className: "font-normal text-muted-foreground", children: ["vs ", vsLabel] })) : null] }));
31
+ }
32
+ export { Delta, deltaTone };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * chart-entity-colors — the entity-color assignment helper for the console
3
+ * chart kit (issue #166; DESIGN.md "Entity-color assignment").
4
+ *
5
+ * Categorical hues assign in fixed order to entities per page context, and the
6
+ * assignment follows the entity identity, never rank, sort position, or array
7
+ * index. Callers build ONE map from the full page-context entity list (not a
8
+ * filtered or re-sorted view) and pass it to every chart widget on that
9
+ * surface, so a filter or re-sort never repaints the entities that remain on
10
+ * screen. Beyond six entities the tail folds into a single neutral "other"
11
+ * register, never a seventh hue.
12
+ *
13
+ * The map is a plain serializable record so a Server Component can compute it
14
+ * and pass it across the RSC boundary to client chart widgets.
15
+ */
16
+ declare const CHART_ENTITY_COLOR_TOKENS: readonly ["var(--nc-chart-1)", "var(--nc-chart-2)", "var(--nc-chart-3)", "var(--nc-chart-4)", "var(--nc-chart-5)", "var(--nc-chart-6)"];
17
+ /**
18
+ * The neutral register for folded "everything else" entities. A muted ink
19
+ * token, never an extra categorical hue.
20
+ */
21
+ declare const CHART_ENTITY_OTHER_COLOR = "var(--muted-foreground)";
22
+ type EntityColorMap = Readonly<Record<string, string>>;
23
+ /**
24
+ * Assigns `var(--nc-chart-1..6)` in fixed order by FIRST APPEARANCE of each
25
+ * stable entity id. Duplicate ids keep their first assignment. Entities past
26
+ * the sixth unique id fold to the neutral "other" register.
27
+ */
28
+ declare function createEntityColorMap(entityIds: readonly string[]): EntityColorMap;
29
+ /**
30
+ * Resolves an entity's assigned color. Ids missing from the map (an entity the
31
+ * page context never registered) read as the neutral "other" register instead
32
+ * of silently minting a new hue.
33
+ */
34
+ declare function entityColor(map: EntityColorMap, entityId: string): string;
35
+ export { CHART_ENTITY_COLOR_TOKENS, CHART_ENTITY_OTHER_COLOR, createEntityColorMap, entityColor, };
36
+ export type { EntityColorMap };
37
+ //# sourceMappingURL=chart-entity-colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-entity-colors.d.ts","sourceRoot":"","sources":["../../src/components/chart-entity-colors.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,yBAAyB,yIAOrB,CAAA;AAEV;;;GAGG;AACH,QAAA,MAAM,wBAAwB,4BAA4B,CAAA;AAE1D,KAAK,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAEtD;;;;GAIG;AACH,iBAAS,oBAAoB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2B1E;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,GACZ,CAAA;AACD,YAAY,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * chart-entity-colors — the entity-color assignment helper for the console
3
+ * chart kit (issue #166; DESIGN.md "Entity-color assignment").
4
+ *
5
+ * Categorical hues assign in fixed order to entities per page context, and the
6
+ * assignment follows the entity identity, never rank, sort position, or array
7
+ * index. Callers build ONE map from the full page-context entity list (not a
8
+ * filtered or re-sorted view) and pass it to every chart widget on that
9
+ * surface, so a filter or re-sort never repaints the entities that remain on
10
+ * screen. Beyond six entities the tail folds into a single neutral "other"
11
+ * register, never a seventh hue.
12
+ *
13
+ * The map is a plain serializable record so a Server Component can compute it
14
+ * and pass it across the RSC boundary to client chart widgets.
15
+ */
16
+ const CHART_ENTITY_COLOR_TOKENS = [
17
+ "var(--nc-chart-1)",
18
+ "var(--nc-chart-2)",
19
+ "var(--nc-chart-3)",
20
+ "var(--nc-chart-4)",
21
+ "var(--nc-chart-5)",
22
+ "var(--nc-chart-6)",
23
+ ];
24
+ /**
25
+ * The neutral register for folded "everything else" entities. A muted ink
26
+ * token, never an extra categorical hue.
27
+ */
28
+ const CHART_ENTITY_OTHER_COLOR = "var(--muted-foreground)";
29
+ /**
30
+ * Assigns `var(--nc-chart-1..6)` in fixed order by FIRST APPEARANCE of each
31
+ * stable entity id. Duplicate ids keep their first assignment. Entities past
32
+ * the sixth unique id fold to the neutral "other" register.
33
+ */
34
+ function createEntityColorMap(entityIds) {
35
+ // Assign through a Map so ids that collide with Object.prototype members
36
+ // ("toString", "__proto__", "constructor") key and dedupe safely during the
37
+ // build — a Map never confuses an entity id with an inherited prototype
38
+ // method, and never triggers the `__proto__` accessor.
39
+ const assignments = new Map();
40
+ for (const entityId of entityIds) {
41
+ if (assignments.has(entityId)) {
42
+ continue;
43
+ }
44
+ assignments.set(entityId, assignments.size < CHART_ENTITY_COLOR_TOKENS.length
45
+ ? CHART_ENTITY_COLOR_TOKENS[assignments.size]
46
+ : CHART_ENTITY_OTHER_COLOR);
47
+ }
48
+ // Return a PLAIN, serializable object so a Server Component can compute the
49
+ // map and pass it across the RSC boundary to client chart widgets (React
50
+ // rejects a null-prototype object). `Object.fromEntries` writes OWN data
51
+ // properties, so an id like "__proto__" becomes an own key with its hue
52
+ // instead of mutating the object's prototype — and `entityColor` reads
53
+ // membership with `Object.hasOwn`, so inherited members never leak.
54
+ return Object.fromEntries(assignments);
55
+ }
56
+ /**
57
+ * Resolves an entity's assigned color. Ids missing from the map (an entity the
58
+ * page context never registered) read as the neutral "other" register instead
59
+ * of silently minting a new hue.
60
+ */
61
+ function entityColor(map, entityId) {
62
+ // Object.hasOwn, not `map[entityId] ??`: the map is serialized across the RSC
63
+ // boundary and rehydrates with Object.prototype, so a bare index on an id like
64
+ // "toString" or "constructor" would return an inherited method instead of
65
+ // folding to the neutral register.
66
+ return Object.hasOwn(map, entityId) ? map[entityId] : CHART_ENTITY_OTHER_COLOR;
67
+ }
68
+ export { CHART_ENTITY_COLOR_TOKENS, CHART_ENTITY_OTHER_COLOR, createEntityColorMap, entityColor, };
@@ -0,0 +1,33 @@
1
+ import * as React from "react";
2
+ import { type DeltaDirection } from "./chart-delta";
3
+ /**
4
+ * chart-kpi-stat — the package KPI stat for the console chart kit
5
+ * (issue #166). The portal's KpiTile stays app-owned; this is the package
6
+ * sibling that adds the contracted delta and sparkline behavior.
7
+ *
8
+ * The value renders semibold with proportional figures (no tabular-nums —
9
+ * a single stat is read, not columnar-scanned). The delta reads as a
10
+ * direction glyph plus tone token with a named comparison period
11
+ * ("vs {vsLabel}"), per DESIGN.md "Delta and trend encoding". The sparkline
12
+ * slot is decorative here: the stat carries the accessible name, so the slot
13
+ * wrapper is `aria-hidden`.
14
+ */
15
+ type KpiStatDelta = {
16
+ /** The formatted change, e.g. "+12%". */
17
+ value: React.ReactNode;
18
+ direction: DeltaDirection;
19
+ /** Whether an upward move is good for this KPI. Defaults to true. */
20
+ upIsGood?: boolean;
21
+ /** The named comparison period, e.g. "previous 30 days". */
22
+ vsLabel: string;
23
+ };
24
+ declare function KpiStat({ label, value, delta, sparkline, className, ...props }: Omit<React.ComponentProps<"div">, "children"> & {
25
+ label: React.ReactNode;
26
+ value: React.ReactNode;
27
+ delta?: KpiStatDelta;
28
+ /** Optional decorative sparkline slot (e.g. a `Sparkline` element). */
29
+ sparkline?: React.ReactNode;
30
+ }): React.JSX.Element;
31
+ export { KpiStat };
32
+ export type { KpiStatDelta };
33
+ //# sourceMappingURL=chart-kpi-stat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-kpi-stat.d.ts","sourceRoot":"","sources":["../../src/components/chart-kpi-stat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAS,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAErE;;;;;;;;;;;GAWG;AAEH,KAAK,YAAY,GAAG;IAClB,yCAAyC;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,SAAS,EAAE,cAAc,CAAA;IACzB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,iBAAS,OAAO,CAAC,EACf,KAAK,EACL,KAAK,EACL,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACjD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,uEAAuE;IACvE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B,qBAoCA;AAED,OAAO,EAAE,OAAO,EAAE,CAAA;AAClB,YAAY,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { Delta } from "./chart-delta.js";
4
+ function KpiStat({ label, value, delta, sparkline, className, ...props }) {
5
+ return (_jsxs("div", { "data-slot": "kpi-stat", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx("div", { "data-slot": "kpi-stat-label", className: "font-mono text-xs text-muted-foreground uppercase", children: label }), _jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("div", { "data-slot": "kpi-stat-value", className: "text-3xl leading-none font-semibold text-foreground", children: value }), sparkline ? (_jsx("div", { "data-slot": "kpi-stat-sparkline", "aria-hidden": "true", children: sparkline })) : null] }), delta ? (_jsx(Delta, { value: delta.value, direction: delta.direction, upIsGood: delta.upIsGood, vsLabel: delta.vsLabel })) : null] }));
6
+ }
7
+ export { KpiStat };
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ /**
3
+ * chart-sparkline — hand-rolled SVG sparkline for the console chart kit
4
+ * (issue #166; ADR 0010 D1 sparkline carve-out; DESIGN.md "Delta and trend
5
+ * encoding", "Mark specs").
6
+ *
7
+ * Sparklines normalize against a meaningful baseline, never the visible
8
+ * min–max span alone: the vertical domain always includes the provided
9
+ * baseline, so a flat-ish series near a distant baseline reads as flat, not
10
+ * as dramatic noise. Marks render in real pixel space (uniform viewBox, no
11
+ * non-uniform scaling), single stroke of at least 2px with round joins/caps.
12
+ *
13
+ * When rendered inside a named stat (KpiStat), the sparkline is decorative
14
+ * and stays `aria-hidden`; when standalone, pass `label` so it carries its
15
+ * own accessible name. A standalone sparkline also renders a visually hidden
16
+ * data alternative next to the `role="img"` svg — the point values in order —
17
+ * so the trend is not vision-only. The decorative (aria-hidden) variant emits
18
+ * no such list.
19
+ */
20
+ type SparklinePoint = {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ /**
25
+ * Baseline-anchored normalization: the vertical domain is
26
+ * [min(baseline, data), max(baseline, data)] — never the visible data
27
+ * min–max alone. Points are inset by half the stroke width so the stroke
28
+ * never clips at the edges. A zero-span domain centers the line vertically.
29
+ */
30
+ declare function normalizeSparklinePoints(data: readonly number[], baseline: number, width: number, height: number, strokeWidth?: number): SparklinePoint[];
31
+ declare function Sparkline({ data, baseline, label, width, height, stroke, strokeWidth, className, ...props }: Omit<React.ComponentProps<"svg">, "children" | "height" | "viewBox" | "width"> & {
32
+ data: readonly number[];
33
+ /** The meaningful comparison baseline the vertical domain anchors to. */
34
+ baseline: number;
35
+ /** Accessible name for standalone use; omit inside a named stat. */
36
+ label?: string;
37
+ width?: number;
38
+ height?: number;
39
+ /** Stroke token: ink or one chart token, never a raw color. */
40
+ stroke?: string;
41
+ strokeWidth?: number;
42
+ }): React.JSX.Element;
43
+ export { Sparkline, normalizeSparklinePoints };
44
+ export type { SparklinePoint };
45
+ //# sourceMappingURL=chart-sparkline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-sparkline.d.ts","sourceRoot":"","sources":["../../src/components/chart-sparkline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;;;;GAiBG;AAEH,KAAK,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9C;;;;;GAKG;AACH,iBAAS,wBAAwB,CAC/B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,WAAW,SAAI,GACd,cAAc,EAAE,CAqBlB;AAED,iBAAS,SAAS,CAAC,EACjB,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,KAAW,EACX,MAAW,EACX,MAA4B,EAC5B,WAAe,EACf,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CACL,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAC3B,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAC5C,GAAG;IACF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBAqDA;AAED,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,CAAA;AAC9C,YAAY,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /**
4
+ * Baseline-anchored normalization: the vertical domain is
5
+ * [min(baseline, data), max(baseline, data)] — never the visible data
6
+ * min–max alone. Points are inset by half the stroke width so the stroke
7
+ * never clips at the edges. A zero-span domain centers the line vertically.
8
+ */
9
+ function normalizeSparklinePoints(data, baseline, width, height, strokeWidth = 2) {
10
+ if (data.length === 0) {
11
+ return [];
12
+ }
13
+ const min = Math.min(baseline, ...data);
14
+ const max = Math.max(baseline, ...data);
15
+ const span = max - min;
16
+ const pad = strokeWidth / 2;
17
+ const usableWidth = Math.max(width - strokeWidth, 0);
18
+ const usableHeight = Math.max(height - strokeWidth, 0);
19
+ return data.map((value, index) => ({
20
+ x: pad +
21
+ (data.length === 1
22
+ ? usableWidth / 2
23
+ : (index / (data.length - 1)) * usableWidth),
24
+ y: span === 0 ? height / 2 : pad + (1 - (value - min) / span) * usableHeight,
25
+ }));
26
+ }
27
+ function Sparkline({ data, baseline, label, width = 120, height = 32, stroke = "var(--nc-chart-1)", strokeWidth = 2, className, ...props }) {
28
+ // The 2px minimum is a DESIGN.md contract floor ("single stroke of at least
29
+ // 2px"); clamp the public prop so a caller passing a thinner stroke still
30
+ // renders at the floor and the normalization inset matches the painted line.
31
+ const effectiveStrokeWidth = Math.max(strokeWidth, 2);
32
+ const points = normalizeSparklinePoints(data, baseline, width, height, effectiveStrokeWidth);
33
+ const svg = (_jsx("svg", { "data-slot": "sparkline", width: width, height: height, viewBox: `0 0 ${width} ${height}`, className: cn("shrink-0", className), ...(label
34
+ ? { role: "img", "aria-label": label }
35
+ : { "aria-hidden": true }), ...props, children: points.length >= 2 ? (_jsx("polyline", { fill: "none", stroke: stroke, strokeWidth: effectiveStrokeWidth, strokeLinejoin: "round", strokeLinecap: "round", points: points.map((point) => `${point.x},${point.y}`).join(" ") })) : null }));
36
+ // Decorative inside a named stat: emit the bare svg, no data twin.
37
+ if (!label) {
38
+ return svg;
39
+ }
40
+ // Standalone: pair the graphic with a visually hidden ordered value list so
41
+ // the trend is available to assistive tech, not vision-only.
42
+ return (_jsxs("span", { "data-slot": "sparkline-standalone", className: "inline-flex", children: [svg, _jsx("span", { "data-slot": "sparkline-values", className: "sr-only", children: `${label}: ${data.join(", ")}` })] }));
43
+ }
44
+ export { Sparkline, normalizeSparklinePoints };
@@ -0,0 +1,33 @@
1
+ import * as React from "react";
2
+ import { type EntityColorMap } from "./chart-entity-colors";
3
+ type TimeSeriesSeries = {
4
+ /** Stable entity id — the data key in each datum and the color-map key. */
5
+ id: string;
6
+ label: string;
7
+ };
8
+ type TimeSeriesDatum = Record<string, number | string>;
9
+ type TimeSeriesProps = Omit<React.ComponentProps<"figure">, "children"> & {
10
+ data: readonly TimeSeriesDatum[];
11
+ /** The datum key carrying the period (e.g. the day). */
12
+ xKey: string;
13
+ series: readonly TimeSeriesSeries[];
14
+ /** The shared page-context entity-color assignment map. */
15
+ entityColorMap: EntityColorMap;
16
+ /** REQUIRED accessible name for the chart and its table alternative. */
17
+ label: string;
18
+ /** Column header for the period column in the table alternative. */
19
+ xLabel?: string;
20
+ valueFormatter?: (value: number) => string;
21
+ /**
22
+ * Y-axis width. Defaults to `"auto"` (recharts 3.8 sizes the gutter to the
23
+ * formatted ticks); pass a number to pin it.
24
+ */
25
+ yAxisWidth?: number;
26
+ /** Label for the folded neutral "Other" series (entities past the sixth). */
27
+ otherLabel?: string;
28
+ };
29
+ declare function TimeSeriesBars({ data, xKey, series, entityColorMap, label, xLabel, valueFormatter, yAxisWidth, otherLabel, className, ...props }: TimeSeriesProps): React.JSX.Element;
30
+ declare function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel, valueFormatter, yAxisWidth, otherLabel, className, ...props }: TimeSeriesProps): React.JSX.Element;
31
+ export { TimeSeriesArea, TimeSeriesBars };
32
+ export type { TimeSeriesDatum, TimeSeriesProps, TimeSeriesSeries };
33
+ //# sourceMappingURL=chart-time-series.d.ts.map
@@ -0,0 +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,CAAA;AAqB9B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAA;AAsDzC,KAAK,gBAAgB,GAAG;IACtB,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAEtD,KAAK,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACxE,IAAI,EAAE,SAAS,eAAe,EAAE,CAAA;IAChC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACnC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAA;IAC9B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAiKD,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAuDjB;AAED,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAgEjB;AAED,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAA;AACzC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAA"}
@@ -0,0 +1,136 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Area, AreaChart, Bar, BarChart, BarStack, CartesianGrid, XAxis, YAxis, } from "recharts";
4
+ import { cn } from "../lib/utils.js";
5
+ import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "./chart.js";
6
+ import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js";
7
+ /**
8
+ * chart-time-series — daily stacked time-series charts for the console chart
9
+ * kit (issue #166), built on the ADR 0010 Recharts foundation
10
+ * (ChartContainer / ChartTooltip / ChartLegend; chart.tsx untouched).
11
+ *
12
+ * Contract highlights (DESIGN.md "Mark specs", "Legends and labels"):
13
+ * - A y-axis is ALWAYS present — never axisless stacks.
14
+ * - Stacked segments keep a surface gap; the whole stack is wrapped in a
15
+ * Recharts `BarStack` so the 4px rounded data-ends sit on the visible top of
16
+ * the stack (anchored square at the baseline) even when the last series is
17
+ * zero-valued — the radius rounds the stack, not one hard-coded series.
18
+ * - The public `valueFormatter` reaches every value surface — the table twin,
19
+ * the y-axis ticks, and the tooltip value — not only the hidden table cells.
20
+ * - Series colors resolve from the shared page-context entity-color map,
21
+ * never from array index.
22
+ * - Direct labels are not feasible on stacked dailies, so at two or more
23
+ * series a legend identifies every series; a single series is titled by
24
+ * the accessible name, not legended.
25
+ * - Every chart names a built-in table alternative: a visually hidden
26
+ * `<table>` twin renders the same series, and the chart root carries the
27
+ * accessible name. Recharts' `accessibilityLayer` (ARIA + arrow-key data
28
+ * navigation) stays enabled, and the focusable chart svg carries its own
29
+ * `title` so the accessibility tree names the graphic, not just the figure.
30
+ */
31
+ /**
32
+ * The stock `ChartContainer` suppresses the Recharts svg outline
33
+ * (`[&_.recharts-surface]:outline-hidden`) so the default browser focus ring
34
+ * never shows, which would leave the `accessibilityLayer` keyboard path with
35
+ * no visible focus. chart.tsx is untouched, so we restore a tokenized ring
36
+ * through the `className` we pass to our own `ChartContainer` instances: the
37
+ * base rule sets `outline: 2px solid transparent` on `.recharts-surface`, and
38
+ * these `:focus-visible` overrides sit on a distinct arbitrary-variant selector
39
+ * (a different tailwind-merge group AND modifier, so `cn()` keeps both) with
40
+ * higher CSS specificity, flipping the outline color to the `--ring` role only
41
+ * while the svg is keyboard-focused.
42
+ */
43
+ const CHART_SURFACE_FOCUS_CLASS = "[&_.recharts-surface:focus-visible]:outline-2 " +
44
+ "[&_.recharts-surface:focus-visible]:outline-ring " +
45
+ "[&_.recharts-surface:focus-visible]:outline-offset-2";
46
+ /**
47
+ * Synthetic id/key for the single aggregated neutral series. Entities past the
48
+ * sixth resolve to the neutral register, and rendering each as its own
49
+ * indistinguishable neutral mark is unreadable, so they fold into ONE "Other"
50
+ * series (values summed per bucket) in the marks, the legend, and the table
51
+ * twin. Unknown to any entity-color map, so `entityColor` resolves it to the
52
+ * neutral register.
53
+ */
54
+ const OTHER_SERIES_ID = "__nc-other-series__";
55
+ function defaultFormat(value) {
56
+ return value.toLocaleString("en-US");
57
+ }
58
+ /**
59
+ * Route the public `valueFormatter` through the visible tooltip, not only the
60
+ * table twin. The stock shadcn tooltip takes a full-row `formatter`, so this
61
+ * re-renders the stock row shape — entity swatch, series label, formatted
62
+ * value — with the formatter applied to the number.
63
+ */
64
+ function tooltipRowFormatter(series, valueFormatter) {
65
+ const labelById = new Map(series.map((entry) => [entry.id, entry.label]));
66
+ return (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "time-series-tooltip-swatch", className: "size-2.5 shrink-0 rounded-[2px]", 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) })] })] }));
67
+ }
68
+ function seriesChartConfig(series, entityColorMap) {
69
+ return Object.fromEntries(series.map((entry) => [
70
+ entry.id,
71
+ { label: entry.label, color: entityColor(entityColorMap, entry.id) },
72
+ ]));
73
+ }
74
+ /**
75
+ * Fold every series whose entity color resolves to the neutral "other"
76
+ * register into ONE aggregated "Other" series (values summed per bucket).
77
+ * Beyond six entities the color map returns the neutral register, and drawing
78
+ * each tail entity as its own indistinguishable neutral mark is unreadable —
79
+ * so the marks, legend, and table twin all show at most one neutral series.
80
+ * Series resolving to a hue pass through unchanged; with no neutral series the
81
+ * inputs return untouched (so ≤6 series are never rewritten).
82
+ */
83
+ function foldNeutralSeries(series, entityColorMap, data, otherLabel) {
84
+ const isNeutral = (entry) => entityColor(entityColorMap, entry.id) === CHART_ENTITY_OTHER_COLOR;
85
+ const neutral = series.filter(isNeutral);
86
+ if (neutral.length === 0) {
87
+ return { series: [...series], data: [...data] };
88
+ }
89
+ const hued = series.filter((entry) => !isNeutral(entry));
90
+ const foldedSeries = [
91
+ ...hued,
92
+ { id: OTHER_SERIES_ID, label: otherLabel },
93
+ ];
94
+ const foldedData = data.map((datum) => {
95
+ const otherSum = neutral.reduce((sum, entry) => {
96
+ const value = datum[entry.id];
97
+ return sum + (typeof value === "number" ? value : 0);
98
+ }, 0);
99
+ const next = { ...datum };
100
+ for (const entry of neutral) {
101
+ delete next[entry.id];
102
+ }
103
+ next[OTHER_SERIES_ID] = otherSum;
104
+ return next;
105
+ });
106
+ return { series: foldedSeries, data: foldedData };
107
+ }
108
+ function TimeSeriesTableAlternative({ data, xKey, xLabel, series, label, valueFormatter, }) {
109
+ 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) => {
110
+ const value = datum[entry.id];
111
+ return (_jsx("td", { children: typeof value === "number"
112
+ ? valueFormatter(value)
113
+ : String(value ?? "") }, entry.id));
114
+ })] }, index))) })] }));
115
+ }
116
+ function TimeSeriesFrame({ label, className, children, ...props }) {
117
+ return (_jsx("figure", { "aria-label": label, className: cn("m-0 flex w-full flex-col", className), ...props, children: children }));
118
+ }
119
+ function TimeSeriesBars({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, otherLabel = "Other", className, ...props }) {
120
+ const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
121
+ const config = seriesChartConfig(folded.series, entityColorMap);
122
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-bars", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", 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 }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", 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, { stackId: "stacked", radius: [4, 4, 0, 0], children: folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, 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 })] }));
123
+ }
124
+ function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, otherLabel = "Other", className, ...props }) {
125
+ const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
126
+ const config = seriesChartConfig(folded.series, entityColorMap);
127
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-area", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", CHART_SURFACE_FOCUS_CLASS), children: _jsxs(AreaChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", 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) => {
128
+ const color = entityColor(entityColorMap, entry.id);
129
+ return (_jsx(Area, { type: "monotone", dataKey: entry.id, stackId: "stacked", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", fill: color, fillOpacity: 0.1,
130
+ // Recharts' default activeDot ring is stroke #fff, which the
131
+ // chart foundation remaps to transparent — an explicit surface
132
+ // ring keeps the DESIGN.md 2px marker-ring spec on hover/focus.
133
+ activeDot: { r: 4, stroke: "var(--background)", strokeWidth: 2 } }, entry.id));
134
+ })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
135
+ }
136
+ export { TimeSeriesArea, TimeSeriesBars };
package/dist/index.d.ts CHANGED
@@ -23,6 +23,15 @@ export * from "./components/card";
23
23
  export * from "./components/card-marketing";
24
24
  export * from "./components/carousel";
25
25
  export * from "./components/chart";
26
+ export * from "./components/chart-breakdown-table";
27
+ export * from "./components/chart-bullet-bar";
28
+ export * from "./components/chart-date-range";
29
+ export * from "./components/chart-date-range-control";
30
+ export * from "./components/chart-delta";
31
+ export * from "./components/chart-entity-colors";
32
+ export * from "./components/chart-kpi-stat";
33
+ export * from "./components/chart-sparkline";
34
+ export * from "./components/chart-time-series";
26
35
  export * from "./components/checkbox";
27
36
  export * from "./components/code-editor-mockup";
28
37
  export * from "./components/collapsible";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,2BAA2B,CAAA;AACzC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,2BAA2B,CAAA;AACzC,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA;AACzC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,qBAAqB,CAAA;AACnC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oBAAoB,CAAA;AAClC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,oBAAoB,CAAA;AAClC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AACtC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,sBAAsB,CAAA;AACpC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mCAAmC,CAAA;AACjD,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AACtC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kCAAkC,CAAA;AAChD,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,iCAAiC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -23,6 +23,15 @@ export * from "./components/card.js";
23
23
  export * from "./components/card-marketing.js";
24
24
  export * from "./components/carousel.js";
25
25
  export * from "./components/chart.js";
26
+ export * from "./components/chart-breakdown-table.js";
27
+ export * from "./components/chart-bullet-bar.js";
28
+ export * from "./components/chart-date-range.js";
29
+ export * from "./components/chart-date-range-control.js";
30
+ export * from "./components/chart-delta.js";
31
+ export * from "./components/chart-entity-colors.js";
32
+ export * from "./components/chart-kpi-stat.js";
33
+ export * from "./components/chart-sparkline.js";
34
+ export * from "./components/chart-time-series.js";
26
35
  export * from "./components/checkbox.js";
27
36
  export * from "./components/code-editor-mockup.js";
28
37
  export * from "./components/collapsible.js";
@@ -79,6 +79,15 @@ Current shipped product subpaths:
79
79
  - `@nadicodeai/ui/components/agent-tool-approval`
80
80
  - `@nadicodeai/ui/components/artifact-panel`
81
81
  - `@nadicodeai/ui/components/automation-section`
82
+ - `@nadicodeai/ui/components/chart-breakdown-table`
83
+ - `@nadicodeai/ui/components/chart-bullet-bar`
84
+ - `@nadicodeai/ui/components/chart-date-range`
85
+ - `@nadicodeai/ui/components/chart-date-range-control`
86
+ - `@nadicodeai/ui/components/chart-delta`
87
+ - `@nadicodeai/ui/components/chart-entity-colors`
88
+ - `@nadicodeai/ui/components/chart-kpi-stat`
89
+ - `@nadicodeai/ui/components/chart-sparkline`
90
+ - `@nadicodeai/ui/components/chart-time-series`
82
91
  - `@nadicodeai/ui/components/dashboard-shell`
83
92
  - `@nadicodeai/ui/components/desktop-shell`
84
93
  - `@nadicodeai/ui/components/feedback-state`
@@ -103,7 +112,21 @@ product-specific copy in the consuming app.
103
112
  Generic data tables, command palettes, and chart cards are app-owned shadcn
104
113
  registry adaptations. Start from `@shadcn/data-table-demo`,
105
114
  `@shadcn/command-dialog`, `@shadcn/chart`, or the relevant `@shadcn/chart-*`
106
- block/example instead of importing a NadicodeAI product abstraction.
115
+ block/example instead of importing a NadicodeAI product abstraction. The
116
+ console chart kit subpaths (`chart-*`) are the plan-approved exception
117
+ (issue #166, ADR 0010): use them for KPI stats, sparklines, stacked
118
+ time-series, cap meters, breakdown tables, entity-color assignment, and the
119
+ date-range control; the chart cards that arrange those widgets stay app-owned.
120
+ The URL codec in `chart-date-range` is server-safe; import it (not
121
+ `chart-date-range-control`) from Server Components.
122
+
123
+ Every data-shaped chart names a data alternative so its data is reachable
124
+ without vision: the time-series charts ship a visually hidden `<table>` twin,
125
+ `BreakdownTable` is itself a real `<table>`, and a standalone `Sparkline`
126
+ renders an sr-only ordered list of its point values beside the `role="img"`
127
+ svg. `BulletBar` (data fully in `aria-valuetext` plus the visible value) and
128
+ `KpiStat` (data fully in visible text) carry no separate table twin by design;
129
+ this narrowing is deliberate, not a gap.
107
130
 
108
131
  Recommended migration order:
109
132
 
package/llms.txt CHANGED
@@ -32,6 +32,15 @@ Component surface:
32
32
  - `@nadicodeai/ui/components/agent-tool-approval`
33
33
  - `@nadicodeai/ui/components/artifact-panel`
34
34
  - `@nadicodeai/ui/components/automation-section`
35
+ - `@nadicodeai/ui/components/chart-breakdown-table`
36
+ - `@nadicodeai/ui/components/chart-bullet-bar`
37
+ - `@nadicodeai/ui/components/chart-date-range`
38
+ - `@nadicodeai/ui/components/chart-date-range-control`
39
+ - `@nadicodeai/ui/components/chart-delta`
40
+ - `@nadicodeai/ui/components/chart-entity-colors`
41
+ - `@nadicodeai/ui/components/chart-kpi-stat`
42
+ - `@nadicodeai/ui/components/chart-sparkline`
43
+ - `@nadicodeai/ui/components/chart-time-series`
35
44
  - `@nadicodeai/ui/components/dashboard-shell`
36
45
  - `@nadicodeai/ui/components/desktop-shell`
37
46
  - `@nadicodeai/ui/components/feedback-state`
@@ -46,7 +55,8 @@ Package surface:
46
55
  - Do not add top-level exports for recipes, blocks, agent, or terminal groupings without concrete source, built files, docs, and tests in the same package API decision.
47
56
  - App routes own routing, persistence, auth behavior, runtime adapters, server state, app-specific copy, and app-owned product features/use cases.
48
57
  - Product-specific names such as `operator-login`, portal activation, and customer credential control stay out of package components.
49
- - Generic data tables, command palettes, and chart cards are app-owned shadcn registry adaptations, not NadicodeAI package abstractions.
58
+ - Generic data tables, command palettes, and chart cards are app-owned shadcn registry adaptations, not NadicodeAI package abstractions. The `chart-*` console chart kit subpaths are plan-approved package surface (issue #166, ADR 0010); chart cards that arrange them stay app-owned.
59
+ - The date-range URL codec (`encodeDateRange`/`decodeDateRange`) ships server-safe from `@nadicodeai/ui/components/chart-date-range`; the interactive picker is `@nadicodeai/ui/components/chart-date-range-control`.
50
60
  - Consumer migration order: web admin or portal validation slice first, installer second, desktop shell third, desktop chat last, TUI through `docs/terminal-tui-semantics.md`.
51
61
  - Read `docs/migration/nadia-consumer-readiness.md` before migrating a consumer; it records CSS requirements, public import paths, fixture rules, and readiness gates.
52
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/ui",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@base-ui/react": "^1.6.0",
54
- "@nadicodeai/design-system": "0.5.2",
54
+ "@nadicodeai/design-system": "0.5.3",
55
55
  "@rive-app/react-webgl2": "^4.29.1",
56
56
  "@streamdown/cjk": "^1.0.3",
57
57
  "@streamdown/code": "^1.1.1",