@siyahi/ui 0.1.9-next.2 → 0.1.9-next.21

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 (196) hide show
  1. package/DESIGN.md +30 -0
  2. package/README.md +82 -176
  3. package/THIRD_PARTY_NOTICES.md +11 -11
  4. package/agents/examples/accordion.tsx +39 -0
  5. package/agents/examples/alert.tsx +23 -0
  6. package/agents/examples/app-sidebar.tsx +38 -0
  7. package/agents/examples/avatar.tsx +14 -0
  8. package/agents/examples/badge.tsx +18 -0
  9. package/agents/examples/bar-list-labels.tsx +22 -0
  10. package/agents/examples/bar-list-sides.tsx +38 -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 +15 -0
  16. package/agents/examples/callout.tsx +33 -0
  17. package/agents/examples/card.tsx +34 -0
  18. package/agents/examples/change-bar.tsx +104 -0
  19. package/agents/examples/chart-area.tsx +39 -0
  20. package/agents/examples/chart-bar-flows.tsx +32 -0
  21. package/agents/examples/chart-bar-hourly.tsx +28 -0
  22. package/agents/examples/chart-bar-losses.tsx +29 -0
  23. package/agents/examples/chart-bar-sectors.tsx +72 -0
  24. package/agents/examples/chart-bar-years.tsx +40 -0
  25. package/agents/examples/chart-bar.tsx +56 -0
  26. package/agents/examples/chart-category-ticks.tsx +50 -0
  27. package/agents/examples/chart-line-events.tsx +47 -0
  28. package/agents/examples/chart-line-timeline.tsx +41 -0
  29. package/agents/examples/chart-line.tsx +57 -0
  30. package/agents/examples/code-block.tsx +38 -0
  31. package/agents/examples/collapsible.tsx +16 -0
  32. package/agents/examples/command.tsx +27 -0
  33. package/agents/examples/context-menu.tsx +15 -0
  34. package/agents/examples/copy-button.tsx +8 -0
  35. package/agents/examples/data-bar.tsx +121 -0
  36. package/agents/examples/data-table-compact.tsx +95 -0
  37. package/agents/examples/data-table-filters.tsx +103 -0
  38. package/agents/examples/data-table.tsx +98 -0
  39. package/agents/examples/delta.tsx +13 -0
  40. package/agents/examples/dialog.tsx +28 -0
  41. package/agents/examples/distribution-bar-budget.tsx +30 -0
  42. package/agents/examples/distribution-bar-tug.tsx +72 -0
  43. package/agents/examples/dropdown-menu.tsx +31 -0
  44. package/agents/examples/formatters.tsx +42 -0
  45. package/agents/examples/heatmap-calendar.tsx +60 -0
  46. package/agents/examples/heatmap-status.tsx +57 -0
  47. package/agents/examples/heatmap-weekday.tsx +75 -0
  48. package/agents/examples/input-states.tsx +33 -0
  49. package/agents/examples/input.tsx +17 -0
  50. package/agents/examples/local-time.tsx +14 -0
  51. package/agents/examples/metric-strip.tsx +46 -0
  52. package/agents/examples/overview-dashboard.tsx +18 -0
  53. package/agents/examples/page-hero.tsx +38 -0
  54. package/agents/examples/page-section.tsx +52 -0
  55. package/agents/examples/popover.tsx +23 -0
  56. package/agents/examples/price-chart.tsx +32 -0
  57. package/agents/examples/progress-tone.tsx +28 -0
  58. package/agents/examples/progress.tsx +10 -0
  59. package/agents/examples/range-position-table.tsx +101 -0
  60. package/agents/examples/range-position.tsx +53 -0
  61. package/agents/examples/scroll-area.tsx +20 -0
  62. package/agents/examples/section-cards-select.tsx +30 -0
  63. package/agents/examples/section-cards-trend.tsx +30 -0
  64. package/agents/examples/section-cards.tsx +48 -0
  65. package/agents/examples/select.tsx +27 -0
  66. package/agents/examples/separator.tsx +13 -0
  67. package/agents/examples/sheet.tsx +15 -0
  68. package/agents/examples/skeleton.tsx +11 -0
  69. package/agents/examples/slider.tsx +5 -0
  70. package/agents/examples/surfaces.tsx +116 -0
  71. package/agents/examples/table.tsx +50 -0
  72. package/agents/examples/tabs.tsx +16 -0
  73. package/agents/examples/target-rows.tsx +70 -0
  74. package/agents/examples/toggle-group.tsx +12 -0
  75. package/agents/examples/toggle.tsx +11 -0
  76. package/agents/examples/tooltip.tsx +13 -0
  77. package/agents/examples/treemap.tsx +88 -0
  78. package/agents/manifest.json +1081 -0
  79. package/agents/report-rules.json +7 -0
  80. package/dist/components/registry/charts/area-chart-card.js +25 -7
  81. package/dist/components/registry/charts/bar-chart-card.d.ts +0 -1
  82. package/dist/components/registry/charts/bar-chart-card.js +40 -26
  83. package/dist/components/registry/charts/line-chart-card.js +2 -2
  84. package/dist/components/registry/dashboard-01/section-cards.d.ts +2 -0
  85. package/dist/components/registry/dashboard-01/section-cards.js +42 -35
  86. package/dist/components/registry/dashboardcn/bar-list.js +18 -11
  87. package/dist/components/registry/dashboardcn/distribution-bar.js +1 -1
  88. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
  89. package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
  90. package/dist/components/registry/dashboardcn/sparkline.d.ts +22 -0
  91. package/dist/components/registry/dashboardcn/sparkline.js +117 -0
  92. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +4 -2
  93. package/dist/components/registry/heatmap/calendar-heatmap.js +74 -33
  94. package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
  95. package/dist/components/registry/heatmap/status-heatmap.js +37 -15
  96. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +9 -3
  97. package/dist/components/registry/heatmap/weekday-heatmap.js +82 -47
  98. package/dist/components/registry/niko-table/core/data-table-context.d.ts +18 -1
  99. package/dist/components/registry/niko-table/core/data-table-context.js +7 -1
  100. package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
  101. package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
  102. package/dist/components/registry/niko-table/core/data-table-structure.js +17 -12
  103. package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
  104. package/dist/components/registry/niko-table/core/data-table.js +5 -1
  105. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +4 -4
  106. package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
  107. package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
  108. package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
  109. package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
  110. package/dist/components/registry/niko-table/lib/styles.js +3 -3
  111. package/dist/components/registry/niko-table/types/index.d.ts +9 -0
  112. package/dist/components/registry/niko-table/ui/table.js +2 -2
  113. package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
  114. package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
  115. package/dist/components/registry/sidebar-07/nav-main.d.ts +2 -1
  116. package/dist/components/registry/sidebar-07/nav-main.js +16 -3
  117. package/dist/components/siyahi/callout.d.ts +24 -0
  118. package/dist/components/siyahi/callout.js +40 -0
  119. package/dist/components/siyahi/change-bar.d.ts +21 -0
  120. package/dist/components/siyahi/change-bar.js +35 -0
  121. package/dist/components/siyahi/data-bar.d.ts +19 -0
  122. package/dist/components/siyahi/data-bar.js +29 -0
  123. package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
  124. package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
  125. package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
  126. package/dist/components/siyahi/data-table-footnote.js +35 -0
  127. package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
  128. package/dist/components/siyahi/default-sidebar-open.js +13 -0
  129. package/dist/components/siyahi/page-hero.d.ts +25 -0
  130. package/dist/components/siyahi/page-hero.js +41 -0
  131. package/dist/components/siyahi/page-section.d.ts +4 -1
  132. package/dist/components/siyahi/page-section.js +6 -3
  133. package/dist/components/siyahi/page-toc.d.ts +20 -0
  134. package/dist/components/siyahi/page-toc.js +76 -0
  135. package/dist/components/siyahi/panel-grid.d.ts +22 -0
  136. package/dist/components/siyahi/panel-grid.js +34 -0
  137. package/dist/components/siyahi/quote.d.ts +12 -0
  138. package/dist/components/siyahi/quote.js +23 -0
  139. package/dist/components/siyahi/range-position.d.ts +24 -0
  140. package/dist/components/siyahi/range-position.js +105 -0
  141. package/dist/components/siyahi/siyahi-provider.d.ts +13 -2
  142. package/dist/components/siyahi/siyahi-provider.js +17 -4
  143. package/dist/components/siyahi/target-rows.d.ts +34 -0
  144. package/dist/components/siyahi/target-rows.js +97 -0
  145. package/dist/components/siyahi/treemap.d.ts +4 -1
  146. package/dist/components/siyahi/treemap.js +78 -28
  147. package/dist/components/siyahi/warning-alert.d.ts +10 -0
  148. package/dist/components/siyahi/warning-alert.js +20 -0
  149. package/dist/components/ui/accordion.js +5 -5
  150. package/dist/components/ui/alert.js +2 -2
  151. package/dist/components/ui/badge.js +1 -1
  152. package/dist/components/ui/breadcrumb.js +2 -2
  153. package/dist/components/ui/button.js +10 -10
  154. package/dist/components/ui/calendar.js +1 -1
  155. package/dist/components/ui/card.js +4 -4
  156. package/dist/components/ui/chart.js +1 -1
  157. package/dist/components/ui/code-block.js +13 -12
  158. package/dist/components/ui/command.js +7 -6
  159. package/dist/components/ui/context-menu.js +7 -7
  160. package/dist/components/ui/dialog.js +5 -5
  161. package/dist/components/ui/dropdown-menu.js +8 -8
  162. package/dist/components/ui/input-group.js +11 -11
  163. package/dist/components/ui/input.js +1 -1
  164. package/dist/components/ui/popover.js +3 -3
  165. package/dist/components/ui/progress.js +1 -1
  166. package/dist/components/ui/select.js +6 -6
  167. package/dist/components/ui/sheet.js +5 -5
  168. package/dist/components/ui/sidebar.js +17 -18
  169. package/dist/components/ui/skeleton.js +1 -1
  170. package/dist/components/ui/slider.js +2 -2
  171. package/dist/components/ui/table.js +2 -2
  172. package/dist/components/ui/tabs.js +2 -2
  173. package/dist/components/ui/textarea.js +1 -1
  174. package/dist/components/ui/toggle-group.js +2 -2
  175. package/dist/components/ui/toggle.js +4 -4
  176. package/dist/components/ui/tooltip.js +2 -2
  177. package/dist/index.d.ts +21 -7
  178. package/dist/index.js +18 -4
  179. package/dist/lib/chart-frame.d.ts +25 -7
  180. package/dist/lib/chart-frame.js +251 -127
  181. package/dist/lib/chart-series.d.ts +12 -6
  182. package/dist/lib/compact-columns.d.ts +19 -0
  183. package/dist/lib/compact-columns.js +55 -0
  184. package/dist/lib/format.d.ts +7 -2
  185. package/dist/lib/format.js +54 -16
  186. package/dist/lib/heatmap-navigation.d.ts +8 -0
  187. package/dist/lib/heatmap-navigation.js +49 -0
  188. package/dist/lib/treemap.js +6 -1
  189. package/dist/lib/use-element-width.d.ts +5 -0
  190. package/dist/lib/use-element-width.js +15 -0
  191. package/package.json +4 -1
  192. package/registry.lock.json +16 -0
  193. package/report.css +33 -32
  194. package/styles/siyahi.css +221 -39
  195. package/dist/lib/recharts.d.ts +0 -2
  196. package/dist/lib/recharts.js +0 -1
