@siyahi/ui 0.1.9-next.3 → 0.1.9-next.31

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 (254) hide show
  1. package/DESIGN.md +251 -0
  2. package/README.md +94 -177
  3. package/THIRD_PARTY_NOTICES.md +11 -11
  4. package/agents/examples/accordion.tsx +39 -0
  5. package/agents/examples/alert.tsx +24 -0
  6. package/agents/examples/app-sidebar.tsx +40 -0
  7. package/agents/examples/avatar.tsx +24 -0
  8. package/agents/examples/badge.tsx +26 -0
  9. package/agents/examples/bar-list-labels.tsx +22 -0
  10. package/agents/examples/bar-list-sides.tsx +40 -0
  11. package/agents/examples/bar-list.tsx +24 -0
  12. package/agents/examples/breadcrumb.tsx +28 -0
  13. package/agents/examples/button-states.tsx +23 -0
  14. package/agents/examples/button.tsx +21 -0
  15. package/agents/examples/calendar.tsx +11 -0
  16. package/agents/examples/callout.tsx +33 -0
  17. package/agents/examples/card.tsx +36 -0
  18. package/agents/examples/change-bar.tsx +104 -0
  19. package/agents/examples/change-grid-money.tsx +36 -0
  20. package/agents/examples/change-grid.tsx +43 -0
  21. package/agents/examples/chart-area-log.tsx +45 -0
  22. package/agents/examples/chart-area.tsx +39 -0
  23. package/agents/examples/chart-bar-flows.tsx +32 -0
  24. package/agents/examples/chart-bar-hourly.tsx +28 -0
  25. package/agents/examples/chart-bar-losses.tsx +29 -0
  26. package/agents/examples/chart-bar-teams.tsx +72 -0
  27. package/agents/examples/chart-bar-years.tsx +42 -0
  28. package/agents/examples/chart-bar.tsx +56 -0
  29. package/agents/examples/chart-category-ticks.tsx +50 -0
  30. package/agents/examples/chart-line-events.tsx +47 -0
  31. package/agents/examples/chart-line-forecast.tsx +36 -0
  32. package/agents/examples/chart-line-log.tsx +39 -0
  33. package/agents/examples/chart-line-quiet.tsx +114 -0
  34. package/agents/examples/chart-line-timeline.tsx +41 -0
  35. package/agents/examples/chart-line.tsx +57 -0
  36. package/agents/examples/chart-panels.tsx +94 -0
  37. package/agents/examples/code-block.tsx +40 -0
  38. package/agents/examples/collapsible.tsx +18 -0
  39. package/agents/examples/command.tsx +29 -0
  40. package/agents/examples/context-menu.tsx +17 -0
  41. package/agents/examples/copy-button.tsx +8 -0
  42. package/agents/examples/data-bar.tsx +114 -0
  43. package/agents/examples/data-table-compact.tsx +93 -0
  44. package/agents/examples/data-table-filters.tsx +103 -0
  45. package/agents/examples/data-table.tsx +96 -0
  46. package/agents/examples/delta.tsx +18 -0
  47. package/agents/examples/dialog.tsx +28 -0
  48. package/agents/examples/distribution-bar-budget.tsx +30 -0
  49. package/agents/examples/distribution-bar-tug.tsx +74 -0
  50. package/agents/examples/drawdown-chart.tsx +35 -0
  51. package/agents/examples/dropdown-menu.tsx +31 -0
  52. package/agents/examples/field.tsx +108 -0
  53. package/agents/examples/formatters.tsx +42 -0
  54. package/agents/examples/heatmap-calendar-signed.tsx +67 -0
  55. package/agents/examples/heatmap-calendar.tsx +62 -0
  56. package/agents/examples/heatmap-status.tsx +61 -0
  57. package/agents/examples/heatmap-weekday.tsx +72 -0
  58. package/agents/examples/histogram-chart.tsx +28 -0
  59. package/agents/examples/input-states.tsx +23 -0
  60. package/agents/examples/input.tsx +17 -0
  61. package/agents/examples/local-time.tsx +14 -0
  62. package/agents/examples/metric-strip.tsx +46 -0
  63. package/agents/examples/overview-dashboard.tsx +180 -0
  64. package/agents/examples/page-hero.tsx +38 -0
  65. package/agents/examples/page-section.tsx +52 -0
  66. package/agents/examples/popover.tsx +23 -0
  67. package/agents/examples/price-chart.tsx +32 -0
  68. package/agents/examples/progress-tone.tsx +29 -0
  69. package/agents/examples/progress.tsx +10 -0
  70. package/agents/examples/range-position-table.tsx +113 -0
  71. package/agents/examples/range-position.tsx +55 -0
  72. package/agents/examples/scatter-chart-quadrants.tsx +63 -0
  73. package/agents/examples/scatter-chart-scales.tsx +46 -0
  74. package/agents/examples/scatter-chart.tsx +78 -0
  75. package/agents/examples/scroll-area.tsx +22 -0
  76. package/agents/examples/section-cards-select.tsx +28 -0
  77. package/agents/examples/section-cards-trend.tsx +32 -0
  78. package/agents/examples/section-cards.tsx +48 -0
  79. package/agents/examples/select.tsx +27 -0
  80. package/agents/examples/separator.tsx +13 -0
  81. package/agents/examples/sheet.tsx +15 -0
  82. package/agents/examples/skeleton.tsx +11 -0
  83. package/agents/examples/slider.tsx +5 -0
  84. package/agents/examples/slope-chart-market.tsx +33 -0
  85. package/agents/examples/slope-chart.tsx +26 -0
  86. package/agents/examples/stale-blocks.tsx +56 -0
  87. package/agents/examples/surfaces.tsx +116 -0
  88. package/agents/examples/table.tsx +60 -0
  89. package/agents/examples/tabs.tsx +16 -0
  90. package/agents/examples/target-rows.tsx +74 -0
  91. package/agents/examples/toggle-group.tsx +12 -0
  92. package/agents/examples/toggle.tsx +11 -0
  93. package/agents/examples/tooltip.tsx +13 -0
  94. package/agents/examples/treemap.tsx +93 -0
  95. package/agents/examples/waterfall-chart-stores.tsx +27 -0
  96. package/agents/examples/waterfall-chart.tsx +37 -0
  97. package/agents/manifest.json +1391 -0
  98. package/agents/report-rules.json +7 -0
  99. package/dist/components/registry/charts/area-chart-card.d.ts +7 -3
  100. package/dist/components/registry/charts/area-chart-card.js +56 -10
  101. package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
  102. package/dist/components/registry/charts/bar-chart-card.js +20 -22
  103. package/dist/components/registry/charts/line-chart-card.d.ts +3 -3
  104. package/dist/components/registry/charts/line-chart-card.js +44 -9
  105. package/dist/components/registry/dashboard-01/section-cards.d.ts +6 -0
  106. package/dist/components/registry/dashboard-01/section-cards.js +51 -35
  107. package/dist/components/registry/dashboardcn/bar-list.js +17 -10
  108. package/dist/components/registry/dashboardcn/distribution-bar.js +6 -4
  109. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
  110. package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
  111. package/dist/components/registry/dashboardcn/sparkline.d.ts +22 -0
  112. package/dist/components/registry/dashboardcn/sparkline.js +117 -0
  113. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +11 -3
  114. package/dist/components/registry/heatmap/calendar-heatmap.js +164 -45
  115. package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
  116. package/dist/components/registry/heatmap/status-heatmap.js +115 -54
  117. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +9 -3
  118. package/dist/components/registry/heatmap/weekday-heatmap.js +83 -53
  119. package/dist/components/registry/niko-table/core/data-table-context.d.ts +18 -1
  120. package/dist/components/registry/niko-table/core/data-table-context.js +7 -1
  121. package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
  122. package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
  123. package/dist/components/registry/niko-table/core/data-table-structure.js +17 -12
  124. package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
  125. package/dist/components/registry/niko-table/core/data-table.js +5 -1
  126. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +5 -5
  127. package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
  128. package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
  129. package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
  130. package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
  131. package/dist/components/registry/niko-table/lib/styles.js +3 -3
  132. package/dist/components/registry/niko-table/types/index.d.ts +9 -0
  133. package/dist/components/registry/niko-table/ui/table.js +2 -2
  134. package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
  135. package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
  136. package/dist/components/registry/sidebar-07/nav-main.d.ts +2 -1
  137. package/dist/components/registry/sidebar-07/nav-main.js +16 -3
  138. package/dist/components/siyahi/callout.d.ts +17 -0
  139. package/dist/components/siyahi/callout.js +34 -0
  140. package/dist/components/siyahi/change-bar.d.ts +21 -0
  141. package/dist/components/siyahi/change-bar.js +35 -0
  142. package/dist/components/siyahi/change-grid.d.ts +31 -0
  143. package/dist/components/siyahi/change-grid.js +74 -0
  144. package/dist/components/siyahi/data-bar.d.ts +19 -0
  145. package/dist/components/siyahi/data-bar.js +29 -0
  146. package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
  147. package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
  148. package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
  149. package/dist/components/siyahi/data-table-footnote.js +35 -0
  150. package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
  151. package/dist/components/siyahi/default-sidebar-open.js +13 -0
  152. package/dist/components/siyahi/delta.d.ts +5 -1
  153. package/dist/components/siyahi/delta.js +14 -9
  154. package/dist/components/siyahi/drawdown-chart.d.ts +34 -0
  155. package/dist/components/siyahi/drawdown-chart.js +102 -0
  156. package/dist/components/siyahi/histogram-chart.d.ts +57 -0
  157. package/dist/components/siyahi/histogram-chart.js +134 -0
  158. package/dist/components/siyahi/page-hero.d.ts +23 -0
  159. package/dist/components/siyahi/page-hero.js +43 -0
  160. package/dist/components/siyahi/page-section.d.ts +4 -1
  161. package/dist/components/siyahi/page-section.js +6 -3
  162. package/dist/components/siyahi/page-toc.d.ts +20 -0
  163. package/dist/components/siyahi/page-toc.js +76 -0
  164. package/dist/components/siyahi/panel-grid.d.ts +24 -0
  165. package/dist/components/siyahi/panel-grid.js +37 -0
  166. package/dist/components/siyahi/quote.d.ts +12 -0
  167. package/dist/components/siyahi/quote.js +23 -0
  168. package/dist/components/siyahi/range-position.d.ts +24 -0
  169. package/dist/components/siyahi/range-position.js +105 -0
  170. package/dist/components/siyahi/scale-toggle.d.ts +32 -0
  171. package/dist/components/siyahi/scale-toggle.js +61 -0
  172. package/dist/components/siyahi/scatter-chart.d.ts +69 -0
  173. package/dist/components/siyahi/scatter-chart.js +395 -0
  174. package/dist/components/siyahi/siyahi-provider.d.ts +15 -2
  175. package/dist/components/siyahi/siyahi-provider.js +17 -4
  176. package/dist/components/siyahi/slope-chart.d.ts +35 -0
  177. package/dist/components/siyahi/slope-chart.js +159 -0
  178. package/dist/components/siyahi/stale-note.d.ts +9 -0
  179. package/dist/components/siyahi/stale-note.js +12 -0
  180. package/dist/components/siyahi/status-alert.d.ts +15 -0
  181. package/dist/components/siyahi/status-alert.js +29 -0
  182. package/dist/components/siyahi/status-badge.d.ts +15 -0
  183. package/dist/components/siyahi/status-badge.js +24 -0
  184. package/dist/components/siyahi/target-rows.d.ts +34 -0
  185. package/dist/components/siyahi/target-rows.js +97 -0
  186. package/dist/components/siyahi/tone-progress.d.ts +1 -1
  187. package/dist/components/siyahi/tone-progress.js +1 -1
  188. package/dist/components/siyahi/treemap.d.ts +8 -5
  189. package/dist/components/siyahi/treemap.js +80 -34
  190. package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
  191. package/dist/components/siyahi/waterfall-chart.js +147 -0
  192. package/dist/components/ui/accordion.js +5 -5
  193. package/dist/components/ui/alert.js +2 -2
  194. package/dist/components/ui/badge.js +1 -1
  195. package/dist/components/ui/breadcrumb.js +2 -2
  196. package/dist/components/ui/button.js +10 -10
  197. package/dist/components/ui/calendar.js +1 -1
  198. package/dist/components/ui/card.js +4 -4
  199. package/dist/components/ui/chart.js +1 -1
  200. package/dist/components/ui/code-block.js +13 -12
  201. package/dist/components/ui/command.js +7 -6
  202. package/dist/components/ui/context-menu.js +7 -7
  203. package/dist/components/ui/dialog.js +5 -5
  204. package/dist/components/ui/dropdown-menu.js +8 -8
  205. package/dist/components/ui/field.d.ts +26 -0
  206. package/dist/components/ui/field.js +109 -0
  207. package/dist/components/ui/input-group.js +11 -11
  208. package/dist/components/ui/input.js +1 -1
  209. package/dist/components/ui/label.d.ts +5 -0
  210. package/dist/components/ui/label.js +13 -0
  211. package/dist/components/ui/popover.js +3 -3
  212. package/dist/components/ui/progress.js +1 -1
  213. package/dist/components/ui/select.js +6 -6
  214. package/dist/components/ui/sheet.js +5 -5
  215. package/dist/components/ui/sidebar.js +17 -18
  216. package/dist/components/ui/skeleton.js +1 -1
  217. package/dist/components/ui/slider.js +2 -2
  218. package/dist/components/ui/table.js +2 -2
  219. package/dist/components/ui/tabs.js +2 -2
  220. package/dist/components/ui/textarea.js +1 -1
  221. package/dist/components/ui/toggle-group.js +2 -2
  222. package/dist/components/ui/toggle.js +4 -4
  223. package/dist/components/ui/tooltip.js +2 -2
  224. package/dist/index.d.ts +35 -8
  225. package/dist/index.js +30 -4
  226. package/dist/lib/chart-frame.d.ts +51 -7
  227. package/dist/lib/chart-frame.js +390 -133
  228. package/dist/lib/chart-marks.d.ts +83 -0
  229. package/dist/lib/chart-marks.js +216 -0
  230. package/dist/lib/chart-series.d.ts +42 -10
  231. package/dist/lib/compact-columns.d.ts +19 -0
  232. package/dist/lib/compact-columns.js +55 -0
  233. package/dist/lib/fit-text.d.ts +6 -0
  234. package/dist/lib/fit-text.js +11 -0
  235. package/dist/lib/format.d.ts +8 -3
  236. package/dist/lib/format.js +55 -17
  237. package/dist/lib/heat.d.ts +7 -0
  238. package/dist/lib/heat.js +14 -0
  239. package/dist/lib/heatmap-navigation.d.ts +8 -0
  240. package/dist/lib/heatmap-navigation.js +49 -0
  241. package/dist/lib/scatter.d.ts +88 -0
  242. package/dist/lib/scatter.js +179 -0
  243. package/dist/lib/ticks.d.ts +27 -0
  244. package/dist/lib/ticks.js +67 -0
  245. package/dist/lib/tone.d.ts +36 -0
  246. package/dist/lib/tone.js +35 -0
  247. package/dist/lib/treemap.d.ts +0 -5
  248. package/dist/lib/treemap.js +1 -11
  249. package/dist/lib/use-element-width.d.ts +5 -0
  250. package/dist/lib/use-element-width.js +15 -0
  251. package/package.json +5 -1
  252. package/registry.lock.json +16 -0
  253. package/report.css +63 -47
  254. package/styles/siyahi.css +708 -55
