@misoto22/design 0.5.0 → 0.6.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 (214) hide show
  1. package/README.md +124 -0
  2. package/bin/misoto22-design.mjs +205 -0
  3. package/dist/agent/Accordion.md +41 -0
  4. package/dist/agent/Alert.md +42 -0
  5. package/dist/agent/AppShell.md +40 -0
  6. package/dist/agent/AreaChart.md +68 -0
  7. package/dist/agent/Article.md +41 -0
  8. package/dist/agent/Avatar.md +32 -0
  9. package/dist/agent/Badge.md +40 -0
  10. package/dist/agent/BarChart.md +65 -0
  11. package/dist/agent/BarList.md +40 -0
  12. package/dist/agent/BigNumber.md +45 -0
  13. package/dist/agent/BoxPlot.md +58 -0
  14. package/dist/agent/Breadcrumb.md +32 -0
  15. package/dist/agent/BulletChart.md +48 -0
  16. package/dist/agent/Button.md +56 -0
  17. package/dist/agent/Calendar.md +42 -0
  18. package/dist/agent/Card.md +49 -0
  19. package/dist/agent/Checkbox.md +35 -0
  20. package/dist/agent/Collapsible.md +46 -0
  21. package/dist/agent/Combobox.md +50 -0
  22. package/dist/agent/Command.md +93 -0
  23. package/dist/agent/ComposedChart.md +57 -0
  24. package/dist/agent/ContextMenu.md +59 -0
  25. package/dist/agent/DatePicker.md +75 -0
  26. package/dist/agent/Diagram.md +46 -0
  27. package/dist/agent/Dialog.md +52 -0
  28. package/dist/agent/DropdownMenu.md +70 -0
  29. package/dist/agent/EmptyState.md +37 -0
  30. package/dist/agent/ErrorState.md +41 -0
  31. package/dist/agent/Facet.md +82 -0
  32. package/dist/agent/Field.md +37 -0
  33. package/dist/agent/FigureBand.md +37 -0
  34. package/dist/agent/FloatingIconButton.md +38 -0
  35. package/dist/agent/FunnelChart.md +52 -0
  36. package/dist/agent/Heatmap.md +53 -0
  37. package/dist/agent/Histogram.md +63 -0
  38. package/dist/agent/Input.md +30 -0
  39. package/dist/agent/Kbd.md +29 -0
  40. package/dist/agent/LineChart.md +60 -0
  41. package/dist/agent/LinkArrow.md +29 -0
  42. package/dist/agent/NativeSelect.md +40 -0
  43. package/dist/agent/NavItem.md +35 -0
  44. package/dist/agent/Pagination.md +43 -0
  45. package/dist/agent/PieChart.md +55 -0
  46. package/dist/agent/Popover.md +57 -0
  47. package/dist/agent/Progress.md +33 -0
  48. package/dist/agent/RadarChart.md +54 -0
  49. package/dist/agent/RadialChart.md +57 -0
  50. package/dist/agent/RadioGroup.md +44 -0
  51. package/dist/agent/SankeyChart.md +61 -0
  52. package/dist/agent/ScatterChart.md +54 -0
  53. package/dist/agent/ScrollArea.md +40 -0
  54. package/dist/agent/SearchableMenu.md +46 -0
  55. package/dist/agent/Select.md +75 -0
  56. package/dist/agent/Separator.md +35 -0
  57. package/dist/agent/Sheet.md +57 -0
  58. package/dist/agent/Skeleton.md +73 -0
  59. package/dist/agent/Slider.md +39 -0
  60. package/dist/agent/Sparkline.md +45 -0
  61. package/dist/agent/Spinner.md +39 -0
  62. package/dist/agent/StatusDot.md +36 -0
  63. package/dist/agent/StatusPill.md +28 -0
  64. package/dist/agent/Steps.md +44 -0
  65. package/dist/agent/Switch.md +29 -0
  66. package/dist/agent/Table.md +85 -0
  67. package/dist/agent/Tabs.md +48 -0
  68. package/dist/agent/Tag.md +35 -0
  69. package/dist/agent/Textarea.md +26 -0
  70. package/dist/agent/Toast.md +26 -0
  71. package/dist/agent/ToggleGroup.md +44 -0
  72. package/dist/agent/Tooltip.md +51 -0
  73. package/dist/agent/TreemapChart.md +52 -0
  74. package/dist/agent/WaterfallChart.md +62 -0
  75. package/dist/agent/catalog.json +1791 -0
  76. package/dist/agent/index.md +131 -0
  77. package/dist/charts/AreaChart/AreaChart.d.ts +191 -0
  78. package/dist/charts/AreaChart/AreaChart.js +480 -0
  79. package/dist/charts/AreaChart/AreaChart.js.map +1 -0
  80. package/dist/charts/BarChart/BarChart.d.ts +179 -0
  81. package/dist/charts/BarChart/BarChart.js +591 -0
  82. package/dist/charts/BarChart/BarChart.js.map +1 -0
  83. package/dist/charts/BarList/BarList.d.ts +57 -0
  84. package/dist/charts/BarList/BarList.js +71 -0
  85. package/dist/charts/BarList/BarList.js.map +1 -0
  86. package/dist/charts/BigNumber/BigNumber.d.ts +56 -0
  87. package/dist/charts/BigNumber/BigNumber.js +31 -0
  88. package/dist/charts/BigNumber/BigNumber.js.map +1 -0
  89. package/dist/charts/BoxPlot/BoxPlot.d.ts +177 -0
  90. package/dist/charts/BoxPlot/BoxPlot.js +495 -0
  91. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -0
  92. package/dist/charts/BulletChart/BulletChart.d.ts +100 -0
  93. package/dist/charts/BulletChart/BulletChart.js +162 -0
  94. package/dist/charts/BulletChart/BulletChart.js.map +1 -0
  95. package/dist/charts/ComposedChart/ComposedChart.d.ts +171 -0
  96. package/dist/charts/ComposedChart/ComposedChart.js +667 -0
  97. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -0
  98. package/dist/charts/Facet/Facet.d.ts +240 -0
  99. package/dist/charts/Facet/Facet.js +133 -0
  100. package/dist/charts/Facet/Facet.js.map +1 -0
  101. package/dist/charts/Facet/panels.d.ts +187 -0
  102. package/dist/charts/Facet/panels.js +203 -0
  103. package/dist/charts/Facet/panels.js.map +1 -0
  104. package/dist/charts/FunnelChart/FunnelChart.d.ts +101 -0
  105. package/dist/charts/FunnelChart/FunnelChart.js +147 -0
  106. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -0
  107. package/dist/charts/Heatmap/Heatmap.d.ts +78 -0
  108. package/dist/charts/Heatmap/Heatmap.js +108 -0
  109. package/dist/charts/Heatmap/Heatmap.js.map +1 -0
  110. package/dist/charts/Histogram/Histogram.d.ts +165 -0
  111. package/dist/charts/Histogram/Histogram.js +350 -0
  112. package/dist/charts/Histogram/Histogram.js.map +1 -0
  113. package/dist/charts/LineChart/LineChart.d.ts +167 -0
  114. package/dist/charts/LineChart/LineChart.js +486 -0
  115. package/dist/charts/LineChart/LineChart.js.map +1 -0
  116. package/dist/charts/PieChart/PieChart.d.ts +128 -0
  117. package/dist/charts/PieChart/PieChart.js +309 -0
  118. package/dist/charts/PieChart/PieChart.js.map +1 -0
  119. package/dist/charts/RadarChart/RadarChart.d.ts +123 -0
  120. package/dist/charts/RadarChart/RadarChart.js +299 -0
  121. package/dist/charts/RadarChart/RadarChart.js.map +1 -0
  122. package/dist/charts/RadialChart/RadialChart.d.ts +124 -0
  123. package/dist/charts/RadialChart/RadialChart.js +257 -0
  124. package/dist/charts/RadialChart/RadialChart.js.map +1 -0
  125. package/dist/charts/SankeyChart/SankeyChart.d.ts +137 -0
  126. package/dist/charts/SankeyChart/SankeyChart.js +501 -0
  127. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -0
  128. package/dist/charts/ScatterChart/ScatterChart.d.ts +128 -0
  129. package/dist/charts/ScatterChart/ScatterChart.js +269 -0
  130. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -0
  131. package/dist/charts/Sparkline/Sparkline.d.ts +53 -0
  132. package/dist/charts/Sparkline/Sparkline.js +93 -0
  133. package/dist/charts/Sparkline/Sparkline.js.map +1 -0
  134. package/dist/charts/TreemapChart/TreemapChart.d.ts +93 -0
  135. package/dist/charts/TreemapChart/TreemapChart.js +118 -0
  136. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -0
  137. package/dist/charts/WaterfallChart/WaterfallChart.d.ts +164 -0
  138. package/dist/charts/WaterfallChart/WaterfallChart.js +319 -0
  139. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -0
  140. package/dist/charts/index.d.ts +41 -0
  141. package/dist/charts/index.js +128 -0
  142. package/dist/charts/index.js.map +1 -0
  143. package/dist/charts/lib/annotations.d.ts +103 -0
  144. package/dist/charts/lib/annotations.js +99 -0
  145. package/dist/charts/lib/annotations.js.map +1 -0
  146. package/dist/charts/lib/axis.d.ts +18 -0
  147. package/dist/charts/lib/axis.js +21 -0
  148. package/dist/charts/lib/axis.js.map +1 -0
  149. package/dist/charts/lib/background.d.ts +33 -0
  150. package/dist/charts/lib/background.js +173 -0
  151. package/dist/charts/lib/background.js.map +1 -0
  152. package/dist/charts/lib/brush.d.ts +94 -0
  153. package/dist/charts/lib/brush.js +535 -0
  154. package/dist/charts/lib/brush.js.map +1 -0
  155. package/dist/charts/lib/chart.d.ts +94 -0
  156. package/dist/charts/lib/chart.js +134 -0
  157. package/dist/charts/lib/chart.js.map +1 -0
  158. package/dist/charts/lib/dot.d.ts +29 -0
  159. package/dist/charts/lib/dot.js +78 -0
  160. package/dist/charts/lib/dot.js.map +1 -0
  161. package/dist/charts/lib/empty.d.ts +31 -0
  162. package/dist/charts/lib/empty.js +13 -0
  163. package/dist/charts/lib/empty.js.map +1 -0
  164. package/dist/charts/lib/export.d.ts +103 -0
  165. package/dist/charts/lib/export.js +204 -0
  166. package/dist/charts/lib/export.js.map +1 -0
  167. package/dist/charts/lib/figure.d.ts +61 -0
  168. package/dist/charts/lib/figure.js +59 -0
  169. package/dist/charts/lib/figure.js.map +1 -0
  170. package/dist/charts/lib/format.d.ts +46 -0
  171. package/dist/charts/lib/format.js +56 -0
  172. package/dist/charts/lib/format.js.map +1 -0
  173. package/dist/charts/lib/legend.d.ts +45 -0
  174. package/dist/charts/lib/legend.js +117 -0
  175. package/dist/charts/lib/legend.js.map +1 -0
  176. package/dist/charts/lib/loading.d.ts +48 -0
  177. package/dist/charts/lib/loading.js +105 -0
  178. package/dist/charts/lib/loading.js.map +1 -0
  179. package/dist/charts/lib/paint.d.ts +154 -0
  180. package/dist/charts/lib/paint.js +551 -0
  181. package/dist/charts/lib/paint.js.map +1 -0
  182. package/dist/charts/lib/sonify-control.d.ts +163 -0
  183. package/dist/charts/lib/sonify-control.js +172 -0
  184. package/dist/charts/lib/sonify-control.js.map +1 -0
  185. package/dist/charts/lib/sonify.d.ts +248 -0
  186. package/dist/charts/lib/sonify.js +262 -0
  187. package/dist/charts/lib/sonify.js.map +1 -0
  188. package/dist/charts/lib/toolbar.d.ts +143 -0
  189. package/dist/charts/lib/toolbar.js +156 -0
  190. package/dist/charts/lib/toolbar.js.map +1 -0
  191. package/dist/charts/lib/tooltip.d.ts +59 -0
  192. package/dist/charts/lib/tooltip.js +135 -0
  193. package/dist/charts/lib/tooltip.js.map +1 -0
  194. package/dist/charts/lib/values.d.ts +56 -0
  195. package/dist/charts/lib/values.js +71 -0
  196. package/dist/charts/lib/values.js.map +1 -0
  197. package/dist/charts/lib/zoom.d.ts +139 -0
  198. package/dist/charts/lib/zoom.js +314 -0
  199. package/dist/charts/lib/zoom.js.map +1 -0
  200. package/dist/semantic.css +19 -0
  201. package/dist/styles.css +1 -1
  202. package/dist/themes.css +50 -0
  203. package/dist/tokens.css +118 -0
  204. package/dist/tokens.d.ts +17 -0
  205. package/dist/tokens.js +101 -0
  206. package/dist/tokens.json +101 -0
  207. package/package.json +26 -4
  208. package/skills/misoto22-design/SKILL.md +171 -0
  209. package/skills/misoto22-design/evals/claims.json +95 -0
  210. package/skills/misoto22-design/rules/a11y.md +115 -0
  211. package/skills/misoto22-design/rules/composition.md +108 -0
  212. package/skills/misoto22-design/rules/forms.md +128 -0
  213. package/skills/misoto22-design/rules/naming.md +155 -0
  214. package/skills/misoto22-design/rules/tokens.md +131 -0
