@coinbase/cds-mcp-server 8.21.7 → 8.22.1

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 (153) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/mcp-docs/mobile/components/AreaChart.txt +39 -37
  3. package/mcp-docs/mobile/components/Avatar.txt +18 -18
  4. package/mcp-docs/mobile/components/AvatarButton.txt +19 -19
  5. package/mcp-docs/mobile/components/Banner.txt +63 -24
  6. package/mcp-docs/mobile/components/BarChart.txt +37 -35
  7. package/mcp-docs/mobile/components/Box.txt +18 -18
  8. package/mcp-docs/mobile/components/BrowserBar.txt +18 -18
  9. package/mcp-docs/mobile/components/Button.txt +19 -19
  10. package/mcp-docs/mobile/components/Carousel.txt +18 -18
  11. package/mcp-docs/mobile/components/CartesianChart.txt +75 -44
  12. package/mcp-docs/mobile/components/CheckboxCell.txt +19 -19
  13. package/mcp-docs/mobile/components/Chip.txt +20 -20
  14. package/mcp-docs/mobile/components/Coachmark.txt +18 -18
  15. package/mcp-docs/mobile/components/ContentCard.txt +18 -18
  16. package/mcp-docs/mobile/components/ContentCardBody.txt +18 -18
  17. package/mcp-docs/mobile/components/ContentCardFooter.txt +18 -18
  18. package/mcp-docs/mobile/components/ContentCardHeader.txt +18 -18
  19. package/mcp-docs/mobile/components/ContentCell.txt +18 -18
  20. package/mcp-docs/mobile/components/ControlGroup.txt +18 -18
  21. package/mcp-docs/mobile/components/DatePicker.txt +1 -1
  22. package/mcp-docs/mobile/components/Divider.txt +18 -18
  23. package/mcp-docs/mobile/components/DotCount.txt +1 -1
  24. package/mcp-docs/mobile/components/DotSymbol.txt +2 -2
  25. package/mcp-docs/mobile/components/Fallback.txt +18 -18
  26. package/mcp-docs/mobile/components/HStack.txt +18 -18
  27. package/mcp-docs/mobile/components/Icon.txt +6 -0
  28. package/mcp-docs/mobile/components/IconButton.txt +19 -19
  29. package/mcp-docs/mobile/components/InputChip.txt +20 -20
  30. package/mcp-docs/mobile/components/Interactable.txt +19 -19
  31. package/mcp-docs/mobile/components/LineChart.txt +1608 -898
  32. package/mcp-docs/mobile/components/Link.txt +18 -18
  33. package/mcp-docs/mobile/components/ListCell.txt +37 -19
  34. package/mcp-docs/mobile/components/Lottie.txt +18 -18
  35. package/mcp-docs/mobile/components/MediaChip.txt +20 -20
  36. package/mcp-docs/mobile/components/MultiContentModule.txt +18 -18
  37. package/mcp-docs/mobile/components/NavigationTitle.txt +18 -18
  38. package/mcp-docs/mobile/components/NavigationTitleSelect.txt +18 -18
  39. package/mcp-docs/mobile/components/Numpad.txt +18 -18
  40. package/mcp-docs/mobile/components/Overlay.txt +18 -18
  41. package/mcp-docs/mobile/components/PageFooter.txt +17 -17
  42. package/mcp-docs/mobile/components/PageHeader.txt +17 -17
  43. package/mcp-docs/mobile/components/PeriodSelector.txt +26 -26
  44. package/mcp-docs/mobile/components/Point.txt +203 -98
  45. package/mcp-docs/mobile/components/Pressable.txt +19 -19
  46. package/mcp-docs/mobile/components/ProgressBar.txt +1 -1
  47. package/mcp-docs/mobile/components/ProgressBarWithFixedLabels.txt +1 -1
  48. package/mcp-docs/mobile/components/ProgressBarWithFloatLabel.txt +1 -1
  49. package/mcp-docs/mobile/components/ProgressCircle.txt +1 -1
  50. package/mcp-docs/mobile/components/RadioCell.txt +19 -19
  51. package/mcp-docs/mobile/components/ReferenceLine.txt +197 -54
  52. package/mcp-docs/mobile/components/RollingNumber.txt +18 -18
  53. package/mcp-docs/mobile/components/Scrubber.txt +597 -79
  54. package/mcp-docs/mobile/components/SegmentedTabs.txt +18 -18
  55. package/mcp-docs/mobile/components/SelectAlpha.txt +1 -1
  56. package/mcp-docs/mobile/components/SelectChip.txt +20 -20
  57. package/mcp-docs/mobile/components/SlideButton.txt +19 -19
  58. package/mcp-docs/mobile/components/Spacer.txt +6 -6
  59. package/mcp-docs/mobile/components/SparklineInteractive.txt +3 -3
  60. package/mcp-docs/mobile/components/Spinner.txt +1 -1
  61. package/mcp-docs/mobile/components/Stepper.txt +18 -18
  62. package/mcp-docs/mobile/components/TabLabel.txt +18 -18
  63. package/mcp-docs/mobile/components/TabNavigation.txt +18 -18
  64. package/mcp-docs/mobile/components/TabbedChips.txt +18 -18
  65. package/mcp-docs/mobile/components/TabbedChipsAlpha.txt +1 -1
  66. package/mcp-docs/mobile/components/Tabs.txt +18 -18
  67. package/mcp-docs/mobile/components/Tag.txt +18 -18
  68. package/mcp-docs/mobile/components/Text.txt +18 -18
  69. package/mcp-docs/mobile/components/Toast.txt +18 -18
  70. package/mcp-docs/mobile/components/Tooltip.txt +17 -1
  71. package/mcp-docs/mobile/components/TopNavBar.txt +18 -18
  72. package/mcp-docs/mobile/components/VStack.txt +18 -18
  73. package/mcp-docs/mobile/components/XAxis.txt +86 -24
  74. package/mcp-docs/mobile/components/YAxis.txt +75 -17
  75. package/mcp-docs/mobile/routes.txt +1 -1
  76. package/mcp-docs/web/components/AreaChart.txt +523 -301
  77. package/mcp-docs/web/components/Avatar.txt +27 -27
  78. package/mcp-docs/web/components/AvatarButton.txt +28 -28
  79. package/mcp-docs/web/components/Banner.txt +72 -33
  80. package/mcp-docs/web/components/BarChart.txt +182 -313
  81. package/mcp-docs/web/components/Box.txt +28 -28
  82. package/mcp-docs/web/components/Button.txt +28 -28
  83. package/mcp-docs/web/components/Calendar.txt +27 -27
  84. package/mcp-docs/web/components/Carousel.txt +27 -27
  85. package/mcp-docs/web/components/CartesianChart.txt +62 -309
  86. package/mcp-docs/web/components/CheckboxCell.txt +25 -25
  87. package/mcp-docs/web/components/Chip.txt +27 -27
  88. package/mcp-docs/web/components/Coachmark.txt +27 -27
  89. package/mcp-docs/web/components/ContainedAssetCard.txt +27 -27
  90. package/mcp-docs/web/components/ContentCard.txt +28 -28
  91. package/mcp-docs/web/components/ContentCardBody.txt +28 -28
  92. package/mcp-docs/web/components/ContentCardFooter.txt +28 -28
  93. package/mcp-docs/web/components/ContentCardHeader.txt +28 -28
  94. package/mcp-docs/web/components/ContentCell.txt +28 -28
  95. package/mcp-docs/web/components/ControlGroup.txt +27 -27
  96. package/mcp-docs/web/components/Divider.txt +27 -27
  97. package/mcp-docs/web/components/Fallback.txt +28 -28
  98. package/mcp-docs/web/components/FloatingAssetCard.txt +27 -27
  99. package/mcp-docs/web/components/Grid.txt +28 -28
  100. package/mcp-docs/web/components/GridColumn.txt +27 -27
  101. package/mcp-docs/web/components/HStack.txt +28 -28
  102. package/mcp-docs/web/components/Icon.txt +27 -27
  103. package/mcp-docs/web/components/IconButton.txt +28 -28
  104. package/mcp-docs/web/components/InputChip.txt +27 -27
  105. package/mcp-docs/web/components/Interactable.txt +28 -28
  106. package/mcp-docs/web/components/LineChart.txt +1598 -1116
  107. package/mcp-docs/web/components/Link.txt +28 -28
  108. package/mcp-docs/web/components/ListCell.txt +48 -30
  109. package/mcp-docs/web/components/Lottie.txt +27 -27
  110. package/mcp-docs/web/components/MediaChip.txt +27 -27
  111. package/mcp-docs/web/components/Modal.txt +27 -27
  112. package/mcp-docs/web/components/ModalBody.txt +27 -27
  113. package/mcp-docs/web/components/ModalFooter.txt +27 -27
  114. package/mcp-docs/web/components/ModalHeader.txt +27 -27
  115. package/mcp-docs/web/components/MultiContentModule.txt +28 -28
  116. package/mcp-docs/web/components/NavigationBar.txt +5 -5
  117. package/mcp-docs/web/components/NudgeCard.txt +27 -27
  118. package/mcp-docs/web/components/Overlay.txt +27 -27
  119. package/mcp-docs/web/components/PageFooter.txt +26 -26
  120. package/mcp-docs/web/components/PageHeader.txt +26 -26
  121. package/mcp-docs/web/components/Pagination.txt +27 -27
  122. package/mcp-docs/web/components/PeriodSelector.txt +49 -49
  123. package/mcp-docs/web/components/Point.txt +228 -79
  124. package/mcp-docs/web/components/Pressable.txt +28 -28
  125. package/mcp-docs/web/components/RadioCell.txt +25 -25
  126. package/mcp-docs/web/components/ReferenceLine.txt +208 -60
  127. package/mcp-docs/web/components/RemoteImage.txt +26 -26
  128. package/mcp-docs/web/components/RollingNumber.txt +28 -28
  129. package/mcp-docs/web/components/Scrubber.txt +463 -68
  130. package/mcp-docs/web/components/SectionHeader.txt +27 -27
  131. package/mcp-docs/web/components/SegmentedTabs.txt +27 -27
  132. package/mcp-docs/web/components/SelectChip.txt +27 -27
  133. package/mcp-docs/web/components/SelectOption.txt +27 -27
  134. package/mcp-docs/web/components/Sidebar.txt +27 -27
  135. package/mcp-docs/web/components/SidebarItem.txt +27 -27
  136. package/mcp-docs/web/components/Spacer.txt +34 -34
  137. package/mcp-docs/web/components/SparklineInteractive.txt +1 -1
  138. package/mcp-docs/web/components/Spinner.txt +27 -27
  139. package/mcp-docs/web/components/Stepper.txt +27 -27
  140. package/mcp-docs/web/components/TabLabel.txt +27 -27
  141. package/mcp-docs/web/components/TabNavigation.txt +26 -26
  142. package/mcp-docs/web/components/TabbedChips.txt +26 -26
  143. package/mcp-docs/web/components/TabbedChipsAlpha.txt +1 -1
  144. package/mcp-docs/web/components/Tabs.txt +27 -27
  145. package/mcp-docs/web/components/Tag.txt +27 -27
  146. package/mcp-docs/web/components/Text.txt +28 -28
  147. package/mcp-docs/web/components/TileButton.txt +28 -28
  148. package/mcp-docs/web/components/Toast.txt +27 -27
  149. package/mcp-docs/web/components/Tooltip.txt +17 -1
  150. package/mcp-docs/web/components/VStack.txt +28 -28
  151. package/mcp-docs/web/components/XAxis.txt +86 -22
  152. package/mcp-docs/web/components/YAxis.txt +133 -89
  153. package/package.json +1 -1