@@ -1,13 +1,88 @@
1
1
  import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card.js";
2
2
  import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from "../components/ui/chart.js";
3
3
  import { cn } from "./utils.js";
4
+ import { useAxisScale } from "../components/siyahi/scale-toggle.js";
5
+ import { StaleNote } from "../components/siyahi/stale-note.js";
4
6
  import { useLocale } from "../components/siyahi/siyahi-provider.js";
7
+ import { EndLabels, QUIET, TimeMarks, endLabelRoom, endLabelsFit, linesEndAtEdge } from "./chart-marks.js";
8
+ import { fitsLog, logAxis } from "./ticks.js";
9
+ import { useElementWidth } from "./use-element-width.js";
5
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { CartesianGrid, ReferenceLine, XAxis, YAxis } from "recharts";
11
+ import { useEffect, useId, useMemo, useState } from "react";
12
+ import { CartesianGrid, Line, ReferenceLine, Text, XAxis, YAxis, useChartWidth, useXAxisTicks, useYAxisTicks } from "recharts";
7
13
  //#region src/lib/chart-frame.tsx
8
14
  const AXIS_TITLE = "fill-muted-foreground text-xs";
9
15
  const TICK = { className: "fill-muted-foreground font-mono" };
10
16
  const NO_LINES = [];