@@ -0,0 +1,94 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, ComponentType, ComponentProps } from 'react';
3
+ import { ResponsiveContainer } from 'recharts';
4
+
5
+ /**
6
+ * How many series the system will paint before it refuses.
7
+ *
8
+ * Eight is not a rendering limit — it is the width of the validated ramp in
9
+ * `tokens.css`. A ninth series has no slot, and generating one would mean
10
+ * inventing a colour nobody checked against the eighth. The catalogued answer
11
+ * to "I have twelve categories" is to fold the tail into an "Other" series or
12
+ * to facet, and this constant is where that decision is enforced rather than
13
+ * left to whoever is looking at the chart.
14
+ */
15
+ declare const SERIES_SLOTS = 8;
16
+ /** Theme name → the selector its block is written under. */
17
+ declare const THEMES: {
18
+ readonly light: "";
19
+ readonly dark: "[data-mode='dark']";
20
+ };
21
+ type ThemeKey = keyof typeof THEMES;
22
+ /** Colours for one series, per theme. At least one theme must be given. */
23
+ type ThemeColors = {
24
+ [K in ThemeKey]: Required<Pick<Record<ThemeKey, string[]>, K>> & Partial<Omit<Record<ThemeKey, string[]>, K>>;
25
+ }[ThemeKey];
26
+ interface ChartSeries {
27
+ /**
28
+ * What the legend, the tooltip and the hidden data table call this series.
29
+ *
30
+ * Required, unlike the shape this was ported from. Colour is never the only
31
+ * carrier of identity in this system, and a legend entry with no label is a
32
+ * swatch that means nothing — least of all in the monochrome default, where
33
+ * two series differ by a step of grey and a texture.
34
+ */
35
+ label: ReactNode;
36
+ /** Drawn in place of the legend/tooltip swatch. Decorative; label still wins. */
37
+ icon?: ComponentType;
38
+ /**
39
+ * Explicit colours for this series, overriding its slot on the series ramp.
40
+ *
41
+ * Reach for this only when the data itself carries a colour the reader
42
+ * already knows — a brand, a traffic light, a map key. For "I want my chart
43
+ * to have colours", set `data-chart-palette="chroma"` on any ancestor: that
44
+ * swaps the whole ramp for a palette validated against both grounds, which a
45
+ * hand-picked array is not.
46
+ *
47
+ * More than one colour paints the series as a horizontal gradient across
48
+ * those stops. Values are plain CSS colours; anything that could break out of
49
+ * a declaration is rejected at the boundary rather than injected.
50
+ */
51
+ colors?: ThemeColors;
52
+ }
53
+ /** Series keys → how each is labelled and painted. Declaration order is slot order. */
54
+ type ChartConfig = Record<string, ChartSeries>;
55
+ interface ChartContextValue {
56
+ config: ChartConfig;
57
+ }
58
+ /** The chart config of the nearest chart root. Throws outside one. */
59
+ declare function useChart(): ChartContextValue;
60
+ /** How many gradient stops a series paints with — one means a flat fill. */
61
+ declare function colorStops(series: ChartSeries | undefined): number;
62
+ /**
63
+ * A series key, reduced to what may appear in a custom property name. Keys come
64
+ * from consumer data, so the same boundary argument as `isSafeColor` applies —
65
+ * here it is the property NAME rather than its value.
66
+ */
67
+ declare function cssName(key: string): string;
68
+ interface ChartContainerProps extends Omit<ComponentProps<'div'>, 'children'>, Pick<ComponentProps<typeof ResponsiveContainer>, 'initialDimension' | 'minHeight' | 'minWidth' | 'maxHeight' | 'height' | 'width' | 'children'> {
69
+ config: ChartConfig;
70
+ /** Rendered under the plot, inside the same measured box — the brush lives here. */
71
+ footer?: ReactNode;
72
+ }
73
+ /**
74
+ * The measured box every chart is drawn into, and the only place Recharts'
75
+ * hard-coded greys are re-pointed at system tokens.
76
+ *
77
+ * Recharts paints its axes, grid, cursor and radial track with literal `#ccc`
78
+ * and `#fff`. The selectors below catch each of those and hand it a token, so a
79
+ * chart follows light/dark and a surface theme without a consumer configuring
80
+ * anything — and so nothing in the package has to pass a colour down by prop.
81
+ */
82
+ declare function ChartContainer({ id, config, initialDimension, className, children, footer, ...rest }: ChartContainerProps): react.JSX.Element;
83
+ /**
84
+ * Reads the config entry behind a Recharts payload item.
85
+ *
86
+ * Recharts hands the same series different names in different chart types — a
87
+ * pie sector's key is on `payload.payload[nameKey]`, a bar's is `dataKey` — so
88
+ * this is the one place that disagreement is resolved.
89
+ */
90
+ declare function seriesFromPayload(config: ChartConfig, payload: unknown, key: string): ChartSeries | undefined;
91
+ /** Formats an expanded stack's axis, where 0–1 means 0–100%. */
92
+ declare function percentTick(value: number): string;
93
+
94
+ export { type ChartConfig, ChartContainer, type ChartContainerProps, type ChartSeries, SERIES_SLOTS, colorStops, cssName, percentTick, seriesFromPayload, useChart };
@@ -0,0 +1,134 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useId,
7
+ useMemo
8
+ } from "react";
9
+ import { ResponsiveContainer } from "recharts";
10
+ import { cn } from "../../lib/cn.js";
11
+ const SERIES_SLOTS = 8;
12
+ const THEMES = { light: "", dark: "[data-mode='dark']" };
13
+ const THEME_KEYS = Object.keys(THEMES);
14
+ const ChartContext = createContext(null);
15
+ function useChart() {
16
+ const context = useContext(ChartContext);
17
+ if (!context) throw new Error("A chart part must be rendered inside a chart root");
18
+ return context;
19
+ }
20
+ const CSS_COLOR = /^[A-Za-z0-9#(),.%\-\s_]{1,120}$/;
21
+ function isSafeColor(value) {
22
+ return typeof value === "string" && CSS_COLOR.test(value) && !value.includes("/*");
23
+ }
24
+ function distribute(colors, slots) {
25
+ if (colors.length >= slots) return colors.slice(0, slots);
26
+ const base = Math.floor(slots / colors.length);
27
+ const extra = slots % colors.length;
28
+ const out = [];
29
+ colors.forEach((color, index) => {
30
+ const count = base + (index >= colors.length - extra ? 1 : 0);
31
+ for (let n = 0; n < count; n += 1) out.push(color);
32
+ });
33
+ return out;
34
+ }
35
+ function colorStops(series) {
36
+ if (!series?.colors) return 1;
37
+ return Math.max(1, ...THEME_KEYS.map((theme) => series.colors?.[theme]?.length ?? 0));
38
+ }
39
+ function ChartStyle({ id, config }) {
40
+ const css = useMemo(() => {
41
+ const entries = Object.entries(config);
42
+ const rules = (theme) => entries.flatMap(([key, series], slot) => {
43
+ const stops = colorStops(series);
44
+ const slotColor = `var(--series-${slot % SERIES_SLOTS + 1})`;
45
+ const given = series.colors?.[theme] ?? series.colors?.light ?? series.colors?.dark;
46
+ const safe = given?.filter(isSafeColor) ?? [];
47
+ const colors = safe.length > 0 ? safe : [slotColor];
48
+ return distribute(colors, stops).map(
49
+ (color, index) => ` --color-${cssName(key)}-${index}: ${color};`
50
+ );
51
+ }).join("\n");
52
+ return THEME_KEYS.map((theme) => {
53
+ const body = rules(theme);
54
+ if (!body) return "";
55
+ return `${THEMES[theme]} [data-chart='${id}'] {
56
+ ${body}
57
+ }`;
58
+ }).filter(Boolean).join("\n");
59
+ }, [id, config]);
60
+ if (!css) return null;
61
+ return /* @__PURE__ */ jsx("style", { children: css });
62
+ }
63
+ function cssName(key) {
64
+ return key.replace(/[^A-Za-z0-9_-]/g, "-");
65
+ }
66
+ function ChartContainer({
67
+ id,
68
+ config,
69
+ initialDimension = { width: 320, height: 200 },
70
+ className,
71
+ children,
72
+ footer,
73
+ ...rest
74
+ }) {
75
+ const generated = useId().replace(/:/g, "");
76
+ const chartId = `chart-${id ?? generated}`;
77
+ return /* @__PURE__ */ jsx(ChartContext.Provider, { value: { config }, children: /* @__PURE__ */ jsxs(
78
+ "div",
79
+ {
80
+ "data-slot": "chart",
81
+ "data-chart": chartId,
82
+ className: cn(
83
+ "relative flex min-h-0 w-full flex-1 flex-col justify-center text-xs",
84
+ // 16:9 is right for a card and wrong for a full-width panel: at
85
+ // 1200px it is a 675px-tall plot with six points in it. The cap is
86
+ // what stops the aspect ratio running away with a wide container,
87
+ // and `min-h` keeps it from collapsing in a narrow one.
88
+ !footer && "aspect-video max-h-[26rem] min-h-[13rem]",
89
+ // Recharts' own defaults, re-pointed at the token layer.
90
+ "[&_.recharts-cartesian-axis-tick_text]:fill-(--chart-axis)",
91
+ "[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-(--chart-grid)",
92
+ "[&_.recharts-curve.recharts-tooltip-cursor]:stroke-(--chart-cursor)",
93
+ "[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-(--chart-grid)",
94
+ "[&_.recharts-radial-bar-background-sector]:fill-(--chart-track)",
95
+ "[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-(--chart-track)",
96
+ "[&_.recharts-reference-line_[stroke='#ccc']]:stroke-(--chart-grid)",
97
+ "[&_.recharts-dot[stroke='#fff']]:stroke-transparent",
98
+ "[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
99
+ // Recharts draws its own focus ring; the system draws one focus ring,
100
+ // in one place, and this is how the duplicate is removed.
101
+ "[&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
102
+ className
103
+ ),
104
+ ...rest,
105
+ children: [
106
+ /* @__PURE__ */ jsx(ChartStyle, { id: chartId, config }),
107
+ /* @__PURE__ */ jsx(ResponsiveContainer, { className: "min-h-0 w-full flex-1", initialDimension, children }),
108
+ footer
109
+ ]
110
+ }
111
+ ) });
112
+ }
113
+ function seriesFromPayload(config, payload, key) {
114
+ if (typeof payload !== "object" || payload === null) return void 0;
115
+ const nested = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : void 0;
116
+ let name = key;
117
+ const direct = payload[key];
118
+ if (typeof direct === "string") name = direct;
119
+ else if (nested && typeof nested[key] === "string") name = nested[key];
120
+ return name in config ? config[name] : config[key];
121
+ }
122
+ function percentTick(value) {
123
+ return `${Math.round(value * 100)}%`;
124
+ }
125
+ export {
126
+ ChartContainer,
127
+ SERIES_SLOTS,
128
+ colorStops,
129
+ cssName,
130
+ percentTick,
131
+ seriesFromPayload,
132
+ useChart
133
+ };
134
+ //# sourceMappingURL=chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/charts/lib/chart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ComponentType,\n type ReactNode,\n} from 'react'\nimport { ResponsiveContainer } from 'recharts'\nimport { cn } from '../../lib/cn'\n\n/**\n * How many series the system will paint before it refuses.\n *\n * Eight is not a rendering limit — it is the width of the validated ramp in\n * `tokens.css`. A ninth series has no slot, and generating one would mean\n * inventing a colour nobody checked against the eighth. The catalogued answer\n * to \"I have twelve categories\" is to fold the tail into an \"Other\" series or\n * to facet, and this constant is where that decision is enforced rather than\n * left to whoever is looking at the chart.\n */\nexport const SERIES_SLOTS = 8\n\n/** Theme name → the selector its block is written under. */\nconst THEMES = { light: '', dark: \"[data-mode='dark']\" } as const\n\ntype ThemeKey = keyof typeof THEMES\n\nconst THEME_KEYS = Object.keys(THEMES) as ThemeKey[]\n\n/** Colours for one series, per theme. At least one theme must be given. */\ntype ThemeColors = {\n [K in ThemeKey]: Required<Pick<Record<ThemeKey, string[]>, K>> &\n Partial<Omit<Record<ThemeKey, string[]>, K>>\n}[ThemeKey]\n\nexport interface ChartSeries {\n /**\n * What the legend, the tooltip and the hidden data table call this series.\n *\n * Required, unlike the shape this was ported from. Colour is never the only\n * carrier of identity in this system, and a legend entry with no label is a\n * swatch that means nothing — least of all in the monochrome default, where\n * two series differ by a step of grey and a texture.\n */\n label: ReactNode\n /** Drawn in place of the legend/tooltip swatch. Decorative; label still wins. */\n icon?: ComponentType\n /**\n * Explicit colours for this series, overriding its slot on the series ramp.\n *\n * Reach for this only when the data itself carries a colour the reader\n * already knows — a brand, a traffic light, a map key. For \"I want my chart\n * to have colours\", set `data-chart-palette=\"chroma\"` on any ancestor: that\n * swaps the whole ramp for a palette validated against both grounds, which a\n * hand-picked array is not.\n *\n * More than one colour paints the series as a horizontal gradient across\n * those stops. Values are plain CSS colours; anything that could break out of\n * a declaration is rejected at the boundary rather than injected.\n */\n colors?: ThemeColors\n}\n\n/** Series keys → how each is labelled and painted. Declaration order is slot order. */\nexport type ChartConfig = Record<string, ChartSeries>\n\ninterface ChartContextValue {\n config: ChartConfig\n}\n\nconst ChartContext = createContext<ChartContextValue | null>(null)\n\n/** The chart config of the nearest chart root. Throws outside one. */\nexport function useChart(): ChartContextValue {\n const context = useContext(ChartContext)\n if (!context) throw new Error('A chart part must be rendered inside a chart root')\n return context\n}\n\n/**\n * A CSS colour, or something that would escape the declaration it is written\n * into.\n *\n * `ChartStyle` writes consumer-supplied values into a `<style>` element, which\n * makes this a system boundary in the security sense: without the check, a\n * `colors` array carrying `red; } html { display: none } .x {` is not a colour,\n * it is a stylesheet. The allowed set is deliberately shaped around what a\n * colour is made of — identifiers, digits, `#`, brackets, commas, percent — so\n * `var(--x)`, `oklch(…)` and `color-mix(in srgb, …)` all pass and no `;`, `{`,\n * `}`, `<`, `\\` or comment sequence does.\n */\nconst CSS_COLOR = /^[A-Za-z0-9#(),.%\\-\\s_]{1,120}$/\n\nfunction isSafeColor(value: unknown): value is string {\n return typeof value === 'string' && CSS_COLOR.test(value) && !value.includes('/*')\n}\n\n/**\n * Distributes fewer colours than slots, giving the surplus to the last stops.\n * Two colours over four slots read as `[a, a, b, b]`; three over four as\n * `[a, b, c, c]`.\n */\nfunction distribute(colors: string[], slots: number): string[] {\n if (colors.length >= slots) return colors.slice(0, slots)\n\n const base = Math.floor(slots / colors.length)\n const extra = slots % colors.length\n const out: string[] = []\n colors.forEach((color, index) => {\n const count = base + (index >= colors.length - extra ? 1 : 0)\n for (let n = 0; n < count; n += 1) out.push(color)\n })\n return out\n}\n\n/** How many gradient stops a series paints with — one means a flat fill. */\nexport function colorStops(series: ChartSeries | undefined): number {\n if (!series?.colors) return 1\n return Math.max(1, ...THEME_KEYS.map((theme) => series.colors?.[theme]?.length ?? 0))\n}\n\n/**\n * The custom properties one chart instance paints from.\n *\n * Every series resolves to `--color-<key>-<n>`, and every mark, swatch and\n * gradient stop in the package reads only those names. That indirection is what\n * lets the same chart be repainted by an ancestor attribute — a theme, a\n * palette, dark mode — without a component knowing anything about colour.\n *\n * A series with no `colors` takes its slot on the ramp, in declaration order.\n * That is the \"assign in fixed order, never cycle\" rule expressed as code: the\n * fourth series is `--series-4` whether or not the first three are on screen,\n * so hiding a series never repaints the survivors.\n */\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const css = useMemo(() => {\n const entries = Object.entries(config)\n\n const rules = (theme: ThemeKey) =>\n entries\n .flatMap(([key, series], slot) => {\n const stops = colorStops(series)\n const slotColor = `var(--series-${(slot % SERIES_SLOTS) + 1})`\n const given = series.colors?.[theme] ?? series.colors?.light ?? series.colors?.dark\n const safe = given?.filter(isSafeColor) ?? []\n // A rejected colour falls back to the ramp rather than to nothing: an\n // unpainted series is invisible, which is a worse failure than an\n // ignored value and a harder one to notice.\n const colors = safe.length > 0 ? safe : [slotColor]\n return distribute(colors, stops).map(\n (color, index) => ` --color-${cssName(key)}-${index}: ${color};`,\n )\n })\n .join('\\n')\n\n return THEME_KEYS.map((theme) => {\n const body = rules(theme)\n if (!body) return ''\n return `${THEMES[theme]} [data-chart='${id}'] {\\n${body}\\n}`\n })\n .filter(Boolean)\n .join('\\n')\n }, [id, config])\n\n if (!css) return null\n\n return <style>{css}</style>\n}\n\n/**\n * A series key, reduced to what may appear in a custom property name. Keys come\n * from consumer data, so the same boundary argument as `isSafeColor` applies —\n * here it is the property NAME rather than its value.\n */\nexport function cssName(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-')\n}\n\nexport interface ChartContainerProps\n extends Omit<ComponentProps<'div'>, 'children'>,\n Pick<\n ComponentProps<typeof ResponsiveContainer>,\n 'initialDimension' | 'minHeight' | 'minWidth' | 'maxHeight' | 'height' | 'width' | 'children'\n > {\n config: ChartConfig\n /** Rendered under the plot, inside the same measured box — the brush lives here. */\n footer?: ReactNode\n}\n\n/**\n * The measured box every chart is drawn into, and the only place Recharts'\n * hard-coded greys are re-pointed at system tokens.\n *\n * Recharts paints its axes, grid, cursor and radial track with literal `#ccc`\n * and `#fff`. The selectors below catch each of those and hand it a token, so a\n * chart follows light/dark and a surface theme without a consumer configuring\n * anything — and so nothing in the package has to pass a colour down by prop.\n */\nexport function ChartContainer({\n id,\n config,\n initialDimension = { width: 320, height: 200 },\n className,\n children,\n footer,\n ...rest\n}: ChartContainerProps) {\n const generated = useId().replace(/:/g, '')\n const chartId = `chart-${id ?? generated}`\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n 'relative flex min-h-0 w-full flex-1 flex-col justify-center text-xs',\n // 16:9 is right for a card and wrong for a full-width panel: at\n // 1200px it is a 675px-tall plot with six points in it. The cap is\n // what stops the aspect ratio running away with a wide container,\n // and `min-h` keeps it from collapsing in a narrow one.\n !footer && 'aspect-video max-h-[26rem] min-h-[13rem]',\n // Recharts' own defaults, re-pointed at the token layer.\n \"[&_.recharts-cartesian-axis-tick_text]:fill-(--chart-axis)\",\n \"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-curve.recharts-tooltip-cursor]:stroke-(--chart-cursor)',\n \"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-radial-bar-background-sector]:fill-(--chart-track)',\n '[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-(--chart-track)',\n \"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-(--chart-grid)\",\n \"[&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n \"[&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n // Recharts draws its own focus ring; the system draws one focus ring,\n // in one place, and this is how the duplicate is removed.\n '[&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden',\n className,\n )}\n {...rest}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer className=\"min-h-0 w-full flex-1\" initialDimension={initialDimension}>\n {children}\n </ResponsiveContainer>\n {footer}\n </div>\n </ChartContext.Provider>\n )\n}\n\n/**\n * Reads the config entry behind a Recharts payload item.\n *\n * Recharts hands the same series different names in different chart types — a\n * pie sector's key is on `payload.payload[nameKey]`, a bar's is `dataKey` — so\n * this is the one place that disagreement is resolved.\n */\nexport function seriesFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string,\n): ChartSeries | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined\n\n const nested =\n 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n ? (payload.payload as Record<string, unknown>)\n : undefined\n\n let name = key\n const direct = (payload as Record<string, unknown>)[key]\n if (typeof direct === 'string') name = direct\n else if (nested && typeof nested[key] === 'string') name = nested[key] as string\n\n return name in config ? config[name] : config[key]\n}\n\n/** Formats an expanded stack's axis, where 0–1 means 0–100%. */\nexport function percentTick(value: number): string {\n return `${Math.round(value * 100)}%`\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,29 @@
1
+ import * as react from 'react';
2
+
3
+ /** How a point marker is drawn on a line or an area. */
4
+ type ChartDotVariant = 'default' | 'border' | 'colored-border';
5
+ interface ChartDotProps {
6
+ cx?: number;
7
+ cy?: number;
8
+ /** The series this dot belongs to — it picks up that series' gradient. */
9
+ dataKey: string;
10
+ /** The owning mark's id, which scopes its gradient. */
11
+ chartId: string;
12
+ className?: string;
13
+ fillOpacity?: number;
14
+ variant?: ChartDotVariant;
15
+ /** The intro wipe's mask, so a resting dot reveals with its own line. */
16
+ maskId?: string;
17
+ }
18
+ /**
19
+ * A point marker painted from its series' gradient rather than from a flat
20
+ * colour.
21
+ *
22
+ * The trick each variant shares: a full-width rect filled with the series
23
+ * gradient, clipped to a circle. Filling the circle directly would restart the
24
+ * gradient inside the dot's own bounding box, so every dot on a line would be
25
+ * the same colour instead of sampling the gradient at the x it sits on.
26
+ */
27
+ declare const ChartDot: react.MemoExoticComponent<({ cx, cy, dataKey, chartId, className, fillOpacity, variant, maskId, }: ChartDotProps) => react.JSX.Element | null>;
28
+
29
+ export { ChartDot, type ChartDotProps, type ChartDotVariant };
@@ -0,0 +1,78 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { memo, useId } from "react";
3
+ import { cn } from "../../lib/cn.js";
4
+ const ChartDot = memo(function ChartDot2({
5
+ cx,
6
+ cy,
7
+ dataKey,
8
+ chartId,
9
+ className,
10
+ fillOpacity = 1,
11
+ variant = "default",
12
+ maskId
13
+ }) {
14
+ const dotId = useId().replace(/:/g, "");
15
+ if (cx === void 0 || cy === void 0) return null;
16
+ const paint = `url(#${chartId}-colors-${dataKey})`;
17
+ const mask = maskId ? `url(#${maskId})` : void 0;
18
+ if (variant === "border") {
19
+ const r2 = 6;
20
+ const inner = r2 - 2.5;
21
+ return /* @__PURE__ */ jsxs("g", { className: cn("text-(--chart-surface)", className), mask, children: [
22
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: `dot-${dotId}`, children: /* @__PURE__ */ jsx("circle", { cx, cy, r: inner }) }) }),
23
+ /* @__PURE__ */ jsx("circle", { cx, cy, r: r2, fill: "currentColor" }),
24
+ /* @__PURE__ */ jsx(
25
+ "rect",
26
+ {
27
+ x: "0",
28
+ y: cy - inner,
29
+ width: "100%",
30
+ height: inner * 2,
31
+ fill: paint,
32
+ fillOpacity,
33
+ clipPath: `url(#dot-${dotId})`
34
+ }
35
+ )
36
+ ] });
37
+ }
38
+ if (variant === "colored-border") {
39
+ const r2 = 3;
40
+ const stroke = 1;
41
+ return /* @__PURE__ */ jsxs("g", { className: cn("text-(--chart-surface)", className), mask, children: [
42
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: `dot-${dotId}`, children: /* @__PURE__ */ jsx("circle", { cx, cy, r: r2 + stroke / 2 }) }) }),
43
+ /* @__PURE__ */ jsx(
44
+ "rect",
45
+ {
46
+ x: "0",
47
+ y: cy - r2 - stroke / 2,
48
+ width: "100%",
49
+ height: (r2 + stroke / 2) * 2,
50
+ fill: paint,
51
+ fillOpacity,
52
+ clipPath: `url(#dot-${dotId})`
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsx("circle", { cx, cy, r: r2 - stroke / 2, fill: "currentColor" })
56
+ ] });
57
+ }
58
+ const r = 3;
59
+ return /* @__PURE__ */ jsxs("g", { className, mask, children: [
60
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: `dot-${dotId}`, children: /* @__PURE__ */ jsx("circle", { cx, cy, r }) }) }),
61
+ /* @__PURE__ */ jsx(
62
+ "rect",
63
+ {
64
+ x: "0",
65
+ y: cy - r,
66
+ width: "100%",
67
+ height: r * 2,
68
+ fill: paint,
69
+ fillOpacity,
70
+ clipPath: `url(#dot-${dotId})`
71
+ }
72
+ )
73
+ ] });
74
+ });
75
+ export {
76
+ ChartDot
77
+ };
78
+ //# sourceMappingURL=dot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/charts/lib/dot.tsx"],"sourcesContent":["import { memo, useId } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** How a point marker is drawn on a line or an area. */\nexport type ChartDotVariant = 'default' | 'border' | 'colored-border'\n\nexport interface ChartDotProps {\n cx?: number\n cy?: number\n /** The series this dot belongs to — it picks up that series' gradient. */\n dataKey: string\n /** The owning mark's id, which scopes its gradient. */\n chartId: string\n className?: string\n fillOpacity?: number\n variant?: ChartDotVariant\n /** The intro wipe's mask, so a resting dot reveals with its own line. */\n maskId?: string\n}\n\n/**\n * A point marker painted from its series' gradient rather than from a flat\n * colour.\n *\n * The trick each variant shares: a full-width rect filled with the series\n * gradient, clipped to a circle. Filling the circle directly would restart the\n * gradient inside the dot's own bounding box, so every dot on a line would be\n * the same colour instead of sampling the gradient at the x it sits on.\n */\nexport const ChartDot = memo(function ChartDot({\n cx,\n cy,\n dataKey,\n chartId,\n className,\n fillOpacity = 1,\n variant = 'default',\n maskId,\n}: ChartDotProps) {\n const dotId = useId().replace(/:/g, '')\n\n if (cx === undefined || cy === undefined) return null\n\n const paint = `url(#${chartId}-colors-${dataKey})`\n const mask = maskId ? `url(#${maskId})` : undefined\n\n if (variant === 'border') {\n // A wide ground ring in the page colour, so the dot reads as sitting on top\n // of the line rather than being a bulge in it.\n const r = 6\n const inner = r - 2.5\n return (\n <g className={cn('text-(--chart-surface)', className)} mask={mask}>\n <defs>\n <clipPath id={`dot-${dotId}`}>\n <circle cx={cx} cy={cy} r={inner} />\n </clipPath>\n </defs>\n <circle cx={cx} cy={cy} r={r} fill=\"currentColor\" />\n <rect\n x=\"0\"\n y={cy - inner}\n width=\"100%\"\n height={inner * 2}\n fill={paint}\n fillOpacity={fillOpacity}\n clipPath={`url(#dot-${dotId})`}\n />\n </g>\n )\n }\n\n if (variant === 'colored-border') {\n const r = 3\n const stroke = 1\n return (\n <g className={cn('text-(--chart-surface)', className)} mask={mask}>\n <defs>\n <clipPath id={`dot-${dotId}`}>\n <circle cx={cx} cy={cy} r={r + stroke / 2} />\n </clipPath>\n </defs>\n <rect\n x=\"0\"\n y={cy - r - stroke / 2}\n width=\"100%\"\n height={(r + stroke / 2) * 2}\n fill={paint}\n fillOpacity={fillOpacity}\n clipPath={`url(#dot-${dotId})`}\n />\n <circle cx={cx} cy={cy} r={r - stroke / 2} fill=\"currentColor\" />\n </g>\n )\n }\n\n const r = 3\n return (\n <g className={className} mask={mask}>\n <defs>\n <clipPath id={`dot-${dotId}`}>\n <circle cx={cx} cy={cy} r={r} />\n </clipPath>\n </defs>\n <rect\n x=\"0\"\n y={cy - r}\n width=\"100%\"\n height={r * 2}\n fill={paint}\n fillOpacity={fillOpacity}\n clipPath={`url(#dot-${dotId})`}\n />\n </g>\n )\n})\n"],"mappings":"AAoDM,SAGM,KAHN;AApDN,SAAS,MAAM,aAAa;AAC5B,SAAS,UAAU;AA4BZ,MAAM,WAAW,KAAK,SAASA,UAAS;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,UAAU;AAAA,EACV;AACF,GAAkB;AAChB,QAAM,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAAE;AAEtC,MAAI,OAAO,UAAa,OAAO,OAAW,QAAO;AAEjD,QAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,QAAM,OAAO,SAAS,QAAQ,MAAM,MAAM;AAE1C,MAAI,YAAY,UAAU;AAGxB,UAAMC,KAAI;AACV,UAAM,QAAQA,KAAI;AAClB,WACE,qBAAC,OAAE,WAAW,GAAG,0BAA0B,SAAS,GAAG,MACrD;AAAA,0BAAC,UACC,8BAAC,cAAS,IAAI,OAAO,KAAK,IACxB,8BAAC,YAAO,IAAQ,IAAQ,GAAG,OAAO,GACpC,GACF;AAAA,MACA,oBAAC,YAAO,IAAQ,IAAQ,GAAGA,IAAG,MAAK,gBAAe;AAAA,MAClD;AAAA,QAAC;AAAA;AAAA,UACC,GAAE;AAAA,UACF,GAAG,KAAK;AAAA,UACR,OAAM;AAAA,UACN,QAAQ,QAAQ;AAAA,UAChB,MAAM;AAAA,UACN;AAAA,UACA,UAAU,YAAY,KAAK;AAAA;AAAA,MAC7B;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,YAAY,kBAAkB;AAChC,UAAMA,KAAI;AACV,UAAM,SAAS;AACf,WACE,qBAAC,OAAE,WAAW,GAAG,0BAA0B,SAAS,GAAG,MACrD;AAAA,0BAAC,UACC,8BAAC,cAAS,IAAI,OAAO,KAAK,IACxB,8BAAC,YAAO,IAAQ,IAAQ,GAAGA,KAAI,SAAS,GAAG,GAC7C,GACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,GAAE;AAAA,UACF,GAAG,KAAKA,KAAI,SAAS;AAAA,UACrB,OAAM;AAAA,UACN,SAASA,KAAI,SAAS,KAAK;AAAA,UAC3B,MAAM;AAAA,UACN;AAAA,UACA,UAAU,YAAY,KAAK;AAAA;AAAA,MAC7B;AAAA,MACA,oBAAC,YAAO,IAAQ,IAAQ,GAAGA,KAAI,SAAS,GAAG,MAAK,gBAAe;AAAA,OACjE;AAAA,EAEJ;AAEA,QAAM,IAAI;AACV,SACE,qBAAC,OAAE,WAAsB,MACvB;AAAA,wBAAC,UACC,8BAAC,cAAS,IAAI,OAAO,KAAK,IACxB,8BAAC,YAAO,IAAQ,IAAQ,GAAM,GAChC,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,GAAE;AAAA,QACF,GAAG,KAAK;AAAA,QACR,OAAM;AAAA,QACN,QAAQ,IAAI;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,QACA,UAAU,YAAY,KAAK;AAAA;AAAA,IAC7B;AAAA,KACF;AAEJ,CAAC;","names":["ChartDot","r"]}
@@ -0,0 +1,31 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface ChartEmptyProps {
5
+ /** What is missing. Says what happened, not "no data". */
6
+ title?: ReactNode;
7
+ /** Why, or what to change — a filter to widen, a range to move. */
8
+ description?: ReactNode;
9
+ /** The one thing to do next. */
10
+ action?: ReactNode;
11
+ }
12
+ /**
13
+ * What a chart shows when it has nothing to draw.
14
+ *
15
+ * The state almost every chart library forgets, and the one a real dashboard
16
+ * hits within a week: a filter that matches nothing, a new account, a range
17
+ * before the data starts. Without it a chart hands the reader an empty pair of
18
+ * axes, which is indistinguishable from a chart that failed to load — so the
19
+ * reader reloads the page, and it is still empty.
20
+ *
21
+ * Distinct from the loading skeleton, which says "not yet". This says "there
22
+ * is nothing here", which is a different sentence and usually has an action
23
+ * attached to it.
24
+ *
25
+ * Reuses the system's own `EmptyState` rather than inventing a chart-shaped
26
+ * one: an empty chart and an empty table are the same event, and a reader
27
+ * should not have to learn two vocabularies for it.
28
+ */
29
+ declare function ChartEmpty({ title, description, action, }: ChartEmptyProps): react.JSX.Element;
30
+
31
+ export { ChartEmpty, type ChartEmptyProps };
@@ -0,0 +1,13 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { EmptyState } from "../../components/EmptyState/EmptyState.js";
3
+ function ChartEmpty({
4
+ title = "No data in this range",
5
+ description = "Nothing matched the current filters. Widen the range, or clear a filter.",
6
+ action
7
+ }) {
8
+ return /* @__PURE__ */ jsx("div", { className: "flex aspect-video max-h-[26rem] min-h-[13rem] w-full items-center justify-center", children: /* @__PURE__ */ jsx(EmptyState, { title, description, action, className: "py-0" }) });
9
+ }
10
+ export {
11
+ ChartEmpty
12
+ };
13
+ //# sourceMappingURL=empty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/charts/lib/empty.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { EmptyState } from '../../components/EmptyState/EmptyState'\n\nexport interface ChartEmptyProps {\n /** What is missing. Says what happened, not \"no data\". */\n title?: ReactNode\n /** Why, or what to change — a filter to widen, a range to move. */\n description?: ReactNode\n /** The one thing to do next. */\n action?: ReactNode\n}\n\n/**\n * What a chart shows when it has nothing to draw.\n *\n * The state almost every chart library forgets, and the one a real dashboard\n * hits within a week: a filter that matches nothing, a new account, a range\n * before the data starts. Without it a chart hands the reader an empty pair of\n * axes, which is indistinguishable from a chart that failed to load — so the\n * reader reloads the page, and it is still empty.\n *\n * Distinct from the loading skeleton, which says \"not yet\". This says \"there\n * is nothing here\", which is a different sentence and usually has an action\n * attached to it.\n *\n * Reuses the system's own `EmptyState` rather than inventing a chart-shaped\n * one: an empty chart and an empty table are the same event, and a reader\n * should not have to learn two vocabularies for it.\n */\nexport function ChartEmpty({\n title = 'No data in this range',\n description = 'Nothing matched the current filters. Widen the range, or clear a filter.',\n action,\n}: ChartEmptyProps) {\n return (\n <div className=\"flex aspect-video max-h-[26rem] min-h-[13rem] w-full items-center justify-center\">\n <EmptyState title={title} description={description} action={action} className=\"py-0\" />\n </div>\n )\n}\n"],"mappings":"AAoCM;AAnCN,SAAS,kBAAkB;AA4BpB,SAAS,WAAW;AAAA,EACzB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd;AACF,GAAoB;AAClB,SACE,oBAAC,SAAI,WAAU,oFACb,8BAAC,cAAW,OAAc,aAA0B,QAAgB,WAAU,QAAO,GACvF;AAEJ;","names":[]}
@@ -0,0 +1,103 @@
1
+ import { ChartColumn } from './figure.js';
2
+ import 'react';
3
+
4
+ interface ChartPngOptions {
5
+ /**
6
+ * Printed above the plot in the exported image. Pass the figure's title: a
7
+ * PNG travels into a slide deck with none of the page's context around it,
8
+ * and an unnamed plot in a deck is a plot nobody can cite.
9
+ */
10
+ title?: string;
11
+ /** Multiplier on the on-screen size. 2 is a retina-sharp export. */
12
+ scale?: number;
13
+ /**
14
+ * Painted behind the plot. Defaults to the resolved `--chart-surface`, which
15
+ * is what makes a dark-mode export come out dark rather than transparent.
16
+ */
17
+ background?: string;
18
+ /** Space around the plot, in CSS pixels. */
19
+ padding?: number;
20
+ }
21
+ /**
22
+ * The chart's `<svg>`, rasterised to a PNG blob.
23
+ *
24
+ * ## The problem this function exists to solve
25
+ *
26
+ * Serialising the `<svg>` and handing it to an `<img>` puts it in a document
27
+ * of its own. That document has none of the page's stylesheets, so every
28
+ * `var(--color-desktop-0)`, `var(--chart-fill)` and `var(--ink)` in the markup
29
+ * resolves to nothing — and a paint that resolves to nothing is not a fallback
30
+ * colour, it is an invisible mark. Serialise a chart naively and you get an
31
+ * empty rectangle with axes on it.
32
+ *
33
+ * ## The solution
34
+ *
35
+ * Walk the live tree and the clone in step, and on every node write
36
+ * `getComputedStyle`'s answer for the painted properties into an inline
37
+ * `style`. Computed values have already had `var()` substituted by the engine,
38
+ * so `fill: var(--color-desktop-0)` arrives as `fill: rgb(16, 16, 16)` — and,
39
+ * because the walk reads the LIVE element, it reads it under whichever theme
40
+ * is currently applied. Light and dark come out right for free, with no theme
41
+ * argument anywhere in this file.
42
+ *
43
+ * `<stop>` elements are walked too, which is the half that is easy to miss: a
44
+ * series is painted `fill="url(#…-colors-desktop)"`, the url survives
45
+ * serialisation because the gradient is inside the same `<svg>`, and it is the
46
+ * gradient's STOPS that carry the custom properties.
47
+ *
48
+ * ## What this cannot capture, honestly
49
+ *
50
+ * - **Anything drawn in HTML.** The legend, the tooltip, the brush strip and
51
+ * the hidden data table are DOM, not SVG. The PNG is the plot.
52
+ * - **Web fonts.** The isolated document cannot fetch the page's `@font-face`
53
+ * sources, so text is rasterised in whatever the resolved family stack finds
54
+ * locally. Type metrics will differ from the screen.
55
+ * - **Cross-origin images.** A chart with an external `<image>` in it taints
56
+ * the canvas and `toBlob` throws; there is no way around it from script.
57
+ * - **The exact rendering.** This is the browser's SVG rasteriser re-drawing
58
+ * the markup, not a screenshot. Antialiasing, `backdrop-filter` and any
59
+ * effect a page stylesheet applied from outside the `<svg>` are not in it.
60
+ *
61
+ * @example
62
+ * const blob = await chartToPng(plotRef.current, { title: 'Visitors per month' })
63
+ * downloadBlob(blob, 'visitors-per-month.png')
64
+ */
65
+ declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: ChartPngOptions): Promise<Blob>;
66
+ /**
67
+ * The chart's rows as RFC 4180 CSV.
68
+ *
69
+ * Quotes a field only when it needs quoting — a comma, a double quote, or a
70
+ * line break in it — doubles any quote inside, and separates records with CRLF
71
+ * as the RFC requires rather than with the `\n` a Unix habit would reach for.
72
+ *
73
+ * Numbers are written unformatted on purpose. `1,234` would need quoting and
74
+ * would then land in the spreadsheet as text, which is the one thing a numeric
75
+ * export must not do; formatting belongs to the chart, not to the file.
76
+ *
77
+ * A column's `label` is a `ReactNode`, so only a string or a number can be a
78
+ * header. Anything else falls back to the column's key, which is a worse
79
+ * header than the label but a better one than an empty cell.
80
+ *
81
+ * @example
82
+ * const csv = chartToCsv(data, [{ key: 'desktop', label: 'Desktop' }])
83
+ * downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'visitors.csv')
84
+ */
85
+ declare function chartToCsv(rows: Record<string, unknown>[], columns: ChartColumn[]): string;
86
+ /**
87
+ * Hands a blob to the browser as a download.
88
+ *
89
+ * Throws rather than no-oping without a `document`: a download is something a
90
+ * person just asked for, and a silent return would look to them like a click
91
+ * that did nothing.
92
+ */
93
+ declare function downloadBlob(blob: Blob, filename: string): void;
94
+ /**
95
+ * A figure's title as a filename.
96
+ *
97
+ * Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into
98
+ * shells, zip archives and Windows paths where a title's spaces and slashes
99
+ * are someone else's problem.
100
+ */
101
+ declare function exportFilename(title: string, extension: string): string;
102
+
103
+ export { type ChartPngOptions, chartToCsv, chartToPng, downloadBlob, exportFilename };