@nadicodeai/ui 7.1.3 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -7
- package/dist/components/agent-card.js +1 -1
- package/dist/components/agent-visual.js +1 -1
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/automation-section.js +1 -1
- package/dist/components/card-marketing.d.ts +1 -1
- package/dist/components/card-marketing.js +4 -4
- package/dist/components/card.js +3 -3
- package/dist/components/chart-area-stacked.d.ts +4 -0
- package/dist/components/chart-area-stacked.d.ts.map +1 -0
- package/dist/components/chart-area-stacked.js +24 -0
- package/dist/components/chart-axis.d.ts +38 -0
- package/dist/components/chart-axis.d.ts.map +1 -0
- package/dist/components/chart-axis.js +83 -0
- package/dist/components/chart-bar-multiple.d.ts +9 -0
- package/dist/components/chart-bar-multiple.d.ts.map +1 -0
- package/dist/components/chart-bar-multiple.js +26 -0
- package/dist/components/chart-bar-stacked.d.ts +10 -0
- package/dist/components/chart-bar-stacked.d.ts.map +1 -0
- package/dist/components/chart-bar-stacked.js +41 -0
- package/dist/components/chart-card.d.ts +75 -0
- package/dist/components/chart-card.d.ts.map +1 -0
- package/dist/components/chart-card.js +71 -0
- package/dist/components/chart-date-range-control.d.ts +4 -6
- package/dist/components/chart-date-range-control.d.ts.map +1 -1
- package/dist/components/chart-date-range-control.js +34 -32
- package/dist/components/chart-sparkline.d.ts +6 -42
- package/dist/components/chart-sparkline.d.ts.map +1 -1
- package/dist/components/chart-sparkline.js +22 -35
- package/dist/components/chart.d.ts +3 -4
- package/dist/components/chart.d.ts.map +1 -1
- package/dist/components/chart.js +7 -16
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.js +2 -2
- package/dist/components/context-menu.js +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/drawer.js +1 -1
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/glass-control.js +1 -1
- package/dist/components/hover-card.js +1 -1
- package/dist/components/kpi-card.d.ts +37 -0
- package/dist/components/kpi-card.d.ts.map +1 -0
- package/dist/components/kpi-card.js +20 -0
- package/dist/components/live-demo.js +1 -1
- package/dist/components/menubar.js +1 -1
- package/dist/components/model-mark.js +1 -1
- package/dist/components/navigation-menu.js +2 -2
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.d.ts.map +1 -1
- package/dist/components/popover.js +2 -2
- package/dist/components/progress.d.ts +18 -2
- package/dist/components/progress.d.ts.map +1 -1
- package/dist/components/progress.js +40 -10
- package/dist/components/select.js +2 -2
- package/dist/components/sheet.js +1 -1
- package/dist/components/sidebar.js +1 -1
- package/dist/components/toggle.js +1 -1
- package/dist/components/trend-badge.d.ts +22 -0
- package/dist/components/trend-badge.d.ts.map +1 -0
- package/dist/components/trend-badge.js +31 -0
- package/dist/index.d.ts +13 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -14
- package/docs/component-classification.md +1 -1
- package/docs/contract.md +19 -21
- package/package.json +9 -9
- package/dist/components/chart-breakdown-table.d.ts +0 -69
- package/dist/components/chart-breakdown-table.d.ts.map +0 -1
- package/dist/components/chart-breakdown-table.js +0 -35
- package/dist/components/chart-bullet-bar.d.ts +0 -53
- package/dist/components/chart-bullet-bar.d.ts.map +0 -1
- package/dist/components/chart-bullet-bar.js +0 -63
- package/dist/components/chart-delta.d.ts +0 -30
- package/dist/components/chart-delta.d.ts.map +0 -1
- package/dist/components/chart-delta.js +0 -32
- package/dist/components/chart-kpi-stat.d.ts +0 -44
- package/dist/components/chart-kpi-stat.d.ts.map +0 -1
- package/dist/components/chart-kpi-stat.js +0 -51
- package/dist/components/chart-time-series-layout.d.ts +0 -6
- package/dist/components/chart-time-series-layout.d.ts.map +0 -1
- package/dist/components/chart-time-series-layout.js +0 -6
- package/dist/components/chart-time-series.d.ts +0 -103
- package/dist/components/chart-time-series.d.ts.map +0 -1
- package/dist/components/chart-time-series.js +0 -310
- package/dist/components/stat-block.d.ts +0 -24
- package/dist/components/stat-block.d.ts.map +0 -1
- package/dist/components/stat-block.js +0 -26
- package/dist/components/stats-band.d.ts +0 -22
- package/dist/components/stats-band.d.ts.map +0 -1
- package/dist/components/stats-band.js +0 -32
package/AGENTS.md
CHANGED
|
@@ -21,10 +21,3 @@ When a brand-icon or model-maker allow-list changes, run
|
|
|
21
21
|
`generate:brand-icons`.
|
|
22
22
|
Use current shadcn or assistant-ui discovery for their components;
|
|
23
23
|
`--overwrite` requires authorization to replace the affected component.
|
|
24
|
-
|
|
25
|
-
Charts are shadcn charts: `src/components/chart.tsx` is shadcn's `chart` over
|
|
26
|
-
Recharts, and the `chart-*.tsx` kit is built on it. A surface takes its chart
|
|
27
|
-
from this kit. When the kit lacks one, look it up in the shadcn registry
|
|
28
|
-
(`npx shadcn@latest search @shadcn -q chart`, run here) and add it to the kit
|
|
29
|
-
with NadicodeAI tokens before any app draws its own. `recharts` is a dependency
|
|
30
|
-
of this package only.
|
|
@@ -10,6 +10,6 @@ function AgentCard({ action, className, job, meta, name, status, visual, ...prop
|
|
|
10
10
|
const hasMeta = hasSlotContent(meta);
|
|
11
11
|
const hasAction = hasSlotContent(action);
|
|
12
12
|
const hasFooter = hasStatus || hasMeta || hasAction;
|
|
13
|
-
return (_jsxs(Card, { className: cn("h-full", className), ...props, children: [_jsxs(CardHeader, { className: "relative min-h-40 pr-32", children: [_jsx(CardTitle, { className: "text-lg", children: name }), _jsx(CardDescription, { className: "max-w-sm leading-6", children: job }), _jsx("div", { className: "absolute top-5 right-5 size-24 overflow-hidden rounded-
|
|
13
|
+
return (_jsxs(Card, { className: cn("h-full", className), ...props, children: [_jsxs(CardHeader, { className: "relative min-h-40 pr-32", children: [_jsx(CardTitle, { className: "text-lg", children: name }), _jsx(CardDescription, { className: "max-w-sm leading-6", children: job }), _jsx("div", { className: "absolute top-5 right-5 size-24 overflow-hidden rounded-md", "data-slot": "agent-card-visual", children: visual })] }), hasFooter ? (_jsxs(CardContent, { className: "mt-auto grid gap-5", children: [_jsx(Separator, {}), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-3 text-xs text-muted-foreground", children: [hasStatus ? _jsx("div", { "data-slot": "agent-card-status", children: status }) : null, hasMeta ? (_jsx("div", { className: "min-w-0 flex-1", "data-slot": "agent-card-meta", children: meta })) : null, hasAction ? (_jsx("div", { className: "ml-auto", "data-slot": "agent-card-action", children: action })) : null] })] })) : null] }));
|
|
14
14
|
}
|
|
15
15
|
export { AgentCard };
|
|
@@ -58,6 +58,6 @@ function AgentVisual({ className, decorative = true, label, presentation = "card
|
|
|
58
58
|
return () => observer.disconnect();
|
|
59
59
|
}, []);
|
|
60
60
|
const hasAmbientRenderer = canAnimate && isInViewport;
|
|
61
|
-
return (_jsxs("div", { "aria-hidden": decorative || undefined, "aria-label": decorative ? undefined : label, className: cn("relative isolate size-full overflow-hidden rounded-
|
|
61
|
+
return (_jsxs("div", { "aria-hidden": decorative || undefined, "aria-label": decorative ? undefined : label, className: cn("relative isolate size-full overflow-hidden rounded-md bg-nc-identity-ink", presentation === "card" && "min-h-20", presentation === "detail" && "min-h-48", className), "data-agent-visual-motion": hasAmbientRenderer ? "ambient" : "static", "data-agent-visual-presentation": presentation, "data-agent-visual-recipe": recipe, ref: hostRef, role: decorative ? undefined : "img", children: [_jsx(AgentVisualPoster, { recipe: recipe }), hasAmbientRenderer && hostRef.current ? (_jsx("div", { "aria-hidden": "true", className: cn("absolute inset-0 opacity-0 transition-opacity duration-[var(--nc-duration-close)] ease-[var(--nc-ease-out-strong)]", rendererReady && "opacity-100"), "data-agent-visual-renderer": "paper-metaballs", children: _jsx(AgentVisualErrorBoundary, { onUnavailable: handleUnavailable, children: _jsx(Suspense, { fallback: null, children: _jsx(LazyAgentVisualRenderer, { host: hostRef.current, onReady: handleReady, onUnavailable: handleUnavailable, recipe: recipe }) }) }) })) : null] }));
|
|
62
62
|
}
|
|
63
63
|
export { AgentVisual };
|
|
@@ -17,7 +17,7 @@ function AlertDialogOverlay({ className, ...props }) {
|
|
|
17
17
|
return (_jsx(AlertDialogPrimitive.Backdrop, { "data-slot": "alert-dialog-overlay", className: cn("fixed inset-0 isolate z-50 bg-scrim/scrim", overlayBackdropMotion, className), ...props }));
|
|
18
18
|
}
|
|
19
19
|
function AlertDialogContent({ className, size = "default", ...props }) {
|
|
20
|
-
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Popup, { "data-slot": "alert-dialog-content", "data-size": size, className: cn("group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-nadicode-md rounded-
|
|
20
|
+
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Popup, { "data-slot": "alert-dialog-content", "data-size": size, className: cn("group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-nadicode-md rounded-md border-0 bg-popover p-nadicode-lg text-popover-foreground nc-elevation-modal outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm", modalOverlayMotion, className), ...props })] }));
|
|
21
21
|
}
|
|
22
22
|
function AlertDialogHeader({ className, ...props }) {
|
|
23
23
|
return (_jsx("div", { "data-slot": "alert-dialog-header", className: cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className), ...props }));
|
|
@@ -47,7 +47,7 @@ const environmentStateMeta = {
|
|
|
47
47
|
invalid: { label: "Needs review", variant: "destructive" },
|
|
48
48
|
};
|
|
49
49
|
function AutomationSection({ title, description, actions, automations = [], emptyTitle = "No automations configured", emptyDescription = "Add automation rows from the consuming app when there is work to run.", onAutomationEnabledChange, children, className, ...props }) {
|
|
50
|
-
return (_jsxs("section", { "data-slot": "automation-section", className: cn("grid gap-5 rounded-
|
|
50
|
+
return (_jsxs("section", { "data-slot": "automation-section", className: cn("grid gap-5 rounded-md border bg-background p-5", className), ...props, children: [(title || description || actions) && (_jsxs("div", { "data-slot": "automation-section-header", className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "grid gap-1", children: [title && (_jsx("h2", { "data-slot": "automation-section-title", className: "text-base font-semibold", children: title })), description && (_jsx("p", { "data-slot": "automation-section-description", className: "text-sm text-muted-foreground", children: description }))] }), actions && _jsx("div", { "data-slot": "automation-section-actions", children: actions })] })), automations.length > 0 ? (_jsx(ItemGroup, { "data-slot": "automation-section-items", className: "gap-2", children: automations.map((automation) => {
|
|
51
51
|
const status = automation.status ?? "ready";
|
|
52
52
|
return (_jsxs(Item, { role: "listitem", "data-slot": "automation-section-item", "data-status": status, "data-enabled": automation.enabled, "data-disabled": automation.disabled ? true : undefined, variant: "outline", children: [_jsx(ItemMedia, { variant: "icon", "aria-hidden": "true", children: automationStatusMeta[status].icon }), _jsxs(ItemContent, { children: [_jsx(ItemTitle, { children: automation.title }), automation.description && (_jsx(ItemDescription, { children: automation.description })), automation.meta && (_jsx("div", { className: "text-sm text-muted-foreground", children: automation.meta }))] }), _jsxs(ItemActions, { children: [_jsx(Badge, { variant: automationStatusMeta[status].variant, children: automationStatusMeta[status].label }), _jsx(Switch, { "aria-label": automation.toggleLabel ?? "Toggle automation", checked: Boolean(automation.enabled), disabled: automation.disabled, onCheckedChange: (enabled) => onAutomationEnabledChange?.(automation.id, enabled) }), automation.actions] })] }, automation.id));
|
|
53
53
|
}) })) : (_jsx(FeedbackState, { "data-slot": "automation-section-empty", status: "empty", title: emptyTitle, description: emptyDescription, compact: true, className: "rounded-md" })), children] }));
|
|
@@ -13,7 +13,7 @@ import * as React from "react";
|
|
|
13
13
|
* square top corners to the card radius (the card owns the outer radius; the media
|
|
14
14
|
* never re-rounds it). No other layer repeats either coordinate.
|
|
15
15
|
*
|
|
16
|
-
* `size="lg"` steps up to
|
|
16
|
+
* `size="lg"` steps up to larger padding; `surface="soft"` uses the
|
|
17
17
|
* `canvas-soft` fill. Server-renderable: the media-present padding compression is a
|
|
18
18
|
* `:has()` variant and the title/media sizing is driven by `data-size` descendant
|
|
19
19
|
* selectors on the parent — no client context. Token-only; no `dark:` styling.
|
|
@@ -14,20 +14,20 @@ import { cn } from "../lib/utils.js";
|
|
|
14
14
|
* square top corners to the card radius (the card owns the outer radius; the media
|
|
15
15
|
* never re-rounds it). No other layer repeats either coordinate.
|
|
16
16
|
*
|
|
17
|
-
* `size="lg"` steps up to
|
|
17
|
+
* `size="lg"` steps up to larger padding; `surface="soft"` uses the
|
|
18
18
|
* `canvas-soft` fill. Server-renderable: the media-present padding compression is a
|
|
19
19
|
* `:has()` variant and the title/media sizing is driven by `data-size` descendant
|
|
20
20
|
* selectors on the parent — no client context. Token-only; no `dark:` styling.
|
|
21
21
|
*/
|
|
22
22
|
function CardMarketing({ className, size = "default", surface = "canvas", ...props }) {
|
|
23
|
-
return (_jsx("div", { "data-slot": "card-marketing", "data-size": size, "data-surface": surface, className: cn("grid content-start overflow-hidden border border-border text-foreground", surface === "soft" ? "bg-[var(--nc-canvas-soft)]" : "bg-card", size === "default" && [
|
|
24
|
-
"gap-3
|
|
23
|
+
return (_jsx("div", { "data-slot": "card-marketing", "data-size": size, "data-surface": surface, className: cn("grid content-start overflow-hidden rounded-md border border-border text-foreground", surface === "soft" ? "bg-[var(--nc-canvas-soft)]" : "bg-card", size === "default" && [
|
|
24
|
+
"gap-3 p-6",
|
|
25
25
|
// media present -> compress padding; the media insets follow (below).
|
|
26
26
|
"has-[[data-slot=card-marketing-media]]:gap-3 has-[[data-slot=card-marketing-media]]:p-4",
|
|
27
27
|
"[&_[data-slot=card-marketing-title]]:text-xl",
|
|
28
28
|
"[&_[data-slot=card-marketing-media]]:-mx-4 [&_[data-slot=card-marketing-media]]:-mt-4 [&_[data-slot=card-marketing-media]]:mb-1",
|
|
29
29
|
], size === "lg" && [
|
|
30
|
-
"gap-4
|
|
30
|
+
"gap-4 p-8",
|
|
31
31
|
"has-[[data-slot=card-marketing-media]]:p-6",
|
|
32
32
|
"[&_[data-slot=card-marketing-title]]:text-2xl",
|
|
33
33
|
"[&_[data-slot=card-marketing-media]]:-mx-6 [&_[data-slot=card-marketing-media]]:-mt-6 [&_[data-slot=card-marketing-media]]:mb-1.5",
|
package/dist/components/card.js
CHANGED
|
@@ -5,8 +5,8 @@ import { cn } from "../lib/utils.js";
|
|
|
5
5
|
function Card({ className, render, size = "default", surface = "resting", variant = "default", ...props }) {
|
|
6
6
|
const structure = cn("group/card flex flex-col gap-(--card-spacing) overflow-hidden bg-card py-(--card-spacing) text-sm text-card-foreground [--card-spacing:var(--spacing-nadicode-lg)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:var(--spacing-nadicode-md)] data-[size=sm]:has-data-[slot=card-footer]:pb-0", variant === "interactive" && "cursor-pointer", variant === "link" && "nc-transition-paint");
|
|
7
7
|
const surfaceClassName = surface === "outlined"
|
|
8
|
-
? cn(structure, "rounded-
|
|
9
|
-
: cn(structure, "rounded-
|
|
8
|
+
? cn(structure, "rounded-md border border-border", className)
|
|
9
|
+
: cn(structure, "rounded-md border-0 *:[img:first-child]:rounded-t-md *:[img:last-child]:rounded-b-md", surface === "resting" && "nc-elevation-resting", surface === "raised" && "nc-elevation-raised", className);
|
|
10
10
|
return useRender({
|
|
11
11
|
defaultTagName: "div",
|
|
12
12
|
props: mergeProps({ className: surfaceClassName }, props),
|
|
@@ -15,7 +15,7 @@ function Card({ className, render, size = "default", surface = "resting", varian
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
function CardHeader({ className, ...props }) {
|
|
18
|
-
return (_jsx("div", { "data-slot": "card-header", className: cn("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-
|
|
18
|
+
return (_jsx("div", { "data-slot": "card-header", className: cn("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-md px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)", className), ...props }));
|
|
19
19
|
}
|
|
20
20
|
function CardTitle({ className, focusTarget = false, ...props }) {
|
|
21
21
|
return (_jsx("div", { "data-slot": "card-title", "data-focus-target": focusTarget || undefined, className: cn("text-base font-medium group-data-[size=sm]/card:text-sm", focusTarget && "rounded-sm", className), ...props }));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ChartCardProps } from "./chart-card.js";
|
|
2
|
+
declare function ChartAreaStacked({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter, axisFormatter, otherLabel, emptyState, size, className, }: ChartCardProps): import("react").JSX.Element;
|
|
3
|
+
export { ChartAreaStacked };
|
|
4
|
+
//# sourceMappingURL=chart-area-stacked.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-area-stacked.d.ts","sourceRoot":"","sources":["../../src/components/chart-area-stacked.tsx"],"names":[],"mappings":"AAcA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAGjC,iBAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,cAAsC,EACtC,aAAa,EACb,UAAoB,EACpB,UAAU,EACV,IAAgB,EAChB,SAAS,GACV,EAAE,cAAc,+BAwEhB;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Pulled from the shadcn registry item `chart-area-stacked`
|
|
2
|
+
// (https://ui.shadcn.com/r/styles/new-york-v4/chart-area-stacked.json).
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
|
6
|
+
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "./chart.js";
|
|
7
|
+
import { CHART_Y_AXIS_TICK_COUNT, niceAxisTicks, stackedAxisMax } from "./chart-axis.js";
|
|
8
|
+
import { CHART_LEGEND_CLASS, ChartCard, chartPlotClassName, chartSeriesConfig, defaultValueFormatter, foldOtherSeries, } from "./chart-card.js";
|
|
9
|
+
import { entityColor } from "./chart-entity-colors.js";
|
|
10
|
+
function ChartAreaStacked({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter = defaultValueFormatter, axisFormatter, otherLabel = "Other", emptyState, size = "default", className, }) {
|
|
11
|
+
const folded = foldOtherSeries(series, data, entityColorMap, otherLabel);
|
|
12
|
+
const chartConfig = chartSeriesConfig(folded.series, entityColorMap);
|
|
13
|
+
const domainMax = stackedAxisMax(folded.data, folded.series.map((entry) => entry.id));
|
|
14
|
+
return (_jsx(ChartCard, { title: title, description: description, action: action, footer: footer, ...(headingLevel ? { headingLevel } : {}), className: className, children: (titleId) => emptyState && data.length === 0 ? (emptyState) : (_jsx(ChartContainer, { config: chartConfig, className: chartPlotClassName(size), children: _jsxs(AreaChart, { accessibilityLayer: true, "aria-labelledby": titleId, data: folded.data, margin: {
|
|
15
|
+
left: 12,
|
|
16
|
+
right: 12,
|
|
17
|
+
}, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(YAxis, { width: "auto", tickLine: false, axisLine: false, tickMargin: 8, tickCount: CHART_Y_AXIS_TICK_COUNT, ...(domainMax === undefined
|
|
18
|
+
? {}
|
|
19
|
+
: {
|
|
20
|
+
domain: [0, domainMax],
|
|
21
|
+
ticks: niceAxisTicks(domainMax),
|
|
22
|
+
}), tickFormatter: (value) => (axisFormatter ?? valueFormatter)(Number(value)) }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { indicator: "line", valueFormatter: valueFormatter }) }), _jsx(ChartLegend, { itemSorter: null, content: _jsx(ChartLegendContent, { className: CHART_LEGEND_CLASS }) }), folded.series.map((entry) => (_jsx(Area, { dataKey: entry.id, type: "natural", fill: entityColor(entityColorMap, entry.id), fillOpacity: 0.4, stroke: entityColor(entityColorMap, entry.id), stackId: "a" }, entry.id)))] }) })) }));
|
|
23
|
+
}
|
|
24
|
+
export { ChartAreaStacked };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chart-axis — the kit's one axis deviation from the stock shadcn chart blocks.
|
|
3
|
+
*
|
|
4
|
+
* Left to itself Recharts divides the data's own maximum by its tick count,
|
|
5
|
+
* which produces steps of whatever the data happens to reach: one console chart
|
|
6
|
+
* read $0/$5/$10 while the chart beside it read EUR0/9/18/27/36, and the two
|
|
7
|
+
* stopped sharing a numeric register. Every chart in this kit therefore draws a
|
|
8
|
+
* y axis of five ticks whose maximum is rounded up to a step a reader counts
|
|
9
|
+
* in: 1, 2, 2.5, 5 or 10 per order of magnitude.
|
|
10
|
+
*/
|
|
11
|
+
/** Ticks the y axis draws, ends included. */
|
|
12
|
+
declare const CHART_Y_AXIS_TICK_COUNT = 5;
|
|
13
|
+
declare function niceStep(rawStep: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* Rounds a raw maximum up to a whole number of nice steps. `undefined` when
|
|
16
|
+
* there is nothing to bound, so the axis falls back to Recharts' own domain
|
|
17
|
+
* rather than pinning itself to zero.
|
|
18
|
+
*/
|
|
19
|
+
declare function niceAxisMax(max: number): number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* The ticks for a domain `niceAxisMax` built: that maximum is four nice steps, so
|
|
22
|
+
* the five ticks land on those steps exactly. Handing them to the axis is what
|
|
23
|
+
* keeps Recharts from choosing its own count and putting a rounded label on an
|
|
24
|
+
* unrounded position (a tick at 6.67 reading "$6").
|
|
25
|
+
*/
|
|
26
|
+
declare function niceAxisTicks(max: number | undefined): number[] | undefined;
|
|
27
|
+
type AxisDatum = Record<string, number | string>;
|
|
28
|
+
/** The tallest stacked column across the data, as a nice axis maximum. */
|
|
29
|
+
declare function stackedAxisMax(data: readonly AxisDatum[], keys: readonly string[]): number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The largest single value across the keys, as a nice axis maximum. Grouped
|
|
32
|
+
* bars bound one value rather than a column's sum. A negative value returns
|
|
33
|
+
* `undefined`: a diverging read fits its own domain instead of being clipped to
|
|
34
|
+
* a zero-based one.
|
|
35
|
+
*/
|
|
36
|
+
declare function seriesAxisMax(data: readonly AxisDatum[], keys: readonly string[]): number | undefined;
|
|
37
|
+
export { CHART_Y_AXIS_TICK_COUNT, niceAxisMax, niceAxisTicks, niceStep, seriesAxisMax, stackedAxisMax, };
|
|
38
|
+
//# sourceMappingURL=chart-axis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-axis.d.ts","sourceRoot":"","sources":["../../src/components/chart-axis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,6CAA6C;AAC7C,QAAA,MAAM,uBAAuB,IAAI,CAAC;AAKlC,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzC;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIpD;AAED;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAOpE;AAED,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEjD,0EAA0E;AAC1E,iBAAS,cAAc,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAW/F;AAED;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,aAAa,EACb,QAAQ,EACR,aAAa,EACb,cAAc,GACf,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chart-axis — the kit's one axis deviation from the stock shadcn chart blocks.
|
|
3
|
+
*
|
|
4
|
+
* Left to itself Recharts divides the data's own maximum by its tick count,
|
|
5
|
+
* which produces steps of whatever the data happens to reach: one console chart
|
|
6
|
+
* read $0/$5/$10 while the chart beside it read EUR0/9/18/27/36, and the two
|
|
7
|
+
* stopped sharing a numeric register. Every chart in this kit therefore draws a
|
|
8
|
+
* y axis of five ticks whose maximum is rounded up to a step a reader counts
|
|
9
|
+
* in: 1, 2, 2.5, 5 or 10 per order of magnitude.
|
|
10
|
+
*/
|
|
11
|
+
/** Ticks the y axis draws, ends included. */
|
|
12
|
+
const CHART_Y_AXIS_TICK_COUNT = 5;
|
|
13
|
+
/** The steps a reader counts in, per order of magnitude. */
|
|
14
|
+
const NICE_STEP_MULTIPLES = [1, 2, 2.5, 5, 10];
|
|
15
|
+
function niceStep(rawStep) {
|
|
16
|
+
if (!Number.isFinite(rawStep) || rawStep <= 0)
|
|
17
|
+
return 1;
|
|
18
|
+
const magnitude = 10 ** Math.floor(Math.log10(rawStep));
|
|
19
|
+
const normalized = rawStep / magnitude;
|
|
20
|
+
const multiple = NICE_STEP_MULTIPLES.find((candidate) => normalized <= candidate) ?? 10;
|
|
21
|
+
return multiple * magnitude;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Rounds a raw maximum up to a whole number of nice steps. `undefined` when
|
|
25
|
+
* there is nothing to bound, so the axis falls back to Recharts' own domain
|
|
26
|
+
* rather than pinning itself to zero.
|
|
27
|
+
*/
|
|
28
|
+
function niceAxisMax(max) {
|
|
29
|
+
if (!Number.isFinite(max) || max <= 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
const step = niceStep(max / (CHART_Y_AXIS_TICK_COUNT - 1));
|
|
32
|
+
return step * (CHART_Y_AXIS_TICK_COUNT - 1);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The ticks for a domain `niceAxisMax` built: that maximum is four nice steps, so
|
|
36
|
+
* the five ticks land on those steps exactly. Handing them to the axis is what
|
|
37
|
+
* keeps Recharts from choosing its own count and putting a rounded label on an
|
|
38
|
+
* unrounded position (a tick at 6.67 reading "$6").
|
|
39
|
+
*/
|
|
40
|
+
function niceAxisTicks(max) {
|
|
41
|
+
if (max === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
const intervals = CHART_Y_AXIS_TICK_COUNT - 1;
|
|
44
|
+
const step = max / intervals;
|
|
45
|
+
return Array.from({ length: CHART_Y_AXIS_TICK_COUNT }, (_unused, index) => Number((step * index).toPrecision(12)));
|
|
46
|
+
}
|
|
47
|
+
/** The tallest stacked column across the data, as a nice axis maximum. */
|
|
48
|
+
function stackedAxisMax(data, keys) {
|
|
49
|
+
let max = 0;
|
|
50
|
+
for (const datum of data) {
|
|
51
|
+
let total = 0;
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
const value = datum[key];
|
|
54
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
55
|
+
total += value;
|
|
56
|
+
}
|
|
57
|
+
if (total > max)
|
|
58
|
+
max = total;
|
|
59
|
+
}
|
|
60
|
+
return niceAxisMax(max);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The largest single value across the keys, as a nice axis maximum. Grouped
|
|
64
|
+
* bars bound one value rather than a column's sum. A negative value returns
|
|
65
|
+
* `undefined`: a diverging read fits its own domain instead of being clipped to
|
|
66
|
+
* a zero-based one.
|
|
67
|
+
*/
|
|
68
|
+
function seriesAxisMax(data, keys) {
|
|
69
|
+
let max = 0;
|
|
70
|
+
for (const datum of data) {
|
|
71
|
+
for (const key of keys) {
|
|
72
|
+
const value = datum[key];
|
|
73
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
74
|
+
continue;
|
|
75
|
+
if (value < 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (value > max)
|
|
78
|
+
max = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return niceAxisMax(max);
|
|
82
|
+
}
|
|
83
|
+
export { CHART_Y_AXIS_TICK_COUNT, niceAxisMax, niceAxisTicks, niceStep, seriesAxisMax, stackedAxisMax, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChartCardProps } from "./chart-card.js";
|
|
2
|
+
/**
|
|
3
|
+
* Grouped bars: two or more measures side by side per period, on ONE axis in
|
|
4
|
+
* one unit. A rate over those measures (a margin percentage) is a KPI card, not
|
|
5
|
+
* a second axis on this plot.
|
|
6
|
+
*/
|
|
7
|
+
declare function ChartBarMultiple({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter, axisFormatter, otherLabel, emptyState, size, className, }: ChartCardProps): import("react").JSX.Element;
|
|
8
|
+
export { ChartBarMultiple };
|
|
9
|
+
//# sourceMappingURL=chart-bar-multiple.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-bar-multiple.d.ts","sourceRoot":"","sources":["../../src/components/chart-bar-multiple.tsx"],"names":[],"mappings":"AAcA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAGjC;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,cAAsC,EACtC,aAAa,EACb,UAAoB,EACpB,UAAU,EACV,IAAgB,EAChB,SAAS,GACV,EAAE,cAAc,+BA8DhB;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Pulled from the shadcn registry item `chart-bar-multiple`
|
|
2
|
+
// (https://ui.shadcn.com/r/styles/new-york-v4/chart-bar-multiple.json).
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
|
6
|
+
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "./chart.js";
|
|
7
|
+
import { CHART_Y_AXIS_TICK_COUNT, niceAxisTicks, seriesAxisMax } from "./chart-axis.js";
|
|
8
|
+
import { CHART_LEGEND_CLASS, ChartCard, chartPlotClassName, chartSeriesConfig, defaultValueFormatter, foldOtherSeries, } from "./chart-card.js";
|
|
9
|
+
import { entityColor } from "./chart-entity-colors.js";
|
|
10
|
+
/**
|
|
11
|
+
* Grouped bars: two or more measures side by side per period, on ONE axis in
|
|
12
|
+
* one unit. A rate over those measures (a margin percentage) is a KPI card, not
|
|
13
|
+
* a second axis on this plot.
|
|
14
|
+
*/
|
|
15
|
+
function ChartBarMultiple({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter = defaultValueFormatter, axisFormatter, otherLabel = "Other", emptyState, size = "default", className, }) {
|
|
16
|
+
const folded = foldOtherSeries(series, data, entityColorMap, otherLabel);
|
|
17
|
+
const chartConfig = chartSeriesConfig(folded.series, entityColorMap);
|
|
18
|
+
const domainMax = seriesAxisMax(folded.data, folded.series.map((entry) => entry.id));
|
|
19
|
+
return (_jsx(ChartCard, { title: title, description: description, action: action, footer: footer, ...(headingLevel ? { headingLevel } : {}), className: className, children: (titleId) => emptyState && data.length === 0 ? (emptyState) : (_jsx(ChartContainer, { config: chartConfig, className: chartPlotClassName(size), children: _jsxs(BarChart, { accessibilityLayer: true, "aria-labelledby": titleId, data: folded.data, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: xKey, tickLine: false, tickMargin: 10, axisLine: false }), _jsx(YAxis, { width: "auto", tickLine: false, axisLine: false, tickMargin: 8, tickCount: CHART_Y_AXIS_TICK_COUNT, ...(domainMax === undefined
|
|
20
|
+
? {}
|
|
21
|
+
: {
|
|
22
|
+
domain: [0, domainMax],
|
|
23
|
+
ticks: niceAxisTicks(domainMax),
|
|
24
|
+
}), tickFormatter: (value) => (axisFormatter ?? valueFormatter)(Number(value)) }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { indicator: "line", valueFormatter: valueFormatter }) }), _jsx(ChartLegend, { itemSorter: null, content: _jsx(ChartLegendContent, { className: CHART_LEGEND_CLASS }) }), folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, fill: entityColor(entityColorMap, entry.id), radius: 4, maxBarSize: 48 }, entry.id)))] }) })) }));
|
|
25
|
+
}
|
|
26
|
+
export { ChartBarMultiple };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ChartCardProps } from "./chart-card.js";
|
|
2
|
+
declare function ChartBarStacked({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter, axisFormatter, otherLabel, emptyState, size, share, className, }: ChartCardProps & {
|
|
3
|
+
/**
|
|
4
|
+
* One bar across the card, each entity a segment of 100%: the share of a
|
|
5
|
+
* total with no time axis. `data` is then a single row keyed by series id.
|
|
6
|
+
*/
|
|
7
|
+
share?: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
export { ChartBarStacked };
|
|
10
|
+
//# sourceMappingURL=chart-bar-stacked.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-bar-stacked.d.ts","sourceRoot":"","sources":["../../src/components/chart-bar-stacked.tsx"],"names":[],"mappings":"AAcA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAmBjC,iBAAS,eAAe,CAAC,EACvB,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,cAAsC,EACtC,aAAa,EACb,UAAoB,EACpB,UAAU,EACV,IAAgB,EAChB,KAAa,EACb,SAAS,GACV,EAAE,cAAc,GAAG;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,+BA8FA;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Pulled from the shadcn registry item `chart-bar-stacked`
|
|
2
|
+
// (https://ui.shadcn.com/r/styles/new-york-v4/chart-bar-stacked.json).
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
|
6
|
+
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "./chart.js";
|
|
7
|
+
import { CHART_Y_AXIS_TICK_COUNT, niceAxisTicks, stackedAxisMax } from "./chart-axis.js";
|
|
8
|
+
import { CHART_LEGEND_CLASS, ChartCard, chartPlotClassName, chartSeriesConfig, defaultValueFormatter, foldOtherSeries, } from "./chart-card.js";
|
|
9
|
+
import { entityColor } from "./chart-entity-colors.js";
|
|
10
|
+
/** Stock stacked-bar corner radius: the bottom bar rounds down, the top bar up. */
|
|
11
|
+
function stackRadius(index, count) {
|
|
12
|
+
if (count === 1)
|
|
13
|
+
return [4, 4, 4, 4];
|
|
14
|
+
if (index === 0)
|
|
15
|
+
return [0, 0, 4, 4];
|
|
16
|
+
if (index === count - 1)
|
|
17
|
+
return [4, 4, 0, 0];
|
|
18
|
+
return [0, 0, 0, 0];
|
|
19
|
+
}
|
|
20
|
+
/** The same stock rounding, turned on its side: the first segment rounds left, the last right. */
|
|
21
|
+
function shareRadius(index, count) {
|
|
22
|
+
if (count === 1)
|
|
23
|
+
return [4, 4, 4, 4];
|
|
24
|
+
if (index === 0)
|
|
25
|
+
return [4, 0, 0, 4];
|
|
26
|
+
if (index === count - 1)
|
|
27
|
+
return [0, 4, 4, 0];
|
|
28
|
+
return [0, 0, 0, 0];
|
|
29
|
+
}
|
|
30
|
+
function ChartBarStacked({ title, description, action, footer, headingLevel, data, xKey, series, entityColorMap, valueFormatter = defaultValueFormatter, axisFormatter, otherLabel = "Other", emptyState, size = "default", share = false, className, }) {
|
|
31
|
+
const folded = foldOtherSeries(series, data, entityColorMap, otherLabel);
|
|
32
|
+
const chartConfig = chartSeriesConfig(folded.series, entityColorMap);
|
|
33
|
+
const domainMax = stackedAxisMax(folded.data, folded.series.map((entry) => entry.id));
|
|
34
|
+
return (_jsx(ChartCard, { title: title, description: description, action: action, footer: footer, ...(headingLevel ? { headingLevel } : {}), className: className, children: (titleId) => emptyState && data.length === 0 ? (emptyState) : share ? (_jsx(ChartContainer, { config: chartConfig, className: "aspect-auto h-16 w-full", children: _jsxs(BarChart, { accessibilityLayer: true, "aria-labelledby": titleId, data: folded.data, layout: "vertical", stackOffset: "expand", children: [_jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { type: "category", dataKey: xKey, hide: true }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { hideLabel: true, indicator: "line", valueFormatter: valueFormatter }) }), _jsx(ChartLegend, { itemSorter: null, content: _jsx(ChartLegendContent, { className: CHART_LEGEND_CLASS }) }), folded.series.map((entry, index) => (_jsx(Bar, { dataKey: entry.id, stackId: "a", fill: entityColor(entityColorMap, entry.id), radius: shareRadius(index, folded.series.length) }, entry.id)))] }) })) : (_jsx(ChartContainer, { config: chartConfig, className: chartPlotClassName(size), children: _jsxs(BarChart, { accessibilityLayer: true, "aria-labelledby": titleId, data: folded.data, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: xKey, tickLine: false, tickMargin: 10, axisLine: false }), _jsx(YAxis, { width: "auto", tickLine: false, axisLine: false, tickMargin: 8, tickCount: CHART_Y_AXIS_TICK_COUNT, ...(domainMax === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: {
|
|
37
|
+
domain: [0, domainMax],
|
|
38
|
+
ticks: niceAxisTicks(domainMax),
|
|
39
|
+
}), tickFormatter: (value) => (axisFormatter ?? valueFormatter)(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { indicator: "line", valueFormatter: valueFormatter }) }), _jsx(ChartLegend, { itemSorter: null, content: _jsx(ChartLegendContent, { className: CHART_LEGEND_CLASS }) }), folded.series.map((entry, index) => (_jsx(Bar, { dataKey: entry.id, stackId: "a", fill: entityColor(entityColorMap, entry.id), radius: stackRadius(index, folded.series.length), maxBarSize: 48 }, entry.id)))] }) })) }));
|
|
40
|
+
}
|
|
41
|
+
export { ChartBarStacked };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { ChartConfig } from "./chart.js";
|
|
3
|
+
import { type EntityColorMap } from "./chart-entity-colors.js";
|
|
4
|
+
/**
|
|
5
|
+
* chart-card — the Card frame and series plumbing every chart in this kit
|
|
6
|
+
* renders, so the four registry blocks (`chart-bar-stacked`,
|
|
7
|
+
* `chart-area-stacked`, `chart-bar-multiple`) hold only
|
|
8
|
+
* their own plot. It is the stock block's own shell —
|
|
9
|
+
* `Card > CardHeader(CardTitle, CardDescription, CardAction) > CardContent >
|
|
10
|
+
* CardFooter?` — named once instead of copied into each file. A page never
|
|
11
|
+
* builds a chart card frame by hand.
|
|
12
|
+
*/
|
|
13
|
+
type ChartSeries = {
|
|
14
|
+
/** Stable entity id: the data key in each datum and the color-map key. */
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
};
|
|
18
|
+
type ChartDatum = Record<string, number | string>;
|
|
19
|
+
type ChartCardSize = "default" | "lg";
|
|
20
|
+
type ChartCardProps = {
|
|
21
|
+
title: React.ReactNode;
|
|
22
|
+
description?: React.ReactNode;
|
|
23
|
+
/** The title's heading level in the page outline. Defaults to 2. */
|
|
24
|
+
headingLevel?: 2 | 3 | 4;
|
|
25
|
+
/** Trailing header slot, e.g. an "Explore" link. */
|
|
26
|
+
action?: React.ReactNode;
|
|
27
|
+
footer?: React.ReactNode;
|
|
28
|
+
data: readonly ChartDatum[];
|
|
29
|
+
/** The datum key carrying the category or period. */
|
|
30
|
+
xKey: string;
|
|
31
|
+
series: readonly ChartSeries[];
|
|
32
|
+
/** The shared page-context entity-color assignment map. */
|
|
33
|
+
entityColorMap: EntityColorMap;
|
|
34
|
+
/** Formats the y-axis ticks and the tooltip values. */
|
|
35
|
+
valueFormatter?: (value: number) => string;
|
|
36
|
+
/** Axis ticks only: a compact reading (1.6B, 800M, $14) where the tooltip keeps the exact figure. Defaults to `valueFormatter`. */
|
|
37
|
+
axisFormatter?: (value: number) => string;
|
|
38
|
+
/** Label for the folded neutral series (entities past the sixth). */
|
|
39
|
+
otherLabel?: string;
|
|
40
|
+
/** Rendered in CardContent instead of the plot when `data` is empty. */
|
|
41
|
+
emptyState?: React.ReactNode;
|
|
42
|
+
size?: ChartCardSize;
|
|
43
|
+
className?: string;
|
|
44
|
+
};
|
|
45
|
+
declare function defaultValueFormatter(value: number): string;
|
|
46
|
+
/** The chart config the legend and tooltip read: each series' label and color. */
|
|
47
|
+
declare function chartSeriesConfig(series: readonly ChartSeries[], entityColorMap: EntityColorMap): ChartConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Folds every series whose entity color resolves to the neutral register into
|
|
50
|
+
* ONE aggregated series (values summed per bucket). Past the sixth entity the
|
|
51
|
+
* color map returns that register, and drawing each tail entity as its own
|
|
52
|
+
* indistinguishable neutral mark is unreadable. Series resolving to a hue pass
|
|
53
|
+
* through unchanged, so six or fewer are never rewritten.
|
|
54
|
+
*/
|
|
55
|
+
declare function foldOtherSeries(series: readonly ChartSeries[], data: readonly ChartDatum[], entityColorMap: EntityColorMap, otherLabel: string): {
|
|
56
|
+
series: ChartSeries[];
|
|
57
|
+
data: ChartDatum[];
|
|
58
|
+
};
|
|
59
|
+
declare function ChartCard({ title, description, action, footer, headingLevel, className, children, }: Pick<ChartCardProps, "title" | "description" | "action" | "footer" | "headingLevel" | "className"> & {
|
|
60
|
+
/** Receives the title's id, which the plot takes as `aria-labelledby`. */
|
|
61
|
+
children: (titleId: string) => React.ReactNode;
|
|
62
|
+
}): React.JSX.Element;
|
|
63
|
+
/** The plot frame class: fixed height, no aspect ratio, full width. */
|
|
64
|
+
declare function chartPlotClassName(size: ChartCardSize): string;
|
|
65
|
+
/**
|
|
66
|
+
* Stock `ChartContainer` hides the plot's outline, and Recharts' accessibility
|
|
67
|
+
* layer makes that same svg focusable, so a keyboard user would land on nothing
|
|
68
|
+
* visible. The ring comes back only while the svg has keyboard focus.
|
|
69
|
+
*/
|
|
70
|
+
declare const CHART_FOCUS_CLASS = "[&_.recharts-surface:focus-visible]:nc-focus-outline [&_.recharts-surface:focus-visible]:outline-ring";
|
|
71
|
+
/** Legend below the plot, left-aligned, wrapping to a second line. */
|
|
72
|
+
declare const CHART_LEGEND_CLASS = "flex-wrap justify-start";
|
|
73
|
+
export { CHART_FOCUS_CLASS, CHART_LEGEND_CLASS, ChartCard, chartPlotClassName, chartSeriesConfig, defaultValueFormatter, foldOtherSeries, };
|
|
74
|
+
export type { ChartCardProps, ChartCardSize, ChartDatum, ChartSeries };
|
|
75
|
+
//# sourceMappingURL=chart-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-card.d.ts","sourceRoot":"","sources":["../../src/components/chart-card.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAY/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;GAQG;AAEH,KAAK,WAAW,GAAG;IACjB,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAElD,KAAK,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;AAWtC,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,oDAAoD;IACpD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,IAAI,EAAE,SAAS,UAAU,EAAE,CAAC;IAC5B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,uDAAuD;IACvD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,mIAAmI;IACnI,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,iBAAS,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,kFAAkF;AAClF,iBAAS,iBAAiB,CACxB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,cAAc,EAAE,cAAc,GAC7B,WAAW,CAOb;AAED;;;;;;GAMG;AACH,iBAAS,eAAe,CACtB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,IAAI,EAAE,SAAS,UAAU,EAAE,EAC3B,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,CA6B/C;AAED,iBAAS,SAAS,CAAC,EACjB,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,YAAgB,EAChB,SAAS,EACT,QAAQ,GACT,EAAE,IAAI,CACL,cAAc,EACd,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,WAAW,CAC7E,GAAG;IACF,0EAA0E;IAC1E,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAChD,qBAoBA;AAED,uEAAuE;AACvE,iBAAS,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAEvD;AAED;;;;GAIG;AACH,QAAA,MAAM,iBAAiB,0GACkF,CAAC;AAE1G,sEAAsE;AACtE,QAAA,MAAM,kBAAkB,4BAA4B,CAAC;AAErD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,GAChB,CAAC;AACF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./card.js";
|
|
6
|
+
import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js";
|
|
7
|
+
/** Fixed plot heights, so charts on a page share one register. */
|
|
8
|
+
const chartSizes = {
|
|
9
|
+
default: "h-64",
|
|
10
|
+
lg: "h-80",
|
|
11
|
+
};
|
|
12
|
+
/** Synthetic key for the aggregated neutral series. */
|
|
13
|
+
const OTHER_SERIES_ID = "__nc-other-series__";
|
|
14
|
+
function defaultValueFormatter(value) {
|
|
15
|
+
return value.toLocaleString();
|
|
16
|
+
}
|
|
17
|
+
/** The chart config the legend and tooltip read: each series' label and color. */
|
|
18
|
+
function chartSeriesConfig(series, entityColorMap) {
|
|
19
|
+
return Object.fromEntries(series.map((entry) => [
|
|
20
|
+
entry.id,
|
|
21
|
+
{ label: entry.label, color: entityColor(entityColorMap, entry.id) },
|
|
22
|
+
]));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Folds every series whose entity color resolves to the neutral register into
|
|
26
|
+
* ONE aggregated series (values summed per bucket). Past the sixth entity the
|
|
27
|
+
* color map returns that register, and drawing each tail entity as its own
|
|
28
|
+
* indistinguishable neutral mark is unreadable. Series resolving to a hue pass
|
|
29
|
+
* through unchanged, so six or fewer are never rewritten.
|
|
30
|
+
*/
|
|
31
|
+
function foldOtherSeries(series, data, entityColorMap, otherLabel) {
|
|
32
|
+
const isNeutral = (entry) => entityColor(entityColorMap, entry.id) === CHART_ENTITY_OTHER_COLOR;
|
|
33
|
+
const neutral = series.filter(isNeutral);
|
|
34
|
+
if (neutral.length === 0) {
|
|
35
|
+
return { series: [...series], data: [...data] };
|
|
36
|
+
}
|
|
37
|
+
const folded = [
|
|
38
|
+
...series.filter((entry) => !isNeutral(entry)),
|
|
39
|
+
{ id: OTHER_SERIES_ID, label: otherLabel },
|
|
40
|
+
];
|
|
41
|
+
const foldedData = data.map((datum) => {
|
|
42
|
+
const otherSum = neutral.reduce((sum, entry) => {
|
|
43
|
+
const value = datum[entry.id];
|
|
44
|
+
return sum + (typeof value === "number" ? value : 0);
|
|
45
|
+
}, 0);
|
|
46
|
+
const next = { ...datum };
|
|
47
|
+
for (const entry of neutral) {
|
|
48
|
+
delete next[entry.id];
|
|
49
|
+
}
|
|
50
|
+
next[OTHER_SERIES_ID] = otherSum;
|
|
51
|
+
return next;
|
|
52
|
+
});
|
|
53
|
+
return { series: folded, data: foldedData };
|
|
54
|
+
}
|
|
55
|
+
function ChartCard({ title, description, action, footer, headingLevel = 2, className, children, }) {
|
|
56
|
+
const titleId = React.useId();
|
|
57
|
+
return (_jsxs(Card, { "data-slot": "chart-card", className: className, children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { id: titleId, role: "heading", "aria-level": headingLevel, children: title }), description ? _jsx(CardDescription, { children: description }) : null, action ? _jsx(CardAction, { children: action }) : null] }), _jsx(CardContent, { children: children(titleId) }), footer ? (_jsx(CardFooter, { className: "flex-col items-start gap-2 text-sm", children: footer })) : null] }));
|
|
58
|
+
}
|
|
59
|
+
/** The plot frame class: fixed height, no aspect ratio, full width. */
|
|
60
|
+
function chartPlotClassName(size) {
|
|
61
|
+
return cn("aspect-auto w-full", chartSizes[size], CHART_FOCUS_CLASS);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Stock `ChartContainer` hides the plot's outline, and Recharts' accessibility
|
|
65
|
+
* layer makes that same svg focusable, so a keyboard user would land on nothing
|
|
66
|
+
* visible. The ring comes back only while the svg has keyboard focus.
|
|
67
|
+
*/
|
|
68
|
+
const CHART_FOCUS_CLASS = "[&_.recharts-surface:focus-visible]:nc-focus-outline [&_.recharts-surface:focus-visible]:outline-ring";
|
|
69
|
+
/** Legend below the plot, left-aligned, wrapping to a second line. */
|
|
70
|
+
const CHART_LEGEND_CLASS = "flex-wrap justify-start";
|
|
71
|
+
export { CHART_FOCUS_CLASS, CHART_LEGEND_CLASS, ChartCard, chartPlotClassName, chartSeriesConfig, defaultValueFormatter, foldOtherSeries, };
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type DateRangePreset, type DateRangeValue } from "./chart-date-range.js";
|
|
3
|
-
declare function DateRangeControl({ value, onChange, label, presets, presetLabels, customLabel, formatCustomValue, className,
|
|
3
|
+
declare function DateRangeControl({ value, onChange, label, presets, presetLabels, customLabel, formatCustomValue, className, }: {
|
|
4
4
|
value: DateRangeValue;
|
|
5
5
|
onChange: (value: DateRangeValue) => void;
|
|
6
|
-
/** Accessible name for the control
|
|
6
|
+
/** Accessible name for the control. */
|
|
7
7
|
label?: string;
|
|
8
|
-
/**
|
|
9
|
-
* Which preset buttons to render, in order. Defaults to the full codec set;
|
|
10
|
-
* pass a subset to offer fewer presets (the codec enum is unchanged).
|
|
11
|
-
*/
|
|
8
|
+
/** Which presets to offer, in order. Defaults to the full codec set. */
|
|
12
9
|
presets?: readonly DateRangePreset[];
|
|
13
10
|
presetLabels?: Partial<Record<DateRangePreset, string>>;
|
|
14
11
|
customLabel?: string;
|
|
@@ -22,6 +19,7 @@ declare function DateRangeControl({ value, onChange, label, presets, presetLabel
|
|
|
22
19
|
from: string;
|
|
23
20
|
to: string;
|
|
24
21
|
}) => string;
|
|
22
|
+
className?: string;
|
|
25
23
|
}): React.JSX.Element;
|
|
26
24
|
export { DateRangeControl };
|
|
27
25
|
//# sourceMappingURL=chart-date-range-control.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-date-range-control.d.ts","sourceRoot":"","sources":["../../src/components/chart-date-range-control.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chart-date-range-control.d.ts","sourceRoot":"","sources":["../../src/components/chart-date-range-control.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,+BAA+B,CAAC;AA2BvC,iBAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,KAAoB,EACpB,OAA4B,EAC5B,YAAY,EACZ,WAAsB,EACtB,iBAAiB,EACjB,SAAS,GACV,EAAE;IACD,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,qBA6EA;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|