panelui-native 0.25.0 → 0.28.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 (193) hide show
  1. package/lib/module/components/accordion/index.js +8 -8
  2. package/lib/module/components/accordion/index.js.map +1 -1
  3. package/lib/module/components/alert/index.js +2 -2
  4. package/lib/module/components/alert/index.js.map +1 -1
  5. package/lib/module/components/area-chart/index.js +182 -50
  6. package/lib/module/components/area-chart/index.js.map +1 -1
  7. package/lib/module/components/attachment/index.js +2 -1
  8. package/lib/module/components/attachment/index.js.map +1 -1
  9. package/lib/module/components/avatar/index.js +3 -3
  10. package/lib/module/components/avatar/index.js.map +1 -1
  11. package/lib/module/components/badge/index.js +4 -4
  12. package/lib/module/components/badge/index.js.map +1 -1
  13. package/lib/module/components/bar-chart/index.js +197 -56
  14. package/lib/module/components/bar-chart/index.js.map +1 -1
  15. package/lib/module/components/bottom-sheet/index.js +15 -10
  16. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  17. package/lib/module/components/breadcrumb/index.js +6 -6
  18. package/lib/module/components/breadcrumb/index.js.map +1 -1
  19. package/lib/module/components/button/index.js +11 -6
  20. package/lib/module/components/button/index.js.map +1 -1
  21. package/lib/module/components/calendar/index.js +557 -0
  22. package/lib/module/components/calendar/index.js.map +1 -0
  23. package/lib/module/components/carousel/index.js +680 -0
  24. package/lib/module/components/carousel/index.js.map +1 -0
  25. package/lib/module/components/chip/index.js +4 -4
  26. package/lib/module/components/chip/index.js.map +1 -1
  27. package/lib/module/components/date-picker/index.js +158 -0
  28. package/lib/module/components/date-picker/index.js.map +1 -0
  29. package/lib/module/components/dialog/index.js +7 -2
  30. package/lib/module/components/dialog/index.js.map +1 -1
  31. package/lib/module/components/direction/index.js +2 -1
  32. package/lib/module/components/direction/index.js.map +1 -1
  33. package/lib/module/components/empty-state/index.js +2 -2
  34. package/lib/module/components/empty-state/index.js.map +1 -1
  35. package/lib/module/components/field/index.js +5 -5
  36. package/lib/module/components/field/index.js.map +1 -1
  37. package/lib/module/components/flow/index.js +3 -3
  38. package/lib/module/components/flow/index.js.map +1 -1
  39. package/lib/module/components/frame/index.js +5 -5
  40. package/lib/module/components/frame/index.js.map +1 -1
  41. package/lib/module/components/input-group/index.js +3 -2
  42. package/lib/module/components/input-group/index.js.map +1 -1
  43. package/lib/module/components/item/index.js +9 -9
  44. package/lib/module/components/item/index.js.map +1 -1
  45. package/lib/module/components/label/index.js +4 -4
  46. package/lib/module/components/label/index.js.map +1 -1
  47. package/lib/module/components/line-chart/index.js +167 -44
  48. package/lib/module/components/line-chart/index.js.map +1 -1
  49. package/lib/module/components/map/index.js +7 -7
  50. package/lib/module/components/map/index.js.map +1 -1
  51. package/lib/module/components/marker/index.js +3 -3
  52. package/lib/module/components/marker/index.js.map +1 -1
  53. package/lib/module/components/message/index.js +7 -7
  54. package/lib/module/components/message/index.js.map +1 -1
  55. package/lib/module/components/message-scroller/index.js +5 -4
  56. package/lib/module/components/message-scroller/index.js.map +1 -1
  57. package/lib/module/components/popover/index.js +39 -7
  58. package/lib/module/components/popover/index.js.map +1 -1
  59. package/lib/module/components/radio-group/index.js +2 -2
  60. package/lib/module/components/radio-group/index.js.map +1 -1
  61. package/lib/module/components/section-rail/index.js +4 -4
  62. package/lib/module/components/section-rail/index.js.map +1 -1
  63. package/lib/module/components/select/index.js +10 -4
  64. package/lib/module/components/select/index.js.map +1 -1
  65. package/lib/module/components/steps/index.js +4 -4
  66. package/lib/module/components/steps/index.js.map +1 -1
  67. package/lib/module/components/tabs/index.js +7 -7
  68. package/lib/module/components/tabs/index.js.map +1 -1
  69. package/lib/module/components/timeline/index.js +4 -4
  70. package/lib/module/components/timeline/index.js.map +1 -1
  71. package/lib/module/components/toast/index.js +2 -2
  72. package/lib/module/components/toast/index.js.map +1 -1
  73. package/lib/module/components/toggle-button/index.js +5 -5
  74. package/lib/module/components/toggle-button/index.js.map +1 -1
  75. package/lib/module/components/tooltip/index.js +492 -0
  76. package/lib/module/components/tooltip/index.js.map +1 -0
  77. package/lib/module/components/typography/index.js +2 -2
  78. package/lib/module/components/typography/index.js.map +1 -1
  79. package/lib/module/hooks/index.js +1 -0
  80. package/lib/module/hooks/index.js.map +1 -1
  81. package/lib/module/hooks/use-back-handler.js +37 -0
  82. package/lib/module/hooks/use-back-handler.js.map +1 -0
  83. package/lib/module/icons/index.js +63 -0
  84. package/lib/module/icons/index.js.map +1 -1
  85. package/lib/module/index.js +5 -1
  86. package/lib/module/index.js.map +1 -1
  87. package/lib/module/primitives/keyboard-avoider.js +2 -1
  88. package/lib/module/primitives/keyboard-avoider.js.map +1 -1
  89. package/lib/module/primitives/text.js +25 -1
  90. package/lib/module/primitives/text.js.map +1 -1
  91. package/lib/module/utils/date.js +386 -0
  92. package/lib/module/utils/date.js.map +1 -0
  93. package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/area-chart/index.d.ts +41 -1
  96. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/attachment/index.d.ts.map +1 -1
  98. package/lib/typescript/src/components/bar-chart/index.d.ts +44 -2
  99. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  103. package/lib/typescript/src/components/calendar/index.d.ts +108 -0
  104. package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
  105. package/lib/typescript/src/components/carousel/index.d.ts +179 -0
  106. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -0
  107. package/lib/typescript/src/components/chip/index.d.ts.map +1 -1
  108. package/lib/typescript/src/components/date-picker/index.d.ts +91 -0
  109. package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -0
  110. package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
  111. package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
  112. package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
  113. package/lib/typescript/src/components/field/index.d.ts.map +1 -1
  114. package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
  115. package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
  116. package/lib/typescript/src/components/item/index.d.ts.map +1 -1
  117. package/lib/typescript/src/components/label/index.d.ts.map +1 -1
  118. package/lib/typescript/src/components/line-chart/index.d.ts +41 -2
  119. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  120. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  121. package/lib/typescript/src/components/marker/index.d.ts.map +1 -1
  122. package/lib/typescript/src/components/message/index.d.ts.map +1 -1
  123. package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
  124. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  125. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  126. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  127. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  128. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  129. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  130. package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -1
  131. package/lib/typescript/src/components/tooltip/index.d.ts +125 -0
  132. package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -0
  133. package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
  134. package/lib/typescript/src/hooks/index.d.ts +1 -0
  135. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  136. package/lib/typescript/src/hooks/use-back-handler.d.ts +19 -0
  137. package/lib/typescript/src/hooks/use-back-handler.d.ts.map +1 -0
  138. package/lib/typescript/src/icons/index.d.ts +3 -0
  139. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  140. package/lib/typescript/src/index.d.ts +8 -4
  141. package/lib/typescript/src/index.d.ts.map +1 -1
  142. package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
  143. package/lib/typescript/src/primitives/text.d.ts +20 -0
  144. package/lib/typescript/src/primitives/text.d.ts.map +1 -1
  145. package/lib/typescript/src/utils/date.d.ts +116 -0
  146. package/lib/typescript/src/utils/date.d.ts.map +1 -0
  147. package/package.json +1 -1
  148. package/src/components/accordion/index.tsx +8 -12
  149. package/src/components/alert/index.tsx +2 -2
  150. package/src/components/area-chart/index.tsx +208 -55
  151. package/src/components/attachment/index.tsx +2 -2
  152. package/src/components/avatar/index.tsx +3 -3
  153. package/src/components/badge/index.tsx +6 -6
  154. package/src/components/bar-chart/index.tsx +214 -54
  155. package/src/components/bottom-sheet/index.tsx +17 -16
  156. package/src/components/breadcrumb/index.tsx +6 -8
  157. package/src/components/button/index.tsx +12 -8
  158. package/src/components/calendar/index.tsx +703 -0
  159. package/src/components/carousel/index.tsx +788 -0
  160. package/src/components/chip/index.tsx +4 -6
  161. package/src/components/date-picker/index.tsx +237 -0
  162. package/src/components/dialog/index.tsx +7 -2
  163. package/src/components/direction/index.tsx +2 -1
  164. package/src/components/empty-state/index.tsx +2 -2
  165. package/src/components/field/index.tsx +3 -5
  166. package/src/components/flow/index.tsx +3 -3
  167. package/src/components/frame/index.tsx +5 -7
  168. package/src/components/input-group/index.tsx +3 -2
  169. package/src/components/item/index.tsx +9 -9
  170. package/src/components/label/index.tsx +2 -2
  171. package/src/components/line-chart/index.tsx +189 -50
  172. package/src/components/map/index.tsx +7 -9
  173. package/src/components/marker/index.tsx +3 -3
  174. package/src/components/message/index.tsx +7 -7
  175. package/src/components/message-scroller/index.tsx +5 -4
  176. package/src/components/popover/index.tsx +43 -7
  177. package/src/components/radio-group/index.tsx +2 -2
  178. package/src/components/section-rail/index.tsx +4 -4
  179. package/src/components/select/index.tsx +10 -4
  180. package/src/components/steps/index.tsx +4 -4
  181. package/src/components/tabs/index.tsx +7 -9
  182. package/src/components/timeline/index.tsx +4 -4
  183. package/src/components/toast/index.tsx +2 -2
  184. package/src/components/toggle-button/index.tsx +5 -7
  185. package/src/components/tooltip/index.tsx +610 -0
  186. package/src/components/typography/index.tsx +2 -2
  187. package/src/hooks/index.ts +1 -0
  188. package/src/hooks/use-back-handler.ts +37 -0
  189. package/src/icons/index.tsx +45 -0
  190. package/src/index.ts +45 -0
  191. package/src/primitives/keyboard-avoider.tsx +2 -1
  192. package/src/primitives/text.tsx +28 -1
  193. package/src/utils/date.ts +446 -0
