panelui-native 0.38.0 → 0.41.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.
- package/README.md +3 -1
- package/lib/module/components/card/index.js +172 -3
- package/lib/module/components/card/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +13 -4
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +113 -6
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +72 -12
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/pagination/index.js +529 -0
- package/lib/module/components/pagination/index.js.map +1 -0
- package/lib/module/components/panelside/index.js +68 -18
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/ring-chart/index.js +183 -45
- package/lib/module/components/ring-chart/index.js.map +1 -1
- package/lib/module/components/steps/index.js +8 -6
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/table/index.js +100 -26
- package/lib/module/components/table/index.js.map +1 -1
- package/lib/module/components/text-animation/index.js +857 -0
- package/lib/module/components/text-animation/index.js.map +1 -0
- package/lib/module/components/timeline/index.js +8 -7
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/icons/index.js +25 -7
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/card/index.d.ts +20 -0
- package/lib/typescript/src/components/card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +13 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +51 -3
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +37 -2
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pagination/index.d.ts +285 -0
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -0
- package/lib/typescript/src/components/panelside/index.d.ts +50 -4
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ring-chart/index.d.ts +75 -8
- package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/index.d.ts +8 -6
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/table/index.d.ts +59 -17
- package/lib/typescript/src/components/table/index.d.ts.map +1 -1
- package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
- package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +8 -7
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +6 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +6 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/card/index.tsx +227 -2
- package/src/components/drawer/index.tsx +31 -9
- package/src/components/heatmap-chart/index.tsx +147 -5
- package/src/components/map/index.tsx +158 -14
- package/src/components/pagination/index.tsx +603 -0
- package/src/components/panelside/index.tsx +134 -19
- package/src/components/ring-chart/index.tsx +269 -47
- package/src/components/steps/index.tsx +8 -6
- package/src/components/table/index.tsx +146 -30
- package/src/components/text-animation/index.tsx +1093 -0
- package/src/components/timeline/index.tsx +8 -7
- package/src/icons/index.tsx +23 -4
- package/src/index.ts +28 -1
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ```tsx
|
|
5
5
|
* <RingChart data={goals}>
|
|
6
|
+
* <RingChart.Header title="Today" value="486 kcal" legend />
|
|
6
7
|
* <RingChart.Ring index={0} />
|
|
7
8
|
* <RingChart.Ring index={1} />
|
|
8
9
|
* <RingChart.Center />
|
|
9
|
-
* <RingChart.Legend />
|
|
10
10
|
* </RingChart>
|
|
11
11
|
* ```
|
|
12
12
|
*
|
|
@@ -25,11 +25,15 @@
|
|
|
25
25
|
*
|
|
26
26
|
* ## Drawing
|
|
27
27
|
*
|
|
28
|
-
* Each ring is two arcs — a
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* Each ring is two arcs — a track and the progress over it — with only the
|
|
29
|
+
* progress animated, through `strokeDasharray`. Sweeping the arc by rebuilding
|
|
30
|
+
* its path would work, but a dash offset is two numbers moving on an unchanged
|
|
31
|
+
* path, and it keeps the rounded cap pinned to the moving end for free.
|
|
32
|
+
*
|
|
33
|
+
* The same dash pattern is what makes the other two shapes possible without any
|
|
34
|
+
* more geometry. An open gauge is the pattern cut short of the circumference
|
|
35
|
+
* and the whole circle turned to put the gap where it is wanted; a segmented
|
|
36
|
+
* ring is the pattern repeated, one pair per tick. Both stay one `Circle`.
|
|
33
37
|
*
|
|
34
38
|
* Touch, not hover: a ring is selected by pressing it, and pressing the same
|
|
35
39
|
* one again clears the selection. There is no equivalent of a pointer resting
|
|
@@ -65,6 +69,12 @@ import { cn } from '../../utils/cn';
|
|
|
65
69
|
|
|
66
70
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Where a child is drawn. Rings go inside the SVG, the header above it, and
|
|
74
|
+
* everything else over it as ordinary views.
|
|
75
|
+
*/
|
|
76
|
+
type Slot = 'ring' | 'overlay' | 'header';
|
|
77
|
+
|
|
68
78
|
/** One ring per datum, and the datum is the whole of its contract. */
|
|
69
79
|
export interface RingDatum {
|
|
70
80
|
/** Name for the legend and the centre readout. */
|
|
@@ -88,6 +98,10 @@ interface RingChartContextValue {
|
|
|
88
98
|
activeIndex: number;
|
|
89
99
|
setActiveIndex: (index: number) => void;
|
|
90
100
|
radiusOf: (index: number) => number;
|
|
101
|
+
/** Where the arcs begin, in degrees clockwise from twelve o'clock. */
|
|
102
|
+
startAngle: number;
|
|
103
|
+
/** How far they run, as a fraction of a full turn. 1 is a closed ring. */
|
|
104
|
+
arc: number;
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
const RingChartContext = createContext<RingChartContextValue | null>(null);
|
|
@@ -119,6 +133,17 @@ export interface RingChartProps extends ViewProps {
|
|
|
119
133
|
strokeWidth?: number;
|
|
120
134
|
/** Gap between one ring and the next. */
|
|
121
135
|
ringGap?: number;
|
|
136
|
+
/**
|
|
137
|
+
* Where the arcs begin, in degrees clockwise from twelve o'clock. `0` is the
|
|
138
|
+
* top, `90` the right-hand side.
|
|
139
|
+
*/
|
|
140
|
+
startAngle?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Where they end, on the same clock. Leaving a turn's worth between the two
|
|
143
|
+
* gives a closed ring; anything less leaves a gap and reads as a gauge —
|
|
144
|
+
* `startAngle={-90} endAngle={90}` is the half circle over the top.
|
|
145
|
+
*/
|
|
146
|
+
endAngle?: number;
|
|
122
147
|
/** Milliseconds for the arcs to sweep in. */
|
|
123
148
|
animationDuration?: number;
|
|
124
149
|
/** Selected ring. Leave unset to let the chart track it. */
|
|
@@ -140,6 +165,8 @@ const RingChartRoot = forwardRef<RingChartHandle, RingChartProps>(function RingC
|
|
|
140
165
|
size,
|
|
141
166
|
strokeWidth = 12,
|
|
142
167
|
ringGap = 6,
|
|
168
|
+
startAngle = 0,
|
|
169
|
+
endAngle = 360,
|
|
143
170
|
animationDuration = 1100,
|
|
144
171
|
activeIndex: activeIndexProp,
|
|
145
172
|
onActiveIndexChange,
|
|
@@ -166,6 +193,13 @@ const RingChartRoot = forwardRef<RingChartHandle, RingChartProps>(function RingC
|
|
|
166
193
|
|
|
167
194
|
const box = size ?? measured;
|
|
168
195
|
|
|
196
|
+
/*
|
|
197
|
+
* The sweep as a fraction of a turn, which is the form every arc length here
|
|
198
|
+
* is wanted in. Clamped to one turn because a ring drawn past 360° laps
|
|
199
|
+
* itself, and clamped above zero because a ring of no length is not a chart.
|
|
200
|
+
*/
|
|
201
|
+
const arc = Math.min(Math.max(endAngle - startAngle, 0), 360) / 360;
|
|
202
|
+
|
|
169
203
|
/*
|
|
170
204
|
* Outermost ring first, so `data[0]` is the one the eye lands on. Each ring
|
|
171
205
|
* inside it steps in by its own thickness plus the gap.
|
|
@@ -231,47 +265,76 @@ const RingChartRoot = forwardRef<RingChartHandle, RingChartProps>(function RingC
|
|
|
231
265
|
activeIndex,
|
|
232
266
|
setActiveIndex,
|
|
233
267
|
radiusOf,
|
|
268
|
+
startAngle,
|
|
269
|
+
arc,
|
|
234
270
|
}),
|
|
235
|
-
[
|
|
271
|
+
[
|
|
272
|
+
data,
|
|
273
|
+
box,
|
|
274
|
+
strokeWidth,
|
|
275
|
+
ringGap,
|
|
276
|
+
colors,
|
|
277
|
+
reveal,
|
|
278
|
+
activeIndex,
|
|
279
|
+
setActiveIndex,
|
|
280
|
+
radiusOf,
|
|
281
|
+
startAngle,
|
|
282
|
+
arc,
|
|
283
|
+
]
|
|
236
284
|
);
|
|
237
285
|
|
|
238
286
|
const rings: ReactNode[] = [];
|
|
239
287
|
const overlay: ReactNode[] = [];
|
|
288
|
+
const header: ReactNode[] = [];
|
|
240
289
|
Children.forEach(children, (child, index) => {
|
|
241
290
|
if (!isValidElement(child)) return;
|
|
242
|
-
const slot = (child.type as { slot?:
|
|
243
|
-
(slot === 'ring' ? rings : overlay).push(
|
|
291
|
+
const slot = (child.type as { slot?: Slot }).slot ?? 'overlay';
|
|
292
|
+
(slot === 'ring' ? rings : slot === 'header' ? header : overlay).push(
|
|
244
293
|
<ChildSlot key={index}>{child}</ChildSlot>
|
|
245
294
|
);
|
|
246
295
|
});
|
|
247
296
|
|
|
248
297
|
return (
|
|
249
298
|
<RingChartContext.Provider value={context}>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
299
|
+
{/*
|
|
300
|
+
* Two views, because the header is not part of the plot. The square and
|
|
301
|
+
* the layout measurement belong to the drawing area alone — measured on
|
|
302
|
+
* the outer view they would take in the header too, and the rings would
|
|
303
|
+
* be laid out inside a box taller than the one they are drawn in.
|
|
304
|
+
*/}
|
|
305
|
+
<View {...props} style={props.style} className={cn('w-full', className)}>
|
|
306
|
+
{header}
|
|
307
|
+
{/*
|
|
308
|
+
* Given a size the plot is that square and sits in the middle of
|
|
309
|
+
* whatever it was handed; left to measure, it takes the width and is
|
|
310
|
+
* as tall as it is wide. `size` is the usual answer inside a card —
|
|
311
|
+
* a full-width square is twice the height the other charts take.
|
|
312
|
+
*/}
|
|
313
|
+
<View
|
|
314
|
+
onLayout={onLayout}
|
|
315
|
+
style={size ? { width: size, height: size } : { aspectRatio: 1 }}
|
|
316
|
+
className={cn('items-center justify-center', size ? 'self-center' : 'w-full')}
|
|
317
|
+
>
|
|
318
|
+
{box > 0 ? (
|
|
319
|
+
<>
|
|
320
|
+
<Svg width={box} height={box}>
|
|
321
|
+
{rings}
|
|
322
|
+
</Svg>
|
|
323
|
+
{/*
|
|
324
|
+
* The centre and the legend sit over the SVG rather than inside
|
|
325
|
+
* it: both are text, and SVG text ignores the platform's text
|
|
326
|
+
* scaling and the theme's font.
|
|
327
|
+
*/}
|
|
328
|
+
<View
|
|
329
|
+
pointerEvents="box-none"
|
|
330
|
+
style={{ position: 'absolute', width: box, height: box }}
|
|
331
|
+
className="items-center justify-center"
|
|
332
|
+
>
|
|
333
|
+
{overlay}
|
|
334
|
+
</View>
|
|
335
|
+
</>
|
|
336
|
+
) : null}
|
|
337
|
+
</View>
|
|
275
338
|
</View>
|
|
276
339
|
</RingChartContext.Provider>
|
|
277
340
|
);
|
|
@@ -289,10 +352,23 @@ export interface RingChartRingProps {
|
|
|
289
352
|
color?: string;
|
|
290
353
|
/** Which of the five chart tokens to take, when the datum names no colour. */
|
|
291
354
|
colorIndex?: SeriesColorIndex;
|
|
292
|
-
/**
|
|
355
|
+
/**
|
|
356
|
+
* Rounded ends, or square ones. Defaults to round, and to square when the
|
|
357
|
+
* ring is segmented — a rounded cap on a tick as long as it is wide draws a
|
|
358
|
+
* lozenge rather than a tick.
|
|
359
|
+
*/
|
|
293
360
|
lineCap?: 'round' | 'butt';
|
|
294
361
|
/** Opacity of the track behind the arc. */
|
|
295
362
|
trackOpacity?: number;
|
|
363
|
+
/**
|
|
364
|
+
* Break the ring into this many ticks, lit one at a time as the value
|
|
365
|
+
* climbs. For a target made of countable things — eight of twelve sessions
|
|
366
|
+
* reads off ticks you can count, and off a smooth arc only as "about two
|
|
367
|
+
* thirds".
|
|
368
|
+
*/
|
|
369
|
+
segments?: number;
|
|
370
|
+
/** Gap between one tick and the next, in points. */
|
|
371
|
+
segmentGap?: number;
|
|
296
372
|
}
|
|
297
373
|
|
|
298
374
|
/**
|
|
@@ -308,11 +384,23 @@ function RingChartRing({
|
|
|
308
384
|
index,
|
|
309
385
|
color,
|
|
310
386
|
colorIndex,
|
|
311
|
-
lineCap
|
|
387
|
+
lineCap,
|
|
312
388
|
trackOpacity = 0.15,
|
|
389
|
+
segments,
|
|
390
|
+
segmentGap = 3,
|
|
313
391
|
}: RingChartRingProps) {
|
|
314
|
-
const {
|
|
315
|
-
|
|
392
|
+
const {
|
|
393
|
+
data,
|
|
394
|
+
size,
|
|
395
|
+
strokeWidth,
|
|
396
|
+
colors,
|
|
397
|
+
reveal,
|
|
398
|
+
activeIndex,
|
|
399
|
+
setActiveIndex,
|
|
400
|
+
radiusOf,
|
|
401
|
+
startAngle,
|
|
402
|
+
arc,
|
|
403
|
+
} = useChart('RingChart.Ring');
|
|
316
404
|
|
|
317
405
|
const datum = data[index];
|
|
318
406
|
const tokenColor = useSeriesColor(undefined, colorIndex ?? 1);
|
|
@@ -321,6 +409,14 @@ function RingChartRing({
|
|
|
321
409
|
const radius = radiusOf(index);
|
|
322
410
|
const circumference = 2 * Math.PI * radius;
|
|
323
411
|
const centre = size / 2;
|
|
412
|
+
/* The drawn part of the circle. The whole of it for a closed ring. */
|
|
413
|
+
const arcLength = circumference * arc;
|
|
414
|
+
const ticks = segments && segments > 0 ? Math.floor(segments) : 0;
|
|
415
|
+
/* One tick and the gap after it. The gap is taken out of the tick, not added
|
|
416
|
+
to it, so N ticks still span exactly the arc asked for. */
|
|
417
|
+
const slot = ticks ? arcLength / ticks : 0;
|
|
418
|
+
const dash = ticks ? Math.max(slot - segmentGap, 0.5) : 0;
|
|
419
|
+
const cap = lineCap ?? (ticks ? 'butt' : 'round');
|
|
324
420
|
|
|
325
421
|
const fraction = datum && datum.maxValue > 0 ? datum.value / datum.maxValue : 0;
|
|
326
422
|
// A value past its target fills the ring and stops. Going round twice would
|
|
@@ -337,16 +433,38 @@ function RingChartRing({
|
|
|
337
433
|
|
|
338
434
|
const props = useAnimatedProps(() => {
|
|
339
435
|
const progress = Math.max(0, Math.min(1, (reveal.value - start) / 0.6));
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
436
|
+
if (!ticks) {
|
|
437
|
+
return { strokeDasharray: [arcLength * clamped * progress, circumference] };
|
|
438
|
+
}
|
|
439
|
+
/*
|
|
440
|
+
* A tick is lit or it is not, so the sweep rounds to whole ones. The array
|
|
441
|
+
* stays the same length whatever is lit — an unlit tick is a dash of no
|
|
442
|
+
* length rather than a missing pair — because a dash pattern that changes
|
|
443
|
+
* length between frames is a new pattern, not an animated one.
|
|
444
|
+
*/
|
|
445
|
+
const lit = Math.round(ticks * clamped * progress);
|
|
446
|
+
const pattern: number[] = [];
|
|
447
|
+
for (let tick = 0; tick < ticks; tick += 1) {
|
|
448
|
+
if (tick < lit) pattern.push(dash, slot - dash);
|
|
449
|
+
else pattern.push(0, slot);
|
|
450
|
+
}
|
|
451
|
+
return { strokeDasharray: pattern };
|
|
344
452
|
});
|
|
345
453
|
|
|
346
454
|
const dimmed = activeIndex >= 0 && activeIndex !== index;
|
|
347
455
|
|
|
348
456
|
if (!datum || radius <= 0) return null;
|
|
349
457
|
|
|
458
|
+
/*
|
|
459
|
+
* A circle's stroke starts at three o'clock, so everything is turned back a
|
|
460
|
+
* quarter to put the start at twelve — an arc starting at three reads as a
|
|
461
|
+
* gauge that has already been running. `startAngle` turns it on from there.
|
|
462
|
+
*/
|
|
463
|
+
const rotate = `rotate(${startAngle - 90} ${centre} ${centre})`;
|
|
464
|
+
/* The unlit part of a segmented ring is the gaps between its ticks, so the
|
|
465
|
+
track is ticked too; an open gauge's track stops where its arc stops. */
|
|
466
|
+
const trackDash = ticks ? [dash, slot - dash] : [arcLength, circumference];
|
|
467
|
+
|
|
350
468
|
return (
|
|
351
469
|
<G opacity={dimmed ? 0.35 : 1}>
|
|
352
470
|
<Circle
|
|
@@ -356,7 +474,10 @@ function RingChartRing({
|
|
|
356
474
|
stroke={stroke}
|
|
357
475
|
strokeOpacity={trackOpacity}
|
|
358
476
|
strokeWidth={strokeWidth}
|
|
477
|
+
strokeLinecap={cap}
|
|
478
|
+
strokeDasharray={trackDash}
|
|
359
479
|
fill="none"
|
|
480
|
+
transform={rotate}
|
|
360
481
|
/>
|
|
361
482
|
<AnimatedCircle
|
|
362
483
|
animatedProps={props}
|
|
@@ -365,16 +486,15 @@ function RingChartRing({
|
|
|
365
486
|
r={radius}
|
|
366
487
|
stroke={stroke}
|
|
367
488
|
strokeWidth={strokeWidth}
|
|
368
|
-
strokeLinecap={
|
|
489
|
+
strokeLinecap={cap}
|
|
369
490
|
fill="none"
|
|
370
|
-
|
|
371
|
-
// three reads as a gauge that has already been running.
|
|
372
|
-
transform={`rotate(-90 ${centre} ${centre})`}
|
|
491
|
+
transform={rotate}
|
|
373
492
|
/>
|
|
374
493
|
{/*
|
|
375
494
|
* The touch target is the ring itself, drawn as a transparent stroke
|
|
376
495
|
* over it and made wider than the ring — a twelve-point band is below
|
|
377
|
-
* the size a finger can reliably hit.
|
|
496
|
+
* the size a finger can reliably hit. It follows the sweep rather than
|
|
497
|
+
* closing the circle, so the dead half of a gauge stays dead.
|
|
378
498
|
*/}
|
|
379
499
|
<Circle
|
|
380
500
|
cx={centre}
|
|
@@ -382,7 +502,9 @@ function RingChartRing({
|
|
|
382
502
|
r={radius}
|
|
383
503
|
stroke="transparent"
|
|
384
504
|
strokeWidth={Math.max(strokeWidth, 28)}
|
|
505
|
+
strokeDasharray={[arcLength, circumference]}
|
|
385
506
|
fill="none"
|
|
507
|
+
transform={rotate}
|
|
386
508
|
onPress={() => setActiveIndex(activeIndex === index ? -1 : index)}
|
|
387
509
|
/>
|
|
388
510
|
</G>
|
|
@@ -536,7 +658,107 @@ function RingChartLegend({ className, showValue = true, ...props }: RingChartLeg
|
|
|
536
658
|
RingChartLegend.displayName = 'RingChart.Legend';
|
|
537
659
|
RingChartLegend.slot = 'overlay' as const;
|
|
538
660
|
|
|
661
|
+
export interface RingChartHeaderProps extends ViewProps {
|
|
662
|
+
className?: string;
|
|
663
|
+
/** Small line above the value — what the chart is of. */
|
|
664
|
+
title?: string;
|
|
665
|
+
/** The readout. The largest thing on the card, and the first thing read. */
|
|
666
|
+
value?: string;
|
|
667
|
+
/** One muted line under the value — a period, a comparison, a target. */
|
|
668
|
+
caption?: string;
|
|
669
|
+
/** Prettier names for the rings, keyed by their `label`. */
|
|
670
|
+
labels?: Record<string, string>;
|
|
671
|
+
/**
|
|
672
|
+
* Draw a swatch and a name per ring along the trailing edge. Prefer this to
|
|
673
|
+
* `RingChart.Legend` on a chart that has a header: that legend hangs off the
|
|
674
|
+
* bottom of the square, where it overlaps whatever is under the chart.
|
|
675
|
+
*/
|
|
676
|
+
legend?: boolean;
|
|
677
|
+
/** Trailing slot — a control, a badge, a range picker. Wins over `legend`. */
|
|
678
|
+
children?: ReactNode;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* The strip above the rings: what the chart is of, what it currently reads, and
|
|
683
|
+
* what the colours mean.
|
|
684
|
+
*
|
|
685
|
+
* It belongs to the chart rather than to the card around it because it is about
|
|
686
|
+
* the *rings* — the number changes as one is selected, and the legend is the
|
|
687
|
+
* list the chart itself is holding. The card's header is a caption on the tray
|
|
688
|
+
* the chart sits in; this is the chart introducing itself.
|
|
689
|
+
*
|
|
690
|
+
* The value is not derived here. There is no total to derive: the rings measure
|
|
691
|
+
* different things against different targets. Take it from `onActiveIndexChange`
|
|
692
|
+
* and pass the formatted string down, so one header can show the headline figure
|
|
693
|
+
* when nothing is selected and a ring's own when something is.
|
|
694
|
+
*/
|
|
695
|
+
function RingChartHeader({
|
|
696
|
+
className,
|
|
697
|
+
title,
|
|
698
|
+
value,
|
|
699
|
+
caption,
|
|
700
|
+
labels,
|
|
701
|
+
legend = false,
|
|
702
|
+
children,
|
|
703
|
+
...props
|
|
704
|
+
}: RingChartHeaderProps) {
|
|
705
|
+
const { data, colors } = useChart('RingChart.Header');
|
|
706
|
+
const trailing =
|
|
707
|
+
children ??
|
|
708
|
+
(legend && data.length ? (
|
|
709
|
+
<View className="flex-row flex-wrap items-center justify-end gap-x-3 gap-y-1">
|
|
710
|
+
{data.map((ring, index) => (
|
|
711
|
+
<View key={ring.label} className="flex-row items-center gap-1.5">
|
|
712
|
+
<View
|
|
713
|
+
style={{
|
|
714
|
+
width: 8,
|
|
715
|
+
height: 8,
|
|
716
|
+
borderRadius: 4,
|
|
717
|
+
backgroundColor: colors[index],
|
|
718
|
+
}}
|
|
719
|
+
/>
|
|
720
|
+
<Text size="xs" muted numberOfLines={1}>
|
|
721
|
+
{labels?.[ring.label] ?? ring.label}
|
|
722
|
+
</Text>
|
|
723
|
+
</View>
|
|
724
|
+
))}
|
|
725
|
+
</View>
|
|
726
|
+
) : null);
|
|
727
|
+
|
|
728
|
+
return (
|
|
729
|
+
<View
|
|
730
|
+
{...props}
|
|
731
|
+
className={cn('flex-row items-start justify-between gap-3 pb-3', className)}
|
|
732
|
+
>
|
|
733
|
+
<View className="flex-1 gap-0.5">
|
|
734
|
+
{title ? (
|
|
735
|
+
<Text size="xs" muted>
|
|
736
|
+
{title}
|
|
737
|
+
</Text>
|
|
738
|
+
) : null}
|
|
739
|
+
{value ? (
|
|
740
|
+
<Text size="xl" weight="bold">
|
|
741
|
+
{value}
|
|
742
|
+
</Text>
|
|
743
|
+
) : null}
|
|
744
|
+
{caption ? (
|
|
745
|
+
<Text size="xs" muted>
|
|
746
|
+
{caption}
|
|
747
|
+
</Text>
|
|
748
|
+
) : null}
|
|
749
|
+
</View>
|
|
750
|
+
{/* Shrinkable, unlike a view's default in React Native. Held rigid, a
|
|
751
|
+
three-ring key takes the width it wants and the caption underneath
|
|
752
|
+
the value wraps to two lines to make room for it. */}
|
|
753
|
+
{trailing ? <View className="shrink pt-1">{trailing}</View> : null}
|
|
754
|
+
</View>
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
RingChartHeader.displayName = 'RingChart.Header';
|
|
758
|
+
RingChartHeader.slot = 'header' as const;
|
|
759
|
+
|
|
539
760
|
export const RingChart = Object.assign(RingChartRoot, {
|
|
761
|
+
Header: RingChartHeader,
|
|
540
762
|
Ring: RingChartRing,
|
|
541
763
|
Center: RingChartCenter,
|
|
542
764
|
Legend: RingChartLegend,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Steps — a stepper for multi-step flows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A row or column of numbered stops, each one completed, active, inactive or
|
|
5
|
+
* loading. An item works that state out from its own position against the
|
|
6
|
+
* root's `value` rather than being told it, so the four states stay mutually
|
|
7
|
+
* exclusive and in order — there is no way to write two active steps, or to
|
|
8
|
+
* mark step four done while step three is still pending.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* The state is resolved in JS and passed down through context into `tv()`
|
|
11
|
+
* variants, because React Native has no attribute selectors a stylesheet could
|
|
12
|
+
* key off. Alert and Timeline resolve their states the same way.
|
|
11
13
|
*
|
|
12
14
|
* Steps does not own your flow: it reflects whatever step your app says is
|
|
13
15
|
* active. Pass `value` to control it, or `defaultValue` to let it manage
|