@@ -10,9 +10,9 @@ import { Scrubber } from '@coinbase/cds-mobile-visualization'
10
10
 
11
11
  ## Examples
12
12
 
13
- ### Basic Example
13
+ ### Basics
14
14
 
15
- Scrubber can be used to provide horizontal interaction with a chart. As your mouse hovers over the chart, you will see a reference line and scrubber head following your cursor.
15
+ Scrubber can be used to provide horizontal interaction with a chart. As you drag over the chart, you will see a line and scrubber beacon following.
16
16
 
17
17
  ```jsx
18
18
  <LineChart
@@ -24,20 +24,17 @@ Scrubber can be used to provide horizontal interaction with a chart. As your mou
24
24
  data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
25
25
  },
26
26
  ]}
27
- curve="monotone"
28
27
  showYAxis
29
28
  showArea
30
29
  yAxis={{
31
30
  showGrid: true,
32
31
  }}
33
32
  >
34
- <Scrubber />
33
+ <Scrubber idlePulse />
35
34
  </LineChart>
36
35
  ```
37
36
 
38
- ### Multiple Series
39
-
40
- All series will be scrubbed by default. You can set the `seriesIds` prop to restrict the scrubbing to specific series.
37
+ All series will be scrubbed by default. You can set `seriesIds` to show only specific series.
41
38
 