@@ -30,7 +30,7 @@ import Animated, {
30
30
  } from 'react-native-reanimated';
31
31
  import { tv } from 'tailwind-variants';
32
32
  import { ChevronDownIcon } from '../../icons';
33
- import { Text, type TextProps } from '../../primitives/text';
33
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
34
34
 
35
35
  export type AccordionVariant =
36
36
  | 'default'
@@ -226,7 +226,7 @@ const AccordionItem = forwardRef<View, AccordionItemProps>(
226
226
  className={item({ className })}
227
227
  {...props}
228
228
  >
229
- {children}
229
+ {textChildren(children)}
230
230
  </Animated.View>
231
231
  </AccordionItemContext.Provider>
232
232
  );
@@ -256,11 +256,9 @@ const AccordionTrigger = forwardRef<View, AccordionTriggerProps>(
256
256
  className={trigger({ className })}
257
257
  {...props}
258
258
  >
259
- {typeof children === 'string' ? (
260
- <Text className={title()}>{children}</Text>
261
- ) : (
262
- children
263
- )}
259
+ {textChildren(children, (text) => (
260
+ <Text className={title()}>{text}</Text>
261
+ ))}
264
262
  </Pressable>
265
263
  );
266
264
  }
@@ -319,11 +317,9 @@ const AccordionContent = forwardRef<View, AccordionContentProps>(
319
317
 
320
318
  return (
321
319
  <View ref={ref} className={content({ className })} {...props}>
322
- {typeof children === 'string' ? (
323
- <Text className={contentText()}>{children}</Text>
324
- ) : (
325
- children
326
- )}
320
+ {textChildren(children, (text) => (
321
+ <Text className={contentText()}>{text}</Text>
322
+ ))}
327
323
  </View>
328
324
  );
329
325
  }
