basalt-ui 1.14.0 → 1.16.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 (176) 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 +279 -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/agent/templates/CLAUDE-block.md.tpl +9 -6
  9. package/configs/oxlint-plugin.js +152 -0
  10. package/configs/oxlint-plugin.test.ts +107 -0
  11. package/configs/oxlint.json +2 -0
  12. package/dist/charts/cursor/resolve.d.ts +33 -0
  13. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  14. package/dist/charts/cursor/resolve.js +61 -0
  15. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  16. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  17. package/dist/charts/cursor/scope.d.ts +17 -0
  18. package/dist/charts/cursor/scope.d.ts.map +1 -0
  19. package/dist/charts/cursor/scope.js +20 -0
  20. package/dist/charts/cursor/store.d.ts +31 -0
  21. package/dist/charts/cursor/store.d.ts.map +1 -0
  22. package/dist/charts/cursor/store.js +25 -0
  23. package/dist/charts/cursor/store.test.d.ts +2 -0
  24. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  26. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  27. package/dist/charts/hooks/useChartCursor.js +104 -0
  28. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  29. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  30. package/dist/charts/index.d.ts +16 -13
  31. package/dist/charts/index.d.ts.map +1 -1
  32. package/dist/charts/index.js +38 -19
  33. package/dist/charts/kinds/Bars.d.ts +45 -54
  34. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  35. package/dist/charts/kinds/Bars.js +235 -392
  36. package/dist/charts/kinds/Bars.test.d.ts +2 -0
  37. package/dist/charts/kinds/Bars.test.d.ts.map +1 -0
  38. package/dist/charts/kinds/Donut.d.ts +5 -4
  39. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  40. package/dist/charts/kinds/Donut.js +17 -14
  41. package/dist/charts/kinds/DualPanel.d.ts +13 -0
  42. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  43. package/dist/charts/kinds/DualPanel.js +185 -92
  44. package/dist/charts/kinds/DualPanel.test.d.ts +2 -0
  45. package/dist/charts/kinds/DualPanel.test.d.ts.map +1 -0
  46. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  47. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  48. package/dist/charts/kinds/Heatmap.js +38 -22
  49. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  50. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/MultiLine.js +122 -235
  52. package/dist/charts/kinds/MultiLine.test.d.ts +2 -0
  53. package/dist/charts/kinds/MultiLine.test.d.ts.map +1 -0
  54. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  55. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  56. package/dist/charts/kinds/StackedArea.js +74 -132
  57. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  58. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  59. package/dist/charts/kinds/ZonedLine.js +104 -184
  60. package/dist/charts/kinds/ZonedLine.test.d.ts +2 -0
  61. package/dist/charts/kinds/ZonedLine.test.d.ts.map +1 -0
  62. package/dist/charts/layout/auto-margin.d.ts +60 -0
  63. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  64. package/dist/charts/layout/auto-margin.js +37 -0
  65. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  66. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  67. package/dist/charts/primitives/CartesianChart.d.ts +161 -0
  68. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  69. package/dist/charts/primitives/CartesianChart.js +359 -0
  70. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  71. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  72. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  73. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  74. package/dist/charts/primitives/ChartFrame.js +17 -2
  75. package/dist/charts/primitives/ChartLegend.d.ts +15 -1
  76. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  77. package/dist/charts/primitives/ChartLegend.js +25 -3
  78. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  79. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  80. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  81. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  82. package/dist/charts/primitives/ChartTooltip.js +45 -19
  83. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  84. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  85. package/dist/charts/primitives/HoverOverlay.js +22 -1
  86. package/dist/charts/primitives/XZoneRects.d.ts +54 -0
  87. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  88. package/dist/charts/primitives/XZoneRects.js +65 -0
  89. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  90. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  91. package/dist/charts/series.d.ts +28 -3
  92. package/dist/charts/series.d.ts.map +1 -1
  93. package/dist/charts/series.js +5 -2
  94. package/dist/charts/series.test.d.ts +2 -0
  95. package/dist/charts/series.test.d.ts.map +1 -0
  96. package/dist/charts/utils/measure-text.d.ts +25 -0
  97. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  98. package/dist/charts/utils/measure-text.js +64 -0
  99. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  100. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  101. package/dist/content/fence-block.d.ts.map +1 -1
  102. package/dist/surfaces.d.ts +1 -1
  103. package/dist/surfaces.d.ts.map +1 -1
  104. package/dist/surfaces.js +1 -1
  105. package/dist/tokens/chart-margin.test.d.ts +2 -0
  106. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  107. package/dist/tokens/index.d.ts +29 -0
  108. package/dist/tokens/index.d.ts.map +1 -1
  109. package/dist/tokens/index.js +10 -0
  110. package/dist/tokens/palette.d.ts +4 -0
  111. package/dist/tokens/palette.d.ts.map +1 -1
  112. package/dist/tokens/palette.js +3 -0
  113. package/llms.txt +3 -3
  114. package/package.json +1 -1
  115. package/src/charts/cursor/resolve.test.ts +61 -0
  116. package/src/charts/cursor/resolve.ts +102 -0
  117. package/src/charts/cursor/scope.tsx +32 -0
  118. package/src/charts/cursor/store.test.ts +46 -0
  119. package/src/charts/cursor/store.ts +53 -0
  120. package/src/charts/hooks/useChartCursor.ts +163 -0
  121. package/src/charts/hooks/useChartSize.ts +8 -4
  122. package/src/charts/index.ts +35 -14
  123. package/src/charts/kinds/Bars.test.tsx +98 -0
  124. package/src/charts/kinds/Bars.tsx +350 -467
  125. package/src/charts/kinds/Donut.tsx +31 -23
  126. package/src/charts/kinds/DualPanel.test.tsx +131 -0
  127. package/src/charts/kinds/DualPanel.tsx +301 -135
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.test.tsx +37 -0
  130. package/src/charts/kinds/MultiLine.tsx +171 -290
  131. package/src/charts/kinds/StackedArea.tsx +116 -175
  132. package/src/charts/kinds/ZonedLine.test.tsx +37 -0
  133. package/src/charts/kinds/ZonedLine.tsx +149 -255
  134. package/src/charts/layout/auto-margin.test.ts +42 -0
  135. package/src/charts/layout/auto-margin.ts +110 -0
  136. package/src/charts/primitives/CartesianChart.test.tsx +160 -0
  137. package/src/charts/primitives/CartesianChart.tsx +598 -0
  138. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  139. package/src/charts/primitives/ChartFrame.tsx +40 -4
  140. package/src/charts/primitives/ChartLegend.test.tsx +78 -0
  141. package/src/charts/primitives/ChartLegend.tsx +52 -2
  142. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  143. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  144. package/src/charts/primitives/XZoneRects.test.tsx +123 -0
  145. package/src/charts/primitives/XZoneRects.tsx +132 -0
  146. package/src/charts/series.test.ts +112 -0
  147. package/src/charts/series.ts +33 -5
  148. package/src/charts/utils/measure-text.test.ts +36 -0
  149. package/src/charts/utils/measure-text.ts +109 -0
  150. package/src/content/fence-block.tsx +2 -3
  151. package/src/surfaces.ts +2 -1
  152. package/src/theme/spacing.test.ts +2 -0
  153. package/src/tokens/chart-margin.test.ts +39 -0
  154. package/src/tokens/density.test.ts +1 -0
  155. package/src/tokens/index.ts +36 -0
  156. package/src/tokens/palette.ts +3 -0
  157. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  158. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  159. package/dist/charts/hooks/useChartTooltip.js +0 -26
  160. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  161. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  162. package/dist/charts/hooks/useHoverSync.js +0 -75
  163. package/dist/charts/hover-context.d.ts +0 -21
  164. package/dist/charts/hover-context.d.ts.map +0 -1
  165. package/dist/charts/hover-context.js +0 -12
  166. package/dist/charts/hover-sync.d.ts +0 -14
  167. package/dist/charts/hover-sync.d.ts.map +0 -1
  168. package/dist/charts/hover-sync.js +0 -17
  169. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  170. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  171. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  172. package/src/charts/hooks/useChartTooltip.ts +0 -31
  173. package/src/charts/hooks/useHoverSync.ts +0 -152
  174. package/src/charts/hover-context.ts +0 -28
  175. package/src/charts/hover-sync.tsx +0 -37
  176. package/src/charts/primitives/ResponsiveChart.tsx +0 -64