42
39
  ```jsx
43
40
  <LineChart
@@ -48,38 +45,41 @@ All series will be scrubbed by default. You can set the `seriesIds` prop to rest
48
45
  id: 'top',
49
46
  data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38],
50
47
  },
51
- {
52
- id: 'upperMiddle',
53
- data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
54
- color: '#ef4444',
55
- type: 'dotted',
56
- },
57
- {
58
- id: 'lowerMiddle',
59
- data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
60
- color: '#f59e0b',
61
- curve: 'natural',
62
- LineComponent: (props) => (
63
- <GradientLine {...props} endColor="#F7931A" startColor="#E3D74D" strokeWidth={4} />
64
- ),
65
- },
66
48
  {
67
49
  id: 'bottom',
68
50
  data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
69
- color: '#800080',
70
- curve: 'step',
71
- AreaComponent: DottedArea,
72
- showArea: true,
73
51
  },
74
52
  ]}
75
53
  >
76
- <Scrubber seriesIds={['top', 'lowerMiddle']} />
54
+ <Scrubber seriesIds={['top']} />
55
+ </LineChart>
56
+ ```
57
+
58
+ ### Labels
59
+
60
+ Setting `label` on a series will display a label to the side of the scrubber beacon, and
61
+ setting `label` on Scrubber displays a label above the scrubber line.
62
+
63
+ ```jsx
64
+ <LineChart
65
+ enableScrubbing
66
+ height={150}
67
+ series={[
68
+ {
69
+ id: 'prices',
70
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
71
+ label: 'Price',
72
+ },
73
+ ]}
74
+ showArea
75
+ >
76
+ <Scrubber label={(dataIndex: number) => `Day ${dataIndex + 1}`} />
77
77
  </LineChart>
78
78
  ```
79
79
 
80
80
  ### Pulsing
81
81
 
82
- Setting the `idlePulse` prop will cause the scrubber heads to pulse when the user is not actively scrubbing.
82
+ Setting `idlePulse` to `true` will cause the scrubber beacons to pulse when the user is not actively scrubbing.
83
83
 
84
84
  ```jsx
85
85
  <LineChart
@@ -89,22 +89,24 @@ Setting the `idlePulse` prop will cause the scrubber heads to pulse when the use
89
89
  {
90
90
  id: 'prices',
91
91
  data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
92
+ color: 'var(--color-fgPositive)',
92
93
  },
93
94
  ]}
94
- curve="monotone"
95
95
  showArea
96
96
  >
97
+ <ReferenceLine
98
+ LineComponent={(props) => <DottedLine {...props} dashIntervals={[0, 16]} strokeWidth={3} />}
99
+ dataY={10}
100
+ stroke="var(--color-fg)"
101
+ />
97
102
  <Scrubber idlePulse />
98
103
  </LineChart>
99
104
  ```
