@photon-ai/pho-ui 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/chunks/badge-DlLJPLeZ.js +78 -0
  2. package/dist/chunks/badge-DlLJPLeZ.js.map +1 -0
  3. package/dist/chunks/bar-chart-BJfr3eyd.js +190 -0
  4. package/dist/chunks/bar-chart-BJfr3eyd.js.map +1 -0
  5. package/dist/chunks/legend-C0mU7dqd.js +60 -0
  6. package/dist/chunks/legend-C0mU7dqd.js.map +1 -0
  7. package/dist/chunks/line-chart-CUR3AxrR.js +453 -0
  8. package/dist/chunks/line-chart-CUR3AxrR.js.map +1 -0
  9. package/dist/chunks/stat-hnNqjWVG.js +85 -0
  10. package/dist/chunks/stat-hnNqjWVG.js.map +1 -0
  11. package/dist/chunks/use-measure-CMVgLouO.js +616 -0
  12. package/dist/chunks/use-measure-CMVgLouO.js.map +1 -0
  13. package/dist/components/badge.js +5 -75
  14. package/dist/components/bar-chart.js +5 -0
  15. package/dist/components/chart.js +8 -0
  16. package/dist/components/line-chart.js +5 -0
  17. package/dist/components/stat.js +6 -0
  18. package/dist/components/toggle.js +14 -14
  19. package/dist/components/toggle.js.map +1 -1
  20. package/dist/index.js +89 -76
  21. package/dist/src/components/bar-chart/bar-chart.d.ts +9 -0
  22. package/dist/src/components/bar-chart/index.d.ts +2 -0
  23. package/dist/src/components/chart/axes.d.ts +22 -0
  24. package/dist/src/components/chart/color.d.ts +10 -0
  25. package/dist/src/components/chart/format.d.ts +25 -0
  26. package/dist/src/components/chart/index.d.ts +8 -0
  27. package/dist/src/components/chart/legend.d.ts +26 -0
  28. package/dist/src/components/chart/scale.d.ts +34 -0
  29. package/dist/src/components/chart/shell.d.ts +42 -0
  30. package/dist/src/components/chart/tooltip.d.ts +30 -0
  31. package/dist/src/components/chart/types.d.ts +76 -0
  32. package/dist/src/components/chart/use-measure.d.ts +8 -0
  33. package/dist/src/components/line-chart/index.d.ts +2 -0
  34. package/dist/src/components/line-chart/line-chart.d.ts +11 -0
  35. package/dist/src/components/stat/index.d.ts +1 -0
  36. package/dist/src/components/stat/stat.d.ts +61 -0
  37. package/dist/src/components/toggle/toggle.d.ts +5 -1
  38. package/dist/src/index.d.ts +4 -0
  39. package/package.json +23 -1
  40. package/src/styles/theme.css +51 -0
  41. package/dist/components/badge.js.map +0 -1
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { t as a } from "./cn-ChIgwEl3.js";
3
+ import { t as o } from "./resolve-variant-CcD8iG5l.js";
4
+ import "react";
5
+ import { jsx as l } from "react/jsx-runtime";
6
+ var p = "inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-medium", d = "bg-current/10 ring-1 ring-current/20 ring-inset", h = {
7
+ xs: {
8
+ classes: "h-4 gap-0.5 px-1.5 text-[10px] leading-none",
9
+ description: "Extra small — 16px; inline next to 14px labels (plan chips)"
10
+ },
11
+ sm: {
12
+ classes: "h-5 gap-0.5 px-2 text-xs leading-none",
13
+ description: "Small — 20px; dense rows, table cells"
14
+ },
15
+ md: {
16
+ classes: "h-6 gap-1 px-2.5 text-xs leading-none",
17
+ description: "Medium — 24px; the default"
18
+ },
19
+ lg: {
20
+ classes: "h-7 gap-1 px-3 text-base leading-none",
21
+ description: "Large — 28px; beside a page title (BasicPage.Title trailing)"
22
+ }
23
+ }, c = {
24
+ gray: {
25
+ high: "bg-pho-inverse text-pho-inverse",
26
+ low: "text-pho-secondary",
27
+ description: "Neutral — the low-signal default (solid inverse)"
28
+ },
29
+ muted: {
30
+ high: "bg-pho-secondary text-pho-primary",
31
+ low: "text-pho-description",
32
+ description: "Muted — soft solid surface for quieter labels (Free, Shared)"
33
+ },
34
+ brand: {
35
+ high: "bg-pho-brand-solid text-pho-on-brand",
36
+ low: "text-pho-brand",
37
+ description: "Brand — plan tiers, feature flags, highlights"
38
+ },
39
+ success: {
40
+ high: "bg-pho-success-ink text-pho-on-brand",
41
+ low: "text-pho-success",
42
+ description: "Success — healthy, live, complete"
43
+ },
44
+ warning: {
45
+ high: "bg-pho-warning-ink text-pho-on-brand",
46
+ low: "text-pho-warning",
47
+ description: "Warning — needs attention, degraded"
48
+ },
49
+ error: {
50
+ high: "bg-pho-error-ink text-pho-on-brand",
51
+ low: "text-pho-error",
52
+ description: "Error — failed, blocked, over limit"
53
+ }
54
+ };
55
+ function g(t) {
56
+ const { color: r = "gray", contrast: i = "high", size: n = "md", className: s } = t ?? {}, e = c[r];
57
+ return a(p, o(h, n), i === "low" ? a(d, e?.low) : e?.high, s);
58
+ }
59
+ function x({ color: t, contrast: r, size: i, className: n, children: s, ...e }) {
60
+ return /* @__PURE__ */ l("span", {
61
+ className: a(g({
62
+ color: t,
63
+ contrast: r,
64
+ size: i
65
+ }), n),
66
+ ...e,
67
+ children: s
68
+ });
69
+ }
70
+ x.displayName = "Badge";
71
+ export {
72
+ g as i,
73
+ h as n,
74
+ x as r,
75
+ c as t
76
+ };
77
+
78
+ //# sourceMappingURL=badge-DlLJPLeZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge-DlLJPLeZ.js","names":[],"sources":["../../src/components/badge/badge.tsx"],"sourcesContent":["import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\n\n/**\n * Design notes (Geist-aligned):\n * - ONE color axis, a tight semantic set (`gray` / `muted` / `brand` /\n * `success` / `warning` / `error`). Untitled UI's `type` triplication\n * (modern / pill / badge-color) is dropped.\n * - TWO contrasts (`high` / `low`, default `high`). High is the solid state\n * fill — the site's \"Most Popular\" pill — pairing each color's ink with\n * flipping on-ink content. Low is the soft accent material: fill and\n * hairline ring derive from `currentColor` at low opacity, so every color\n * shares one recipe.\n * - FOUR sizes (`xs` / `sm` / `md` / `lg`, default `md`). A badge is inherently\n * small; `lg` is the one step up, for a pill seated beside a page title.\n * - The richer Untitled features (dot, icon, avatar, count, dismiss button) are\n * deliberately left out — a dismissible or selectable chip is a Tag, not a\n * Badge, and a badge that triggers an action is a Button.\n */\n\nconst BADGE_BASE =\n \"inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-medium\";\n\n/** Low-contrast material — tint and hairline derived from the color's text token. */\nconst LOW_MATERIAL = \"bg-current/10 ring-1 ring-current/20 ring-inset\";\n\n/**\n * Size axis — fixed heights on a 4px step (16 / 20 / 24 / 28) so the ladder\n * stays proportional. Horizontal padding and type scale with each step;\n * vertical centering comes from `items-center` on the base (no `py`).\n */\nexport const BADGE_SIZES = {\n xs: {\n classes: \"h-4 gap-0.5 px-1.5 text-[10px] leading-none\",\n description: \"Extra small — 16px; inline next to 14px labels (plan chips)\",\n },\n sm: {\n classes: \"h-5 gap-0.5 px-2 text-xs leading-none\",\n description: \"Small — 20px; dense rows, table cells\",\n },\n md: {\n classes: \"h-6 gap-1 px-2.5 text-xs leading-none\",\n description: \"Medium — 24px; the default\",\n },\n lg: {\n classes: \"h-7 gap-1 px-3 text-base leading-none\",\n description: \"Large — 28px; beside a page title (BasicPage.Title trailing)\",\n },\n} satisfies VariantMap;\n\n/**\n * Color axis — a closed semantic set with one recipe per contrast. `high`\n * fills with the state ink (text flips with it per mode); `low` tints text\n * only and lets `LOW_MATERIAL` derive fill and ring from it.\n */\nexport const BADGE_COLORS = {\n gray: {\n high: \"bg-pho-inverse text-pho-inverse\",\n low: \"text-pho-secondary\",\n description: \"Neutral — the low-signal default (solid inverse)\",\n },\n muted: {\n high: \"bg-pho-secondary text-pho-primary\",\n low: \"text-pho-description\",\n description: \"Muted — soft solid surface for quieter labels (Free, Shared)\",\n },\n brand: {\n high: \"bg-pho-brand-solid text-pho-on-brand\",\n low: \"text-pho-brand\",\n description: \"Brand — plan tiers, feature flags, highlights\",\n },\n success: {\n high: \"bg-pho-success-ink text-pho-on-brand\",\n low: \"text-pho-success\",\n description: \"Success — healthy, live, complete\",\n },\n warning: {\n high: \"bg-pho-warning-ink text-pho-on-brand\",\n low: \"text-pho-warning\",\n description: \"Warning — needs attention, degraded\",\n },\n error: {\n high: \"bg-pho-error-ink text-pho-on-brand\",\n low: \"text-pho-error\",\n description: \"Error — failed, blocked, over limit\",\n },\n} satisfies Record<string, { high: string; low: string; description: string }>;\n\nexport type BadgeSize = keyof typeof BADGE_SIZES;\nexport type BadgeColor = keyof typeof BADGE_COLORS;\nexport type BadgeContrast = \"high\" | \"low\";\n\n/** Compose the full class string for a badge — exported for reuse/testing. */\nexport function badgeVariants(opts?: {\n color?: BadgeColor;\n contrast?: BadgeContrast;\n size?: BadgeSize;\n className?: string;\n}): string {\n const {\n color = \"gray\",\n contrast = \"high\",\n size = \"md\",\n className,\n } = opts ?? {};\n const def = BADGE_COLORS[color] as\n (typeof BADGE_COLORS)[BadgeColor] | undefined;\n return cn(\n BADGE_BASE,\n resolveVariant(BADGE_SIZES, size),\n contrast === \"low\" ? cn(LOW_MATERIAL, def?.low) : def?.high,\n className,\n );\n}\n\ninterface BadgeBaseProps {\n /** Semantic color. @default \"gray\" */\n color?: BadgeColor;\n /** Solid state fill (`high`) or the soft currentColor tint (`low`). @default \"high\" */\n contrast?: BadgeContrast;\n /** Size variant. @default \"md\" */\n size?: BadgeSize;\n}\n\nexport interface BadgeProps\n extends BadgeBaseProps, Omit<ComponentProps<\"span\">, keyof BadgeBaseProps> {}\n\n/**\n * Badge — a small, non-interactive status label: a plan tier (`Pro`), a state\n * (`Live`, `Failed`), a count. It's a plain `<span>`, so it never takes focus or\n * handles clicks — for a dismissible or selectable chip reach for a `Tag`, and\n * for a standalone action use a `Button`. Let `color` carry the meaning and keep\n * the label to a word or two.\n */\nexport function Badge({\n color,\n contrast,\n size,\n className,\n children,\n ...props\n}: BadgeProps) {\n return (\n <span\n className={cn(badgeVariants({ color, contrast, size }), className)}\n {...props}\n >\n {children}\n </span>\n );\n}\n\nBadge.displayName = \"Badge\";\n"],"mappings":";;;;;AAqBA,IAAM,IACJ,gFAGI,IAAe,mDAOR,IAAc;AAAA,EACzB,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF,GAOa,IAAe;AAAA,EAC1B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AACF;AAOA,SAAgB,EAAc,GAKnB;AACT,QAAM,EACJ,OAAA,IAAQ,QACR,UAAA,IAAW,QACX,MAAA,IAAO,MACP,WAAA,EAAA,IACE,KAAQ,CAAC,GACP,IAAM,EAAa,CAAA;AAEzB,SAAO,EACL,GACA,EAAe,GAAa,CAAI,GAChC,MAAa,QAAQ,EAAG,GAAc,GAAK,GAAG,IAAI,GAAK,MACvD,CACF;AACF;AAqBA,SAAgB,EAAM,EACpB,OAAA,GACA,UAAA,GACA,MAAA,GACA,WAAA,GACA,UAAA,GACA,GAAG,EAAA,GACU;AACb,SACE,gBAAA,EAAC,QAAD;AAAA,IACE,WAAW,EAAG,EAAc;AAAA,MAAE,OAAA;AAAA,MAAO,UAAA;AAAA,MAAU,MAAA;AAAA,IAAK,CAAC,GAAG,CAAS;AAAA,IACjE,GAAI;AAAA,IAEH,UAAA;AAAA,EACG,CAAA;AAEV;AAEA,EAAM,cAAc"}
@@ -0,0 +1,190 @@
1
+ "use client";
2
+ import { i as ct, t as mt } from "./legend-C0mU7dqd.js";
3
+ import { _ as ut, a as ft, c as ht, d as pt, f as $t, g as vt, h as K, i as bt, l as yt, m as dt, n as Mt, o as xt, p as gt, r as kt, s as O, t as Ft, u as Tt, v as Ct } from "./use-measure-CMVgLouO.js";
4
+ import { useMemo as z, useState as Lt } from "react";
5
+ import { jsx as M, jsxs as St } from "react/jsx-runtime";
6
+ var wt = 24, Pt = 4;
7
+ function At(f, e, b, $, w) {
8
+ const t = Math.min(Pt, b / 2, $), C = e + $;
9
+ return t <= 0 ? `M${f},${e}h${b}v${$}h${-b}Z` : w === "top" ? `M${f},${C}v${-($ - t)}a${t},${t} 0 0 1 ${t},${-t}h${b - 2 * t}a${t},${t} 0 0 1 ${t},${t}v${$ - t}Z` : `M${f},${e}h${b}v${$ - t}a${t},${t} 0 0 1 ${-t},${t}h${-(b - 2 * t)}a${t},${t} 0 0 1 ${-t},${-t}Z`;
10
+ }
11
+ function It({ data: f, series: e, label: b, description: $, height: w = 200, width: t, valueFormat: C, timeFormat: j, locale: F, legend: Q, baseline: E = "zero", stacked: x = !1, breakdownTitle: tt, loading: H, empty: ot = "Nothing here yet.", className: nt, ...rt }) {
12
+ const [at, P] = Ft(t), [g, Y] = Lt(null), o = z(() => {
13
+ const n = e.map((p, s) => ct(p.color, s)), a = f.map((p) => gt(p.x)), l = ft(a), h = C ?? K(F, !0), S = C ?? K(F), m = j ?? xt(F, l), i = f.map((p) => {
14
+ let s = 0, d = 0;
15
+ return e.map((k) => {
16
+ const r = vt(p, k.key);
17
+ if (!x) return {
18
+ value: r,
19
+ from: 0,
20
+ to: r ?? 0
21
+ };
22
+ if (r == null || r === 0) return {
23
+ value: r,
24
+ from: r ?? 0,
25
+ to: r ?? 0
26
+ };
27
+ if (r > 0) {
28
+ const it = s;
29
+ return s += r, {
30
+ value: r,
31
+ from: it,
32
+ to: s
33
+ };
34
+ }
35
+ const Z = d;
36
+ return d += r, {
37
+ value: r,
38
+ from: Z,
39
+ to: d
40
+ };
41
+ });
42
+ });
43
+ let u = 1 / 0, v = -1 / 0;
44
+ for (const p of i) for (const s of p)
45
+ s.value != null && (u = Math.min(u, s.from, s.to), v = Math.max(v, s.from, s.to));
46
+ u === 1 / 0 && (u = 0, v = 0), E === "zero" && (u = Math.min(0, u), v = Math.max(0, v));
47
+ const T = yt(u, v);
48
+ return {
49
+ colors: n,
50
+ times: a,
51
+ span: l,
52
+ axisFormat: h,
53
+ fullFormat: S,
54
+ headingFormat: m,
55
+ columns: i,
56
+ y: T,
57
+ yLabels: T.ticks.map(h),
58
+ described: $ ?? dt({
59
+ data: f,
60
+ series: e,
61
+ kind: "bar",
62
+ valueFormat: S,
63
+ timeFormat: m
64
+ })
65
+ };
66
+ }, [
67
+ f,
68
+ e,
69
+ x,
70
+ E,
71
+ C,
72
+ j,
73
+ F,
74
+ $
75
+ ]), c = bt(P, w, o.yLabels), y = f.length, _ = o.span > 0 ? o.span / 2 : 1, U = (o.times[0] ?? 0) - _, V = (o.times[y - 1] ?? 0) + _, { left: A, right: I } = c, B = z(() => O([U, V], [A, I]), [
76
+ U,
77
+ V,
78
+ A,
79
+ I
80
+ ]), D = O(o.y.domain, [c.bottom, c.top]), L = y > 0 ? c.innerWidth / y : 0, X = L >= 6 ? 2 : L >= 3 ? 1 : 0, N = Math.max(1, Math.min(wt, L - X)), q = x ? 1 : e.length, G = Math.max(1, (N - (q - 1) * Math.min(2, X)) / q), R = z(() => o.times.map((n) => B(n)), [o.times, B]), et = z(() => {
81
+ const n = I - A;
82
+ if (y === 0 || n <= 0) return [];
83
+ const a = $t(F), l = pt(o.times[0], o.times[y - 1], n);
84
+ return Tt(l, a, n).map((h) => ({
85
+ x: B(h.getTime()),
86
+ label: a(h)
87
+ }));
88
+ }, [
89
+ y,
90
+ o.times,
91
+ A,
92
+ I,
93
+ B,
94
+ F
95
+ ]), st = (n) => {
96
+ if (y === 0) return;
97
+ const a = n.currentTarget.getBoundingClientRect(), l = a.width > 0 ? P / a.width : 1, h = (n.clientX - a.left) * l;
98
+ Y(ht(R, h));
99
+ }, W = g != null ? f[g] : void 0, lt = Q ?? e.length > 1, J = D(0);
100
+ return /* @__PURE__ */ St(kt, {
101
+ label: b,
102
+ description: o.described,
103
+ height: w,
104
+ width: P,
105
+ containerRef: at,
106
+ loading: H,
107
+ empty: ot,
108
+ showEmpty: y === 0 && !H,
109
+ onPlotPointerMove: st,
110
+ onPlotPointerLeave: () => Y(null),
111
+ className: nt,
112
+ legend: lt ? /* @__PURE__ */ M(mt, { items: e.map((n, a) => ({
113
+ label: n.label,
114
+ color: o.colors[a],
115
+ kind: "rect"
116
+ })) }) : null,
117
+ tooltip: g != null && W != null ? /* @__PURE__ */ M(Mt, {
118
+ x: R[g],
119
+ width: P,
120
+ top: c.top,
121
+ heading: o.headingFormat(new Date(W.x)),
122
+ rows: e.map((n, a) => {
123
+ const l = o.columns[g][a].value;
124
+ return {
125
+ label: n.label,
126
+ value: l == null ? "No data" : o.fullFormat(l),
127
+ color: o.colors[a],
128
+ kind: "rect"
129
+ };
130
+ }),
131
+ breakdown: W.breakdown,
132
+ breakdownTitle: tt,
133
+ valueFormat: o.fullFormat
134
+ }) : null,
135
+ ...rt,
136
+ children: [
137
+ /* @__PURE__ */ M(Ct, {
138
+ layout: c,
139
+ ticks: o.y.ticks,
140
+ labels: o.yLabels,
141
+ scale: D
142
+ }),
143
+ /* @__PURE__ */ M(ut, {
144
+ layout: c,
145
+ ticks: et
146
+ }),
147
+ g != null && W != null && /* @__PURE__ */ M("rect", {
148
+ x: R[g] - L / 2,
149
+ y: c.top,
150
+ width: L,
151
+ height: c.innerHeight,
152
+ fill: "var(--background-color-pho-ghost-hover)"
153
+ }),
154
+ /* @__PURE__ */ M("g", {
155
+ "data-bars": "",
156
+ children: o.columns.map((n, a) => {
157
+ const l = R[a];
158
+ let h = -1, S = -1;
159
+ return n.forEach((m, i) => {
160
+ m.value == null || m.value === 0 || (m.value > 0 ? h = i : S = i);
161
+ }), n.map((m, i) => {
162
+ const { value: u } = m;
163
+ if (u == null || u === 0) return null;
164
+ const v = x ? l - N / 2 : l - N / 2 + i * (G + Math.min(2, X)), T = x ? N : G, p = D(m.from), s = D(m.to);
165
+ let d = Math.min(p, s), k = Math.abs(p - s);
166
+ const r = u > 0, Z = !x || (r ? i === h : i === S);
167
+ return x && k > 3 && (Z || (r && (d += 1), k -= 1), m.from !== 0 && (r || (d += 1), k -= 1)), /* @__PURE__ */ M("path", {
168
+ d: Z ? At(v, d, T, k, r ? "top" : "bottom") : `M${v},${d}h${T}v${k}h${-T}Z`,
169
+ fill: o.colors[i],
170
+ "data-series": e[i].key
171
+ }, e[i].key);
172
+ });
173
+ })
174
+ }),
175
+ o.y.domain[0] < 0 && o.y.domain[1] > 0 ? null : /* @__PURE__ */ M("line", {
176
+ x1: c.left,
177
+ x2: c.right,
178
+ y1: J,
179
+ y2: J,
180
+ stroke: "var(--color-pho-chart-crosshair)"
181
+ })
182
+ ]
183
+ });
184
+ }
185
+ It.displayName = "BarChart";
186
+ export {
187
+ It as t
188
+ };
189
+
190
+ //# sourceMappingURL=bar-chart-BJfr3eyd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-chart-BJfr3eyd.js","names":[],"sources":["../../src/components/bar-chart/bar-chart.tsx"],"sourcesContent":["import { useMemo, useState, type PointerEvent } from \"react\";\nimport { XAxis, YAxis } from \"../chart/axes\";\nimport { chartColor } from \"../chart/color\";\nimport { describeChart, numberFormatter, readValue } from \"../chart/format\";\nimport { ChartLegend } from \"../chart/legend\";\nimport {\n bucketSpan,\n bucketTimeFormatter,\n linearScale,\n nearestIndex,\n niceLinearDomain,\n thinTicks,\n timeAxisTicks,\n timeTickFormatter,\n toTime,\n} from \"../chart/scale\";\nimport { ChartShell, chartLayout } from \"../chart/shell\";\nimport { ChartTooltip } from \"../chart/tooltip\";\nimport type { ChartBaseProps, ChartDatum } from \"../chart/types\";\nimport { useMeasuredWidth } from \"../chart/use-measure\";\n\n/**\n * BarChart — buckets over time as columns: messages a day, requests an\n * hour, deliveries. One series, several side by side, or `stacked` for\n * parts of a whole (inbound / outbound, successful / failed).\n *\n * Design notes:\n * - Marks are thin: a column never fills its slot and never runs past\n * 24px; the leftover is air. The data end is rounded (4px), the\n * baseline end square — in a stack only the top segment gets the\n * corners, and a 2px gap in the surface colour separates segments\n * instead of a stroke.\n * - Gridlines are hairlines one step off the surface; the zero line a\n * touch stronger. Axis text is description ink at 11px; the y-axis\n * labels thin themselves to three to five round numbers, the x-axis to\n * the round times that fit.\n * - Hover lifts the bucket with the ghost wash and opens the tooltip on\n * it: the bucket's time, every series' value, and the bucket's\n * `breakdown` when it has one (errors by route).\n * - The figure is `role=\"img\"` with the `label` and a spoken summary; the\n * legend is real text below the plot, present for two or more series.\n */\n\nconst BAR_MAX = 24;\nconst RADIUS = 4;\n\nexport interface BarChartProps<T extends ChartDatum> extends ChartBaseProps<T> {\n /** Stack the series in one column per bucket, first series at the bottom. */\n stacked?: boolean;\n}\n\n/** A column with its data end rounded and its baseline end square. */\nfunction barPath(\n x: number,\n top: number,\n width: number,\n height: number,\n end: \"top\" | \"bottom\",\n): string {\n const r = Math.min(RADIUS, width / 2, height);\n const bottom = top + height;\n if (r <= 0) return `M${x},${top}h${width}v${height}h${-width}Z`;\n if (end === \"top\") {\n return `M${x},${bottom}v${-(height - r)}a${r},${r} 0 0 1 ${r},${-r}h${width - 2 * r}a${r},${r} 0 0 1 ${r},${r}v${height - r}Z`;\n }\n return `M${x},${top}h${width}v${height - r}a${r},${r} 0 0 1 ${-r},${r}h${-(width - 2 * r)}a${r},${r} 0 0 1 ${-r},${-r}Z`;\n}\n\nexport function BarChart<T extends ChartDatum>({\n data,\n series,\n label,\n description,\n height = 200,\n width: fixedWidth,\n valueFormat,\n timeFormat,\n locale,\n legend,\n baseline = \"zero\",\n stacked = false,\n breakdownTitle,\n loading,\n empty = \"Nothing here yet.\",\n className,\n ...props\n}: BarChartProps<T>) {\n const [containerRef, width] = useMeasuredWidth<HTMLDivElement>(fixedWidth);\n const [hovered, setHovered] = useState<number | null>(null);\n\n const model = useMemo(() => {\n const colors = series.map((s, index) => chartColor(s.color, index));\n const times = data.map((d) => toTime(d.x));\n const span = bucketSpan(times);\n const axisFormat = valueFormat ?? numberFormatter(locale, true);\n const fullFormat = valueFormat ?? numberFormatter(locale);\n const headingFormat = timeFormat ?? bucketTimeFormatter(locale, span);\n // Per bucket, per series: the value and, when stacked, where it sits.\n const columns = data.map((d) => {\n let positive = 0;\n let negative = 0;\n return series.map((s) => {\n const value = readValue(d, s.key);\n if (!stacked) return { value, from: 0, to: value ?? 0 };\n if (value == null || value === 0) {\n return { value, from: value ?? 0, to: value ?? 0 };\n }\n if (value > 0) {\n const from = positive;\n positive += value;\n return { value, from, to: positive };\n }\n const from = negative;\n negative += value;\n return { value, from, to: negative };\n });\n });\n let min = Infinity;\n let max = -Infinity;\n for (const column of columns) {\n for (const segment of column) {\n if (segment.value == null) continue;\n min = Math.min(min, segment.from, segment.to);\n max = Math.max(max, segment.from, segment.to);\n }\n }\n if (min === Infinity) {\n min = 0;\n max = 0;\n }\n if (baseline === \"zero\") {\n min = Math.min(0, min);\n max = Math.max(0, max);\n }\n const y = niceLinearDomain(min, max);\n const yLabels = y.ticks.map(axisFormat);\n return {\n colors,\n times,\n span,\n axisFormat,\n fullFormat,\n headingFormat,\n columns,\n y,\n yLabels,\n described:\n description ??\n describeChart({\n data,\n series,\n kind: \"bar\",\n valueFormat: fullFormat,\n timeFormat: headingFormat,\n }),\n };\n }, [\n data,\n series,\n stacked,\n baseline,\n valueFormat,\n timeFormat,\n locale,\n description,\n ]);\n\n const layout = chartLayout(width, height, model.yLabels);\n const n = data.length;\n // Buckets are centred in equal bands; positions come from the time\n // scale so a missing bucket leaves its hole rather than closing up.\n const halfSpan = model.span > 0 ? model.span / 2 : 1;\n const t0 = (model.times[0] ?? 0) - halfSpan;\n const t1 = (model.times[n - 1] ?? 0) + halfSpan;\n const { left, right } = layout;\n const xScale = useMemo(\n () => linearScale([t0, t1], [left, right]),\n [t0, t1, left, right],\n );\n const yScale = linearScale(model.y.domain, [layout.bottom, layout.top]);\n const band = n > 0 ? layout.innerWidth / n : 0;\n const gap = band >= 6 ? 2 : band >= 3 ? 1 : 0;\n const slot = Math.max(1, Math.min(BAR_MAX, band - gap));\n const groups = stacked ? 1 : series.length;\n const barWidth = Math.max(\n 1,\n (slot - (groups - 1) * Math.min(2, gap)) / groups,\n );\n const centers = useMemo(\n () => model.times.map((t) => xScale(t)),\n [model.times, xScale],\n );\n\n const xTicks = useMemo(() => {\n const innerWidth = right - left;\n if (n === 0 || innerWidth <= 0) return [];\n const format = timeTickFormatter(locale);\n const ticks = timeAxisTicks(\n model.times[0]!,\n model.times[n - 1]!,\n innerWidth,\n );\n return thinTicks(ticks, format, innerWidth).map((tick) => ({\n x: xScale(tick.getTime()),\n label: format(tick),\n }));\n }, [n, model.times, left, right, xScale, locale]);\n\n const onPointerMove = (event: PointerEvent<SVGSVGElement>) => {\n if (n === 0) return;\n const rect = event.currentTarget.getBoundingClientRect();\n const scale = rect.width > 0 ? width / rect.width : 1;\n const px = (event.clientX - rect.left) * scale;\n setHovered(nearestIndex(centers, px));\n };\n\n const hoveredDatum = hovered != null ? data[hovered] : undefined;\n const showLegend = legend ?? series.length > 1;\n const zeroY = yScale(0);\n\n return (\n <ChartShell\n label={label}\n description={model.described}\n height={height}\n width={width}\n containerRef={containerRef}\n loading={loading}\n empty={empty}\n showEmpty={n === 0 && !loading}\n onPlotPointerMove={onPointerMove}\n onPlotPointerLeave={() => setHovered(null)}\n className={className}\n legend={\n showLegend ? (\n <ChartLegend\n items={series.map((s, index) => ({\n label: s.label,\n color: model.colors[index]!,\n kind: \"rect\",\n }))}\n />\n ) : null\n }\n tooltip={\n hovered != null && hoveredDatum != null ? (\n <ChartTooltip\n x={centers[hovered]!}\n width={width}\n top={layout.top}\n heading={model.headingFormat(new Date(hoveredDatum.x))}\n rows={series.map((s, index) => {\n const value = model.columns[hovered]![index]!.value;\n return {\n label: s.label,\n value: value == null ? \"No data\" : model.fullFormat(value),\n color: model.colors[index]!,\n kind: \"rect\",\n };\n })}\n breakdown={hoveredDatum.breakdown}\n breakdownTitle={breakdownTitle}\n valueFormat={model.fullFormat}\n />\n ) : null\n }\n {...props}\n >\n <YAxis\n layout={layout}\n ticks={model.y.ticks}\n labels={model.yLabels}\n scale={yScale}\n />\n <XAxis layout={layout} ticks={xTicks} />\n {hovered != null && hoveredDatum != null && (\n <rect\n x={centers[hovered]! - band / 2}\n y={layout.top}\n width={band}\n height={layout.innerHeight}\n fill=\"var(--background-color-pho-ghost-hover)\"\n />\n )}\n <g data-bars=\"\">\n {model.columns.map((column, i) => {\n const cx = centers[i]!;\n // The topmost drawn segment in each direction gets the corners.\n let lastPositive = -1;\n let lastNegative = -1;\n column.forEach((segment, index) => {\n if (segment.value == null || segment.value === 0) return;\n if (segment.value > 0) lastPositive = index;\n else lastNegative = index;\n });\n return column.map((segment, index) => {\n const { value } = segment;\n if (value == null || value === 0) return null;\n const x = stacked\n ? cx - slot / 2\n : cx - slot / 2 + index * (barWidth + Math.min(2, gap));\n const w = stacked ? slot : barWidth;\n const yFrom = yScale(segment.from);\n const yTo = yScale(segment.to);\n let top = Math.min(yFrom, yTo);\n let h = Math.abs(yFrom - yTo);\n const positive = value > 0;\n // Side by side, every column is its own and gets the corners.\n const outer =\n !stacked ||\n (positive ? index === lastPositive : index === lastNegative);\n // The 2px surface gap: shave 1px off each side of a boundary\n // between two segments, when the segment can spare it.\n if (stacked && h > 3) {\n if (!outer) {\n if (positive) top += 1;\n h -= 1;\n }\n if (segment.from !== 0) {\n if (!positive) top += 1;\n h -= 1;\n }\n }\n return (\n <path\n key={series[index]!.key}\n d={\n outer\n ? barPath(x, top, w, h, positive ? \"top\" : \"bottom\")\n : `M${x},${top}h${w}v${h}h${-w}Z`\n }\n fill={model.colors[index]}\n data-series={series[index]!.key}\n />\n );\n });\n })}\n </g>\n {model.y.domain[0] < 0 && model.y.domain[1] > 0 ? null : (\n <line\n x1={layout.left}\n x2={layout.right}\n y1={zeroY}\n y2={zeroY}\n stroke=\"var(--color-pho-chart-crosshair)\"\n />\n )}\n </ChartShell>\n );\n}\n\nBarChart.displayName = \"BarChart\";\n"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { t as c } from "./cn-ChIgwEl3.js";
3
+ import "react";
4
+ import { jsx as n, jsxs as h } from "react/jsx-runtime";
5
+ var s = [
6
+ "chart-1",
7
+ "chart-2",
8
+ "chart-3",
9
+ "chart-4",
10
+ "chart-5",
11
+ "chart-6"
12
+ ];
13
+ function p(a, e) {
14
+ const r = a ?? s[e % s.length];
15
+ return `var(--color-pho-${r.startsWith("chart-") ? r : `chart-${r}`})`;
16
+ }
17
+ function i({ color: a, kind: e = "rect", dashed: r }) {
18
+ return e === "line" ? /* @__PURE__ */ n("svg", {
19
+ "aria-hidden": !0,
20
+ width: "14",
21
+ height: "6",
22
+ className: "shrink-0",
23
+ children: /* @__PURE__ */ n("line", {
24
+ x1: "1",
25
+ x2: "13",
26
+ y1: "3",
27
+ y2: "3",
28
+ stroke: a,
29
+ strokeWidth: "2",
30
+ strokeLinecap: "round",
31
+ strokeDasharray: r ? "3 3" : void 0
32
+ })
33
+ }) : /* @__PURE__ */ n("span", {
34
+ "aria-hidden": !0,
35
+ className: "inline-block size-2 shrink-0 rounded-[2px]",
36
+ style: { background: a }
37
+ });
38
+ }
39
+ function u({ items: a, className: e, ...r }) {
40
+ return /* @__PURE__ */ n("ul", {
41
+ className: c("text-pho-secondary flex flex-wrap gap-x-4 gap-y-1 text-xs", e),
42
+ ...r,
43
+ children: a.map((t) => /* @__PURE__ */ h("li", {
44
+ className: "flex items-center gap-1.5",
45
+ children: [/* @__PURE__ */ n(i, {
46
+ color: t.color,
47
+ kind: t.kind,
48
+ dashed: t.dashed
49
+ }), t.label]
50
+ }, t.label))
51
+ });
52
+ }
53
+ export {
54
+ p as i,
55
+ i as n,
56
+ s as r,
57
+ u as t
58
+ };
59
+
60
+ //# sourceMappingURL=legend-C0mU7dqd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legend-C0mU7dqd.js","names":[],"sources":["../../src/components/chart/color.ts","../../src/components/chart/legend.tsx"],"sourcesContent":["import type { ChartColor } from \"./types\";\n\n/** The categorical slots, in the order series take them. */\nexport const CHART_SLOTS = [\n \"chart-1\",\n \"chart-2\",\n \"chart-3\",\n \"chart-4\",\n \"chart-5\",\n \"chart-6\",\n] as const satisfies ReadonlyArray<ChartColor>;\n\n/**\n * The CSS value for a series colour: the named colour, or the categorical\n * slot at `index` when the series names none. Slots are an ordered,\n * validated set — a seventh series wraps, so fold a long tail into\n * \"Other\" before it gets there.\n */\nexport function chartColor(\n color: ChartColor | undefined,\n index: number,\n): string {\n const name = color ?? CHART_SLOTS[index % CHART_SLOTS.length]!;\n const token = name.startsWith(\"chart-\") ? name : `chart-${name}`;\n return `var(--color-pho-${token})`;\n}\n","import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** A legend or tooltip key mirrors its mark: a square for a bar, a stroke for a line. */\nexport type ChartSwatchKind = \"rect\" | \"line\";\n\nexport interface ChartSwatchProps {\n /** A CSS colour value — what `chartColor` returns. */\n color: string;\n kind?: ChartSwatchKind;\n dashed?: boolean;\n}\n\nexport function ChartSwatch({\n color,\n kind = \"rect\",\n dashed,\n}: ChartSwatchProps) {\n if (kind === \"line\") {\n return (\n <svg aria-hidden width=\"14\" height=\"6\" className=\"shrink-0\">\n <line\n x1=\"1\"\n x2=\"13\"\n y1=\"3\"\n y2=\"3\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeDasharray={dashed ? \"3 3\" : undefined}\n />\n </svg>\n );\n }\n return (\n <span\n aria-hidden\n className=\"inline-block size-2 shrink-0 rounded-[2px]\"\n style={{ background: color }}\n />\n );\n}\n\nexport interface ChartLegendItem {\n label: string;\n /** A CSS colour value — what `chartColor` returns. */\n color: string;\n kind?: ChartSwatchKind;\n dashed?: boolean;\n}\n\nexport interface ChartLegendProps extends Omit<\n ComponentProps<\"ul\">,\n \"children\"\n> {\n items: ReadonlyArray<ChartLegendItem>;\n}\n\n/**\n * The legend a chart of two or more series carries below the plot: a key\n * per series, in series order, wrapping when the row runs out. Text stays\n * in the UI ink; the swatch alone carries the colour.\n */\nexport function ChartLegend({ items, className, ...props }: ChartLegendProps) {\n return (\n <ul\n className={cn(\n \"text-pho-secondary flex flex-wrap gap-x-4 gap-y-1 text-xs\",\n className,\n )}\n {...props}\n >\n {items.map((item) => (\n <li key={item.label} className=\"flex items-center gap-1.5\">\n <ChartSwatch\n color={item.color}\n kind={item.kind}\n dashed={item.dashed}\n />\n {item.label}\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":";;;;AAGA,IAAa,IAAc;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAgB,EACd,GACA,GACQ;AACR,QAAM,IAAO,KAAS,EAAY,IAAQ,EAAY,MAAA;AAEtD,SAAO,mBADO,EAAK,WAAW,QAAQ,IAAI,IAAO,SAAS,CAAA,EAAA;AAE5D;ACZA,SAAgB,EAAY,EAC1B,OAAA,GACA,MAAA,IAAO,QACP,QAAA,EAAA,GACmB;AACnB,SAAI,MAAS,SAET,gBAAA,EAAC,OAAD;AAAA,IAAK,eAAA;AAAA,IAAY,OAAM;AAAA,IAAK,QAAO;AAAA,IAAI,WAAU;AAAA,IAC/C,UAAA,gBAAA,EAAC,QAAD;AAAA,MACE,IAAG;AAAA,MACH,IAAG;AAAA,MACH,IAAG;AAAA,MACH,IAAG;AAAA,MACH,QAAQ;AAAA,MACR,aAAY;AAAA,MACZ,eAAc;AAAA,MACd,iBAAiB,IAAS,QAAQ;AAAA,IACnC,CAAA;AAAA,EACE,CAAA,IAIP,gBAAA,EAAC,QAAD;AAAA,IACE,eAAA;AAAA,IACA,WAAU;AAAA,IACV,OAAO,EAAE,YAAY,EAAM;AAAA,EAC5B,CAAA;AAEL;AAsBA,SAAgB,EAAY,EAAE,OAAA,GAAO,WAAA,GAAW,GAAG,EAAA,GAA2B;AAC5E,SACE,gBAAA,EAAC,MAAD;AAAA,IACE,WAAW,EACT,6DACA,CACF;AAAA,IACA,GAAI;AAAA,IAEH,UAAA,EAAM,IAAA,CAAK,MACV,gBAAA,EAAC,MAAD;AAAA,MAAqB,WAAU;AAAA,MAA/B,UAAA,CACE,gBAAA,EAAC,GAAD;AAAA,QACE,OAAO,EAAK;AAAA,QACZ,MAAM,EAAK;AAAA,QACX,QAAQ,EAAK;AAAA,MACd,CAAA,GACA,EAAK,KACJ;AAAA,IAPK,GAAA,EAAK,KAOV,CACL;AAAA,EACC,CAAA;AAER"}