17
+ const SERIES_LEGEND = /* @__PURE__ */ jsx(ChartLegendContent, { className: "flex-wrap gap-y-1" });
18
+ /**
19
+ * A bar's fill: a tint of its colour, inside a 1px edge of the full colour the bar is drawn with. A selection dims
20
+ * the bars it leaves out to a fainter tint; their edges stay, so they keep 3:1 against the card.
21
+ */
22
+ function barFill(color, dim = false) {
23
+ return `color-mix(in oklab, ${color} ${dim ? 10 : 25}%, var(--card))`;
24
+ }
25
+ /** Renders the component again once the page's fonts load, so text measured on a canvas is measured in Geist. */
26
+ function useRenderWhenFontsLoad() {
27
+ const [loaded, setLoaded] = useState(() => typeof document === "undefined" || document.fonts?.status === "loaded");
28
+ useEffect(() => {
29
+ if (loaded || !document.fonts) return;
30
+ let live = true;
31
+ document.fonts.ready.then(() => live && setLoaded(true));
32
+ return () => {
33
+ live = false;
34
+ };
35
+ }, [loaded]);
36
+ }
37
+ /**
38
+ * The legend when some series are quiet: every series by name. The grey ones share a colour, so a legend square
39
+ * cannot say which line is which; on lines each grey name is a button that raises its line in the text colour, over
40
+ * the rest. A stack's grey bands sit in the legend's order, bottom to top.
41
+ */
42
+ function QuietLegend({ series, raisable, raised, onRaise }) {
43
+ return /* @__PURE__ */ jsx("div", {
44
+ className: "flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3",
45
+ onFocus: (event) => event.stopPropagation(),
46
+ onKeyDown: (event) => event.stopPropagation(),
47
+ children: series.map(({ key, label, color }) => raisable && color === "var(--muted-foreground)" ? /* @__PURE__ */ jsxs("button", {
48
+ type: "button",
49
+ "aria-pressed": raised === key,
50
+ onClick: () => onRaise(raised === key ? void 0 : key),
51
+ className: "flex items-center gap-1.5 text-muted-foreground aria-pressed:text-foreground",
52
+ children: [/* @__PURE__ */ jsx("div", { className: "size-2 shrink-0 bg-current" }), label]
53
+ }, key) : /* @__PURE__ */ jsxs("div", {
54
+ className: cn("flex items-center gap-1.5", color === "var(--muted-foreground)" && "text-muted-foreground"),
55
+ children: [/* @__PURE__ */ jsx("div", {
56
+ className: "size-2 shrink-0",
57
+ style: { backgroundColor: color }
58
+ }), label]
59
+ }, key))
60
+ });
61
+ }
62
+ /**
63
+ * A reference line over a card-coloured band, so it stays traceable where it crosses a bar or an area: the text
64
+ * colour on --chart-1 is only 2:1, but each against the card is well over 3:1.
65
+ */
66
+ function haloLine({ x1, y1, x2, y2, stroke, strokeDasharray }) {
67
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
68
+ "data-slot": "reference-line-halo",
69
+ x1,
70
+ y1,
71
+ x2,
72
+ y2,
73
+ stroke: "var(--card)",
74
+ strokeWidth: 4
75
+ }), /* @__PURE__ */ jsx("line", {
76
+ className: "recharts-reference-line-line",
77
+ x1,
78
+ y1,
79
+ x2,
80
+ y2,
81
+ stroke,
82
+ strokeDasharray,
83
+ strokeWidth: 1
84
+ })] });
85
+ }
11
86
  /**
12
87
  * The colour a series' Bar takes, which its legend chip shows: the negative colour when every value drawn is below
13
88
  * zero, since each bar's own Cell follows its sign but the chip follows the Bar.
@@ -16,6 +91,27 @@ function seriesFill(item, data) {
16
91
  const values = data.map((row) => row[item.key]).filter((value) => Number.isFinite(value)).map(Number);
17
92
  return item.negativeColor && values.length > 0 && values.every((value) => value < 0) ? item.negativeColor : item.color;
18
93
  }
94
+ /**
95
+ * A Line or Area's dots: one solid dot on the latest point, ringed in the card's colour so it stands clear of the
96
+ * grid, and none elsewhere. The eye lands on now, where a reader starts.
97
+ */
98
+ function endDot(color, count, hollow = false) {
99
+ return ({ cx, cy, index }) => index === count - 1 && cx !== void 0 && cy !== void 0 ? hollow ? /* @__PURE__ */ jsx("circle", {
100
+ cx,
101
+ cy,
102
+ r: 3.5,
103
+ fill: "var(--card)",
104
+ stroke: color,
105
+ strokeWidth: 1.5
106
+ }, "end") : /* @__PURE__ */ jsx("circle", {
107
+ cx,
108
+ cy,
109
+ r: 4,
110
+ fill: color,
111
+ stroke: "var(--card)",
112
+ strokeWidth: 2
113
+ }, "end") : /* @__PURE__ */ jsx("g", {}, index);
114
+ }
19
115
  /** A tooltip row: the series' colour and label, then its value as the page formats it, or — when the row has none. */