@@ -1,10 +1,9 @@
1
1
  import { Group } from '@visx/group'
2
2
  import { Pie } from '@visx/shape'
3
- import { memo, useMemo } from 'react'
4
- import type { ReactNode } from 'react'
5
- import { ChartTooltip, TooltipBody, TooltipRow, useTooltipStyles } from '../primitives/ChartTooltip'
3
+ import { memo, useMemo, useState } from 'react'
4
+ import type { PointerEvent, ReactNode } from 'react'
5
+ import { ChartTooltipFloat, TooltipBody, TooltipRow } from '../primitives/ChartTooltip'
6
6
  import { ChartFrame } from '../primitives/ChartFrame'
7
- import { useChartTooltip } from '../hooks/useChartTooltip'
8
7
  import { VX } from '../../tokens'
9
8
  import type { SeriesStyle } from '../series'
10
9
  import type { SeriesKey } from '../../register'
@@ -60,10 +59,11 @@ export type DonutProps<K extends string = SeriesKey> = {
60
59
  /**
61
60
  * Radial slice-share chart with a punched-out center label. Composes `ChartFrame` for a
62
61
  * categorical legend derived from the slices (one `SeriesStyle` per slice, `mark: 'bar'`) so the
63
- * legend can never drift from what's plotted. No crosshair — meaningless for a radial layout.
64
- * Hover stays local to the pie (dimming siblings on hover) rather than joining the shared
65
- * `HoverContext`: a date-keyed cursor has no counterpart on a donut, and cross-kind category sync
66
- * (donut ↔ bar, via the generalized key) is a distinct, deliberately deferred feature.
62
+ * legend can never drift from what's plotted — legend-hidden slices drop out of the ring, the
63
+ * center total, and the tooltip's "Share" row together (`docs/CHARTS-SPEC.md` §5). No crosshair —
64
+ * meaningless for a radial layout. Hover stays local to the pie (dimming siblings on hover) rather
65
+ * than joining the shared cursor: a date-keyed cursor has no counterpart on a donut, and cross-kind
66
+ * category sync (donut ↔ bar, via a generalized key) is a distinct, deliberately deferred feature.
67
67
  */
68
68
  function DonutInner<K extends string = SeriesKey>(props: DonutProps<K>) {
69
69
  const { data, height, colorForKey, seriesLabel = (k) => k, ariaLabel, isPending } = props
@@ -88,9 +88,12 @@ function DonutInner<K extends string = SeriesKey>(props: DonutProps<K>) {
88
88
  }
89
89
 
90
90
  type DonutPlotProps<K extends string = SeriesKey> = DonutProps<K> & {
91
- plot: { width: number; height: number }
91
+ plot: { width: number; height: number; hidden: ReadonlySet<string> }
92
92
  }
93
93
 
94
+ /** A hovered slice plus the viewport anchor `ChartTooltipFloat` positions against. */
95
+ type DonutTip<K extends string> = { key: K; value: number; anchor: { x: number; y: number } }
96
+
94
97
  /** The measured plot — split from {@link DonutInner} so it only draws once `ChartFrame` has
95
98
  * resolved a non-empty plot rect (radius/center depend on the measured size). */
96
99
  function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
@@ -106,26 +109,31 @@ function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
106
109
  innerRatio = 0.6,
107
110
  padAngle = 0.01,
108
111
  } = props
109
- const { width, height } = plot
112
+ const { width, height, hidden } = plot
110
113
 
111
- const tooltipStyles = useTooltipStyles()
114
+ const [tip, setTip] = useState<DonutTip<K> | null>(null)
115
+ const hoveredKey = tip?.key ?? null
112
116
 
113
- const { tip, show, hide, tooltipRef } = useChartTooltip<DonutDatum<K>>()
114
- const hoveredKey = tip?.data.key ?? null
117
+ const visibleData = useMemo(() => data.filter((d) => !hidden.has(d.key)), [data, hidden])
115
118
 
116
119
  const radius = Math.min(width, height) / 2 - 4
117
120
  const innerRadius = radius * innerRatio
118
121
  const centerX = width / 2
119
122
  const centerY = height / 2
120
123
 
121
- const total = useMemo(() => data.reduce((sum, d) => sum + d.value, 0), [data])
124
+ const total = useMemo(() => visibleData.reduce((sum, d) => sum + d.value, 0), [visibleData])
125
+
126
+ const show = (d: DonutDatum<K>, event: PointerEvent<SVGGElement>) => {
127
+ setTip({ key: d.key, value: d.value, anchor: { x: event.clientX, y: event.clientY } })
128
+ }
129
+ const hide = () => setTip(null)
122
130
 
123
131
  return (
124
132
  <div style={{ position: 'relative' }}>
125
133
  <svg width={width} height={height}>
126
134
  <Group left={centerX} top={centerY}>
127
135
  <Pie<DonutDatum<K>>
128
- data={data}
136
+ data={visibleData}
129
137
  pieValue={(d) => d.value}
130
138
  pieSortValues={() => 0}
131
139
  outerRadius={radius}
@@ -210,24 +218,24 @@ function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
210
218
  </div>
211
219
  )}