@@ -1,12 +1,36 @@
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
+ import { cn } from "./utils.js";
3
4
  import { useLocale } from "../components/siyahi/siyahi-provider.js";
4
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { CartesianGrid, ReferenceLine, XAxis, YAxis } from "recharts";
6
+ import { useMemo } from "react";
7
+ import { CartesianGrid, ReferenceLine, Text, XAxis, YAxis, useChartWidth, useXAxisTicks, useYAxisTicks } from "recharts";
6
8
  //#region src/lib/chart-frame.tsx
7
9
  const AXIS_TITLE = "fill-muted-foreground text-xs";
8
10
  const TICK = { className: "fill-muted-foreground font-mono" };
9
11
  const NO_LINES = [];
12
+ /**
13
+ * The colour a series' Bar takes, which its legend chip shows: the negative colour when every value drawn is below
14
+ * zero, since each bar's own Cell follows its sign but the chip follows the Bar.
15
+ */
16
+ function seriesFill(item, data) {
17
+ const values = data.map((row) => row[item.key]).filter((value) => Number.isFinite(value)).map(Number);
18
+ return item.negativeColor && values.length > 0 && values.every((value) => value < 0) ? item.negativeColor : item.color;
19
+ }
20
+ /**
21
+ * 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
22
+ * grid, and none elsewhere. The eye lands on now, where a reader starts.
23
+ */
24
+ function endDot(color, count) {
25
+ return ({ cx, cy, index }) => index === count - 1 && cx !== void 0 && cy !== void 0 ? /* @__PURE__ */ jsx("circle", {
26
+ cx,
27
+ cy,
28
+ r: 4,
29
+ fill: color,
30
+ stroke: "var(--card)",
31
+ strokeWidth: 2
32
+ }, "end") : /* @__PURE__ */ jsx("g", {}, index);
33
+ }
10
34
  /** A tooltip row: the series' colour and label, then its value as the page formats it, or — when the row has none. */