@@ -10,7 +10,7 @@ import { View, type Text as RNText, type ViewProps } from 'react-native';
10
10
  import { tv, type VariantProps } from 'tailwind-variants';
11
11
  import { useCSSVariable } from 'uniwind';
12
12
  import { cn } from '../../utils/cn';
13
- import { Text, type TextProps } from '../../primitives/text';
13
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
14
14
  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon } from '../../icons';
15
15
 
16
16
  const alertVariants = tv({
@@ -98,7 +98,7 @@ const AlertRoot = forwardRef<View, AlertProps>(
98
98
  {icon ? (
99
99
  <>
100
100
  <View className={indicator()}>{icon}</View>
101
- <View className={content()}>{children}</View>
101
+ <View className={content()}>{textChildren(children)}</View>
102
102
  </>
103
103
  ) : (
104
104
  children
@@ -88,12 +88,25 @@ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
88
88
 
89
89
  const PADDING = { top: 12, right: 10, bottom: 22, left: 10 };
90
90
  const LABEL_WIDTH = 132;
91
+
92
+ /**
93
+ * Box each x label is centred in. Wide enough for a short label to sit over
94
+ * its point without the neighbours colliding; a longer one is ellipsised
95
+ * rather than allowed to push the others out of place.
96
+ */
97
+ const POINT_LABEL_WIDTH = 56;
98
+
99
+ /** Line height of an `xs` label, for centring one on the grid line it names. */
100
+ const AXIS_LABEL_HEIGHT = 16;
91
101
  const DOT = 9;
92
102
 
93
103
  /** Left gutter reserved when a `YAxis` is present, for its labels to sit in. */
94
- const Y_AXIS_WIDTH = 40;
104
+ const Y_AXIS_WIDTH = 44;
105
+
106
+ /** Gap between the value labels and the plot they sit beside. */
107
+ const Y_AXIS_GUTTER = 6;
95
108
 
96
- type Layer = 'svg' | 'overlay';
109
+ type Layer = 'svg' | 'overlay' | 'header';
97
110
 
98
111
  export type AreaChartStatus = 'loading' | 'ready';
99
112
  export type AreaChartDatum = Record<string, string | number | null | undefined>;
@@ -190,13 +203,14 @@ function ChildSlot({ children }: { children: ReactNode }) {
190
203
  function partition(children: ReactNode) {
191
204
  const svg: ReactNode[] = [];
192
205
  const overlay: ReactNode[] = [];
206
+ const header: ReactNode[] = [];
193
207
  Children.forEach(children, (child, index) => {
194
208
  if (!isValidElement(child)) return;
195
209
  const layer = (child.type as { layer?: Layer }).layer ?? 'svg';
196
210
  const slot = <ChildSlot key={index}>{child}</ChildSlot>;
197
- (layer === 'overlay' ? overlay : svg).push(slot);
211
+ (layer === 'header' ? header : layer === 'overlay' ? overlay : svg).push(slot);
198
212
  });
199
- return { svg, overlay };
213
+ return { svg, overlay, header };
200
214
  }
201
215
 
202
216
  const AreaChartRoot = forwardRef<AreaChartHandle, AreaChartProps>(function AreaChartRoot(
@@ -413,39 +427,43 @@ const AreaChartRoot = forwardRef<AreaChartHandle, AreaChartProps>(function AreaC
413
427
  ]
414
428
  );
415
429
 
416
- const { svg, overlay } = partition(children);
430
+ const { svg, overlay, header } = partition(children);
417
431
 
432
+ /*
433
+ * Two views, because the header is not part of the plot. `aspectRatio` and
434
+ * the layout measurement belong to the drawing area alone — measured on the
435
+ * outer view they would take in the header too, and the plot would lose as
436
+ * much height as the readout took while still claiming the shape asked for.
437
+ */
418
438
  return (
419
439
  <AreaChartContext.Provider value={context}>
420
- <View
421
- {...props}
422
- onLayout={onLayout}
423
- style={[{ aspectRatio }, props.style]}
424
- className={cn('w-full', className)}
425
- >
426
- {plot.width > 0 ? (
427
- <>
428
- <Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
429
- <Defs>
430
- {/*
431
- * One clip for everything in the plot. Sharing it is what makes
432
- * the reveal read as the chart arriving rather than as several
433
- * bands animating in at once.
434
- */}
435
- <ClipPath id={clipId}>
436
- <AnimatedRect
437
- x={plot.left}
438
- y={0}
439
- height={size.height}
440
- animatedProps={clipProps}
441
- />
442
- </ClipPath>
443
- </Defs>
444
- {svg}
445
- </Svg>
446
- {overlay}
447
- </>
448
- ) : null}
440
+ <View {...props} style={props.style} className={cn('w-full', className)}>
441
+ {header}
442
+ <View onLayout={onLayout} style={{ aspectRatio }} className="w-full">
443
+ {plot.width > 0 ? (
444
+ <>
445
+ <Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
446
+ <Defs>
447
+ {/*
448
+ * One clip for everything in the plot. Sharing it is what
449
+ * makes the reveal read as the chart arriving rather than as
450
+ * several bands animating in at once.
451
+ */}
452
+ <ClipPath id={clipId}>
453
+ <AnimatedRect
454
+ x={plot.left}
455
+ y={0}
456
+ height={size.height}
457
+ animatedProps={clipProps}
458
+ />
459
+ </ClipPath>
460
+ </Defs>
461
+ {svg}
462
+ </Svg>
463
+ {overlay}
464
+ </>
465
+ ) : null}
466
+ </View>
449
467
  </View>
450
468
  </AreaChartContext.Provider>
451
469
  );
@@ -635,20 +653,44 @@ function AreaChartXAxis({ ticks = 4, format, className }: AreaChartXAxisProps) {
635
653
  }).filter((label): label is { key: number; text: string } => label !== null);
636
654
  }, [data, ticks, format, xDataKey]);
637
655
 
656
+ /*
657
+ * Each label sits on its own point rather than being spread along the axis.
658
+ * The indices `ticks` picks are not evenly spaced — twelve months shown five
659
+ * at a time gives 0,3,6,8,11 — so spreading them evenly put the ones in
660
+ * between over the wrong part of the line.
661
+ *
662
+ * The box is backed off by half its own width rather than translated by
663
+ * `-50%`, which is not reliable across React Native versions.
664
+ */
638
665
  return (
639
666
  <View
640
- pointerEvents="none"
641
- style={{
642
- position: 'absolute',
643
- left: plot.left,
644
- right: PADDING.right,
645
- bottom: 0,
646
- height: PADDING.bottom,
647
- }}
648
- className={cn('flex-row items-center justify-between', className)}
667
+ style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}
668
+ className={cn(className)}
649
669
  >
650
670
  {labels.map((label) => (
651
- <Text key={label.key} size="xs" muted>
671
+ <Text
672
+ key={label.key}
673
+ size="xs"
674
+ muted
675
+ numberOfLines={1}
676
+ style={{
677
+ position: 'absolute',
678
+ bottom: 0,
679
+ // Centred on its point, then held inside the chart. The first and
680
+ // last points sit on the plot's own edges, so a box centred on
681
+ // them hangs half its width off the side — the clamp slides those
682
+ // two back in rather than letting the numbers leave the frame.
683
+ left: Math.max(
684
+ 0,
685
+ Math.min(
686
+ xOf(label.key, data.length, plot) - POINT_LABEL_WIDTH / 2,
687
+ plot.left + plot.width + PADDING.right - POINT_LABEL_WIDTH
688
+ )
689
+ ),
690
+ width: POINT_LABEL_WIDTH,
691
+ textAlign: 'center',
692
+ }}
693
+ >
652
694
  {label.text}
653
695
  </Text>
654
696
  ))}
@@ -680,8 +722,20 @@ function AreaChartYAxis({ ticks = 4, format, className }: AreaChartYAxisProps) {
680
722
  return (
681
723
  <View
682
724
  pointerEvents="none"
683
- style={{ position: 'absolute', left: 0, top: plot.top, height: plot.height }}
684
- className={cn('justify-between', className)}
725
+ style={{
726
+ position: 'absolute',
727
+ left: 0,
728
+ // Centred on the grid line each label names: the strip is lifted half
729
+ // a label and grown by a whole one, so `justify-between` lands the
730
+ // text's middle on the line rather than its top edge on the first line
731
+ // and its bottom edge on the last.
732
+ top: plot.top - AXIS_LABEL_HEIGHT / 2,
733
+ height: plot.height + AXIS_LABEL_HEIGHT,
734
+ // The gutter the root reserved, less a little breathing room, so the
735
+ // numbers sit clear of the plot instead of against it.
736
+ width: Math.max(plot.left - Y_AXIS_GUTTER, 0),
737
+ }}
738
+ className={cn('items-end justify-between', className)}
685
739
  >
686
740
  {labels.map((label) => (
687
741
  <Text key={label.key} size="xs" muted numberOfLines={1}>
@@ -965,18 +1019,11 @@ function AreaChartLegend({ className, labels, ...props }: AreaChartLegendProps)
965
1019
  return (
966
1020
  <View
967
1021
  {...props}
968
- pointerEvents="none"
1022
+ style={[{ pointerEvents: 'none' }, props.style]}
969
1023
  className={cn('absolute right-2 top-1 flex-row gap-3', className)}
970
1024
  >
971
1025
  {ordered.map(([key, color]) => (
972
- <View key={key} className="flex-row items-center gap-1.5">
973
- <View
974
- style={{ width: 8, height: 8, borderRadius: 2, backgroundColor: color }}
975
- />
976
- <Text size="xs" muted>
977
- {labels?.[key] ?? key}
978
- </Text>
979
- </View>
1026
+ <SeriesSwatch key={key} color={color} label={labels?.[key] ?? key} />
980
1027
  ))}
981
1028
  </View>
982
1029
  );
@@ -984,7 +1031,113 @@ function AreaChartLegend({ className, labels, ...props }: AreaChartLegendProps)
984
1031
  AreaChartLegend.displayName = 'AreaChart.Legend';
985
1032
  AreaChartLegend.layer = 'overlay' as Layer;
986
1033
 
1034
+ /** One series' colour and name. Shared by the legend and the header. */
1035
+ function SeriesSwatch({ color, label }: { color: string; label: string }) {
1036
+ return (
1037
+ <View className="flex-row items-center gap-1.5">
1038
+ <View style={{ backgroundColor: color }} className="h-2 w-2 rounded-full" />
1039
+ <Text size="xs" muted>
1040
+ {label}
1041
+ </Text>
1042
+ </View>
1043
+ );
1044
+ }
1045
+
1046
+ /* -------------------------------------------------------------------------- */
1047
+ /* Header layer */
1048
+ /* -------------------------------------------------------------------------- */
1049
+
1050
+ export interface AreaChartHeaderProps extends ViewProps {
1051
+ className?: string;
1052
+ /** Small line above the value — what the chart is of. */
1053
+ title?: string;
1054
+ /** The readout. The largest thing on the card, and the first thing read. */
1055
+ value?: string;
1056
+ /** One muted line under the value — a period, a comparison, a total. */
1057
+ caption?: string;
1058
+ /** Prettier names for the series keys, as the legend takes. */
1059
+ labels?: Record<string, string>;
1060
+ /**
1061
+ * Draw a swatch and a name per series along the trailing edge, in the order
1062
+ * the bands appear on a stack. Prefer this to `AreaChart.Legend` on a chart
1063
+ * that has a header: the legend floats over the plot, where a tall band and a
1064
+ * key end up in the same corner.
1065
+ */
1066
+ legend?: boolean;
1067
+ /** Trailing slot — a control, a badge, a range picker. Wins over `legend`. */
1068
+ children?: ReactNode;
1069
+ }
1070
+
1071
+ /**
1072
+ * The strip above the plot: what the chart is of, what it currently reads, and
1073
+ * what the colours mean.
1074
+ *
1075
+ * It belongs to the chart rather than to the card around it because it is about
1076
+ * the *plot* — the number changes as a finger moves along the bands, and the
1077
+ * legend is the series list the chart itself is holding. The card's header is a
1078
+ * caption on the tray the chart sits in; this is the chart introducing itself.
1079
+ *
1080
+ * The value is not derived here. A readout that follows the finger belongs to
1081
+ * whoever owns the data — take it from `onActiveIndexChange` and pass the
1082
+ * formatted string down, so one header can show a total when nothing is pressed
1083
+ * and a point's value when something is.
1084
+ */
1085
+ function AreaChartHeader({
1086
+ className,
1087
+ title,
1088
+ value,
1089
+ caption,
1090
+ labels,
1091
+ legend = false,
1092
+ children,
1093
+ ...props
1094
+ }: AreaChartHeaderProps) {
1095
+ const { series, stacked } = useChart('AreaChart.Header');
1096
+ const ordered = stacked ? [...series].reverse() : series;
1097
+ const trailing =
1098
+ children ??
1099
+ (legend && ordered.length ? (
1100
+ <View className="flex-row flex-wrap items-center justify-end gap-x-3 gap-y-1">
1101
+ {ordered.map(([key, color]) => (
1102
+ <SeriesSwatch key={key} color={color} label={labels?.[key] ?? key} />
1103
+ ))}
1104
+ </View>
1105
+ ) : null);
1106
+
1107
+ return (
1108
+ <View
1109
+ {...props}
1110
+ className={cn('flex-row items-start justify-between gap-3 pb-3', className)}
1111
+ >
1112
+ <View className="flex-1 gap-0.5">
1113
+ {title ? (
1114
+ <Text size="xs" muted>
1115
+ {title}
1116
+ </Text>
1117
+ ) : null}
1118
+ {value ? (
1119
+ <Text size="xl" weight="bold">
1120
+ {value}
1121
+ </Text>
1122
+ ) : null}
1123
+ {caption ? (
1124
+ <Text size="xs" muted>
1125
+ {caption}
1126
+ </Text>
1127
+ ) : null}
1128
+ </View>
1129
+ {/* Shrinkable, unlike a view's default in React Native. Held rigid, a
1130
+ three-series key takes the width it wants and the caption underneath
1131
+ the value wraps to two lines to make room for it. */}
1132
+ {trailing ? <View className="shrink pt-1">{trailing}</View> : null}
1133
+ </View>
1134
+ );
1135
+ }
1136
+ AreaChartHeader.displayName = 'AreaChart.Header';
1137
+ AreaChartHeader.layer = 'header' as Layer;
1138
+
987
1139
  export const AreaChart = Object.assign(AreaChartRoot, {
1140
+ Header: AreaChartHeader,
988
1141
  Grid: AreaChartGrid,
989
1142
  Area: AreaChartArea,
990
1143
  XAxis: AreaChartXAxis,
@@ -31,7 +31,7 @@ import { View, type ViewProps } from 'react-native';
31
31
  import { tv } from 'tailwind-variants';
32
32
  import { useCSSVariable } from 'uniwind';
33
33
  import { AnimatedPressable, type AnimatedPressableProps } from '../../primitives/animated-pressable';
34
- import { Text, type TextProps } from '../../primitives/text';
34
+ import { Text, type TextProps, textChildren } from '../../primitives/text';
35
35
  import { cn } from '../../utils/cn';
36
36
  import { Item } from '../item';
37
37
  import { Shimmer } from '../shimmer';
@@ -262,7 +262,7 @@ const AttachmentAction = forwardRef<View, AttachmentActionProps>(
262
262
  )}
263
263
  {...props}
264
264
  >
265
- {children}
265
+ {textChildren(children)}
266
266
  </AnimatedPressable>
267
267
  )
268
268
  );
@@ -7,7 +7,7 @@ import {
7
7
  type ViewProps,
8
8
  } from 'react-native';
9
9
  import { tv, type VariantProps } from 'tailwind-variants';
10
- import { Text } from '../../primitives/text';
10
+ import { Text, textChildren } from '../../primitives/text';
11
11
  import { cn } from '../../utils/cn';
12
12
 
13
13
  const avatarVariants = tv({
@@ -77,7 +77,7 @@ const AvatarRoot = forwardRef<View, AvatarProps>(
77
77
  <View accessibilityRole="image" className={root()}>
78
78
  {face}
79
79
  </View>
80
- {children}
80
+ {textChildren(children)}
81
81
  </View>
82
82
  );
83
83
  }
@@ -105,7 +105,7 @@ const AvatarBadge = forwardRef<View, AvatarBadgeProps>(
105
105
  )}
106
106
  {...props}
107
107
  >
108
- {children}
108
+ {textChildren(children)}
109
109
  </View>
110
110
  )
111
111
  );
@@ -1,7 +1,7 @@
1
1
  import { forwardRef, type ReactNode } from 'react';
2
2
  import { View, type ViewProps } from 'react-native';
3
3
  import { tv, type VariantProps } from 'tailwind-variants';
4
- import { Text } from '../../primitives/text';
4
+ import { Text, textChildren } from '../../primitives/text';
5
5
 
6
6
  const badgeVariants = tv({
7
7
  slots: {
@@ -67,11 +67,11 @@ export const Badge = forwardRef<View, BadgeProps>(
67
67
  className={root({ className })}
68
68
  {...props}
69
69
  >
70
- {resolvedShape === 'dot' ? null : typeof content === 'string' ? (
71
- <Text className={label({ className: labelClassName })}>{content}</Text>
72
- ) : (
73
- content
74
- )}
70
+ {resolvedShape === 'dot'
71
+ ? null
72
+ : textChildren(content, (text) => (
73
+ <Text className={label({ className: labelClassName })}>{text}</Text>
74
+ ))}
75
75
  </View>
76
76
  );
77
77
  }