20
116
  function seriesRow(series, formatValue) {
21
117
  return (value, _name, item) => {
@@ -37,6 +133,21 @@ function seriesRow(series, formatValue) {
37
133
  ] });
38
134
  };
39
135
  }
136
+ /**
137
+ * A value-axis tick formatted against the axis' largest tick, so every label shares one unit: ₹1.6L down to ₹0.4L,
138
+ * never ₹80,000 halfway up. Recharts' tickFormatter sees one value at a time; the axis' ticks come from its hooks.
139
+ * The rest of Recharts' tick props pass on as its own tick does, including the class it measures an auto width by.
140
+ */
141
+ function ValueTick({ payload, axis, formatter, ownUnit = false, ...tick }) {
142
+ const xTicks = useXAxisTicks();
143
+ const yTicks = useYAxisTicks();
144
+ const value = Number(payload?.value);
145
+ const scale = ownUnit ? Math.abs(value) : Math.max(Math.abs(value), ...((axis === "x" ? xTicks : yTicks) ?? []).map((t) => Math.abs(Number(t.value))));
146
+ return /* @__PURE__ */ jsx(Text, {
147
+ ...tick,
148
+ children: formatter(value, scale)
149
+ });
150
+ }
40
151
  const GEIST_WIDTH = 6.6;