100
105
 
101
- #### With Imperative Handle
102
-
103
- You can also use the imperative handle to pulse the scrubber heads programmatically.
106
+ You can also use the imperative handle to pulse the scrubber beacons programmatically.
104
107
 
105
108
  ```jsx
106
109
  function ImperativeHandle() {
107
- const theme = useTheme();
108
110
  const scrubberRef = useRef(null);
109
111
  return (
110
112
  <VStack gap={2}>
@@ -113,70 +115,579 @@ function ImperativeHandle() {
113
115
  height={150}
114
116
  series={[
115
117
  {
116
- id: 'priceA',
117
- data: [2400, 1398, 9800, 3908, 4800, 3800, 4300],
118
- color: theme.color.accentBoldBlue,
119
- curve: 'natural',
120
- },
121
- {
122
- id: 'priceB',
123
- data: [2000, 2491, 4501, 6049, 5019, 4930, 5910],
124
- color: theme.color.accentBoldGreen,
125
- curve: 'natural',
126
- },
127
- {
128
- id: 'priceC',
129
- data: [1000, 4910, 2300, 5910, 3940, 2940, 1940],
130
- color: theme.color.accentBoldPurple,
131
- curve: 'natural',
132
- },
133
- {
134
- id: 'priceD',
135
- data: [4810, 2030, 5810, 3940, 2940, 1940, 940],
136
- color: theme.color.accentBoldYellow,
137
- curve: 'natural',
118
+ id: 'prices',
119
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
138
120
  },
139
121
  ]}
122
+ showYAxis
123
+ showArea
140
124
  xAxis={{
141
- range: ({ min, max }) => ({ min, max: max - 32 }),
125
+ /* Give space between the scrubber and the axis */
126
+ range: ({ min, max }) => ({ min, max: max - 8 }),
142
127
  }}
143
- showYAxis
144
128
  yAxis={{
145
- domain: {
146
- min: 0,
147
- },
148
129
  showGrid: true,
149
- tickLabelFormatter: (value) => value.toLocaleString(),
150
130
  }}
151
131
  >
152
132
  <Scrubber ref={scrubberRef} />
153
133
  </LineChart>
154
- <Button onClick={() => scrubberRef.current?.pulse()}>Pulse</Button>
134
+ <Button onPress={() => scrubberRef.current?.pulse()}>Pulse</Button>
155
135
  </VStack>
156
136
  );
157
137
  }
158
138
  ```
159
139
 
