basalt-ui 1.13.0 → 1.15.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 (180) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +38 -2
  4. package/agent/rules/basalt-charts.md +260 -108
  5. package/agent/rules/basalt-mantine.md +4 -2
  6. package/agent/skills/basalt-charts/SKILL.md +114 -64
  7. package/agent/skills/basalt-design/SKILL.md +4 -3
  8. package/configs/oxlint-plugin.js +152 -0
  9. package/configs/oxlint-plugin.test.ts +107 -0
  10. package/configs/oxlint.json +2 -0
  11. package/dist/charts/cursor/resolve.d.ts +33 -0
  12. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  13. package/dist/charts/cursor/resolve.js +61 -0
  14. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  15. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  16. package/dist/charts/cursor/scope.d.ts +17 -0
  17. package/dist/charts/cursor/scope.d.ts.map +1 -0
  18. package/dist/charts/cursor/scope.js +20 -0
  19. package/dist/charts/cursor/store.d.ts +31 -0
  20. package/dist/charts/cursor/store.d.ts.map +1 -0
  21. package/dist/charts/cursor/store.js +25 -0
  22. package/dist/charts/cursor/store.test.d.ts +2 -0
  23. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  24. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  26. package/dist/charts/hooks/useChartCursor.js +104 -0
  27. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  28. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  29. package/dist/charts/index.d.ts +16 -13
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/index.js +38 -19
  32. package/dist/charts/kinds/Bars.d.ts +25 -47
  33. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  34. package/dist/charts/kinds/Bars.js +230 -390
  35. package/dist/charts/kinds/Donut.d.ts +5 -4
  36. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  37. package/dist/charts/kinds/Donut.js +17 -14
  38. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  39. package/dist/charts/kinds/DualPanel.js +136 -90
  40. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  41. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  42. package/dist/charts/kinds/Heatmap.js +38 -22
  43. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  44. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  45. package/dist/charts/kinds/MultiLine.js +114 -237
  46. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  47. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  48. package/dist/charts/kinds/StackedArea.js +74 -132
  49. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  50. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/ZonedLine.js +103 -184
  52. package/dist/charts/layout/auto-margin.d.ts +60 -0
  53. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  54. package/dist/charts/layout/auto-margin.js +37 -0
  55. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  56. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  57. package/dist/charts/primitives/CartesianChart.d.ts +143 -0
  58. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  59. package/dist/charts/primitives/CartesianChart.js +349 -0
  60. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  61. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  62. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  63. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  64. package/dist/charts/primitives/ChartFrame.js +17 -2
  65. package/dist/charts/primitives/ChartLegend.d.ts +12 -1
  66. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartLegend.js +21 -3
  68. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  69. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  70. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  71. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  72. package/dist/charts/primitives/ChartTooltip.js +45 -19
  73. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  74. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  75. package/dist/charts/primitives/HoverOverlay.js +22 -1
  76. package/dist/charts/primitives/XZoneRects.d.ts +40 -0
  77. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  78. package/dist/charts/primitives/XZoneRects.js +28 -0
  79. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  80. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  81. package/dist/charts/series.d.ts +16 -1
  82. package/dist/charts/series.d.ts.map +1 -1
  83. package/dist/charts/series.js +3 -1
  84. package/dist/charts/series.test.d.ts +2 -0
  85. package/dist/charts/series.test.d.ts.map +1 -0
  86. package/dist/charts/utils/measure-text.d.ts +25 -0
  87. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  88. package/dist/charts/utils/measure-text.js +64 -0
  89. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  90. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  91. package/dist/content/fence-block.d.ts.map +1 -1
  92. package/dist/shell/app-sidebar.d.ts +19 -1
  93. package/dist/shell/app-sidebar.d.ts.map +1 -1
  94. package/dist/shell/app-sidebar.js +30 -26
  95. package/dist/shell/app-sidebar.module.css +11 -0
  96. package/dist/shell/app-sidebar.module.css.d.ts +1 -0
  97. package/dist/shell/app-sidebar.test.d.ts +2 -0
  98. package/dist/shell/app-sidebar.test.d.ts.map +1 -0
  99. package/dist/shell/index.d.ts +10 -1
  100. package/dist/shell/index.d.ts.map +1 -1
  101. package/dist/shell/index.js +2 -0
  102. package/dist/shell/index.test.d.ts +2 -0
  103. package/dist/shell/index.test.d.ts.map +1 -0
  104. package/dist/surfaces.d.ts +1 -1
  105. package/dist/surfaces.d.ts.map +1 -1
  106. package/dist/surfaces.js +1 -1
  107. package/dist/tokens/chart-margin.test.d.ts +2 -0
  108. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  109. package/dist/tokens/index.d.ts +29 -0
  110. package/dist/tokens/index.d.ts.map +1 -1
  111. package/dist/tokens/index.js +10 -0
  112. package/dist/tokens/palette.d.ts +4 -0
  113. package/dist/tokens/palette.d.ts.map +1 -1
  114. package/dist/tokens/palette.js +3 -0
  115. package/llms.txt +3 -3
  116. package/package.json +1 -1
  117. package/src/charts/cursor/resolve.test.ts +61 -0
  118. package/src/charts/cursor/resolve.ts +102 -0
  119. package/src/charts/cursor/scope.tsx +32 -0
  120. package/src/charts/cursor/store.test.ts +46 -0
  121. package/src/charts/cursor/store.ts +53 -0
  122. package/src/charts/hooks/useChartCursor.ts +163 -0
  123. package/src/charts/hooks/useChartSize.ts +8 -4
  124. package/src/charts/index.ts +34 -14
  125. package/src/charts/kinds/Bars.tsx +327 -460
  126. package/src/charts/kinds/Donut.tsx +31 -23
  127. package/src/charts/kinds/DualPanel.tsx +228 -134
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.tsx +158 -294
  130. package/src/charts/kinds/StackedArea.tsx +116 -175
  131. package/src/charts/kinds/ZonedLine.tsx +148 -255
  132. package/src/charts/layout/auto-margin.test.ts +42 -0
  133. package/src/charts/layout/auto-margin.ts +110 -0
  134. package/src/charts/primitives/CartesianChart.test.ts +99 -0
  135. package/src/charts/primitives/CartesianChart.tsx +566 -0
  136. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  137. package/src/charts/primitives/ChartFrame.tsx +40 -4
  138. package/src/charts/primitives/ChartLegend.test.tsx +48 -0
  139. package/src/charts/primitives/ChartLegend.tsx +45 -2
  140. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  141. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  142. package/src/charts/primitives/XZoneRects.test.tsx +63 -0
  143. package/src/charts/primitives/XZoneRects.tsx +67 -0
  144. package/src/charts/series.test.ts +61 -0
  145. package/src/charts/series.ts +18 -1
  146. package/src/charts/utils/measure-text.test.ts +36 -0
  147. package/src/charts/utils/measure-text.ts +109 -0
  148. package/src/content/fence-block.tsx +2 -3
  149. package/src/shell/app-sidebar.module.css +11 -0
  150. package/src/shell/app-sidebar.module.css.d.ts +1 -0
  151. package/src/shell/app-sidebar.test.tsx +98 -0
  152. package/src/shell/app-sidebar.tsx +22 -0
  153. package/src/shell/index.test.tsx +35 -0
  154. package/src/shell/index.tsx +11 -0
  155. package/src/surfaces.ts +2 -1
  156. package/src/theme/spacing.test.ts +2 -0
  157. package/src/tokens/chart-margin.test.ts +39 -0
  158. package/src/tokens/density.test.ts +1 -0
  159. package/src/tokens/index.ts +36 -0
  160. package/src/tokens/palette.ts +3 -0
  161. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  162. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  163. package/dist/charts/hooks/useChartTooltip.js +0 -26
  164. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  165. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  166. package/dist/charts/hooks/useHoverSync.js +0 -75
  167. package/dist/charts/hover-context.d.ts +0 -21
  168. package/dist/charts/hover-context.d.ts.map +0 -1
  169. package/dist/charts/hover-context.js +0 -12
  170. package/dist/charts/hover-sync.d.ts +0 -14
  171. package/dist/charts/hover-sync.d.ts.map +0 -1
  172. package/dist/charts/hover-sync.js +0 -17
  173. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  174. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  175. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  176. package/src/charts/hooks/useChartTooltip.ts +0 -31
  177. package/src/charts/hooks/useHoverSync.ts +0 -152
  178. package/src/charts/hover-context.ts +0 -28
  179. package/src/charts/hover-sync.tsx +0 -37
  180. package/src/charts/primitives/ResponsiveChart.tsx +0 -64