11
35
  function seriesRow(series, formatValue) {
12
36
  return (value, _name, item) => {
@@ -28,170 +52,270 @@ function seriesRow(series, formatValue) {
28
52
  ] });
29
53
  };
30
54
  }
31
- const CHAR_WIDTH = 6.6;
32
- /** Squeezes a line into its column with SVG's own textLength when it would run into the next one. */
33
- const fitTo = (text, room) => room > 0 && text.length * CHAR_WIDTH > room ? {
34
- textLength: room,
35
- lengthAdjust: "spacingAndGlyphs"
36
- } : {};
37
55
  /**
38
- * An x tick with a second line from its row. Under a column the lines hang below the axis; beside a horizontal
39
- * bar they straddle its middle. Both lines are set in Geist, not Mono: a name with a figure under it reads as a
40
- * label, as in BarList, and sector names in Mono would take most of a phone's width.
56
+ * A value-axis tick formatted against the axis' largest tick, so every label shares one unit: ₹1.6L down to ₹0.4L,
57
+ * never ₹80,000 halfway up. Recharts' tickFormatter sees one value at a time; the axis' ticks come from its hooks.
58
+ * The rest of Recharts' tick props pass on as its own tick does, including the class it measures an auto width by.
59
+ */
60
+ function ValueTick({ payload, axis, formatter, ...tick }) {
61
+ const xTicks = useXAxisTicks();
62
+ const yTicks = useYAxisTicks();
63
+ const value = Number(payload?.value);
64
+ const scale = Math.max(Math.abs(value), ...((axis === "x" ? xTicks : yTicks) ?? []).map((t) => Math.abs(Number(t.value))));
65
+ return /* @__PURE__ */ jsx(Text, {
66
+ ...tick,
67
+ children: formatter(value, scale)
68
+ });
69
+ }
70
+ const GEIST_WIDTH = 6.6;
71
+ const MONO_WIDTH = 7.2;
72
+ /**
73
+ * Room to keep right of the plot when every named tick is drawn: half the last label, which centres on the
74
+ * domain's right end, and at least 24px.
75
+ */
76
+ function endRoom(xFormatter, xTicks) {
77
+ const last = xTicks.at(-1);
78
+ return Math.max(24, Math.ceil((last === void 0 ? 0 : xFormatter(last).length * MONO_WIDTH) / 2) + 4);
79
+ }
80
+ /**
81
+ * An x tick with a second line or explicit position: Recharts'
82
+ * own ticks would rather drop a label than let it run into the next. With a detail, the second line comes from the
83
+ * tick's row; under a column the lines hang below the axis, and beside a horizontal bar they straddle its middle.
84
+ * 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
85
+ * sector names in Mono would take most of a phone's width.
41
86
  */