160
- ### Disable Overlay When Scrubbing
140
+ ### Styling
141
+
142
+ #### Beacons
143
+
144
+ You can use `BeaconComponent` to customize the visual appearance of scrubber beacons.
145
+
146
+ ```jsx
147
+ function OutlineBeacon() {
148
+ // Simple outline beacon with no pulse animation
149
+ const OutlineBeaconComponent = memo(({ dataX, dataY, seriesId, isIdle, animate = true }: ScrubberBeaconProps) => {
150
+ const theme = useTheme();
151
+ const { getSeries, getXSerializableScale, getYSerializableScale } = useCartesianChartContext();
152
+
153
+ const targetSeries = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
154
+ const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
155
+ const yScale = useMemo(
156
+ () => getYSerializableScale(targetSeries?.yAxisId),
157
+ [getYSerializableScale, targetSeries?.yAxisId],
158
+ );
159
+
160
+ const color = useMemo(
161
+ () => targetSeries?.color ?? theme.color.fgPrimary,
162
+ [targetSeries?.color, theme.color.fgPrimary],
163
+ );
164
+
165
+ const animatedX = useSharedValue(0);
166
+ const animatedY = useSharedValue(0);
167
+
168
+ // Calculate the target point position - project data to pixels
169
+ const targetPoint = useDerivedValue(() => {
170
+ if (!xScale || !yScale) return { x: 0, y: 0 };
171
+ return projectPointWithSerializableScale({
172
+ x: unwrapAnimatedValue(dataX),
173
+ y: unwrapAnimatedValue(dataY),
174
+ xScale,
175
+ yScale,
176
+ });
177
+ }, [dataX, dataY, xScale, yScale]);
178
+
179
+ useAnimatedReaction(
180
+ () => {
181
+ return { point: targetPoint.value, isIdle: unwrapAnimatedValue(isIdle) };
182
+ },
183
+ (current, previous) => {
184
+ // When animation is disabled, on initial render, or when we are starting,
185
+ // continuing, or finishing scrubbing we should immediately transition
186
+ if (!animate || previous === null || !previous.isIdle || !current.isIdle) {
187
+ animatedX.value = current.point.x;
188
+ animatedY.value = current.point.y;
189
+ return;
190
+ }
191
+
192
+ animatedX.value = buildTransition(current.point.x, defaultTransition);
193
+ animatedY.value = buildTransition(current.point.y, defaultTransition);
194
+ },
195
+ [animate],
196
+ );
197
+
198
+ // Create animated point using the animated values
199
+ const animatedPoint = useDerivedValue(() => {
200
+ return { x: animatedX.value, y: animatedY.value };
201
+ }, [animatedX, animatedY]);
202
+
203
+ return (
204
+ <>
205
+ <Circle c={animatedPoint} color={color} r={6} />
206
+ <Circle c={animatedPoint} color={theme.color.bg} r={3} />
207
+ </>
208
+ );
209
+ });
210
+
211
+ const dataCount = 14;
212
+ const minDataValue = 0;
213
+ const maxDataValue = 100;
214
+ const minStepOffset = 5;
215
+ const maxStepOffset = 20;
216
+ const updateInterval = 2000;
217
+
218
+ function generateNextValue(previousValue: number) {
219
+ const range = maxStepOffset - minStepOffset;
220
+ const offset = Math.random() * range + minStepOffset;
221
+
222
+ let direction;
223
+ if (previousValue >= maxDataValue) {
224
+ direction = -1;
225
+ } else if (previousValue <= minDataValue) {
226
+ direction = 1;
227
+ } else {
228
+ direction = Math.random() < 0.5 ? -1 : 1;
229
+ }
230
+
231
+ const newValue = previousValue + offset * direction;
232
+ return Math.max(minDataValue, Math.min(maxDataValue, newValue));
233
+ }
234
+
235
+ function generateInitialData() {
236
+ const data = [];
237
+ let previousValue = Math.random() * (maxDataValue - minDataValue) + minDataValue;
238
+ data.push(previousValue);
239
+
240
+ for (let i = 1; i < dataCount; i++) {
241
+ const newValue = generateNextValue(previousValue);
242
+ data.push(newValue);
243
+ previousValue = newValue;
244
+ }
245
+ return data;
246
+ }
247
+
248
+
249
+ const OutlineBeaconChart = memo(() => {
250
+ const [data, setData] = useState(generateInitialData);
251
+
252
+ useEffect(() => {
253
+ const intervalId = setInterval(() => {
254
+ setData((currentData) => {
255
+ const lastValue = currentData[currentData.length - 1] ?? 50;
256
+ const newValue = generateNextValue(lastValue);
257
+ return [...currentData.slice(1), newValue];
258
+ });
259
+ }, updateInterval);
260
+
261
+ return () => clearInterval(intervalId);
262
+ }, []);
263
+
264
+ return (
265
+ <LineChart
266
+ enableScrubbing
267
+ showArea
268
+ showYAxis
269
+ height={150}
270
+ series={[
271
+ {
272
+ id: 'prices',
273
+ data,
274
+ color: 'var(--color-fg)',
275
+ },
276
+ ]}
277
+ xAxis={{
278
+ range: ({ min, max }) => ({ min, max: max - 16 }),
279
+ }}
280
+ yAxis={{
281
+ showGrid: true,
282
+ domain: { min: 0, max: 100 }
283
+ }}
284
+ >
285
+ <Scrubber BeaconComponent={OutlineBeaconComponent} />
286
+ </LineChart>
287
+ );
288
+ });
289
+
290
+ return <OutlineBeaconChart />;
291
+ }
292
+ ```
293
+
294
+ #### Labels
161
295
 
162
- By default, the scrubber will show an overlay to de-emphasize future data. You can hide this by setting the `hideOverlay` prop to `true`.
296
+ You can use `BeaconLabelComponent` to customize the labels for each scrubber beacon.
163
297
 