41
152
  const MONO_WIDTH = 7.2;
42
153
  /**
@@ -47,26 +158,51 @@ function endRoom(xFormatter, xTicks) {
47
158
  const last = xTicks.at(-1);
48
159
  return Math.max(24, Math.ceil((last === void 0 ? 0 : xFormatter(last).length * MONO_WIDTH) / 2) + 4);
49
160
  }
50
- /** Squeezes a line into its column with SVG's own textLength when it would run into the next one. */
51
- const fitTo = (text, room, charWidth) => room > 0 && text.length * charWidth > room ? {
52
- textLength: room,
53
- lengthAdjust: "spacingAndGlyphs"
54
- } : {};
55
161
  /**
56
- * An x tick fitted to its column, drawn whenever a page names its ticks or gives each a second line: Recharts'
162
+ * An x tick with a second line or explicit position: Recharts'
57
163
  * own ticks would rather drop a label than let it run into the next. With a detail, the second line comes from the
58
164
  * tick's row; under a column the lines hang below the axis, and beside a horizontal bar they straddle its middle.
59
165
  * A two-line tick is set in Geist, not Mono: a name with a figure under it reads as a label, as in BarList, and
60
- * sector names in Mono would take most of a phone's width.
166
+ * category names in Mono would take most of a phone's width.
61
167
  */