42
- function twoLineTick(data, xKey, xFormatter, detailOf, beside) {
43
- return ({ x, y, width, visibleTicksCount, payload }) => {
87
+ function fittedTick(data, xKey, xFormatter, detailOf, beside, labelWidth) {
88
+ const rows = /* @__PURE__ */ new Map();
89
+ for (const row of data) if (!rows.has(row[xKey])) rows.set(row[xKey], row);
90
+ return ({ x, y, payload }) => {
44
91
  const value = payload?.value;
45
- const row = data.find((r) => r[xKey] === value);
92
+ const row = rows.get(value);
46
93
  const name = xFormatter(value);
47
- const detail = row ? detailOf(row) : null;
48
- const room = beside || !visibleTicksCount ? 0 : Number(width) / visibleTicksCount - 6;
94
+ const detail = row && detailOf ? detailOf(row) : null;
95
+ if (beside) return /* @__PURE__ */ jsxs("g", {
96
+ className: "fill-muted-foreground",
97
+ children: [/* @__PURE__ */ jsx(Text, {
98
+ x,
99
+ y: Number(y) - (detail ? 4 : 0),
100
+ width: labelWidth,
101
+ maxLines: 2,
102
+ textAnchor: "end",
103
+ verticalAnchor: detail ? "end" : "middle",
104
+ lineHeight: 14,
105
+ style: { fontSize: "var(--text-xs)" },
106
+ "aria-label": name,
107
+ className: "recharts-cartesian-axis-tick-value fill-muted-foreground",
108
+ children: name
109
+ }), detail !== null && /* @__PURE__ */ jsx(Text, {
110
+ x,
111
+ y: Number(y) + 4,
112
+ width: labelWidth,
113
+ textAnchor: "end",
114
+ verticalAnchor: "start",
115
+ lineHeight: 14,
116
+ style: {
117
+ fontSize: "var(--text-xs)",
118
+ fontVariantNumeric: "tabular-nums"
119
+ },
120
+ "aria-label": detail,
121
+ className: "fill-muted-foreground tabular-nums",
122
+ children: detail
123
+ })]
124
+ });
49
125
  return /* @__PURE__ */ jsxs("text", {
50
126
  x,
51
127
  y,
52
128
  textAnchor: beside ? "end" : "middle",
53
- className: "recharts-cartesian-axis-tick-value fill-muted-foreground",
129
+ className: cn("recharts-cartesian-axis-tick-value fill-muted-foreground", !detailOf && "font-mono"),
54
130
  children: [/* @__PURE__ */ jsx("tspan", {
55
131
  x,
56
- dy: beside ? -4 : 12,
57
- ...fitTo(name, room),
132
+ dy: 12,
58
133
  children: name
59
134
  }), detail !== null && /* @__PURE__ */ jsx("tspan", {
60
135
  x,
61
136
  dy: 15,
62
137
  className: "tabular-nums",
63
- ...fitTo(detail, room),
64
138
  children: detail
65
139
  })]
66
140
  });
67
141
  };
68
142
  }
143
+ function HorizontalAxis({ data, xKey, xFormatter, xTickDetail, xTicks }) {
144
+ const width = Math.min(240, Math.floor((useChartWidth() ?? 320) * .45));
145
+ return /* @__PURE__ */ jsx(YAxis, {
146
+ dataKey: xKey,
147
+ type: "category",
148
+ width,
149
+ tickLine: false,
150
+ axisLine: false,
151
+ tickMargin: 8,
152
+ tickFormatter: xFormatter,
153
+ interval: 0,
154
+ ticks: xTicks,
155
+ tick: fittedTick(data, xKey, xFormatter, xTickDetail, true, width - 16)
156
+ });
157
+ }
69
158
  /**
70
159
  * The card, axes, grid, tooltip and legend the three chart cards share. Both
71
160
  * axes keep Recharts' default ids: marks find their axes by id, and a page
72
161
  * that gave its category axis its own id (dash#88) got a plot split in two
73
162
  * and every tooltip headed with the series name instead of the category.
74
163
  */
75
- 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 }) {
164
+ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, onClick, 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 }) {
76
165
  const { numberLocale } = useLocale();
77
- const formatValue = valueFormatter ?? new Intl.NumberFormat(numberLocale).format;
166
+ const formatValue = useMemo(() => valueFormatter ?? new Intl.NumberFormat(numberLocale).format, [valueFormatter, numberLocale]);
78
167
  const config = Object.fromEntries(series.map(({ key, label, color }) => [key, {
79
168
  label,
80
169
  color
81
170
  }]));
82
171
  const crossesZero = data.some((row) => series.some(({ key }) => Number(row[key]) < 0));
172
+ const detailedRows = horizontal && xTickDetail !== void 0;
173
+ const rowCount = (xTicks ?? data).length;
174
+ const plotClass = detailedRows ? "min-h-(--wrapped-chart-height) @min-[24rem]/chart:min-h-0" : void 0;
175
+ let minWidth;
176
+ if (!horizontal && (xTicks || xTickDetail)) {
177
+ const labelledValues = xTicks ?? data.map((row) => row[xKey]);
178
+ let tickWidth = 0;
179
+ for (const value of labelledValues) tickWidth = Math.max(tickWidth, xFormatter(value).length * MONO_WIDTH);
180
+ if (xTickDetail) for (const row of data) tickWidth = Math.max(tickWidth, xTickDetail(row).length * GEIST_WIDTH);
181
+ minWidth = Math.ceil((tickWidth + 12) * labelledValues.length + 64);
182
+ }
183
+ const plotStyle = {
184
+ height: detailedRows ? Math.max(height, rowCount * 72 + 48) : height,
185
+ minWidth,
186
+ ...detailedRows ? { "--wrapped-chart-height": `${rowCount * 104 + 48}px` } : {}
187
+ };
83
188
  return /* @__PURE__ */ jsxs(Card, { children: [
84
189
  /* @__PURE__ */ jsxs(CardHeader, { children: [
85
190
  /* @__PURE__ */ jsx(CardTitle, { children: title }),
86
191
  description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
87
192
  action && /* @__PURE__ */ jsx(CardAction, { children: action })
88
193
  ] }),
89
- /* @__PURE__ */ jsx(CardContent, { children: placeholder ? /* @__PURE__ */ jsx("div", {
90
- "data-slot": "chart-placeholder",
91
- className: "flex items-center justify-center",
92
- style: { height },
93
- children: placeholder
94
- }) : /* @__PURE__ */ jsx(ChartContainer, {
95
- config,
96
- className: "aspect-auto w-full",
97
- style: { height },
98
- children: /* @__PURE__ */ jsxs(Chart, {
99
- data,
100
- layout: horizontal ? "vertical" : "horizontal",
101
- stackOffset,
102
- margin: {
103
- left: 0,
104
- right: 8
105
- },
106
- children: [
107
- /* @__PURE__ */ jsx(CartesianGrid, {
108
- vertical: horizontal,
109
- horizontal: !horizontal,
110
- strokeDasharray: "3 3"
111
- }),
112
- horizontal ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
113
- type: "number",
114
- hide: true,
115
- domain: yDomain,
116
- ticks: yTicks
117
- }), /* @__PURE__ */ jsx(YAxis, {
118
- dataKey: xKey,
119
- type: "category",
120
- width: "auto",
121
- tickLine: false,
122
- axisLine: false,
123
- tickMargin: 8,
124
- tickFormatter: (value) => xFormatter(value),
125
- tick: xTickDetail ? twoLineTick(data, xKey, xFormatter, xTickDetail, horizontal) : TICK,
126
- interval: 0
127
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
128
- dataKey: xKey,
129
- type: xDomain ? "number" : "category",
130
- domain: xDomain,
131
- ticks: xTicks,
132
- height: (xTickDetail ? 40 : 30) + (xLabel ? 18 : 0),
133
- label: xLabel ? {
134
- value: xLabel,
135
- position: "insideBottom",
136
- className: AXIS_TITLE
137
- } : void 0,
138
- tickLine: false,
139
- axisLine: false,
140
- tickMargin: 8,
141
- tickFormatter: (value) => xFormatter(value),
142
- tick: xTickDetail ? twoLineTick(data, xKey, xFormatter, xTickDetail, false) : TICK,
143
- interval: xTickDetail ? 0 : void 0
144
- }), /* @__PURE__ */ jsx(YAxis, {
145
- tickLine: false,
146
- axisLine: false,
147
- width: "auto",
148
- domain: yDomain,
149
- ticks: yTicks,
150
- label: yLabel ? {
151
- value: yLabel,
152
- angle: -90,
153
- position: "insideLeft",
154
- style: { textAnchor: "middle" },
155
- className: AXIS_TITLE
156
- } : void 0,
157
- tickFormatter: (value) => (yTickFormatter ?? formatValue)(value),
158
- tick: TICK
159
- })] }),
160
- crossesZero && (horizontal ? /* @__PURE__ */ jsx(ReferenceLine, {
161
- x: 0,
162
- stroke: "var(--border)"
163
- }) : /* @__PURE__ */ jsx(ReferenceLine, {
164
- y: 0,
165
- stroke: "var(--border)"
166
- })),
167
- referenceLines.map(({ label, emphasis = "muted", labelPosition = "insideTopLeft", ...at }) => /* @__PURE__ */ jsx(ReferenceLine, {
168
- ...horizontal ? {
169
- x: at.y,
170
- y: at.x
171
- } : at,
172
- stroke: emphasis === "strong" ? "var(--foreground)" : "var(--muted-foreground)",
173
- strokeDasharray: at.y === void 0 ? void 0 : "4 4",
174
- label: label === void 0 ? void 0 : {
175
- value: label,
176
- position: labelPosition,
177
- className: "fill-muted-foreground text-xs"
178
- }
179
- }, `${at.x ?? ""}:${at.y ?? ""}:${label ?? ""}`)),
180
- /* @__PURE__ */ jsx(ChartTooltip, { content: /* @__PURE__ */ jsx(ChartTooltipContent, {
181
- labelFormatter: (_, payload) => {
182
- const row = payload[0]?.payload;
183
- return row ? xTooltipFormatter(row[xKey]) : null;
184
- },
185
- formatter: seriesRow(series, formatValue)
186
- }) }),
187
- children,
188
- /* @__PURE__ */ jsx(ChartLegend, {
189
- content: /* @__PURE__ */ jsx(ChartLegendContent, {}),
190
- itemSorter: null
191
- })
192
- ]
194
+ /* @__PURE__ */ jsx(CardContent, {
195
+ className: "@container/chart overflow-x-auto",
196
+ role: minWidth ? "region" : void 0,
197
+ "aria-label": minWidth ? "Chart, scroll horizontally for all labelled values" : void 0,
198
+ tabIndex: minWidth ? 0 : void 0,
199
+ children: placeholder ? /* @__PURE__ */ jsx("div", {
200
+ "data-slot": "chart-placeholder",
201
+ className: cn("flex items-center justify-center", plotClass),
202
+ style: plotStyle,
203
+ children: placeholder
204
+ }) : /* @__PURE__ */ jsx(ChartContainer, {
205
+ config,
206
+ className: cn("aspect-auto w-full", plotClass, onClick && "cursor-pointer"),
207
+ style: plotStyle,
208
+ children: /* @__PURE__ */ jsxs(Chart, {
209
+ data,
210
+ layout: horizontal ? "vertical" : "horizontal",
211
+ stackOffset,
212
+ onClick,
213
+ throttledEvents: onClick ? [] : void 0,
214
+ margin: {
215
+ top: 12,
216
+ left: 0,
217
+ right: !horizontal && xTicks ? endRoom(xFormatter, xTicks) : horizontal && yTickFormatter ? 28 : 8
218
+ },
219
+ children: [
220
+ /* @__PURE__ */ jsx(CartesianGrid, {
221
+ vertical: horizontal,
222
+ horizontal: !horizontal,
223
+ strokeDasharray: "3 3"
224
+ }),
225
+ horizontal ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
226
+ type: "number",
227
+ niceTicks: "snap125",
228
+ hide: !yTickFormatter,
229
+ domain: yDomain,
230
+ ticks: yTicks,
231
+ tickLine: false,
232
+ axisLine: false,
233
+ tickMargin: 8,
234
+ tick: yTickFormatter ? /* @__PURE__ */ jsx(ValueTick, {
235
+ axis: "x",
236
+ formatter: yTickFormatter,
237
+ ...TICK
238
+ }) : TICK
239
+ }), /* @__PURE__ */ jsx(HorizontalAxis, {
240
+ data,
241
+ xKey,
242
+ xFormatter,
243
+ xTickDetail,
244
+ xTicks
245
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
246
+ dataKey: xKey,
247
+ type: xDomain ? "number" : "category",
248
+ domain: xDomain,
249
+ ticks: xTicks,
250
+ height: (xTickDetail ? 40 : 30) + (xLabel ? 18 : 0),
251
+ label: xLabel ? {
252
+ value: xLabel,
253
+ position: "insideBottom",
254
+ className: AXIS_TITLE
255
+ } : void 0,
256
+ tickLine: false,
257
+ axisLine: false,
258
+ tickMargin: 8,
259
+ tickFormatter: (value) => xFormatter(value),
260
+ tick: xTickDetail || xTicks ? fittedTick(data, xKey, xFormatter, xTickDetail, false) : TICK,
261
+ interval: xTickDetail || xTicks ? 0 : void 0
262
+ }), /* @__PURE__ */ jsx(YAxis, {
263
+ niceTicks: "snap125",
264
+ tickLine: false,
265
+ axisLine: false,
266
+ width: "auto",
267
+ domain: yDomain,
268
+ ticks: yTicks,
269
+ label: yLabel ? {
270
+ value: yLabel,
271
+ angle: -90,
272
+ position: "insideLeft",
273
+ style: { textAnchor: "middle" },
274
+ className: AXIS_TITLE
275
+ } : void 0,
276
+ tick: yTickFormatter ? /* @__PURE__ */ jsx(ValueTick, {
277
+ axis: "y",
278
+ formatter: yTickFormatter,
279
+ ...TICK
280
+ }) : TICK,
281
+ tickFormatter: yTickFormatter ? void 0 : (value) => formatValue(value)
282
+ })] }),
283
+ crossesZero && (horizontal ? /* @__PURE__ */ jsx(ReferenceLine, {
284
+ x: 0,
285
+ stroke: "var(--border)"
286
+ }) : /* @__PURE__ */ jsx(ReferenceLine, {
287
+ y: 0,
288
+ stroke: "var(--border)"
289
+ })),
290
+ referenceLines.map(({ label, emphasis = "muted", labelPosition = "insideTopLeft", ...at }) => /* @__PURE__ */ jsx(ReferenceLine, {
291
+ ...horizontal ? {
292
+ x: at.y,
293
+ y: at.x
294
+ } : at,
295
+ stroke: emphasis === "strong" ? "var(--foreground)" : "var(--muted-foreground)",
296
+ strokeDasharray: at.y === void 0 ? void 0 : "4 4",
297
+ label: label === void 0 ? void 0 : {
298
+ value: label,
299
+ position: labelPosition,
300
+ className: "fill-muted-foreground text-xs"
301
+ }
302
+ }, `${at.x ?? ""}:${at.y ?? ""}:${label ?? ""}`)),
303
+ /* @__PURE__ */ jsx(ChartTooltip, { content: /* @__PURE__ */ jsx(ChartTooltipContent, {
304
+ labelFormatter: (_, payload) => {
305
+ const row = payload[0]?.payload;
306
+ return row ? xTooltipFormatter(row[xKey]) : null;
307
+ },
308
+ formatter: seriesRow(series, formatValue)
309
+ }) }),
310
+ children,
311
+ /* @__PURE__ */ jsx(ChartLegend, {
312
+ content: /* @__PURE__ */ jsx(ChartLegendContent, {}),
313
+ itemSorter: null
314
+ })
315
+ ]
316
+ })
193
317
  })
194
- }) }),
318
+ }),
195
319
  footer && /* @__PURE__ */ jsx(CardFooter, {
196
320
  className: "flex-col items-start gap-2 text-sm",
197
321
  children: footer
@@ -199,4 +323,4 @@ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, t
199
323
  ] });