164
298
  ```jsx
299
+ function CustomBeaconLabel() {
300
+ const theme = useTheme();
301
+ // This custom component label shows the percentage value of the data at the scrubber position.
302
+ const MyScrubberBeaconLabel = memo(
303
+ ({ seriesId, color, label, ...props }: ScrubberBeaconLabelProps) => {
304
+ const { getSeriesData, dataLength } = useCartesianChartContext();
305
+ const { scrubberPosition } = useScrubberContext();
306
+
307
+ const seriesData = useMemo(
308
+ () => getLineData(getSeriesData(seriesId)),
309
+ [getSeriesData, seriesId],
310
+ );
311
+
312
+ const dataIndex = useDerivedValue(() => {
313
+ return scrubberPosition.value ?? Math.max(0, dataLength - 1);
314
+ }, [scrubberPosition, dataLength]);
315
+
316
+ const percentageLabel = useDerivedValue(() => {
317
+ if (seriesData !== undefined) {
318
+ const dataAtPosition = seriesData[dataIndex.value];
319
+ return `${unwrapAnimatedValue(label)} · ${dataAtPosition}%`;
320
+ }
321
+ return unwrapAnimatedValue(label);
322
+ }, [label, seriesData, dataIndex]);
323
+
324
+ return (
325
+ <DefaultScrubberBeaconLabel
326
+ {...props}
327
+ background={color}
328
+ color={theme.color.bg}
329
+ label={percentageLabel}
330
+ seriesId={seriesId}
331
+ />
332
+ );
333
+ },
334
+ );
335
+
336
+ return (
337
+ <LineChart
338
+ enableScrubbing
339
+ showArea
340
+ showYAxis
341
+ areaType="dotted"
342
+ height={150}
343
+ series={[
344
+ {
345
+ id: 'Boston',
346
+ data: [25, 30, 35, 45, 60, 100],
347
+ color: `rgb(${theme.spectrum.green40})`,
348
+ label: 'Boston',
349
+ },
350
+ {
351
+ id: 'Miami',
352
+ data: [20, 25, 30, 35, 20, 0],
353
+ color: `rgb(${theme.spectrum.blue40})`,
354
+ label: 'Miami',
355
+ },
356
+ {
357
+ id: 'Denver',
358
+ data: [10, 15, 20, 25, 40, 0],
359
+ color: `rgb(${theme.spectrum.orange40})`,
360
+ label: 'Denver',
361
+ },
362
+ {
363
+ id: 'Phoenix',
364
+ data: [15, 10, 5, 0, 0, 0],
365
+ color: `rgb(${theme.spectrum.red40})`,
366
+ label: 'Phoenix',
367
+ },
368
+ ]}
369
+ yAxis={{
370
+ showGrid: true,
371
+ }}
372
+ >
373
+ <Scrubber BeaconLabelComponent={MyScrubberBeaconLabel} />
374
+ </LineChart>
375
+ );
376
+ }
377
+ ```
378
+
379
+ Using `labelElevated` will elevate the Scrubber's reference line label with a shadow.
380
+
381
+ ```jsx live
165
382
  <LineChart
166
383
  enableScrubbing
167
- height={250}
384
+ height={200}
168
385
  series={[
169
386
  {
170
387
  id: 'prices',
171
388
  data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
172
389
  },
173
390
  ]}
174
- curve="monotone"
175
- showYAxis
176
391
  showArea
177
- yAxis={{
178
- showGrid: true,
179
- }}
392
+ inset={{ top: 60 }}
393
+ >
394
+ <Scrubber label={(dataIndex: number) => `Day ${dataIndex + 1}`} labelElevated />
395
+ </LineChart>
396
+ ```
397
+
398
+ You can use `LabelComponent` to customize this label even further.
399
+
400
+ ```jsx
401
+ function CustomLabelComponent() {
402
+ const CustomLabelComponent = memo((props: ScrubberLabelProps) => {
403
+ const theme = useTheme();
404
+ const { drawingArea } = useCartesianChartContext();
405
+
406
+ if (!drawingArea) return;
407
+
408
+ return (
409
+ <DefaultScrubberLabel
410
+ {...props}
411
+ background={theme.color.bgPrimary}
412
+ color={theme.color.bgPrimaryWash}
413
+ dy={32}
414
+ elevated
415
+ fontWeight={FontWeight.Bold}
416
+ y={drawingArea.y + drawingArea.height}
417
+ />
418
+ );
419
+ });
420
+ return (
421
+ <LineChart
422
+ enableScrubbing
423
+ showArea
424
+ height={200}
425
+ inset={{ top: 16, bottom: 64 }}
426
+ series={[
427
+ {
428
+ id: 'prices',
429
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
430
+ },
431
+ ]}
432
+ >
433
+ <Scrubber
434
+ LabelComponent={CustomLabelComponent}
435
+ label={(dataIndex: number) => `Day ${dataIndex + 1}`}
436
+ />
437
+ </LineChart>
438
+ );
439
+ }
440
+ ```
441
+
442
+ ##### Multi-line Centered Text
443
+
444
+ You can create custom multi-line centered labels using Skia's `ParagraphBuilder` with `TextAlign.Center`. Set `paragraphAlignment={TextAlign.Center}` on your custom label component to ensure proper positioning.
445
+
446
+ ```jsx
447
+ function TwoLineCenteredLabel() {
448
+ const theme = useTheme();
449
+ const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
450
+
451
+ const fontMgr = useMemo(() => Skia.TypefaceFontProvider.Make(), []);
452
+
453
+ const formatPrice = useCallback((price: number) => {
454
+ return new Intl.NumberFormat('en-US', {
455
+ style: 'currency',
456
+ currency: 'USD',
457
+ minimumFractionDigits: 2,
458
+ maximumFractionDigits: 2,
459
+ }).format(price);
460
+ }, []);
461
+
462
+ const scrubberLabel = useCallback(
463
+ (index: number) => {
464
+ const price = formatPrice(data[index]);
465
+ const day = `Day ${index + 1}`;
466
+
467
+ const priceStyle: SkTextStyle = {
468
+ fontFamilies: ['Inter'],
469
+ fontSize: 16,
470
+ fontStyle: { weight: FontWeight.Bold },
471
+ color: Skia.Color(theme.color.fg),
472
+ };
473
+
474
+ const dayStyle: SkTextStyle = {
475
+ fontFamilies: ['Inter'],
476
+ fontSize: 14,
477
+ fontStyle: { weight: FontWeight.Normal },
478
+ color: Skia.Color(theme.color.fgMuted),
479
+ };
480
+
481
+ const builder = Skia.ParagraphBuilder.Make({ textAlign: TextAlign.Center }, fontMgr);
482
+
483
+ builder.pushStyle(priceStyle);
484
+ builder.addText(price);
485
+ builder.addText('\n');
486
+
487
+ builder.pushStyle(dayStyle);
488
+ builder.addText(day);
489
+
490
+ const para = builder.build();
491
+ para.layout(384);
492
+ return para;
493
+ },
494
+ [data, formatPrice, theme.color.fg, theme.color.fgMuted, fontMgr],
495
+ );
496
+
497
+ // Custom label component that sets paragraphAlignment to center
498
+ const CenteredScrubberLabel = memo((props: ScrubberLabelProps) => (
499
+ <DefaultScrubberLabel {...props} paragraphAlignment={TextAlign.Center} />
500
+ ));
501
+
502
+ return (
503
+ <LineChart
504
+ enableScrubbing
505
+ showArea
506
+ height={200}
507
+ inset={{ top: 64 }}
508
+ series={[
509
+ {
510
+ id: 'prices',
511
+ data: data,
512
+ color: theme.color.accentBoldBlue,
513
+ },
514
+ ]}
515
+ >
516
+ <Scrubber
517
+ idlePulse
518
+ labelElevated
519
+ LabelComponent={CenteredScrubberLabel}
520
+ label={scrubberLabel}
521
+ />
522
+ </LineChart>
523
+ );
524
+ }
525
+ ```
526
+
527
+ ##### Fonts
528
+
529
+ You can use `labelFont` to customize the font of the scrubber line label and `beaconLabelFont` to customize the font of the beacon labels.
530
+
531
+ ```jsx
532
+ function CustomLabelFonts() {
533
+ const theme = useTheme();
534
+
535
+ return (
536
+ <LineChart
537
+ enableScrubbing
538
+ showArea
539
+ showYAxis
540
+ height={200}
541
+ series={[
542
+ {
543
+ id: 'btc',
544
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
545
+ label: 'BTC',
546
+ color: assets.btc.color,
547
+ },
548
+ {
549
+ id: 'eth',
550
+ data: [5, 15, 18, 30, 65, 30, 15, 35, 15, 2, 45, 12, 15, 40],
551
+ label: 'ETH',
552
+ color: assets.eth.color,
553
+ },
554
+ ]}
555
+ yAxis={{
556
+ showGrid: true,
557
+ }}
558
+ >
559
+ <Scrubber
560
+ label={(dataIndex: number) => `Day ${dataIndex + 1}`}
561
+ labelFont="legal"
562
+ beaconLabelFont="legal"
563
+ />
564
+ </LineChart>
565
+ );
566
+ }
567
+ ```
568
+
569
+ ##### Bounds
570
+
571
+ Use `labelBoundsInset` to prevent the scrubber line label from getting too close to chart edges.
572
+
573
+ ```jsx
574
+ function WithoutBoundsExample() {
575
+ return (
576
+ <LineChart
577
+ enableScrubbing
578
+ showArea
579
+ height={150}
580
+ inset={{ left: 0, right: 0 }}
581
+ series={[
582
+ {
583
+ id: 'prices',
584
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
585
+ },
586
+ ]}
587
+ >
588
+ <Scrubber label="Without bounds - text touches edge" labelBoundsInset={0} />
589
+ </LineChart>
590
+ );
591
+ }
592
+ ```
593
+
594
+ ```jsx
595
+ function WithBoundsExample() {
596
+ return (
597
+ <LineChart
598
+ enableScrubbing
599
+ showArea
600
+ height={150}
601
+ inset={{ left: 0, right: 0 }}
602
+ series={[
603
+ {
604
+ id: 'prices',
605
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
606
+ },
607
+ ]}
608
+ >
609
+ <Scrubber
610
+ label="With bounds inset - text has space"
611
+ labelBoundsInset={{ left: 12, right: 12 }}
612
+ />
613
+ </LineChart>
614
+ );
615
+ }
616
+ ```
617
+
618
+ #### Line
619
+
620
+ You can use `LineComponent` to customize Scrubber's line. In this case, as a user scrubs, they will see a solid line instead of dotted.
621
+
622
+ ```jsx
623
+ <LineChart
624
+ enableScrubbing
625
+ height={150}
626
+ series={[
627
+ {
628
+ id: 'prices',
629
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
630
+ },
631
+ ]}
632
+ showArea
633
+ >
634
+ <Scrubber LineComponent={SolidLine} />
635
+ </LineChart>
636
+ ```
637
+
638
+ #### Opacity
639
+
640
+ You can use `BeaconComponent` and `BeaconLabelComponent` with the `opacity` prop to hide scrubber beacons and labels when idle.
641
+
642
+ ```jsx
643
+ function HiddenScrubberWhenIdle() {
644
+ const MyScrubberBeacon = memo((props: ScrubberBeaconProps) => {
645
+ const { scrubberPosition } = useScrubberContext();
646
+ const beaconOpacity = useDerivedValue(
647
+ () => (scrubberPosition.value !== undefined ? 1 : 0),
648
+ [scrubberPosition],
649
+ );
650
+
651
+ return <DefaultScrubberBeacon {...props} opacity={beaconOpacity} />;
652
+ });
653
+
654
+ const MyScrubberBeaconLabel = memo((props: ScrubberBeaconLabelProps) => {
655
+ const { scrubberPosition } = useScrubberContext();
656
+ const labelOpacity = useDerivedValue(
657
+ () => (scrubberPosition.value !== undefined ? 1 : 0),
658
+ [scrubberPosition],
659
+ );
660
+
661
+ return <DefaultScrubberBeaconLabel {...props} opacity={labelOpacity} />;
662
+ });
663
+
664
+ return (
665
+ <LineChart
666
+ enableScrubbing
667
+ showArea
668
+ height={150}
669
+ series={[
670
+ {
671
+ id: 'prices',
672
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
673
+ label: 'Price',
674
+ },
675
+ ]}
676
+ >
677
+ <Scrubber BeaconComponent={MyScrubberBeacon} BeaconLabelComponent={MyScrubberBeaconLabel} />
678
+ </LineChart>
679
+ );
680
+ }
681
+ ```
682
+
683
+ #### Overlay
684
+
685
+ By default, Scrubber will show an overlay to de-emphasize future data. You can hide this by setting `hideOverlay` to `true`.
686
+
687
+ ```jsx
688
+ <LineChart
689
+ enableScrubbing
690
+ ...
180
691
  >
181
692
  <Scrubber hideOverlay />
182
693
  </LineChart>
@@ -186,18 +697,25 @@ By default, the scrubber will show an overlay to de-emphasize future data. You c
186
697
 
187
698
  | Prop | Type | Required | Default | Description |
188
699
  | --- | --- | --- | --- | --- |
189
- | `BeaconComponent` | `ComponentClass<ScrubberBeaconProps, any> \| FunctionComponent<ScrubberBeaconProps>` | No | `-` | Custom component for the scrubber beacon. |
190
- | `LineComponent` | `FunctionComponent<ReferenceLineProps> \| ComponentClass<ReferenceLineProps, any>` | No | `-` | Custom component for the scrubber line. |
700
+ | `BeaconComponent` | `ScrubberBeaconComponent` | No | `DefaultScrubberBeacon` | Custom component for the scrubber beacon. |
701
+ | `BeaconLabelComponent` | `ScrubberBeaconLabelComponent` | No | `DefaultScrubberBeaconLabel` | Custom component to render as a scrubber beacon label. |
702
+ | `LabelComponent` | `ReferenceLineLabelComponent` | No | `DefaultReferenceLineLabel` | Component to render the label. |
703
+ | `LineComponent` | `LineComponent` | No | `DottedLine` | Component to render the line. |
704
+ | `beaconLabelFont` | `display1 \| display2 \| display3 \| title1 \| title2 \| title3 \| title4 \| headline \| body \| label1 \| label2 \| caption \| legal` | No | `-` | Font style for the beacon labels. |
705
+ | `beaconLabelHorizontalOffset` | `number` | No | `-` | Horizontal offset for beacon labels from their beacon position. Measured in pixels. |
706
+ | `beaconLabelMinGap` | `number` | No | `-` | Minimum gap between beacon labels to prevent overlap. Measured in pixels. |
707
+ | `beaconTransitions` | `{ update?: Transition; pulse?: Transition \| undefined; pulseRepeatDelay?: number \| undefined; } \| undefined` | No | `-` | Transition configuration for the scrubber beacon. |
191
708
  | `hideLine` | `boolean` | No | `-` | Hides the scrubber line |
192
- | `hideOverlay` | `boolean` | No | `-` | Whether to hide the overlay rect which obscures future data. |
193
- | `idlePulse` | `boolean` | No | `-` | Pulse the scrubber beacon while it is at rest. |
709
+ | `hideOverlay` | `boolean` | No | `-` | Hides the overlay rect which obscures data beyond the scrubber position. |
710
+ | `idlePulse` | `boolean` | No | `-` | Pulse the beacons while at rest. |
194
711
  | `key` | `Key \| null` | No | `-` | - |
195
- | `label` | `ChartTextChildren \| ((dataIndex: number) => ChartTextChildren)` | No | `-` | Label text displayed above the scrubber line. |
196
- | `labelProps` | `ReferenceLineLabelProps` | No | `-` | Props passed to the scrubber lines label. |
712
+ | `label` | `string \| SkParagraph \| ((dataIndex: number) => string \| SkParagraph)` | No | `-` | Label text displayed above the scrubber line. Can be a static string or a function that receives the current dataIndex. |
713
+ | `labelBoundsInset` | `number \| ChartInset` | No | `{ top: 4, bottom: 20, left: 12, right: 12 } when labelElevated is true, otherwise none` | Bounds inset for the scrubber line label to prevent cutoff at chart edges. |
714
+ | `labelElevated` | `boolean` | No | `-` | Whether to elevate the label with a shadow. When true, applies elevation and automatically adds bounds to keep label within chart area. |
715
+ | `labelFont` | `display1 \| display2 \| display3 \| title1 \| title2 \| title3 \| title4 \| headline \| body \| label1 \| label2 \| caption \| legal` | No | `-` | Font style for the scrubber line label. |
197
716
  | `lineStroke` | `string` | No | `-` | Stroke color for the scrubber line. |
198
717
  | `overlayOffset` | `number` | No | `2` | Offset of the overlay rect relative to the drawing area. Useful for when scrubbing over lines, where the stroke width would cause part of the line to be visible. |
199
- | `ref` | `((instance: ScrubberBeaconRef \| null) => void) \| RefObject<ScrubberBeaconRef> \| null` | No | `-` | - |
200
- | `seriesIds` | `string[]` | No | `-` | An array of series IDs that will receive visual emphasis as the user scrubs through the chart. Use this prop to restrict the scrubbing visual behavior to specific series. By default, all series will be highlighted by the Scrubber. |
201
- | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID |
718
+ | `ref` | `((instance: ScrubberBeaconGroupRef \| null) => void) \| RefObject<ScrubberBeaconGroupRef> \| null` | No | `-` | - |
719
+ | `seriesIds` | `string[]` | No | `-` | Array of series IDs to highlight when scrubbing with scrubber beacons. By default, all series will be highlighted. |
202
720
 
203
721