212
220
 
213
- <ChartTooltip tip={tip} tooltipRef={tooltipRef} styles={tooltipStyles}>
214
- {tip && (
221
+ {tip !== null && (
222
+ <ChartTooltipFloat anchor={tip.anchor}>
215
223
  <TooltipBody>
216
224
  <TooltipRow
217
- color={colorForKey(tip.data.key)}
218
- label={seriesLabel(tip.data.key)}
219
- value={formatValue(tip.data.value)}
225
+ color={colorForKey(tip.key)}
226
+ label={seriesLabel(tip.key)}
227
+ value={formatValue(tip.value)}
220
228
  shape="bar"
221
229
  />
222
230
  <TooltipRow
223
231
  color={VX.grid}
224
232
  label="Share"
225
- value={`${total > 0 ? Math.round((tip.data.value / total) * 100) : 0}%`}
233
+ value={`${total > 0 ? Math.round((tip.value / total) * 100) : 0}%`}
226
234
  shape="bar"
227
235
  />
228
236
  </TooltipBody>
229
- )}
230
- </ChartTooltip>
237
+ </ChartTooltipFloat>
238
+ )}
231
239
  </div>
232
240
  )
233
241
  }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `DualPanel` — the bottom-pane domain seam (`bottomYDomain`/`bottomMaxAbsFloor`) and `formatBar`