@@ -1,24 +1,29 @@
1
1
  import { Group } from '@visx/group'
2
- import type { ReactNode } from 'react'
3
- import { memo, useMemo } from 'react'
2
+ import type { PointerEvent, ReactNode } from 'react'
3
+ import { memo, useMemo, useState } from 'react'
4
4
  import {
5
- ChartTooltip,
5
+ ChartTooltipFloat,
6
6
  TooltipBody,
7
7
  TooltipHeader,
8
8
  TooltipRow,
9
- useTooltipStyles,
10
9
  } from '../primitives/ChartTooltip'
11
- import { ChartPending } from '../primitives/ChartPending'
12
- import { useChartTooltip } from '../hooks/useChartTooltip'
10
+ import { ChartFrame } from '../primitives/ChartFrame'
13
11
  import { VX, alpha } from '../../tokens'
14
12
 
15
13
  /** A single resolved heatmap cell — the unit the tooltip and hover operate on. */
16
14
  type HeatmapCell = { row: string; col: string; value: number }
17
15
 
16
+ /** A hovered cell plus the viewport anchor `ChartTooltipFloat` positions against. */
17
+ type HeatmapTip = HeatmapCell & { anchor: { x: number; y: number } }
18
+
18
19
  export type HeatmapProps<T> = {
19
20
  data: T[]
20
- width: number
21
- height: number
21
+ /** Fixed height in pixels. Used when neither `aspectRatio` nor `fill` is set. Default 240. */
22
+ height?: number
23
+ /** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set. */
24
+ aspectRatio?: number
25
+ /** Fill the parent flex/grid cell's measured height instead of a fixed/derived one. */
26
+ fill?: boolean
22
27
  chartId: string
23
28
  /** Extracts the y-axis category (row) from a data point. */
24
29
  getRow: (d: T) => string
@@ -48,13 +53,9 @@ export type HeatmapProps<T> = {
48
53
  renderTooltip?: (cell: HeatmapCell) => ReactNode
49
54
  /** Optional gradient legend strip below the grid (faint → solid color). */
50
55
  legend?: { min: string; max: string }
51
- /**
52
- * The query behind this chart hasn't resolved yet. Renders `ChartPending` (see its JSDoc for the
53
- * three-state rationale) over the full `width` × `height` box in place of the grid, suppressing
54
- * the legend strip and every cell/label/tooltip along with it. Unlike the other six kinds,
55
- * `Heatmap` doesn't compose `ChartFrame` (it takes `width`/`height` directly rather than
56
- * measuring), so this is handled locally instead of forwarded.
57
- */
56
+ /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="img"`). */
57
+ ariaLabel?: string
58
+ /** Forwarded to `ChartFrame` — see `ChartPending`'s JSDoc for the three-state rationale. */
58
59
  isPending?: boolean
59
60
  }
60
61
 
@@ -92,14 +93,42 @@ const LEGEND_LABEL_H = 16
92
93
  * Category × category intensity grid. Each cell is a rounded rect filled with the base
93
94
  * color at an opacity derived from its value; empty cells stay a faint neutral track.
94
95
  * Generalizes argo's day-of-week × hour-of-day heatmap. Categorical axes are rendered as
95
- * plain themed `<text>` (cells are not date-categorical, so no useHoverSync / shared
96
- * cursor) — per-cell hover drives the local tooltip directly.
96
+ * plain themed `<text>` (cells are not date-categorical, so there's no shared cursor here) —
97
+ * per-cell hover drives the local tooltip directly.
98
+ *
99
+ * Composes `ChartFrame` purely for measuring (`height`/`aspectRatio`/`fill`, the same three
100
+ * sizing modes every other kind exposes) — `legend={false}` and an empty `series` opt out of
101
+ * `ChartFrame`'s own derived legend, since Heatmap already ships its own gradient strip.
97
102
  */
98
103
  function HeatmapInner<T>(props: HeatmapProps<T>) {
104
+ const { chartId, height, aspectRatio, fill, ariaLabel, isPending } = props
105
+
106
+ return (
107
+ <ChartFrame
108
+ series={[]}
109
+ chartId={chartId}
110
+ legend={false}
111
+ {...(height !== undefined && { height })}
112
+ {...(aspectRatio !== undefined && { aspectRatio })}
113
+ {...(fill !== undefined && { fill })}
114
+ {...(ariaLabel !== undefined && { ariaLabel })}
115
+ {...(isPending !== undefined && { isPending })}
116
+ >
117
+ {(plot) => <HeatmapPlot {...props} plot={plot} />}
118
+ </ChartFrame>
119
+ )
120
+ }
121
+
122
+ type HeatmapPlotProps<T> = HeatmapProps<T> & {
123
+ plot: { width: number; height: number; hidden: ReadonlySet<string> }
124
+ }
125
+
126
+ /** The measured plot — split from {@link HeatmapInner} so the grid only draws once `ChartFrame`
127
+ * has resolved a non-empty plot rect. */
128
+ function HeatmapPlot<T>(props: HeatmapPlotProps<T>) {
99
129
  const {
100
130
  data,
101
- width,
102
- height,
131
+ plot,
103
132
  chartId,
104
133
  getRow,
105
134
  getCol,
@@ -115,11 +144,10 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
115
144
  colLabel = (c) => c,
116
145
  renderTooltip,
117
146
  legend,
118
- isPending,
119
147
  } = props
148
+ const { width, height } = plot
120
149
 
121
- const tooltipStyles = useTooltipStyles()
122
- const { tip, show, hide, tooltipRef } = useChartTooltip<HeatmapCell>()
150
+ const [tip, setTip] = useState<HeatmapTip | null>(null)
123
151
 
124
152
  const rows = useMemo(() => rowsProp ?? firstSeen(data, getRow), [rowsProp, data, getRow])
125
153
  const cols = useMemo(() => colsProp ?? firstSeen(data, getCol), [colsProp, data, getCol])
@@ -136,12 +164,6 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
136
164
  return { lookup: map, max: m }
137
165
  }, [data, getRow, getCol, getValue])
138
166
 
139
- // Every hook above must still run on a pending render (Rules of Hooks) — the early return comes
140
- // after them. No `ChartFrame` to forward to here (Heatmap measures via required `width`/`height`
141
- // props, not `useChartSize`), so the placeholder is rendered directly in its place, same footprint,
142
- // legend strip and all other content omitted along with it.
143
- if (isPending) return <ChartPending width={width} height={height} />
144
-
145
167
  const legendH = legend ? LEGEND_H + LEGEND_LABEL_H : 0
146
168
  const gridW = Math.max(0, width - PAD_LEFT)
147
169
  const gridH = Math.max(0, height - PAD_TOP - PAD_BOTTOM - legendH)
@@ -149,6 +171,11 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
149
171
  const cellH = rows.length > 0 ? gridH / rows.length : 0
150
172
  const legendGradientId = `${chartId}-heat-legend`
151
173
 
174
+ const show = (row: string, col: string, value: number, event: PointerEvent<SVGRectElement>) => {
175
+ setTip({ row, col, value, anchor: { x: event.clientX, y: event.clientY } })
176
+ }
177
+ const hide = () => setTip(null)
178
+
152
179
  return (
153
180
  <div style={{ position: 'relative' }}>
154
181
  <svg width={width} height={height}>
@@ -157,7 +184,7 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
157
184
  cols.map((col, ci) => {
158
185
  const value = lookup.get(cellKey(row, col))
159
186
  const has = value !== undefined
160
- const fill = has ? alpha(color, intensity(value, max)) : alpha(VX.neutral, 0.04)
187
+ const cellFill = has ? alpha(color, intensity(value, max)) : alpha(VX.neutral, 0.04)
161
188
  return (
162
189
  <rect
163
190
  key={cellKey(row, col)}
@@ -166,9 +193,9 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
166
193
  width={Math.max(0, cellW - cellGap)}
167
194
  height={Math.max(0, cellH - cellGap)}
168
195
  rx={cellRadius}
169
- fill={fill}
196
+ fill={cellFill}
170
197
  style={{ cursor: has ? 'pointer' : 'default' }}
171
- onPointerMove={(e) => has && show({ row, col, value }, e)}
198
+ onPointerMove={(e) => has && show(row, col, value, e)}
172
199
  onPointerLeave={hide}
173
200
  onPointerCancel={hide}
174
201
  />
@@ -245,22 +272,20 @@ function HeatmapInner<T>(props: HeatmapProps<T>) {
245
272
  )}
246
273
  </svg>
247
274
 
248
- <ChartTooltip tip={tip} tooltipRef={tooltipRef} styles={tooltipStyles}>
249
- {tip !== null && (
250
- <>
251
- <TooltipHeader date={rowLabel(tip.data.row)} label={colLabel(tip.data.col)} />
252
- <TooltipBody>
253
- <TooltipRow
254
- color={alpha(color, 0.9)}
255
- shape="bar"
256
- label="Value"
257
- value={formatValue(tip.data.value)}
258
- />
259
- {renderTooltip?.(tip.data)}
260
- </TooltipBody>
261
- </>
262
- )}
263
- </ChartTooltip>
275
+ {tip !== null && (
276
+ <ChartTooltipFloat anchor={tip.anchor}>
277
+ <TooltipHeader date={rowLabel(tip.row)} label={colLabel(tip.col)} />
278
+ <TooltipBody>
279
+ <TooltipRow
280
+ color={alpha(color, 0.9)}
281
+ shape="bar"
282
+ label="Value"
283
+ value={formatValue(tip.value)}
284
+ />
285
+ {renderTooltip?.(tip)}
286
+ </TooltipBody>
287
+ </ChartTooltipFloat>
288
+ )}
264
289
  </div>
265
290
  )
266
291
  }