@nadicodeai/ui 0.5.1 → 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.
- package/AGENTS.md +10 -1
- package/README.md +18 -1
- package/dist/components/chart-breakdown-table.d.ts +39 -0
- package/dist/components/chart-breakdown-table.d.ts.map +1 -0
- package/dist/components/chart-breakdown-table.js +29 -0
- package/dist/components/chart-bullet-bar.d.ts +36 -0
- package/dist/components/chart-bullet-bar.d.ts.map +1 -0
- package/dist/components/chart-bullet-bar.js +30 -0
- package/dist/components/chart-date-range-control.d.ts +12 -0
- package/dist/components/chart-date-range-control.d.ts.map +1 -0
- package/dist/components/chart-date-range-control.js +59 -0
- package/dist/components/chart-date-range.d.ts +38 -0
- package/dist/components/chart-date-range.d.ts.map +1 -0
- package/dist/components/chart-date-range.js +63 -0
- package/dist/components/chart-delta.d.ts +28 -0
- package/dist/components/chart-delta.d.ts.map +1 -0
- package/dist/components/chart-delta.js +32 -0
- package/dist/components/chart-entity-colors.d.ts +37 -0
- package/dist/components/chart-entity-colors.d.ts.map +1 -0
- package/dist/components/chart-entity-colors.js +68 -0
- package/dist/components/chart-kpi-stat.d.ts +33 -0
- package/dist/components/chart-kpi-stat.d.ts.map +1 -0
- package/dist/components/chart-kpi-stat.js +7 -0
- package/dist/components/chart-sparkline.d.ts +45 -0
- package/dist/components/chart-sparkline.d.ts.map +1 -0
- package/dist/components/chart-sparkline.js +44 -0
- package/dist/components/chart-time-series.d.ts +33 -0
- package/dist/components/chart-time-series.d.ts.map +1 -0
- package/dist/components/chart-time-series.js +136 -0
- package/dist/components/chart.d.ts.map +1 -1
- package/dist/components/chart.js +4 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/styles/globals.css +7 -7
- package/docs/agents/nadicodeai-ui.md +24 -1
- package/llms.txt +12 -2
- package/package.json +6 -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 };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../src/components/chart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAKhD,QAAA,MAAM,MAAM;;;CAAwC,CAAA;AAGpD,KAAK,eAAe,GAAG,MAAM,GAAG,MAAM,CAAA;AACtC,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,OAAO,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;
|
|
1
|
+
{"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../src/components/chart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAKhD,QAAA,MAAM,MAAM;;;CAAwC,CAAA;AAGpD,KAAK,eAAe,GAAG,MAAM,GAAG,MAAM,CAAA;AACtC,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,OAAO,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AA4BD,MAAM,MAAM,WAAW,GAAG,MAAM,CAC9B,MAAM,EACN;IACE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;CAC3B,GAAG,CACA;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GACjC;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CACvC,CACF,CAAA;AAkBD,iBAAS,cAAc,CAAC,EACtB,EAAE,EACF,SAAS,EACT,QAAQ,EACR,MAAM,EACN,gBAAoC,EACpC,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,cAAc,CAC5B,OAAO,iBAAiB,CAAC,mBAAmB,CAC7C,CAAC,UAAU,CAAC,CAAA;IACb,gBAAgB,CAAC,EAAE;QACjB,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF,qBA0BA;AAED,QAAA,MAAM,UAAU,GAAI,gBAAgB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,6BAgCtE,CAAA;AAED,QAAA,MAAM,YAAY,kCAA4B,CAAA;AAE9C,iBAAS,mBAAmB,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,SAAS,EACT,SAAiB,EACjB,SAAiB,EACjB,aAAqB,EACrB,KAAK,EACL,cAAc,EACd,cAAc,EACd,SAAS,EACT,KAAK,EACL,OAAO,EACP,QAAQ,GACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,iBAAiB,CAAC,OAAO,CAAC,GACvD,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,IAAI,CACN,iBAAiB,CAAC,0BAA0B,CAC1C,gBAAgB,EAChB,eAAe,CAChB,EACD,oBAAoB,CACrB,4BA6HF;AAED,QAAA,MAAM,WAAW,sGAA2B,CAAA;AAE5C,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,QAAgB,EAChB,OAAO,EACP,aAAwB,EACxB,OAAO,GACR,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,iBAAiB,CAAC,yBAAyB,4BA4C9C;AAsCD,OAAO,EACL,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,UAAU,GACX,CAAA"}
|
package/dist/components/chart.js
CHANGED
|
@@ -6,6 +6,9 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
|
7
7
|
const THEMES = { light: "", dark: ".dark" };
|
|
8
8
|
const INITIAL_DIMENSION = { width: 320, height: 200 };
|
|
9
|
+
// Deviates from stock base-nova (raw interpolation): chart id and color values
|
|
10
|
+
// are sanitized before they reach the injected <style> tag, closing the CSS
|
|
11
|
+
// injection surface. Pinned by tests/chart-style.test.ts; see ADR 0010.
|
|
9
12
|
function sanitizeChartToken(value) {
|
|
10
13
|
const sanitized = value
|
|
11
14
|
.trim()
|
|
@@ -34,7 +37,7 @@ function useChart() {
|
|
|
34
37
|
function ChartContainer({ id, className, children, config, initialDimension = INITIAL_DIMENSION, ...props }) {
|
|
35
38
|
const uniqueId = React.useId();
|
|
36
39
|
const chartId = sanitizeChartToken(`chart-${id ?? uniqueId.replace(/:/g, "")}`);
|
|
37
|
-
return (_jsx(ChartContext.Provider, { value: { config }, children: _jsxs("div", { "data-slot": "chart", "data-chart": chartId, className: cn("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-layer]:outline-hidden [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden", className), ...props, children: [_jsx(ChartStyle, { id: chartId, config: config }), _jsx(RechartsPrimitive.ResponsiveContainer, { initialDimension: initialDimension, children: children })] }) }));
|
|
40
|
+
return (_jsx(ChartContext.Provider, { value: { config }, children: _jsxs("div", { "data-slot": "chart", "data-chart": chartId, className: cn("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", className), ...props, children: [_jsx(ChartStyle, { id: chartId, config: config }), _jsx(RechartsPrimitive.ResponsiveContainer, { initialDimension: initialDimension, children: children })] }) }));
|
|
38
41
|
}
|
|
39
42
|
const ChartStyle = ({ id, config }) => {
|
|
40
43
|
const chartId = sanitizeChartToken(id);
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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";
|
package/dist/styles/globals.css
CHANGED
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
*/
|
|
28
28
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
/*
|
|
31
|
+
* Raw --nc-* dark remaps (state/selection/cross/chart) are generated at
|
|
32
|
+
* :root.dark specificity inside the design-system theme import above; the
|
|
33
|
+
* package-local copy that used to live here is gone so the generated theme
|
|
34
|
+
* stays the single owner. tests/dark-token-cascade.test.ts guards the
|
|
35
|
+
* resolved cascade across this file's import order.
|
|
36
|
+
*/
|
|
37
37
|
|
|
38
38
|
@theme inline {
|
|
39
39
|
--color-background: var(--background);
|
|
@@ -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
|
|
|
@@ -57,7 +67,7 @@ Forms:
|
|
|
57
67
|
- Do not create or import a parallel `FormInput`/`FormTextarea` control family.
|
|
58
68
|
|
|
59
69
|
Rules:
|
|
60
|
-
- Dark-ready through globals only. `@nadicodeai/ui/globals.css` owns `:root` and
|
|
70
|
+
- Dark-ready through globals only. The generated design-system theme (imported by `@nadicodeai/ui/globals.css`) owns the `:root`/`.dark` shadcn role maps and the root-only `:root.dark` raw `--nc-*` remaps (state/selection/cross/chart); `globals.css` owns the Tailwind bridge and UI-local product aliases such as `--nc-app-surface`, `--nc-chat-tool-surface`, and `--nc-terminal-bg`. The `dark` class must sit on the root element (next-themes `attribute="class"`). Components must not add Tailwind `dark:` utilities or OS dark-mode branching.
|
|
61
71
|
- Token-only. Use NadicodeAI/shadcn semantic utilities such as `bg-background`, `text-foreground`, `border-border`, `text-destructive`, UI-local aliases like `bg-[var(--nc-app-surface)]`, and brand token arbitrary values like `text-[var(--nc-link-deep)]`.
|
|
62
72
|
- Do not use raw `#hex`, `rgb()`, `hsl()`, or raw palette utilities such as `text-blue-600`, `bg-zinc-950`, `text-white`, or `bg-black/50`.
|
|
63
73
|
- App routes should compose existing reusable sections/compositions from this package. Extract new shared UI only after a second concrete consumer, a plan-approved shared component, or a named validation slice proves the package boundary.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/ui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/fix-dist-imports.mjs && rm -rf dist/styles && cp -R src/styles dist/styles",
|
|
46
46
|
"build:fast": "node scripts/dev-build.mjs",
|
|
47
47
|
"prepack": "npm run build",
|
|
48
|
+
"lint": "eslint --max-warnings=0",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
48
50
|
"test": "vitest run"
|
|
49
51
|
},
|
|
50
52
|
"dependencies": {
|
|
51
53
|
"@base-ui/react": "^1.6.0",
|
|
52
|
-
"@nadicodeai/design-system": "0.5.
|
|
54
|
+
"@nadicodeai/design-system": "0.5.3",
|
|
53
55
|
"@rive-app/react-webgl2": "^4.29.1",
|
|
54
56
|
"@streamdown/cjk": "^1.0.3",
|
|
55
57
|
"@streamdown/code": "^1.1.1",
|
|
@@ -90,10 +92,12 @@
|
|
|
90
92
|
"@tailwindcss/postcss": "^4.3.1",
|
|
91
93
|
"@types/react": "^19",
|
|
92
94
|
"@types/react-dom": "^19",
|
|
95
|
+
"eslint": "^9.39.4",
|
|
93
96
|
"react": "19.2.7",
|
|
94
97
|
"react-dom": "19.2.7",
|
|
95
98
|
"tailwindcss": "^4.3.1",
|
|
96
99
|
"typescript": "^5",
|
|
100
|
+
"typescript-eslint": "^8.46.0",
|
|
97
101
|
"vitest": "^4.1.6"
|
|
98
102
|
}
|
|
99
103
|
}
|