200
324
  }
201
325
  //#endregion
202
- export { ChartFrame, seriesRow };
326
+ export { ChartFrame, endDot, seriesFill, seriesRow };
@@ -49,19 +49,27 @@ export interface SeriesChartProps<T extends object, K extends keyof T> {
49
49
  /** The field the x axis reads: a month, an hour of day, a price bucket. One row per value; a repeated value is hidden behind the first. */
50
50
  xKey: K;
51
51
  series: ChartSeries<NoInfer<T>>;
52
+ /** Categories to label, or tick positions with xDomain. Every named tick is drawn; other rows keep their marks and tooltips. */
53
+ xTicks?: readonly T[K][];
52
54
  /** The x axis ticks, and the tooltip's heading unless xTooltipFormatter is set. Defaults to the value as text. */
53
55
  xFormatter?: (value: T[K]) => string;
54
56
  /** The tooltip's heading, when it should say more than a tick: "19:34:13 IST, 10 s average" against "19:34". */
55
57
  xTooltipFormatter?: (value: T[K]) => string;
56
58
  /** A series' value in the tooltip, and the y axis unless yTickFormatter is set. Defaults to the locale's numbers. */
57
59
  valueFormatter?: (value: number) => string;
58
- /** The y axis, when its ticks should be shorter than the tooltip's figures: moneyCompact against money. */
59
- yTickFormatter?: (value: number) => string;
60
- /** A fixed range for the value axis, such as [0, 100] for a percentage; by default it fits the data. */
60
+ /**
61
+ * The y axis, when its ticks should be shorter than the tooltip's figures: moneyCompact against money. It gets the
62
+ * axis' largest tick as `scale`, so moneyCompact writes every tick in one unit.
63
+ */
64
+ yTickFormatter?: (value: number, scale: number) => string;
65
+ /**
66
+ * A fixed range for the value axis, such as [0, 100] for a percentage. By default it fits the data; a bar
67
+ * chart's always reaches zero.
68
+ */
61
69
  yDomain?: [number, number];
62
70
  /** Where the value axis puts its ticks, such as [0, 25, 50, 75, 100]. */
63
71
  yTicks?: readonly number[];
64
- /** Height of the plot and legend, in pixels. */
72
+ /** Height of the plot and legend, in pixels. Sideways bars grow when wrapped tick details need more room. */
65
73
  height: number;
66
74
  /** A second line under each x tick, so a row's figure reads without a hover: "FY24" then "▲₹1.2L". */
67
75
  xTickDetail?: (row: T) => string;
@@ -80,8 +88,6 @@ export interface SeriesChartProps<T extends object, K extends keyof T> {
80
88
  export interface NumericXProps {
81
89
  /** The x axis runs over this range of the xKey field, which must hold numbers; without it the axis is one slot per row. */
82
90
  xDomain?: [number, number];
83
- /** Where the x axis puts its ticks, in the xKey field's units. */
84
- xTicks?: readonly number[];
85
91
  }
86
92
  /**
87
93
  * How a line or an area's edge joins its points: "linear" draws straight segments, as measured samples should
@@ -0,0 +1,19 @@
1
+ import { DataTableColumnDef } from "../components/registry/niko-table/types/index.js";
2
+ import { ColumnVisibilityState, RowData } from "@tanstack/react-table";
3
+ //#region src/lib/compact-columns.d.ts
4
+ /** Reports the element's inner width whenever it changes. */
5
+ export declare function useTableWidth(element: HTMLElement | null, onChange: (width: number) => void): void;
6
+ /** Narrower than 40rem, the width a phone gives a table: freezing is suspended and toolbars fold. */
7
+ export declare function isCompactWidth(width: number): boolean;
8
+ export interface FittedColumns {
9
+ /** The caller's visibility with the columns that do not fit turned off. */
10
+ visibility: ColumnVisibilityState;
11
+ /** The columns turned off to fit; empty when everything fits. */
12
+ hidden: ReadonlySet<string>;
13
+ }
14
+ /**
15
+ * Which columns to hide so the table fits `width`: the rightmost columns without `meta.compact` go first. Null
16
+ * when no column sets `meta.compact`, so a table that never opted in keeps every column and scrolls.
17
+ */
18
+ export declare function fitColumns<TData extends RowData>(columns: DataTableColumnDef<TData>[], order: string[], visibility: ColumnVisibilityState, width: number): FittedColumns | null;
19
+ //#endregion
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+ //#region src/lib/compact-columns.ts
3
+ const DEFAULT_SIZE = 150;
4
+ /** Reports the element's inner width whenever it changes. */
5
+ function useTableWidth(element, onChange) {
6
+ React.useLayoutEffect(() => {
7
+ if (!element) return;
8
+ const measure = () => {
9
+ if (element.clientWidth > 0) onChange(element.clientWidth);
10
+ };
11
+ measure();
12
+ const observer = new ResizeObserver(measure);
13
+ observer.observe(element);
14
+ return () => observer.disconnect();
15
+ }, [element, onChange]);
16
+ }
17
+ /** Narrower than 40rem, the width a phone gives a table: freezing is suspended and toolbars fold. */
18
+ function isCompactWidth(width) {
19
+ if (width <= 0) return false;
20
+ return width < 40 * Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
21
+ }
22
+ function columnId(column) {
23
+ if (column.id) return column.id;
24
+ const key = column.accessorKey;
25
+ return typeof key === "string" ? key.replaceAll(".", "_") : void 0;
26
+ }
27
+ /**
28
+ * Which columns to hide so the table fits `width`: the rightmost columns without `meta.compact` go first. Null
29
+ * when no column sets `meta.compact`, so a table that never opted in keeps every column and scrolls.
30
+ */
31
+ function fitColumns(columns, order, visibility, width) {
32
+ if (width <= 0 || !columns.some((column) => column.meta?.compact)) return null;
33
+ const byId = new Map(columns.flatMap((column) => {
34
+ const id = columnId(column);
35
+ return id ? [[id, column]] : [];
36
+ }));
37
+ const shown = [...order.filter((id) => byId.has(id)), ...[...byId.keys()].filter((id) => !order.includes(id))].filter((id) => visibility[id] !== false);
38
+ let total = shown.reduce((sum, id) => sum + (byId.get(id)?.size ?? DEFAULT_SIZE), 0);
39
+ const hidden = /* @__PURE__ */ new Set();
40
+ for (const id of shown.toReversed()) {
41
+ if (total <= width) break;
42
+ const column = byId.get(id);
43
+ if (column?.meta?.compact) continue;
44
+ hidden.add(id);
45
+ total -= column?.size ?? DEFAULT_SIZE;
46
+ }
47
+ const fitted = { ...visibility };
48
+ for (const id of hidden) fitted[id] = false;
49
+ return {
50
+ visibility: fitted,
51
+ hidden
52
+ };
53
+ }
54
+ //#endregion
55
+ export { fitColumns, isCompactWidth, useTableWidth };
@@ -7,8 +7,12 @@ export declare function money(value: number, locale?: SiyahiLocale, fractionDigi
7
7
  * count in thousands up to a lakh, so in an Indian-region locale amounts under
8
8
  * one lakh print in full (₹99,999), never as ₹99.9K. Signed figures belong in
9
9
  * `Delta`, which adds the arrow.
10
+ *
11
+ * Give `scale`, the largest amount on the same axis, and every tick takes that
12
+ * amount's unit, to two decimals so close ticks stay apart: ₹1.5L, ₹0.75L,
13
+ * ₹0, rather than switching to ₹75,000 halfway up.
10
14
  */
11
- export declare function moneyCompact(value: number, locale?: SiyahiLocale): string;
15
+ export declare function moneyCompact(value: number, locale?: SiyahiLocale, scale?: number): string;
12
16
  /** A whole count with the locale's grouping: 1,53,110 in en-IN. */
13
17
  export declare function count(value: number, locale?: SiyahiLocale): string;
14
18
  /** A fraction as a percentage: 0.1234 is 12.3%. */
@@ -22,7 +26,8 @@ export declare function time(value: Date | number | string, locale?: SiyahiLocal
22
26
  export interface Formatters {
23
27
  count: (value: number) => string;
24
28
  money: (value: number, fractionDigits?: number) => string;
25
- moneyCompact: (value: number) => string;
29
+ /** Pass `scale` on an axis so every tick shares one unit; a chart's yTickFormatter gets it as its second argument. */
30
+ moneyCompact: (value: number, scale?: number) => string;
26
31
  pct: (value: number, fractionDigits?: number) => string;
27
32
  time: (value: Date | number | string, options?: TimeOptions) => string;
28
33
  }