62
- function fittedTick(data, xKey, xFormatter, detailOf, beside) {
63
- const charWidth = detailOf ? GEIST_WIDTH : MONO_WIDTH;
64
- return ({ x, y, width, visibleTicksCount, payload }) => {
168
+ function fittedTick(data, xKey, xFormatter, detailOf, beside, labelWidth) {
169
+ const rows = /* @__PURE__ */ new Map();
170
+ for (const row of data) if (!rows.has(row[xKey])) rows.set(row[xKey], row);
171
+ return ({ x, y, payload }) => {
65
172
  const value = payload?.value;
66
- const row = data.find((r) => r[xKey] === value);
173
+ const row = rows.get(value);
67
174
  const name = xFormatter(value);
68
175
  const detail = row && detailOf ? detailOf(row) : null;
69
- const room = beside || !visibleTicksCount ? 0 : Number(width) / visibleTicksCount - 6;
176
+ if (beside) return /* @__PURE__ */ jsxs("g", {
177
+ className: "fill-muted-foreground",
178
+ children: [/* @__PURE__ */ jsx(Text, {
179
+ x,
180
+ y: Number(y) - (detail ? 4 : 0),
181
+ width: labelWidth,
182
+ maxLines: 2,
183
+ textAnchor: "end",
184
+ verticalAnchor: detail ? "end" : "middle",
185
+ lineHeight: 14,
186
+ style: { fontSize: "var(--text-xs)" },
187
+ "aria-label": name,
188
+ className: "recharts-cartesian-axis-tick-value fill-muted-foreground",
189
+ children: name
190
+ }), detail !== null && /* @__PURE__ */ jsx(Text, {
191
+ x,
192
+ y: Number(y) + 4,
193
+ width: labelWidth,
194
+ textAnchor: "end",
195
+ verticalAnchor: "start",
196
+ lineHeight: 14,
197
+ style: {
198
+ fontSize: "var(--text-xs)",
199
+ fontVariantNumeric: "tabular-nums"
200
+ },
201
+ "aria-label": detail,
202
+ className: "fill-muted-foreground tabular-nums",
203
+ children: detail
204
+ })]
205
+ });
70
206
  return /* @__PURE__ */ jsxs("text", {
71
207
  x,
72
208
  y,
@@ -74,150 +210,271 @@ function fittedTick(data, xKey, xFormatter, detailOf, beside) {
74
210
  className: cn("recharts-cartesian-axis-tick-value fill-muted-foreground", !detailOf && "font-mono"),
75
211
  children: [/* @__PURE__ */ jsx("tspan", {
76
212
  x,
77
- dy: beside ? -4 : 12,
78
- ...fitTo(name, room, charWidth),
213
+ dy: 12,
79
214
  children: name
80
215
  }), detail !== null && /* @__PURE__ */ jsx("tspan", {
81
216
  x,
82
217
  dy: 15,
83
218
  className: "tabular-nums",
84
- ...fitTo(detail, room, charWidth),
85
219
  children: detail
86
220
  })]
87
221
  });
88
222
  };
89
223
  }
224
+ function HorizontalAxis({ data, xKey, xFormatter, xTickDetail, xTicks }) {
225
+ const width = Math.min(240, Math.floor((useChartWidth() ?? 320) * .45));
226
+ return /* @__PURE__ */ jsx(YAxis, {
227
+ dataKey: xKey,
228
+ type: "category",
229
+ width,
230
+ tickLine: false,
231
+ axisLine: false,
232
+ tickMargin: 8,
233
+ tickFormatter: xFormatter,
234
+ interval: 0,
235
+ ticks: xTicks,
236
+ tick: fittedTick(data, xKey, xFormatter, xTickDetail, true, width - 16)
237
+ });
238
+ }
90
239
  /**
91
240
  * The card, axes, grid, tooltip and legend the three chart cards share. Both
92
241
  * axes keep Recharts' default ids: marks find their axes by id, and a page
93
242
  * that gave its category axis its own id (dash#88) got a plot split in two
94
243
  * and every tooltip headed with the series name instead of the category.
95
244
  */
96
- function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, title, description, action, data, xKey, series, xFormatter = String, xTooltipFormatter = xFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines = NO_LINES, placeholder, footer, xDomain, xTicks }) {
245
+ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, onClick, tooltipRow, legend = SERIES_LEGEND, title, description, action, data, xKey, series, xFormatter = String, xTooltipFormatter = xFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines = NO_LINES, placeholder, footer, xDomain, xTicks, yScale = "linear", yScaleToggle = false, logBlocked, endLabels = false, forecastHatch = true, curve = "monotone", forecastFrom, today, stale }) {
97
246
  const { numberLocale } = useLocale();
98
- const formatValue = valueFormatter ?? new Intl.NumberFormat(numberLocale).format;
247
+ const clipId = useId();
248
+ const [contentRef, contentWidth] = useElementWidth();
249
+ useRenderWhenFontsLoad();
250
+ const [raisedKey, setRaisedKey] = useState();
251
+ const names = endLabels ? series.map(({ label }) => label) : [];
252
+ const labelRoom = endLabels ? endLabelRoom(names) : 0;
253
+ const plotHeight = height - 12 - (xTickDetail ? 40 : 30) - (xLabel ? 18 : 0);
254
+ const ends = endLabels && linesEndAtEdge(data, xKey, series, xDomain) && endLabelsFit(names, contentWidth, plotHeight);
255
+ const quiet = series.some(({ color }) => color === QUIET);
256
+ const raised = endLabels && !ends ? series.find(({ key, color }) => key === raisedKey && color === "var(--muted-foreground)") : void 0;
257
+ const formatValue = useMemo(() => valueFormatter ?? new Intl.NumberFormat(numberLocale).format, [valueFormatter, numberLocale]);
99
258
  const config = Object.fromEntries(series.map(({ key, label, color }) => [key, {
100
259
  label,
101
260
  color
102
261
  }]));
103
262
  const crossesZero = data.some((row) => series.some(({ key }) => Number(row[key]) < 0));
104
- return /* @__PURE__ */ jsxs(Card, { children: [
105
- /* @__PURE__ */ jsxs(CardHeader, { children: [
106
- /* @__PURE__ */ jsx(CardTitle, { children: title }),
107
- description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
108
- action && /* @__PURE__ */ jsx(CardAction, { children: action })
109
- ] }),
110
- /* @__PURE__ */ jsx(CardContent, { children: placeholder ? /* @__PURE__ */ jsx("div", {
111
- "data-slot": "chart-placeholder",
112
- className: "flex items-center justify-center",
113
- style: { height },
114
- children: placeholder
115
- }) : /* @__PURE__ */ jsx(ChartContainer, {
116
- config,
117
- className: "aspect-auto w-full",
118
- style: { height },
119
- children: /* @__PURE__ */ jsxs(Chart, {
120
- data,
121
- layout: horizontal ? "vertical" : "horizontal",
122
- stackOffset,
123
- margin: {
124
- left: 0,
125
- right: xTicks ? endRoom(xFormatter, xTicks) : 8
126
- },
127
- children: [
128
- /* @__PURE__ */ jsx(CartesianGrid, {
129
- vertical: horizontal,
130
- horizontal: !horizontal,
131
- strokeDasharray: "3 3"
132
- }),
133
- horizontal ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
134
- type: "number",
135
- hide: true,
136
- domain: yDomain,
137
- ticks: yTicks
138
- }), /* @__PURE__ */ jsx(YAxis, {
139
- dataKey: xKey,
140
- type: "category",
141
- width: "auto",
142
- tickLine: false,
143
- axisLine: false,
144
- tickMargin: 8,
145
- tickFormatter: (value) => xFormatter(value),
146
- tick: xTickDetail ? fittedTick(data, xKey, xFormatter, xTickDetail, horizontal) : TICK,
147
- interval: 0
148
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
149
- dataKey: xKey,
150
- type: xDomain ? "number" : "category",
151
- domain: xDomain,
152
- ticks: xTicks,
153
- height: (xTickDetail ? 40 : 30) + (xLabel ? 18 : 0),
154
- label: xLabel ? {
155
- value: xLabel,
156
- position: "insideBottom",
157
- className: AXIS_TITLE
158
- } : void 0,
159
- tickLine: false,
160
- axisLine: false,
161
- tickMargin: 8,
162
- tickFormatter: (value) => xFormatter(value),
163
- tick: xTickDetail || xTicks ? fittedTick(data, xKey, xFormatter, xTickDetail, false) : TICK,
164
- interval: xTickDetail || xTicks ? 0 : void 0
165
- }), /* @__PURE__ */ jsx(YAxis, {
166
- tickLine: false,
167
- axisLine: false,
168
- width: "auto",
169
- domain: yDomain,
170
- ticks: yTicks,
171
- label: yLabel ? {
172
- value: yLabel,
173
- angle: -90,
174
- position: "insideLeft",
175
- style: { textAnchor: "middle" },
176
- className: AXIS_TITLE
177
- } : void 0,
178
- tickFormatter: (value) => (yTickFormatter ?? formatValue)(value),
179
- tick: TICK
180
- })] }),
181
- crossesZero && (horizontal ? /* @__PURE__ */ jsx(ReferenceLine, {
182
- x: 0,
183
- stroke: "var(--border)"
184
- }) : /* @__PURE__ */ jsx(ReferenceLine, {
185
- y: 0,
186
- stroke: "var(--border)"
187
- })),
188
- referenceLines.map(({ label, emphasis = "muted", labelPosition = "insideTopLeft", ...at }) => /* @__PURE__ */ jsx(ReferenceLine, {
189
- ...horizontal ? {
190
- x: at.y,
191
- y: at.x
192
- } : at,
193
- stroke: emphasis === "strong" ? "var(--foreground)" : "var(--muted-foreground)",
194
- strokeDasharray: at.y === void 0 ? void 0 : "4 4",
195
- label: label === void 0 ? void 0 : {
196
- value: label,
197
- position: labelPosition,
198
- className: "fill-muted-foreground text-xs"
199
- }
200
- }, `${at.x ?? ""}:${at.y ?? ""}:${label ?? ""}`)),
201
- /* @__PURE__ */ jsx(ChartTooltip, { content: /* @__PURE__ */ jsx(ChartTooltipContent, {
202
- labelFormatter: (_, payload) => {
203
- const row = payload[0]?.payload;
204
- return row ? xTooltipFormatter(row[xKey]) : null;
263
+ const [low, high] = Array.isArray(yDomain) ? yDomain : [];
264
+ const range = typeof low === "number" && typeof high === "number" ? [low, high] : void 0;
265
+ const values = [...data.flatMap((row) => series.map(({ key }) => row[key])), ...range ?? []].filter((value) => typeof value === "number" && Number.isFinite(value));
266
+ const valueAxis = useAxisScale({
267
+ initial: yScale,
268
+ toggle: yScaleToggle,
269
+ label: yLabel ? `${yLabel} scale` : "Value axis scale",
270
+ logBlocked: logBlocked ?? (fitsLog(values) ? void 0 : "Log needs every value above zero")
271
+ });
272
+ const log = valueAxis.scale === "log" ? logAxis(values, range) : void 0;
273
+ const detailedRows = horizontal && xTickDetail !== void 0;
274
+ const rowCount = (xTicks ?? data).length;
275
+ const plotClass = detailedRows ? "min-h-(--wrapped-chart-height) @min-[24rem]/chart:min-h-0" : void 0;
276
+ let minWidth;
277
+ if (!horizontal && (xTicks || xTickDetail)) {
278
+ const labelledValues = xTicks ?? data.map((row) => row[xKey]);
279
+ let tickWidth = 0;
280
+ for (const value of labelledValues) tickWidth = Math.max(tickWidth, xFormatter(value).length * MONO_WIDTH);
281
+ if (xTickDetail) for (const row of data) tickWidth = Math.max(tickWidth, xTickDetail(row).length * GEIST_WIDTH);
282
+ minWidth = Math.ceil((tickWidth + 12) * labelledValues.length + 64);
283
+ }
284
+ const plotStyle = {
285
+ height: detailedRows ? Math.max(height, rowCount * 72 + 48) : height,
286
+ minWidth,
287
+ ...detailedRows ? { "--wrapped-chart-height": `${rowCount * 104 + 48}px` } : {},
288
+ ...forecastFrom !== void 0 ? {
289
+ "--chart-actual": `url(#${clipId}-actual)`,
290
+ "--chart-forecast": `url(#${clipId}-forecast)`
291
+ } : {}
292
+ };
293
+ const endRight = !horizontal && xTicks ? endRoom(xFormatter, xTicks) : horizontal && yTickFormatter ? 28 : 8;
294
+ return /* @__PURE__ */ jsxs(Card, {
295
+ "data-stale": stale ? "" : void 0,
296
+ children: [
297
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
298
+ /* @__PURE__ */ jsx(CardTitle, { children: title }),
299
+ description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
300
+ stale && /* @__PURE__ */ jsx(StaleNote, { since: stale }),
301
+ (action || valueAxis.buttons) && /* @__PURE__ */ jsxs(CardAction, {
302
+ className: "flex flex-wrap items-center gap-x-3 gap-y-2",
303
+ children: [action, valueAxis.buttons]
304
+ })
305
+ ] }),
306
+ /* @__PURE__ */ jsx(CardContent, {
307
+ ref: contentRef,
308
+ className: "@container/chart overflow-x-auto",
309
+ role: minWidth ? "region" : void 0,
310
+ "aria-label": minWidth ? "Chart, scroll horizontally for all labelled values" : void 0,
311
+ tabIndex: minWidth ? 0 : void 0,
312
+ children: placeholder ? /* @__PURE__ */ jsx("div", {
313
+ "data-slot": "chart-placeholder",
314
+ className: cn("flex items-center justify-center", plotClass),
315
+ style: plotStyle,
316
+ children: placeholder
317
+ }) : /* @__PURE__ */ jsx(ChartContainer, {
318
+ config,
319
+ className: cn("aspect-auto w-full", plotClass, onClick && "cursor-pointer"),
320
+ style: plotStyle,
321
+ children: /* @__PURE__ */ jsxs(Chart, {
322
+ data,
323
+ layout: horizontal ? "vertical" : "horizontal",
324
+ stackOffset,
325
+ onClick,
326
+ throttledEvents: onClick ? [] : void 0,
327
+ margin: {
328
+ top: 12,
329
+ left: 0,
330
+ right: ends ? Math.max(labelRoom, endRight) : endRight
205
331
  },
206
- formatter: seriesRow(series, formatValue)
207
- }) }),
208
- children,
209
- /* @__PURE__ */ jsx(ChartLegend, {
210
- content: /* @__PURE__ */ jsx(ChartLegendContent, {}),
211
- itemSorter: null
332
+ children: [
333
+ /* @__PURE__ */ jsx(CartesianGrid, {
334
+ vertical: horizontal,
335
+ horizontal: !horizontal
336
+ }),
337
+ horizontal ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
338
+ type: "number",
339
+ niceTicks: "snap125",
340
+ hide: !yTickFormatter,
341
+ domain: yDomain,
342
+ ticks: yTicks,
343
+ tickLine: false,
344
+ axisLine: false,
345
+ tickMargin: 8,
346
+ tick: yTickFormatter ? /* @__PURE__ */ jsx(ValueTick, {
347
+ axis: "x",
348
+ formatter: yTickFormatter,
349
+ ...TICK
350
+ }) : TICK
351
+ }), /* @__PURE__ */ jsx(HorizontalAxis, {
352
+ data,
353
+ xKey,
354
+ xFormatter,
355
+ xTickDetail,
356
+ xTicks
357
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
358
+ dataKey: xKey,
359
+ type: xDomain ? "number" : "category",
360
+ domain: xDomain,
361
+ ticks: xTicks,
362
+ height: (xTickDetail ? 40 : 30) + (xLabel ? 18 : 0),
363
+ label: xLabel ? {
364
+ value: xLabel,
365
+ position: "insideBottom",
366
+ className: AXIS_TITLE
367
+ } : void 0,
368
+ tickLine: false,
369
+ axisLine: false,
370
+ tickMargin: 8,
371
+ tickFormatter: (value) => xFormatter(value),
372
+ tick: xTickDetail || xTicks ? fittedTick(data, xKey, xFormatter, xTickDetail, false) : TICK,
373
+ interval: xTickDetail || xTicks ? 0 : void 0
374
+ }), /* @__PURE__ */ jsx(YAxis, {
375
+ scale: valueAxis.scale,
376
+ niceTicks: log ? void 0 : "snap125",
377
+ tickLine: false,
378
+ axisLine: false,
379
+ width: "auto",
380
+ domain: log?.domain ?? yDomain,
381
+ ticks: log?.ticks ?? yTicks,
382
+ label: yLabel ? {
383
+ value: yLabel,
384
+ angle: -90,
385
+ position: "insideLeft",
386
+ style: { textAnchor: "middle" },
387
+ className: AXIS_TITLE
388
+ } : void 0,
389
+ tick: yTickFormatter ? /* @__PURE__ */ jsx(ValueTick, {
390
+ axis: "y",
391
+ formatter: yTickFormatter,
392
+ ownUnit: log !== void 0,
393
+ ...TICK
394
+ }) : TICK,
395
+ tickFormatter: yTickFormatter ? void 0 : (value) => formatValue(value)
396
+ })] }),
397
+ crossesZero && (horizontal ? /* @__PURE__ */ jsx(ReferenceLine, {
398
+ x: 0,
399
+ stroke: "var(--border)"
400
+ }) : /* @__PURE__ */ jsx(ReferenceLine, {
401
+ y: 0,
402
+ stroke: "var(--border)"
403
+ })),
404
+ referenceLines.map(({ label, emphasis = "muted", labelPosition = "insideTopLeft", ...at }) => /* @__PURE__ */ jsx(ReferenceLine, {
405
+ ...horizontal ? {
406
+ x: at.y,
407
+ y: at.x
408
+ } : at,
409
+ stroke: emphasis === "strong" ? "var(--foreground)" : "var(--muted-foreground)",
410
+ shape: haloLine,
411
+ strokeDasharray: at.y === void 0 ? void 0 : "4 4",
412
+ label: label === void 0 ? void 0 : {
413
+ value: label,
414
+ position: labelPosition,
415
+ className: "fill-muted-foreground stroke-card text-xs",
416
+ style: {
417
+ paintOrder: "stroke",
418
+ strokeWidth: 3,
419
+ strokeLinejoin: "round"
420
+ }
421
+ }
422
+ }, `${at.x ?? ""}:${at.y ?? ""}:${label ?? ""}`)),
423
+ /* @__PURE__ */ jsx(ChartTooltip, {
424
+ cursor: { fillOpacity: .4 },
425
+ content: /* @__PURE__ */ jsx(ChartTooltipContent, {
426
+ labelFormatter: (_, payload) => {
427
+ const row = payload[0]?.payload;
428
+ return row ? xTooltipFormatter(row[xKey]) : null;
429
+ },
430
+ formatter: tooltipRow ?? seriesRow(series, formatValue)
431
+ })
432
+ }),
433
+ (forecastFrom !== void 0 || today !== void 0) && /* @__PURE__ */ jsx(TimeMarks, {
434
+ data,
435
+ xKey,
436
+ forecastFrom,
437
+ today,
438
+ clipId,
439
+ hatch: forecastHatch
440
+ }),
441
+ children,
442
+ raised && (forecastFrom === void 0 ? ["solid"] : ["chart-actual", "chart-forecast"]).map((part) => /* @__PURE__ */ jsx(Line, {
443
+ dataKey: raised.key,
444
+ type: curve,
445
+ stroke: "var(--foreground)",
446
+ strokeWidth: 2,
447
+ strokeDasharray: part === "chart-forecast" ? "5 4" : void 0,
448
+ className: part === "solid" ? "chart-raised" : `chart-raised ${part}`,
449
+ dot: false,
450
+ activeDot: false,
451
+ tooltipType: "none",
452
+ legendType: "none",
453
+ isAnimationActive: false
454
+ }, `raised-${part}`)),
455
+ ends ? /* @__PURE__ */ jsx(EndLabels, {
456
+ data,
457
+ xKey,
458
+ series
459
+ }) : /* @__PURE__ */ jsx(ChartLegend, {
460
+ content: quiet ? /* @__PURE__ */ jsx(QuietLegend, {
461
+ series,
462
+ raisable: endLabels,
463
+ raised: raised?.key,
464
+ onRaise: setRaisedKey
465
+ }) : legend,
466
+ itemSorter: null
467
+ })
468
+ ]
212
469
  })
213
- ]
470
+ })
471
+ }),
472
+ footer && /* @__PURE__ */ jsx(CardFooter, {
473
+ className: "flex-col items-start gap-2 text-sm",
474
+ children: footer
214
475
  })
215
- }) }),
216
- footer && /* @__PURE__ */ jsx(CardFooter, {
217
- className: "flex-col items-start gap-2 text-sm",
218
- children: footer
219
- })
220
- ] });
476
+ ]
477
+ });
221
478
  }
222
479
  //#endregion
223
- export { ChartFrame, seriesFill, seriesRow };
480
+ export { ChartFrame, QuietLegend, barFill, endDot, seriesFill, seriesRow };