@coinbase/cds-mobile-visualization 3.4.0 → 3.6.2

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.
@@ -0,0 +1,833 @@
1
+ const _excluded = ["x", "y", "width", "height", "borderRadius", "roundTop", "roundBottom", "d", "fill", "fillOpacity", "dataX", "origin", "dataY", "seriesId", "minSize"];
2
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
+ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { assets } from '@coinbase/cds-common/internal/data/assets';
6
+ import { IconButton } from '@coinbase/cds-mobile/buttons';
7
+ import { Switch } from '@coinbase/cds-mobile/controls';
8
+ import { ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
9
+ import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
10
+ import { Box, HStack, VStack } from '@coinbase/cds-mobile/layout';
11
+ import { RollingNumber } from '@coinbase/cds-mobile/numbers';
12
+ import { Text } from '@coinbase/cds-mobile/typography';
13
+ import { Group, Path as SkiaPath, Skia } from '@shopify/react-native-skia';
14
+ import { useCartesianChartContext } from '../../ChartProvider';
15
+ import { DefaultLegendEntry, DefaultLegendShape, Legend } from '../../legend';
16
+ import { Path } from '../../Path';
17
+ import { getBarPath } from '../../utils';
18
+ import { getDottedAreaPath } from '../../utils/path';
19
+ import { DefaultBar } from '../DefaultBar';
20
+ import { PercentageBarChart } from '../PercentageBarChart';
21
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
22
+ const DOTTED_BAR_PATTERN_SIZE = 4;
23
+ const DOTTED_BAR_DOT_SIZE = 1;
24
+ const DOTTED_BAR_OUTLINE_STROKE_WIDTH = 2;
25
+ const DottedBarComponent = /*#__PURE__*/memo(function DottedBarComponent(props) {
26
+ const {
27
+ x,
28
+ y,
29
+ width,
30
+ height,
31
+ fill,
32
+ d
33
+ } = props;
34
+ const dottedPath = useMemo(() => getDottedAreaPath({
35
+ x,
36
+ y,
37
+ width,
38
+ height
39
+ }, DOTTED_BAR_PATTERN_SIZE, DOTTED_BAR_DOT_SIZE), [x, y, width, height]);
40
+ const barClipPath = useMemo(() => {
41
+ var _Skia$Path$MakeFromSV;
42
+ return d ? (_Skia$Path$MakeFromSV = Skia.Path.MakeFromSVGString(d)) != null ? _Skia$Path$MakeFromSV : undefined : undefined;
43
+ }, [d]);
44
+ const dotsSkiaPath = useMemo(() => {
45
+ var _Skia$Path$MakeFromSV2;
46
+ return dottedPath ? (_Skia$Path$MakeFromSV2 = Skia.Path.MakeFromSVGString(dottedPath)) != null ? _Skia$Path$MakeFromSV2 : undefined : undefined;
47
+ }, [dottedPath]);
48
+ return /*#__PURE__*/_jsxs(_Fragment, {
49
+ children: [/*#__PURE__*/_jsx(Group, {
50
+ clip: barClipPath,
51
+ children: dotsSkiaPath && fill ? /*#__PURE__*/_jsx(SkiaPath, {
52
+ color: fill,
53
+ path: dotsSkiaPath,
54
+ style: "fill"
55
+ }) : null
56
+ }), /*#__PURE__*/_jsx(DefaultBar, _extends({}, props, {
57
+ fill: undefined,
58
+ stroke: fill,
59
+ strokeWidth: DOTTED_BAR_OUTLINE_STROKE_WIDTH
60
+ }))]
61
+ });
62
+ });
63
+
64
+ /**
65
+ * Builds an SVG path for a horizontal bar segment with a pill cap on one end
66
+ * and a slanted straight edge on the other. The two segments' inner edges
67
+ * are parallel, producing a parallelogram-shaped gap between them.
68
+ */
69
+ function getSlantedHorizontalBarPath(x, y, width, height, borderRadius, pillLeft, pillRight, slantDx) {
70
+ if (width <= 0 || height <= 0) return undefined;
71
+ if (pillLeft === pillRight) return undefined;
72
+ const r = Math.min(borderRadius, height / 2, width / 2);
73
+ const s = Math.min(Math.max(0, slantDx), width - r * 2);
74
+ const x0 = x;
75
+ const x1 = x + width;
76
+ const y0 = y;
77
+ const y1 = y + height;
78
+
79
+ // Pill left, slanted right
80
+ if (pillLeft && !pillRight) {
81
+ return ["M " + (x0 + r) + " " + y0, "L " + x1 + " " + y0, "L " + (x1 - s) + " " + y1, "L " + (x0 + r) + " " + y1, "A " + r + " " + r + " 0 0 1 " + x0 + " " + (y1 - r), "L " + x0 + " " + (y0 + r), "A " + r + " " + r + " 0 0 1 " + (x0 + r) + " " + y0, 'Z'].join(' ');
82
+ }
83
+
84
+ // Slanted left, pill right
85
+ if (!pillLeft && pillRight) {
86
+ return ["M " + (x0 + s) + " " + y0, "L " + (x1 - r) + " " + y0, "A " + r + " " + r + " 0 0 1 " + x1 + " " + (y0 + r), "L " + x1 + " " + (y1 - r), "A " + r + " " + r + " 0 0 1 " + (x1 - r) + " " + y1, "L " + x0 + " " + y1, 'Z'].join(' ');
87
+ }
88
+ return undefined;
89
+ }
90
+ const SLANT_DX = 8;
91
+ const BASELINE_THRESHOLD = 1;
92
+ const SlantedStackBar = /*#__PURE__*/memo(function SlantedStackBar(props) {
93
+ const {
94
+ layout
95
+ } = useCartesianChartContext();
96
+ const {
97
+ x,
98
+ y,
99
+ width,
100
+ height,
101
+ borderRadius = 4,
102
+ roundTop,
103
+ roundBottom,
104
+ d: defaultD,
105
+ fill,
106
+ fillOpacity,
107
+ dataX
108
+ } = props,
109
+ rest = _objectWithoutPropertiesLoose(props, _excluded);
110
+ const d = useMemo(() => {
111
+ var _ref, _getSlantedHorizontal;
112
+ if (layout !== 'horizontal') {
113
+ return defaultD != null ? defaultD : getBarPath(x, y, width, height, borderRadius, !!roundTop, !!roundBottom, layout);
114
+ }
115
+ const isLeftmost = Array.isArray(dataX) && Math.abs(dataX[0]) < BASELINE_THRESHOLD;
116
+ return (_ref = (_getSlantedHorizontal = getSlantedHorizontalBarPath(x, y, width, height, borderRadius, isLeftmost, !isLeftmost, SLANT_DX)) != null ? _getSlantedHorizontal : defaultD) != null ? _ref : getBarPath(x, y, width, height, borderRadius, !!roundTop, !!roundBottom, layout);
117
+ }, [layout, defaultD, dataX, x, y, width, height, borderRadius, roundTop, roundBottom]);
118
+ if (!d) return null;
119
+ return /*#__PURE__*/_jsx(Path, _extends({}, rest, {
120
+ animate: true,
121
+ clipPath: null,
122
+ d: d,
123
+ fill: fill,
124
+ fillOpacity: fillOpacity,
125
+ transitions: props.transitions
126
+ }));
127
+ });
128
+ const Basics = () => {
129
+ const theme = useTheme();
130
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
131
+ height: 16,
132
+ series: [{
133
+ id: 'a',
134
+ data: 70,
135
+ label: 'Segment A',
136
+ color: theme.color.fgPositive
137
+ }, {
138
+ id: 'b',
139
+ data: 45,
140
+ label: 'Segment B',
141
+ color: theme.color.fgNegative
142
+ }]
143
+ });
144
+ };
145
+ const StackGap = () => {
146
+ const theme = useTheme();
147
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
148
+ height: 20,
149
+ series: [{
150
+ id: 'a',
151
+ data: 40,
152
+ label: 'A',
153
+ color: theme.color.fgPositive
154
+ }, {
155
+ id: 'b',
156
+ data: 35,
157
+ label: 'B',
158
+ color: theme.color.fgWarning
159
+ }, {
160
+ id: 'c',
161
+ data: 20,
162
+ label: 'C',
163
+ color: theme.color.accentBoldPurple
164
+ }],
165
+ stackGap: 6
166
+ });
167
+ };
168
+ const BorderRadius = () => {
169
+ const theme = useTheme();
170
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
171
+ borderRadius: 1000,
172
+ height: 28,
173
+ series: [{
174
+ id: 'a',
175
+ data: 45,
176
+ color: "rgb(" + theme.spectrum.purple30 + ")",
177
+ label: 'A'
178
+ }, {
179
+ id: 'b',
180
+ data: 30,
181
+ color: "rgb(" + theme.spectrum.blue30 + ")",
182
+ label: 'B'
183
+ }, {
184
+ id: 'c',
185
+ data: 20,
186
+ color: "rgb(" + theme.spectrum.teal30 + ")",
187
+ label: 'C'
188
+ }],
189
+ stackGap: 2
190
+ });
191
+ };
192
+ const DataExample = () => {
193
+ const theme = useTheme();
194
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
195
+ showXAxis: true,
196
+ showYAxis: true,
197
+ barMinSize: 12,
198
+ borderRadius: 8,
199
+ height: 100,
200
+ series: [{
201
+ id: 'a',
202
+ data: [40, null, 20],
203
+ label: 'A',
204
+ color: theme.color.fgPositive
205
+ }, {
206
+ id: 'b',
207
+ data: [-10, 60, 30],
208
+ label: 'B',
209
+ color: theme.color.fgWarning
210
+ }, {
211
+ id: 'c',
212
+ data: [null, 50],
213
+ label: 'C',
214
+ color: theme.color.fgMuted
215
+ }, {
216
+ id: 'd',
217
+ data: 45,
218
+ label: 'D',
219
+ color: theme.color.fgNegative
220
+ }],
221
+ stackGap: 2,
222
+ xAxis: {
223
+ showTickMarks: true
224
+ },
225
+ yAxis: {
226
+ data: ['Q1', 'Q2', 'Q3'],
227
+ position: 'left',
228
+ categoryPadding: 0.45
229
+ }
230
+ });
231
+ };
232
+ const BarStackSpacing = () => {
233
+ const theme = useTheme();
234
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
235
+ legend: true,
236
+ showXAxis: true,
237
+ showYAxis: true,
238
+ barMinSize: 18,
239
+ borderRadius: 24,
240
+ height: 240,
241
+ series: [{
242
+ id: 'a',
243
+ data: [55, 40, 35],
244
+ label: 'A',
245
+ color: theme.color.fgWarning
246
+ }, {
247
+ id: 'b',
248
+ data: [30, 45, 25],
249
+ label: 'B',
250
+ color: theme.color.accentBoldPurple
251
+ }, {
252
+ id: 'c',
253
+ data: [15, 15, 40],
254
+ label: 'C',
255
+ color: theme.color.fgMuted
256
+ }],
257
+ stackGap: 4,
258
+ xAxis: {
259
+ showTickMarks: true
260
+ },
261
+ yAxis: {
262
+ data: ['Q1', 'Q2', 'Q3'],
263
+ position: 'left',
264
+ categoryPadding: 0.7
265
+ }
266
+ });
267
+ };
268
+ const MinimumBarSize = () => {
269
+ const theme = useTheme();
270
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
271
+ barMinSize: 16,
272
+ height: 16,
273
+ series: [{
274
+ id: 'a',
275
+ data: 99,
276
+ label: 'Segment A',
277
+ color: theme.color.fgPositive
278
+ }, {
279
+ id: 'b',
280
+ data: 0.001,
281
+ label: 'Segment B',
282
+ color: theme.color.fgNegative
283
+ }],
284
+ stackGap: 2
285
+ });
286
+ };
287
+ const TaxesStyleConfirmedVsNeedReview = () => {
288
+ const theme = useTheme();
289
+ const series = [{
290
+ id: 'confirmed',
291
+ data: 28,
292
+ label: 'Confirmed',
293
+ color: theme.color.fgPositive
294
+ }, {
295
+ id: 'needs-review',
296
+ data: 2,
297
+ label: 'Needs review',
298
+ color: theme.color.fgWarning
299
+ }];
300
+ return /*#__PURE__*/_jsxs(VStack, {
301
+ gap: 2,
302
+ paddingX: 2,
303
+ children: [/*#__PURE__*/_jsxs(VStack, {
304
+ gap: 0.5,
305
+ children: [/*#__PURE__*/_jsx(Text, {
306
+ color: "fgMuted",
307
+ font: "label2",
308
+ children: "Estimated gain"
309
+ }), /*#__PURE__*/_jsx(Text, {
310
+ font: "title2",
311
+ children: "+$30,000"
312
+ })]
313
+ }), /*#__PURE__*/_jsx(PercentageBarChart, {
314
+ height: 24,
315
+ series: series,
316
+ stackGap: 4
317
+ }), /*#__PURE__*/_jsxs(VStack, {
318
+ children: [/*#__PURE__*/_jsxs(HStack, {
319
+ alignItems: "center",
320
+ gap: 1,
321
+ justifyContent: "space-between",
322
+ children: [/*#__PURE__*/_jsxs(HStack, {
323
+ alignItems: "center",
324
+ gap: 1,
325
+ children: [/*#__PURE__*/_jsx(DefaultLegendShape, {
326
+ color: theme.color.fgPositive,
327
+ shape: "squircle"
328
+ }), /*#__PURE__*/_jsx(Text, {
329
+ font: "label1",
330
+ children: "Confirmed"
331
+ })]
332
+ }), /*#__PURE__*/_jsxs(HStack, {
333
+ alignItems: "center",
334
+ gap: 1,
335
+ children: [/*#__PURE__*/_jsx(Text, {
336
+ font: "body",
337
+ children: "+$28,000"
338
+ }), /*#__PURE__*/_jsx(IconButton, {
339
+ compact: true,
340
+ transparent: true,
341
+ accessibilityLabel: "Confirmed details",
342
+ name: "caretRight",
343
+ onPress: () => {},
344
+ variant: "foregroundMuted"
345
+ })]
346
+ })]
347
+ }), /*#__PURE__*/_jsxs(HStack, {
348
+ alignItems: "center",
349
+ gap: 1,
350
+ justifyContent: "space-between",
351
+ children: [/*#__PURE__*/_jsxs(HStack, {
352
+ alignItems: "center",
353
+ gap: 1,
354
+ children: [/*#__PURE__*/_jsx(DefaultLegendShape, {
355
+ color: theme.color.fgWarning,
356
+ shape: "squircle"
357
+ }), /*#__PURE__*/_jsx(Text, {
358
+ font: "label1",
359
+ children: "Needs review"
360
+ })]
361
+ }), /*#__PURE__*/_jsxs(HStack, {
362
+ alignItems: "center",
363
+ gap: 1,
364
+ children: [/*#__PURE__*/_jsxs(VStack, {
365
+ alignItems: "flex-end",
366
+ gap: 0,
367
+ children: [/*#__PURE__*/_jsx(Text, {
368
+ font: "body",
369
+ children: "Up to $2,000"
370
+ }), /*#__PURE__*/_jsx(Text, {
371
+ color: "fgMuted",
372
+ font: "body",
373
+ children: "11 transfers"
374
+ })]
375
+ }), /*#__PURE__*/_jsx(IconButton, {
376
+ compact: true,
377
+ transparent: true,
378
+ accessibilityLabel: "Needs review details",
379
+ name: "caretRight",
380
+ onPress: () => {},
381
+ variant: "foregroundMuted"
382
+ })]
383
+ })]
384
+ })]
385
+ })]
386
+ });
387
+ };
388
+ const DottedBarFirstSeriesOnly = () => {
389
+ const theme = useTheme();
390
+ const dottedBarSeries = useMemo(() => [{
391
+ id: 'segment-a',
392
+ data: 60,
393
+ label: 'Segment A',
394
+ color: "rgb(" + theme.spectrum.teal60 + ")",
395
+ BarComponent: DottedBarComponent
396
+ }, {
397
+ id: 'segment-b',
398
+ data: 30,
399
+ label: 'Segment B',
400
+ color: "rgb(" + theme.spectrum.chartreuse50 + ")"
401
+ }, {
402
+ id: 'segment-c',
403
+ data: 10,
404
+ label: 'Segment C',
405
+ color: "rgb(" + theme.spectrum.indigo40 + ")"
406
+ }], [theme]);
407
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
408
+ height: 24,
409
+ series: dottedBarSeries,
410
+ stackGap: 4
411
+ });
412
+ };
413
+ const DottedBarChartLevel = () => {
414
+ const theme = useTheme();
415
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
416
+ BarComponent: DottedBarComponent,
417
+ height: 24,
418
+ series: [{
419
+ id: 'segment-a',
420
+ data: 60,
421
+ label: 'Segment A',
422
+ color: "rgb(" + theme.spectrum.teal60 + ")"
423
+ }, {
424
+ id: 'segment-b',
425
+ data: 30,
426
+ label: 'Segment B',
427
+ color: "rgb(" + theme.spectrum.chartreuse50 + ")"
428
+ }, {
429
+ id: 'segment-c',
430
+ data: 10,
431
+ label: 'Segment C',
432
+ color: "rgb(" + theme.spectrum.indigo40 + ")"
433
+ }],
434
+ stackGap: 4
435
+ });
436
+ };
437
+ function randomShares() {
438
+ const raw = [Math.random() + 0.1, Math.random() + 0.1, Math.random() + 0.1];
439
+ const sum = raw[0] + raw[1] + raw[2];
440
+ return raw.map(v => Math.max(1, Math.round(v / sum * 100)));
441
+ }
442
+ function generateAnimationData() {
443
+ return [randomShares(), randomShares(), randomShares()];
444
+ }
445
+ const Animations = () => {
446
+ const theme = useTheme();
447
+ const [animate, setAnimate] = useState(true);
448
+ const [data, setData] = useState(generateAnimationData);
449
+ useEffect(() => {
450
+ const id = setInterval(() => setData(generateAnimationData()), 800);
451
+ return () => clearInterval(id);
452
+ }, []);
453
+ const series = useMemo(() => [{
454
+ id: 'btc',
455
+ data: data.map(q => q[0]),
456
+ label: 'BTC',
457
+ color: assets.btc.color
458
+ }, {
459
+ id: 'eth',
460
+ data: data.map(q => q[1]),
461
+ label: 'ETH',
462
+ color: assets.eth.color
463
+ }, {
464
+ id: 'other',
465
+ data: data.map(q => q[2]),
466
+ label: 'Other',
467
+ color: theme.color.fgMuted
468
+ }], [data, theme]);
469
+ return /*#__PURE__*/_jsxs(VStack, {
470
+ gap: 2,
471
+ children: [/*#__PURE__*/_jsx(HStack, {
472
+ alignItems: "center",
473
+ gap: 1,
474
+ justifyContent: "flex-end",
475
+ children: /*#__PURE__*/_jsx(Switch, {
476
+ checked: animate,
477
+ onChange: () => setAnimate(v => !v),
478
+ children: "Animate"
479
+ })
480
+ }), /*#__PURE__*/_jsx(PercentageBarChart, {
481
+ legend: true,
482
+ showXAxis: true,
483
+ showYAxis: true,
484
+ animate: animate,
485
+ barMinSize: 14,
486
+ borderRadius: 48,
487
+ height: 220,
488
+ inset: {
489
+ left: 24,
490
+ right: 0,
491
+ top: 0,
492
+ bottom: 0
493
+ },
494
+ legendPosition: "top",
495
+ series: series,
496
+ stackGap: 2,
497
+ transitions: {
498
+ enter: {
499
+ type: 'timing',
500
+ duration: 400,
501
+ staggerDelay: 0.2
502
+ },
503
+ update: {
504
+ type: 'timing',
505
+ duration: 300
506
+ }
507
+ },
508
+ xAxis: {
509
+ showTickMarks: true,
510
+ tickLabelFormatter: value => value + "%"
511
+ },
512
+ yAxis: {
513
+ categoryPadding: 0.75,
514
+ data: ['Q1 2025', 'Q2 2025', 'Q3 2025'],
515
+ position: 'left',
516
+ requestedTickCount: 5,
517
+ showTickMarks: true
518
+ }
519
+ })]
520
+ });
521
+ };
522
+
523
+ /** Fake "projected value" copy: scales with live % so subtitles stay in sync with the bar. */
524
+ const liveFeedSubtitleBase = 100;
525
+ const liveFeedYesDollarsPerPercentPoint = (182 - liveFeedSubtitleBase) / 50;
526
+ const liveFeedNoDollarsPerPercentPoint = (222 - liveFeedSubtitleBase) / 50;
527
+ function getLiveFeedProjectedValue(seriesId, percentage) {
528
+ const inverseShare = 100 - percentage;
529
+ if (seriesId === 'yes') {
530
+ return Math.round(liveFeedSubtitleBase + inverseShare * liveFeedYesDollarsPerPercentPoint);
531
+ }
532
+ if (seriesId === 'no') {
533
+ return Math.round(liveFeedSubtitleBase + inverseShare * liveFeedNoDollarsPerPercentPoint);
534
+ }
535
+ return undefined;
536
+ }
537
+ const liveFeedCurrencyFormat = {
538
+ style: 'currency',
539
+ currency: 'USD',
540
+ maximumFractionDigits: 0
541
+ };
542
+ const LiveFeedCTALegendEntry = /*#__PURE__*/memo(function LiveFeedCTALegendEntry(_ref2) {
543
+ var _, _seriesData$data;
544
+ let {
545
+ seriesId,
546
+ label,
547
+ color
548
+ } = _ref2;
549
+ const {
550
+ series: contextSeries
551
+ } = useCartesianChartContext();
552
+ const seriesData = contextSeries.find(s => s.id === seriesId);
553
+ const percentage = (_ = seriesData == null || (_seriesData$data = seriesData.data) == null ? void 0 : _seriesData$data[0]) != null ? _ : 0;
554
+ const projectedValue = getLiveFeedProjectedValue(seriesId, percentage);
555
+ return /*#__PURE__*/_jsx(Box, {
556
+ paddingX: 2,
557
+ paddingY: 1,
558
+ style: {
559
+ backgroundColor: color,
560
+ borderRadius: 200
561
+ },
562
+ children: /*#__PURE__*/_jsxs(VStack, {
563
+ alignItems: "center",
564
+ gap: 0.25,
565
+ children: [/*#__PURE__*/_jsxs(HStack, {
566
+ alignItems: "center",
567
+ gap: 0.5,
568
+ children: [/*#__PURE__*/_jsxs(Text, {
569
+ color: "fgInverse",
570
+ font: "label1",
571
+ children: [label, " ", '· ']
572
+ }), /*#__PURE__*/_jsx(RollingNumber, {
573
+ color: "fgInverse",
574
+ font: "label1",
575
+ format: {
576
+ style: 'percent',
577
+ maximumFractionDigits: 0
578
+ },
579
+ value: percentage / 100
580
+ })]
581
+ }), projectedValue != null && /*#__PURE__*/_jsxs(HStack, {
582
+ alignItems: "center",
583
+ gap: 0.5,
584
+ children: [/*#__PURE__*/_jsxs(Text, {
585
+ color: "fgInverse",
586
+ font: "legal",
587
+ children: ["$", liveFeedSubtitleBase, " \u2192"]
588
+ }), /*#__PURE__*/_jsx(RollingNumber, {
589
+ color: "fgInverse",
590
+ font: "legal",
591
+ format: liveFeedCurrencyFormat,
592
+ value: projectedValue
593
+ })]
594
+ })]
595
+ })
596
+ });
597
+ });
598
+ const LiveUpdatingData = () => {
599
+ const theme = useTheme();
600
+ const [tick, setTick] = useState(0);
601
+ const yesValue = 50 + Math.sin(tick * 0.05) * 49;
602
+ const noValue = 50 - Math.sin(tick * 0.05) * 49;
603
+ const series = [{
604
+ id: 'yes',
605
+ data: yesValue,
606
+ label: 'Yes',
607
+ color: theme.color.fgPositive
608
+ }, {
609
+ id: 'no',
610
+ data: noValue,
611
+ label: 'No',
612
+ color: theme.color.fgNegative
613
+ }];
614
+ useEffect(() => {
615
+ const id = setInterval(() => setTick(t => t + 4), 1000);
616
+ return () => clearInterval(id);
617
+ }, []);
618
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
619
+ barMinSize: 16,
620
+ borderRadius: 1000,
621
+ height: 78,
622
+ legend: /*#__PURE__*/_jsx(Legend, {
623
+ EntryComponent: LiveFeedCTALegendEntry,
624
+ justifyContent: "space-evenly",
625
+ paddingTop: 1
626
+ }),
627
+ legendPosition: "bottom",
628
+ series: series,
629
+ stackGap: 2
630
+ });
631
+ };
632
+ const VerticalMix = () => {
633
+ const theme = useTheme();
634
+ const series = [{
635
+ id: 'btc',
636
+ data: [55, 52, 48, 45, 50, 58, 62, 57, 53, 49, 44, 46],
637
+ label: 'BTC',
638
+ color: assets.btc.color
639
+ }, {
640
+ id: 'eth',
641
+ data: [30, 33, 35, 38, 32, 27, 25, 29, 34, 37, 40, 38],
642
+ label: 'ETH',
643
+ color: assets.eth.color
644
+ }, {
645
+ id: 'other',
646
+ data: [15, 15, 17, 17, 18, 15, 13, 14, 13, 14, 16, 16],
647
+ label: 'Other',
648
+ color: theme.color.fgMuted
649
+ }];
650
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
651
+ legend: true,
652
+ showXAxis: true,
653
+ showYAxis: true,
654
+ barMinSize: 28,
655
+ borderRadius: 48,
656
+ height: 240,
657
+ layout: "vertical",
658
+ legendPosition: "top",
659
+ series: series,
660
+ stackGap: 1,
661
+ xAxis: {
662
+ categoryPadding: 0.5,
663
+ data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
664
+ position: 'bottom',
665
+ showTickMarks: true
666
+ }
667
+ });
668
+ };
669
+ const BuyVsSellLegend = /*#__PURE__*/memo(function BuyVsSellLegend(_ref3) {
670
+ let {
671
+ series
672
+ } = _ref3;
673
+ const [buy, sell] = series;
674
+ return /*#__PURE__*/_jsxs(HStack, {
675
+ gap: 1,
676
+ justifyContent: "space-between",
677
+ children: [/*#__PURE__*/_jsx(DefaultLegendEntry, {
678
+ color: buy.color,
679
+ label: /*#__PURE__*/_jsxs(Text, {
680
+ color: "fgMuted",
681
+ font: "legal",
682
+ children: [buy.data, "% bought"]
683
+ }),
684
+ seriesId: buy.id,
685
+ shape: buy.legendShape
686
+ }), /*#__PURE__*/_jsx(DefaultLegendEntry, {
687
+ color: sell.color,
688
+ label: /*#__PURE__*/_jsxs(Text, {
689
+ color: "fgMuted",
690
+ font: "legal",
691
+ children: [sell.data, "% sold"]
692
+ }),
693
+ seriesId: sell.id,
694
+ shape: sell.legendShape
695
+ })]
696
+ });
697
+ });
698
+ const BuyVsSell = () => {
699
+ const theme = useTheme();
700
+ const buySellSeries = useMemo(() => [{
701
+ id: 'buy',
702
+ data: 76,
703
+ color: theme.color.fgPositive,
704
+ legendShape: 'circle'
705
+ }, {
706
+ id: 'sell',
707
+ data: 24,
708
+ color: theme.color.fgNegative,
709
+ legendShape: 'square'
710
+ }], [theme]);
711
+ return /*#__PURE__*/_jsxs(VStack, {
712
+ gap: 1.5,
713
+ children: [/*#__PURE__*/_jsx(PercentageBarChart, {
714
+ barMinSize: 8,
715
+ borderRadius: 24,
716
+ height: 8,
717
+ series: buySellSeries,
718
+ stackGap: 4
719
+ }), /*#__PURE__*/_jsx(BuyVsSellLegend, {
720
+ series: buySellSeries
721
+ })]
722
+ });
723
+ };
724
+ const SlantedStackGap = () => {
725
+ const theme = useTheme();
726
+ return /*#__PURE__*/_jsx(PercentageBarChart, {
727
+ BarComponent: SlantedStackBar,
728
+ animate: false,
729
+ barMinSize: 12,
730
+ borderRadius: 24,
731
+ height: 12,
732
+ series: [{
733
+ id: 'team-a',
734
+ data: 40,
735
+ color: "rgb(" + theme.spectrum.teal60 + ")"
736
+ }, {
737
+ id: 'team-b',
738
+ data: 61,
739
+ color: theme.color.accentBoldBlue
740
+ }]
741
+ });
742
+ };
743
+ function ExampleNavigator() {
744
+ const [currentIndex, setCurrentIndex] = useState(0);
745
+ const examples = useMemo(() => [{
746
+ title: 'Basics',
747
+ component: /*#__PURE__*/_jsx(Basics, {})
748
+ }, {
749
+ title: 'Stack Gap',
750
+ component: /*#__PURE__*/_jsx(StackGap, {})
751
+ }, {
752
+ title: 'Border Radius',
753
+ component: /*#__PURE__*/_jsx(BorderRadius, {})
754
+ }, {
755
+ title: 'Sparse Data',
756
+ component: /*#__PURE__*/_jsx(DataExample, {})
757
+ }, {
758
+ title: 'Bar Stack Spacing',
759
+ component: /*#__PURE__*/_jsx(BarStackSpacing, {})
760
+ }, {
761
+ title: 'Minimum Bar Size',
762
+ component: /*#__PURE__*/_jsx(MinimumBarSize, {})
763
+ }, {
764
+ title: 'Taxes style',
765
+ component: /*#__PURE__*/_jsx(TaxesStyleConfirmedVsNeedReview, {})
766
+ }, {
767
+ title: 'Slanted stack gap',
768
+ component: /*#__PURE__*/_jsx(SlantedStackGap, {})
769
+ }, {
770
+ title: 'Dotted bar (first series only)',
771
+ component: /*#__PURE__*/_jsx(DottedBarFirstSeriesOnly, {})
772
+ }, {
773
+ title: 'Dotted bar (chart-level)',
774
+ component: /*#__PURE__*/_jsx(DottedBarChartLevel, {})
775
+ }, {
776
+ title: 'Animations',
777
+ component: /*#__PURE__*/_jsx(Animations, {})
778
+ }, {
779
+ title: 'Live-updating Data',
780
+ component: /*#__PURE__*/_jsx(LiveUpdatingData, {})
781
+ }, {
782
+ title: 'Vertical Mix',
783
+ component: /*#__PURE__*/_jsx(VerticalMix, {})
784
+ }, {
785
+ title: 'Buy vs Sell',
786
+ component: /*#__PURE__*/_jsx(BuyVsSell, {})
787
+ }], []);
788
+ const currentExample = examples[currentIndex];
789
+ const handlePrevious = useCallback(() => {
790
+ setCurrentIndex(prev => (prev - 1 + examples.length) % examples.length);
791
+ }, [examples.length]);
792
+ const handleNext = useCallback(() => {
793
+ setCurrentIndex(prev => (prev + 1 + examples.length) % examples.length);
794
+ }, [examples.length]);
795
+ return /*#__PURE__*/_jsx(ExampleScreen, {
796
+ paddingX: 0,
797
+ children: /*#__PURE__*/_jsxs(VStack, {
798
+ gap: 4,
799
+ children: [/*#__PURE__*/_jsxs(HStack, {
800
+ alignItems: "center",
801
+ justifyContent: "space-between",
802
+ padding: 2,
803
+ children: [/*#__PURE__*/_jsx(IconButton, {
804
+ accessibilityHint: "Navigate to previous example",
805
+ accessibilityLabel: "Previous",
806
+ name: "arrowLeft",
807
+ onPress: handlePrevious,
808
+ variant: "secondary"
809
+ }), /*#__PURE__*/_jsxs(VStack, {
810
+ alignItems: "center",
811
+ children: [/*#__PURE__*/_jsx(Text, {
812
+ font: "title3",
813
+ children: currentExample.title
814
+ }), /*#__PURE__*/_jsxs(Text, {
815
+ color: "fgMuted",
816
+ font: "label1",
817
+ children: [currentIndex + 1, " / ", examples.length]
818
+ })]
819
+ }), /*#__PURE__*/_jsx(IconButton, {
820
+ accessibilityHint: "Navigate to next example",
821
+ accessibilityLabel: "Next",
822
+ name: "arrowRight",
823
+ onPress: handleNext,
824
+ variant: "secondary"
825
+ })]
826
+ }), /*#__PURE__*/_jsx(Box, {
827
+ padding: 1,
828
+ children: currentExample.component
829
+ })]
830
+ })
831
+ });
832
+ }
833
+ export default ExampleNavigator;