3
+ * (tooltip-row-only override, `formatBottom` keeps owning the ticks). Same SSR harness as
4
+ * `ChartFrame.test.tsx` for the axis-only assertions (no hover needed); the `formatBar` case needs
5
+ * a live cursor, so it uses the real DOM harness + the overlay's keyboard path (parity with
6
+ * `Bars.test.tsx`) — DualPanel has two `HoverOverlay`s sharing one cursor, but only the TOP one is
7
+ * keyboard-focusable, so `getByRole('slider')` is unambiguous.
8
+ */
9
+ import { fireEvent, render, screen } from '@testing-library/react'
10
+ import { describe, expect, test } from 'bun:test'
11
+ import { renderToStaticMarkup } from 'react-dom/server'
12
+ import { DualPanel } from './DualPanel'
13
+ import type { ChartSeries } from '../series'
14
+
15
+ type Row = { date: string; v: number; bar: number }
16
+
17
+ const rows: Row[] = [{ date: '2026-08-01', v: 10, bar: 0.02 }]
18
+
19
+ const series: ChartSeries<Row>[] = [
20
+ { key: 'v', label: 'V', color: '#111', mark: 'line', getValue: (d) => d.v },
21
+ ]
22
+
23
+ function renderStatic(bottomMaxAbsFloor?: number, bottomYDomain?: [number, number]): string {
24
+ return renderToStaticMarkup(
25
+ <DualPanel<Row>
26
+ data={rows}
27
+ chartId="dp-floor"
28
+ getX={(d) => d.date}
29
+ series={series}
30
+ getBar={(d) => d.bar}
31
+ barLabel="Bar"
32
+ barColorPositive="#0a0"
33
+ barColorNegative="#a00"
34
+ formatTop={(v) => String(v)}
35
+ formatBottom={(v) => v.toFixed(2)}
36
+ {...(bottomMaxAbsFloor !== undefined && { bottomMaxAbsFloor })}
37
+ {...(bottomYDomain !== undefined && { bottomYDomain })}
38
+ />,
39
+ )
40
+ }
41
+
42
+ describe('DualPanel — bottomMaxAbsFloor', () => {
43
+ test('floors the symmetric max-abs so a near-zero plateau does not amplify to full height', () => {
44
+ // The raw data's max abs is 0.02 — nowhere near the 0.5 floor, so "0.50" can only appear on
45
+ // the bottom axis once the floor widens the domain.
46
+ expect(renderStatic()).not.toContain('>0.50<')
47
+ expect(renderStatic(0.5)).toContain('>0.50<')
48
+ })
49
+ })
50
+
51
+ describe('DualPanel — per-point getMarker rendering', () => {
52
+ test('renders exactly one circle per non-null marker, honouring color/r overrides', () => {
53
+ const markerRows: Row[] = [
54
+ { date: '2026-08-01', v: 10, bar: 0.02 },
55
+ { date: '2026-08-02', v: 12, bar: 0.03 },
56
+ { date: '2026-08-03', v: 9, bar: 0.01 },
57
+ ]
58
+ const markerSeries: ChartSeries<Row>[] = [
59
+ {
60
+ key: 'v',
61
+ label: 'V',
62
+ color: '#111',
63
+ mark: 'line',
64
+ getValue: (d) => d.v,
65
+ getMarker: (d) =>
66
+ d.date === '2026-08-01'
67
+ ? { color: '#f00', r: 9 }
68
+ : d.date === '2026-08-03'
69
+ ? { r: 3 }
70
+ : null,
71
+ },
72
+ ]
73
+
74
+ const { container } = render(
75
+ <DualPanel<Row>
76
+ data={markerRows}
77
+ chartId="dp-markers"
78
+ getX={(d) => d.date}
79
+ series={markerSeries}
80
+ getBar={(d) => d.bar}
81
+ barLabel="Bar"
82
+ barColorPositive="#0a0"
83
+ barColorNegative="#a00"
84
+ formatTop={(v) => String(v)}
85
+ formatBottom={(v) => v.toFixed(2)}
86
+ />,
87
+ )
88
+
89
+ const circles = container.querySelectorAll('circle')
90
+ expect(circles).toHaveLength(2)
91
+ expect(circles[0]?.getAttribute('fill')).toBe('#f00')
92
+ expect(circles[0]?.getAttribute('r')).toBe('9')
93
+ // No color override on the second marker — falls back to the series color.
94
+ expect(circles[1]?.getAttribute('fill')).toBe('#111')
95
+ expect(circles[1]?.getAttribute('r')).toBe('3')
96
+ })
97
+ })
98
+
99
+ describe('DualPanel — fixed bottomYDomain ships verbatim (un-niced)', () => {
100
+ test('bottom axis ticks come from the pinned domain, not the computed max-abs', () => {
101
+ // Data's max abs is 0.02 — an auto domain would never produce a "5.00" tick. Pinning
102
+ // bottomYDomain to [-5, 5] must still paint it.
103
+ expect(renderStatic(undefined, [-5, 5])).toContain('>5.00<')
104
+ expect(renderStatic()).not.toContain('>5.00<')
105
+ })
106
+ })
107
+
108
+ describe('DualPanel — formatBar overrides only the tooltip row', () => {
109
+ test('the bar tooltip row reads formatBar, not formatBottom', async () => {
110
+ render(
111
+ <DualPanel<Row>
112
+ data={rows}
113
+ chartId="dp-formatbar"
114
+ getX={(d) => d.date}
115
+ series={series}
116
+ getBar={(d) => d.bar}
117
+ barLabel="Bar"
118
+ barColorPositive="#0a0"
119
+ barColorNegative="#a00"
120
+ formatTop={(v) => String(v)}
121
+ formatBottom={(v) => v.toFixed(2)}
122
+ formatBar={(v, d) => `${v.toFixed(3)} m/s (${d.date})`}
123
+ />,
124
+ )
125
+
126
+ const slider = screen.getByRole('slider')
127
+ fireEvent.keyDown(slider, { key: 'ArrowRight' })
128
+
129
+ expect(await screen.findByText('0.020 m/s (2026-08-01)')).toBeTruthy()
130
+ })
131
+ })