panelui-native 0.61.1 → 0.62.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.
@@ -0,0 +1,719 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * PolarAreaChart — several parts, compared on one measure.
5
+ *
6
+ * ```tsx
7
+ * <PolarAreaChart data={scores}>
8
+ * <PolarAreaChart.Header title="Scores" />
9
+ * <PolarAreaChart.Grid />
10
+ * <PolarAreaChart.Wedges />
11
+ * <PolarAreaChart.Labels />
12
+ * <PolarAreaChart.Legend />
13
+ * </PolarAreaChart>
14
+ * ```
15
+ *
16
+ * ## What it is, against the pie beside it
17
+ *
18
+ * A pie divides one total: the angles are the quantity and they must come to a
19
+ * full turn, so a slice only means anything next to the others. Here every
20
+ * wedge takes the same angle and the *radius* is the quantity, which means the
21
+ * values do not have to add up to anything. Six unrelated readings on one scale
22
+ * are a polar area chart; six parts of one budget are a pie.
23
+ *
24
+ * What it buys over a bar chart is the shape. Bars are easier to read one at a
25
+ * time, and a reader who has to rank the middle three should be given bars. A
26
+ * dial is for the silhouette — which direction the weight sits in, whether one
27
+ * reading runs away from the rest — read at a glance and without a legend walk.
28
+ *
29
+ * ## Radius, and what it overstates
30
+ *
31
+ * `scale` decides what the radius means, and the two answers are not the same
32
+ * chart:
33
+ *
34
+ * - `radius` (the default) puts the value straight on the radius, so a reading
35
+ * can be counted off the rings. The cost is that a wedge worth twice another
36
+ * covers four times the area, and area is what the eye adds up first.
37
+ * - `area` puts it on the square root instead, so the ink is proportional to
38
+ * the value and nothing is overstated. The cost is the rings: they still mark
39
+ * equal steps, but they are no longer equally spaced.
40
+ *
41
+ * The grid is drawn through the same conversion either way, so a ring is always
42
+ * where its value falls rather than where an even split would put it.
43
+ *
44
+ * Touch, not hover: a wedge is selected by pressing it, and pressing the same
45
+ * one again clears the selection.
46
+ */
47
+ import { Children, createContext, forwardRef, isValidElement, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
48
+ import { Pressable, View } from 'react-native';
49
+ import Animated, { Easing, useAnimatedProps, useReducedMotion, useSharedValue, withTiming } from 'react-native-reanimated';
50
+ import Svg, { Circle, G, Line, Path } from 'react-native-svg';
51
+ import { useCSSVariable } from 'uniwind';
52
+ import { Text } from "../../primitives/text.js";
53
+ import { compactNumber, inkOn, polarPoint, seriesColorAt, useSeriesColor, wedgePath } from "../../utils/chart.js";
54
+ import { cn } from "../../utils/cn.js";
55
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
56
+ const AnimatedPath = Animated.createAnimatedComponent(Path);
57
+
58
+ /** How far along its own radius a wedge's label sits. */
59
+ const LABEL_AT = 0.62;
60
+
61
+ /** The side of the invisible box a label is centred in. */
62
+ const LABEL_BOX = 64;
63
+
64
+ /** Below this radius in points a wedge is left unlabelled. */
65
+ const DEFAULT_MIN_LABEL_RADIUS = 34;
66
+ const TOOLTIP_WIDTH = 132;
67
+ const TOOLTIP_HEIGHT = 30;
68
+
69
+ /** Where a child is drawn: inside the SVG, over it, above it, or under it. */
70
+
71
+ /** Whether the chart is showing data or waiting for it. */
72
+
73
+ /** What a wedge's radius stands for. */
74
+
75
+ /** One wedge. Its angle is fixed, so only the value decides how far it reaches. */
76
+
77
+ /** A wedge's place on the dial, in turns clockwise from twelve o'clock. */
78
+
79
+ const PolarAreaChartContext = /*#__PURE__*/createContext(null);
80
+ function useChart(component) {
81
+ const context = useContext(PolarAreaChartContext);
82
+ if (!context) {
83
+ throw new Error(`${component} must be used within a <PolarAreaChart>`);
84
+ }
85
+ return context;
86
+ }
87
+
88
+ /** The selected wedge and its reading, for something rendered inside the chart. */
89
+ export function usePolarAreaChart() {
90
+ const {
91
+ data,
92
+ wedges,
93
+ activeIndex
94
+ } = useChart('usePolarAreaChart');
95
+ return {
96
+ activeIndex,
97
+ activeWedge: activeIndex >= 0 ? data[activeIndex] ?? null : null,
98
+ /** The selected wedge's value against the maximum, 0 to 1. */
99
+ activeFraction: activeIndex >= 0 ? wedges[activeIndex]?.fraction ?? 0 : 0
100
+ };
101
+ }
102
+
103
+ /**
104
+ * The steps a scale is read in, as multiples of a power of ten.
105
+ *
106
+ * Finer than the usual 1/2/5 ladder on purpose. Here the maximum is a *radius*
107
+ * as well as a number: rounding 140 up to 200 leaves the largest wedge stopping
108
+ * at seven tenths of the dial with the outer third of it empty on every chart
109
+ * whose readings happen to start with a one.
110
+ */
111
+ const NICE_STEPS = [1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10];
112
+
113
+ /**
114
+ * The first round number at or above `value`.
115
+ *
116
+ * The outermost ring is the number every other reading is judged against, so it
117
+ * is worth it landing on 150 rather than on 147.
118
+ */
119
+ function niceMax(value) {
120
+ if (!Number.isFinite(value) || value <= 0) return 1;
121
+ const magnitude = 10 ** Math.floor(Math.log10(value));
122
+ const scaled = value / magnitude;
123
+ return (NICE_STEPS.find(step => scaled <= step) ?? 10) * magnitude;
124
+ }
125
+ const PolarAreaChartRoot = /*#__PURE__*/forwardRef(function PolarAreaChartRoot({
126
+ className,
127
+ data,
128
+ size,
129
+ maxValue: maxValueProp,
130
+ scale = 'radius',
131
+ startAngle = 0,
132
+ padAngle = 0,
133
+ status = 'ready',
134
+ animationDuration = 800,
135
+ activeIndex: activeIndexProp,
136
+ onActiveIndexChange,
137
+ children,
138
+ ...props
139
+ }, ref) {
140
+ const [measured, setMeasured] = useState(0);
141
+ const [internalActive, setInternalActive] = useState(-1);
142
+ const reveal = useSharedValue(0);
143
+ const reducedMotion = useReducedMotion();
144
+ const controlled = activeIndexProp !== undefined;
145
+ const activeIndex = controlled ? activeIndexProp : internalActive;
146
+ const setActiveIndex = useMemo(() => index => {
147
+ if (!controlled) setInternalActive(index);
148
+ onActiveIndexChange?.(index);
149
+ }, [controlled, onActiveIndexChange]);
150
+ const box = size ?? measured;
151
+ const radius = Math.max(box / 2, 0);
152
+ const maxValue = useMemo(() => {
153
+ if (maxValueProp !== undefined) return Math.max(maxValueProp, 0) || 1;
154
+ return niceMax(data.reduce((top, datum) => Math.max(top, datum.value), 0));
155
+ }, [data, maxValueProp]);
156
+
157
+ /*
158
+ * One conversion, shared by the wedges, the rings and the readout. Two
159
+ * copies of it would be two chances for a ring to sit somewhere its own
160
+ * value does not.
161
+ */
162
+ const radiusOf = useMemo(() => value => {
163
+ const fraction = Math.min(Math.max(value, 0) / maxValue, 1);
164
+ return radius * (scale === 'area' ? Math.sqrt(fraction) : fraction);
165
+ }, [maxValue, radius, scale]);
166
+ const origin = startAngle / 360;
167
+ const wedges = useMemo(() => {
168
+ const count = data.length;
169
+ if (!count || radius <= 0) return [];
170
+ const each = 1 / count;
171
+ // Never more than half a wedge from each side, or the two gaps meet in
172
+ // the middle and the wedge inverts.
173
+ const pad = Math.min(padAngle / 360, each / 2);
174
+ return data.map((datum, index) => {
175
+ const from = origin + each * index + pad / 2;
176
+ const to = origin + each * (index + 1) - pad / 2;
177
+ return {
178
+ from,
179
+ to,
180
+ mid: (from + to) / 2,
181
+ radius: radiusOf(datum.value),
182
+ fraction: Math.min(Math.max(datum.value, 0) / maxValue, 1)
183
+ };
184
+ });
185
+ }, [data, radius, origin, padAngle, radiusOf, maxValue]);
186
+ const playReveal = useMemo(() => () => {
187
+ if (reducedMotion) {
188
+ reveal.value = 1;
189
+ return;
190
+ }
191
+ reveal.value = 0;
192
+ reveal.value = withTiming(1, {
193
+ duration: animationDuration,
194
+ easing: Easing.bezier(0.85, 0, 0.15, 1)
195
+ });
196
+ }, [reducedMotion, animationDuration, reveal]);
197
+ const loading = status === 'loading';
198
+ const revealed = useRef(false);
199
+ useEffect(() => {
200
+ if (loading) {
201
+ revealed.current = false;
202
+ reveal.value = 0;
203
+ return;
204
+ }
205
+ if (revealed.current || box <= 0 || !wedges.length) return;
206
+ revealed.current = true;
207
+ playReveal();
208
+ }, [loading, box, wedges.length, playReveal, reveal]);
209
+ useImperativeHandle(ref, () => ({
210
+ replay: playReveal
211
+ }), [playReveal]);
212
+
213
+ // Resolved here rather than inside the wedges, so the legend, the header
214
+ // and the readout can name a wedge's colour without drawing one.
215
+ const c1 = useSeriesColor(undefined, 1);
216
+ const c2 = useSeriesColor(undefined, 2);
217
+ const c3 = useSeriesColor(undefined, 3);
218
+ const c4 = useSeriesColor(undefined, 4);
219
+ const c5 = useSeriesColor(undefined, 5);
220
+ const palette = useMemo(() => [c1, c2, c3, c4, c5], [c1, c2, c3, c4, c5]);
221
+ const colors = useMemo(() => data.map((datum, index) => datum.color ?? seriesColorAt(palette, index)), [data, palette]);
222
+ const onLayout = event => {
223
+ const {
224
+ width,
225
+ height
226
+ } = event.nativeEvent.layout;
227
+ const next = Math.round(Math.min(width, height));
228
+ if (next !== measured) setMeasured(next);
229
+ props.onLayout?.(event);
230
+ };
231
+ const context = useMemo(() => ({
232
+ data,
233
+ size: box,
234
+ radius,
235
+ maxValue,
236
+ scale,
237
+ colors,
238
+ wedges,
239
+ radiusOf,
240
+ reveal,
241
+ status,
242
+ activeIndex,
243
+ setActiveIndex
244
+ }), [data, box, radius, maxValue, scale, colors, wedges, radiusOf, reveal, status, activeIndex, setActiveIndex]);
245
+ const slots = {
246
+ svg: [],
247
+ overlay: [],
248
+ header: [],
249
+ footer: []
250
+ };
251
+ Children.forEach(children, (child, index) => {
252
+ if (! /*#__PURE__*/isValidElement(child)) return;
253
+ const slot = child.type.slot ?? 'overlay';
254
+ slots[slot in slots ? slot : 'overlay'].push(/*#__PURE__*/_jsx(ChildSlot, {
255
+ children: child
256
+ }, index));
257
+ });
258
+ return /*#__PURE__*/_jsx(PolarAreaChartContext.Provider, {
259
+ value: context,
260
+ children: /*#__PURE__*/_jsxs(View, {
261
+ ...props,
262
+ style: props.style,
263
+ className: cn('w-full', className),
264
+ children: [slots.header, /*#__PURE__*/_jsx(View, {
265
+ onLayout: onLayout,
266
+ style: size ? {
267
+ width: size,
268
+ height: size
269
+ } : {
270
+ aspectRatio: 1
271
+ },
272
+ className: cn('items-center justify-center', size ? 'self-center' : 'w-full'),
273
+ children: box > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
274
+ children: [/*#__PURE__*/_jsx(Svg, {
275
+ width: box,
276
+ height: box,
277
+ children: slots.svg
278
+ }), /*#__PURE__*/_jsx(View, {
279
+ pointerEvents: "box-none",
280
+ style: {
281
+ position: 'absolute',
282
+ width: box,
283
+ height: box
284
+ },
285
+ children: slots.overlay
286
+ })]
287
+ }) : null
288
+ }), slots.footer]
289
+ })
290
+ });
291
+ });
292
+ PolarAreaChartRoot.displayName = 'PolarAreaChart';
293
+ function ChildSlot({
294
+ children
295
+ }) {
296
+ return /*#__PURE__*/_jsx(_Fragment, {
297
+ children: children
298
+ });
299
+ }
300
+ /**
301
+ * Every wedge, drawn in the order the data lists them.
302
+ *
303
+ * One part rather than one per datum: the wedges share a dial, a maximum and a
304
+ * scale by definition, and a chart where one of them could be given a different
305
+ * maximum would be a chart drawing a lie.
306
+ */
307
+ function PolarAreaChartWedges({
308
+ cornerRadius = 0,
309
+ dimOpacity = 0.35
310
+ }) {
311
+ const {
312
+ data,
313
+ wedges,
314
+ colors,
315
+ size,
316
+ reveal,
317
+ status,
318
+ activeIndex,
319
+ setActiveIndex
320
+ } = useChart('PolarAreaChart.Wedges');
321
+ if (status === 'loading' || !wedges.length) return null;
322
+ return /*#__PURE__*/_jsx(G, {
323
+ children: wedges.map((wedge, index) => {
324
+ const datum = data[index];
325
+ if (!datum || wedge.radius <= 0 || wedge.to <= wedge.from) return null;
326
+ return /*#__PURE__*/_jsx(Wedge, {
327
+ wedge: wedge,
328
+ fill: colors[index] ?? colors[0],
329
+ centre: size / 2,
330
+ cornerRadius: cornerRadius,
331
+ reveal: reveal,
332
+ dimmed: activeIndex >= 0 && activeIndex !== index,
333
+ dimOpacity: dimOpacity,
334
+ label: datum.label,
335
+ value: datum.value,
336
+ onPress: () => setActiveIndex(activeIndex === index ? -1 : index)
337
+ }, `${datum.label}-${index}`);
338
+ })
339
+ });
340
+ }
341
+ PolarAreaChartWedges.displayName = 'PolarAreaChart.Wedges';
342
+ PolarAreaChartWedges.slot = 'svg';
343
+
344
+ /**
345
+ * One wedge, rebuilt on the UI thread every frame it is growing.
346
+ *
347
+ * It grows outward rather than sweeping round, because the radius is the
348
+ * quantity here: a wedge half drawn is a smaller reading, which is the thing
349
+ * the chart is animating towards. A sweep would animate the one dimension that
350
+ * carries nothing.
351
+ */
352
+ function Wedge({
353
+ wedge,
354
+ fill,
355
+ centre,
356
+ cornerRadius,
357
+ reveal,
358
+ dimmed,
359
+ dimOpacity,
360
+ label,
361
+ value,
362
+ onPress
363
+ }) {
364
+ const {
365
+ from,
366
+ to,
367
+ radius
368
+ } = wedge;
369
+ const animatedProps = useAnimatedProps(() => ({
370
+ d: wedgePath(centre, centre, radius * reveal.value, 0, from, to, cornerRadius)
371
+ }));
372
+ return /*#__PURE__*/_jsx(AnimatedPath, {
373
+ animatedProps: animatedProps,
374
+ fill: fill,
375
+ fillOpacity: dimmed ? dimOpacity : 1,
376
+ onPress: onPress
377
+ // An SVG node takes a label but not a role, so the wedges are reachable
378
+ // and named without being announced as buttons. `PolarAreaChart.Legend`
379
+ // is the properly wired way through the same selection.
380
+ ,
381
+ accessibilityLabel: `${label}, ${compactNumber(value)}`
382
+ });
383
+ }
384
+ /**
385
+ * The scale, drawn as rings.
386
+ *
387
+ * Each ring stands for an even step of the value and is placed where that value
388
+ * falls, which under `scale="area"` is not an even step of the radius. Spacing
389
+ * them evenly instead would be quicker and would put every ring in the wrong
390
+ * place on half the charts.
391
+ */
392
+ function PolarAreaChartGrid({
393
+ rings = 4,
394
+ color,
395
+ spokes = false
396
+ }) {
397
+ const {
398
+ size,
399
+ radius,
400
+ maxValue,
401
+ radiusOf,
402
+ wedges
403
+ } = useChart('PolarAreaChart.Grid');
404
+ const token = useCSSVariable('--color-border');
405
+ const stroke = color ?? (typeof token === 'string' ? token : 'rgba(128,128,128,0.2)');
406
+ if (radius <= 0 || rings < 1) return null;
407
+ const centre = size / 2;
408
+ return /*#__PURE__*/_jsxs(G, {
409
+ children: [Array.from({
410
+ length: rings
411
+ }, (_, index) => {
412
+ const r = radiusOf((index + 1) / rings * maxValue);
413
+ if (r <= 0) return null;
414
+ return /*#__PURE__*/_jsx(Circle, {
415
+ cx: centre,
416
+ cy: centre,
417
+ r: r,
418
+ fill: "none",
419
+ stroke: stroke,
420
+ strokeWidth: 1
421
+ }, index);
422
+ }), spokes ? wedges.map((wedge, index) => {
423
+ const point = polarPoint(centre, centre, radius, wedge.from);
424
+ return /*#__PURE__*/_jsx(Line, {
425
+ x1: centre,
426
+ y1: centre,
427
+ x2: point.x,
428
+ y2: point.y,
429
+ stroke: stroke,
430
+ strokeWidth: 1
431
+ }, index);
432
+ }) : null]
433
+ });
434
+ }
435
+ PolarAreaChartGrid.displayName = 'PolarAreaChart.Grid';
436
+ PolarAreaChartGrid.slot = 'svg';
437
+ /**
438
+ * The reading on each wedge that has room for it.
439
+ *
440
+ * A wedge shorter than `minRadius` is left blank — the label would sit outside
441
+ * the wedge it belongs to, next to a neighbour it does not describe. Those are
442
+ * read through `Tooltip` and the legend instead.
443
+ *
444
+ * Each label takes its colour from the wedge under it. A wedge is a theme
445
+ * colour and a theme is free to set that anywhere on the scale, so a fixed
446
+ * white label disappears on the pale ones.
447
+ */
448
+ function PolarAreaChartLabels({
449
+ formatValue,
450
+ minRadius = DEFAULT_MIN_LABEL_RADIUS,
451
+ className
452
+ }) {
453
+ const {
454
+ data,
455
+ wedges,
456
+ colors,
457
+ size,
458
+ status
459
+ } = useChart('PolarAreaChart.Labels');
460
+ const backdrop = useCSSVariable('--color-background');
461
+ if (status === 'loading' || !wedges.length) return null;
462
+ const format = formatValue ?? (value => compactNumber(value));
463
+ const behind = typeof backdrop === 'string' ? backdrop : undefined;
464
+ const centre = size / 2;
465
+ return /*#__PURE__*/_jsx(_Fragment, {
466
+ children: wedges.map((wedge, index) => {
467
+ const datum = data[index];
468
+ if (!datum || wedge.radius < minRadius) return null;
469
+ const point = polarPoint(centre, centre, wedge.radius * LABEL_AT, wedge.mid);
470
+ const ink = inkOn(colors[index] ?? colors[0], behind, 1);
471
+ return /*#__PURE__*/_jsx(View, {
472
+ // Not a target: the wedge underneath is one, and a box this size
473
+ // over the middle of a dial would take the presses meant for its
474
+ // neighbours as well as its own.
475
+ pointerEvents: "none",
476
+ style: {
477
+ position: 'absolute',
478
+ left: point.x - LABEL_BOX / 2,
479
+ top: point.y - LABEL_BOX / 2,
480
+ width: LABEL_BOX,
481
+ height: LABEL_BOX
482
+ },
483
+ className: cn('items-center justify-center', className),
484
+ children: /*#__PURE__*/_jsx(Text, {
485
+ size: "xs",
486
+ weight: "semibold",
487
+ numberOfLines: 1,
488
+ style: {
489
+ color: ink.color
490
+ },
491
+ children: format(datum.value, datum)
492
+ })
493
+ }, `${datum.label}-${index}`);
494
+ })
495
+ });
496
+ }
497
+ PolarAreaChartLabels.displayName = 'PolarAreaChart.Labels';
498
+ PolarAreaChartLabels.slot = 'overlay';
499
+ /**
500
+ * The readout for the selected wedge, floating over the dial.
501
+ *
502
+ * This is how the short wedges are named. They are the ones with no room for a
503
+ * label, so without it the chart answers questions about its largest readings
504
+ * only — which is the half the reader could already see.
505
+ */
506
+ function PolarAreaChartTooltip({
507
+ formatValue,
508
+ className
509
+ }) {
510
+ const {
511
+ data,
512
+ wedges,
513
+ size,
514
+ activeIndex,
515
+ status
516
+ } = useChart('PolarAreaChart.Tooltip');
517
+ if (status === 'loading' || activeIndex < 0) return null;
518
+ const datum = data[activeIndex];
519
+ const wedge = wedges[activeIndex];
520
+ if (!datum || !wedge) return null;
521
+ const format = formatValue ?? (value => compactNumber(value));
522
+ const centre = size / 2;
523
+ const point = polarPoint(centre, centre, wedge.radius * LABEL_AT, wedge.mid);
524
+
525
+ // Over the wedge, then pushed back inside the square — a readout half off the
526
+ // edge is one the reader has to guess the rest of.
527
+ const left = Math.max(0, Math.min(size - TOOLTIP_WIDTH, point.x - TOOLTIP_WIDTH / 2));
528
+ const top = Math.max(0, Math.min(size - TOOLTIP_HEIGHT, point.y - TOOLTIP_HEIGHT / 2));
529
+ return /*#__PURE__*/_jsxs(View, {
530
+ pointerEvents: "none",
531
+ style: {
532
+ position: 'absolute',
533
+ left,
534
+ top,
535
+ width: TOOLTIP_WIDTH
536
+ },
537
+ className: cn('items-center rounded-lg border border-border bg-popover px-2 py-1 shadow-sm', className),
538
+ children: [/*#__PURE__*/_jsx(Text, {
539
+ size: "xs",
540
+ weight: "medium",
541
+ numberOfLines: 1,
542
+ children: datum.label
543
+ }), /*#__PURE__*/_jsx(Text, {
544
+ size: "xs",
545
+ muted: true,
546
+ numberOfLines: 1,
547
+ children: format(datum.value, datum)
548
+ })]
549
+ });
550
+ }
551
+ PolarAreaChartTooltip.displayName = 'PolarAreaChart.Tooltip';
552
+ PolarAreaChartTooltip.slot = 'overlay';
553
+ /**
554
+ * A swatch, a name and a reading per wedge, under the dial and across the width
555
+ * of it. Pressable in the same way the wedges are, and the easier target of the
556
+ * two for anything short.
557
+ *
558
+ * The reading rather than a share, unlike the pie next door: these values need
559
+ * not add up to anything, so a percentage of their sum would be a number about
560
+ * nothing.
561
+ */
562
+ function PolarAreaChartLegend({
563
+ className,
564
+ showValue = true,
565
+ formatValue,
566
+ ...props
567
+ }) {
568
+ const {
569
+ data,
570
+ colors,
571
+ activeIndex,
572
+ setActiveIndex
573
+ } = useChart('PolarAreaChart.Legend');
574
+ if (!data.length) return null;
575
+ const format = formatValue ?? (value => compactNumber(value));
576
+ return /*#__PURE__*/_jsx(View, {
577
+ ...props,
578
+ className: cn('w-full flex-row flex-wrap items-center justify-center gap-x-3 gap-y-1.5 pt-3', className),
579
+ children: data.map((datum, index) => {
580
+ const dimmed = activeIndex >= 0 && activeIndex !== index;
581
+ return /*#__PURE__*/_jsxs(Pressable, {
582
+ accessibilityRole: "button",
583
+ accessibilityState: {
584
+ selected: activeIndex === index
585
+ },
586
+ accessibilityLabel: `${datum.label}, ${format(datum.value, datum)}`,
587
+ onPress: () => setActiveIndex(activeIndex === index ? -1 : index),
588
+ style: {
589
+ opacity: dimmed ? 0.4 : 1
590
+ },
591
+ className: "max-w-full flex-row items-center gap-1.5",
592
+ children: [/*#__PURE__*/_jsx(View, {
593
+ style: {
594
+ width: 8,
595
+ height: 8,
596
+ borderRadius: 4,
597
+ backgroundColor: colors[index]
598
+ }
599
+ }), /*#__PURE__*/_jsx(Text, {
600
+ size: "xs",
601
+ muted: true,
602
+ numberOfLines: 1,
603
+ className: "shrink",
604
+ children: datum.label
605
+ }), showValue ? /*#__PURE__*/_jsx(Text, {
606
+ size: "xs",
607
+ weight: "medium",
608
+ children: format(datum.value, datum)
609
+ }) : null]
610
+ }, `${datum.label}-${index}`);
611
+ })
612
+ });
613
+ }
614
+ PolarAreaChartLegend.displayName = 'PolarAreaChart.Legend';
615
+ PolarAreaChartLegend.slot = 'footer';
616
+ /**
617
+ * The loading state: the dial as one plain disc, with nothing divided up yet.
618
+ *
619
+ * Deliberately undivided. Placeholder wedges would be a made-up set of
620
+ * readings, and a reader has no way to tell an invented one from a real one
621
+ * until it changes under them.
622
+ */
623
+ function PolarAreaChartSkeleton({
624
+ color
625
+ }) {
626
+ const {
627
+ size,
628
+ radius,
629
+ status
630
+ } = useChart('PolarAreaChart.Skeleton');
631
+ const token = useCSSVariable('--color-skeleton');
632
+ const fill = color ?? (typeof token === 'string' ? token : 'rgba(128,128,128,0.2)');
633
+ if (status !== 'loading' || radius <= 0) return null;
634
+ return /*#__PURE__*/_jsx(Circle, {
635
+ cx: size / 2,
636
+ cy: size / 2,
637
+ r: radius * 0.62,
638
+ fill: fill
639
+ });
640
+ }
641
+ PolarAreaChartSkeleton.displayName = 'PolarAreaChart.Skeleton';
642
+ PolarAreaChartSkeleton.slot = 'svg';
643
+ /**
644
+ * The strip above the dial: what the chart is of, what it reads, and what the
645
+ * colours mean.
646
+ *
647
+ * The value is not derived here even though there are values to derive one
648
+ * from, because the formatting is not the chart's to guess: 18420 is a count, a
649
+ * currency or a duration depending on what was measured.
650
+ */
651
+ function PolarAreaChartHeader({
652
+ className,
653
+ title,
654
+ value,
655
+ caption,
656
+ labels,
657
+ legend = false,
658
+ children,
659
+ ...props
660
+ }) {
661
+ const {
662
+ data,
663
+ colors
664
+ } = useChart('PolarAreaChart.Header');
665
+ const trailing = children ?? (legend && data.length ? /*#__PURE__*/_jsx(View, {
666
+ className: "flex-row flex-wrap items-center justify-end gap-x-3 gap-y-1",
667
+ children: data.map((datum, index) => /*#__PURE__*/_jsxs(View, {
668
+ className: "flex-row items-center gap-1.5",
669
+ children: [/*#__PURE__*/_jsx(View, {
670
+ style: {
671
+ width: 8,
672
+ height: 8,
673
+ borderRadius: 4,
674
+ backgroundColor: colors[index]
675
+ }
676
+ }), /*#__PURE__*/_jsx(Text, {
677
+ size: "xs",
678
+ muted: true,
679
+ numberOfLines: 1,
680
+ children: labels?.[datum.label] ?? datum.label
681
+ })]
682
+ }, `${datum.label}-${index}`))
683
+ }) : null);
684
+ return /*#__PURE__*/_jsxs(View, {
685
+ ...props,
686
+ className: cn('flex-row items-start justify-between gap-3 pb-3', className),
687
+ children: [/*#__PURE__*/_jsxs(View, {
688
+ className: "flex-1 gap-0.5",
689
+ children: [title ? /*#__PURE__*/_jsx(Text, {
690
+ size: "xs",
691
+ muted: true,
692
+ children: title
693
+ }) : null, value ? /*#__PURE__*/_jsx(Text, {
694
+ size: "xl",
695
+ weight: "bold",
696
+ children: value
697
+ }) : null, caption ? /*#__PURE__*/_jsx(Text, {
698
+ size: "xs",
699
+ muted: true,
700
+ children: caption
701
+ }) : null]
702
+ }), trailing ? /*#__PURE__*/_jsx(View, {
703
+ className: "max-w-[55%] shrink pt-1",
704
+ children: trailing
705
+ }) : null]
706
+ });
707
+ }
708
+ PolarAreaChartHeader.displayName = 'PolarAreaChart.Header';
709
+ PolarAreaChartHeader.slot = 'header';
710
+ export const PolarAreaChart = Object.assign(PolarAreaChartRoot, {
711
+ Header: PolarAreaChartHeader,
712
+ Grid: PolarAreaChartGrid,
713
+ Wedges: PolarAreaChartWedges,
714
+ Labels: PolarAreaChartLabels,
715
+ Tooltip: PolarAreaChartTooltip,
716
+ Legend: PolarAreaChartLegend,
717
+ Skeleton: PolarAreaChartSkeleton
718
+ });
719
+ //# sourceMappingURL=index.js.map