@expokit/cli 0.4.3 → 0.5.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.
Files changed (242) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +313 -4
  4. package/package.json +1 -1
  5. package/registry/_docs/action-sheet.json +1 -1
  6. package/registry/_docs/alert.json +6 -0
  7. package/registry/_docs/bottom-sheet.json +6 -0
  8. package/registry/_docs/bullet.json +6 -0
  9. package/registry/_docs/dialog.json +1 -1
  10. package/registry/_docs/drawer.json +3 -3
  11. package/registry/_docs/flip-calendar.json +106 -0
  12. package/registry/_docs/glass-search-bar.json +1 -1
  13. package/registry/_docs/grain.json +6 -1
  14. package/registry/_docs/isometric-bar.json +6 -0
  15. package/registry/_docs/liquidglass-tab.json +1 -1
  16. package/registry/_docs/password-input.json +1 -1
  17. package/registry/_docs/paywall.json +144 -0
  18. package/registry/_docs/pixel-bar.json +6 -0
  19. package/registry/_docs/polar-area.json +6 -0
  20. package/registry/_docs/qr-code-button.json +9 -3
  21. package/registry/_docs/ruler-time-picker.json +147 -0
  22. package/registry/_docs/scratch-card.json +7 -1
  23. package/registry/_docs/search-bar.json +1 -1
  24. package/registry/_docs/select.json +2 -2
  25. package/registry/_docs/slide-to-confirm.json +2 -2
  26. package/registry/_docs/spinner.json +1 -1
  27. package/registry/_docs/stateful-button.json +2 -2
  28. package/registry/_docs/swipe-deck.json +1 -1
  29. package/registry/_docs/time-picker.json +2 -2
  30. package/registry/_docs/timeline-chart.json +6 -0
  31. package/registry/_docs/toast.json +15 -3
  32. package/registry/_docs/treemap.json +6 -0
  33. package/registry/_docs/waterfall.json +6 -0
  34. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  35. package/registry/accordion/files/src/lib/kit.tsx +462 -12
  36. package/registry/accordion.json +1 -0
  37. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +50 -58
  38. package/registry/action-sheet/files/src/lib/kit.tsx +462 -12
  39. package/registry/action-sheet.json +1 -0
  40. package/registry/alert/files/src/components/ui/alert.tsx +20 -13
  41. package/registry/alert/files/src/lib/kit.tsx +462 -12
  42. package/registry/alert.json +3 -4
  43. package/registry/assistant-app.json +1 -0
  44. package/registry/avatar/files/src/components/ui/avatar.tsx +89 -74
  45. package/registry/avatar/files/src/lib/kit.tsx +462 -12
  46. package/registry/avatar.json +1 -0
  47. package/registry/badge/files/src/components/ui/badge.tsx +36 -36
  48. package/registry/badge/files/src/lib/kit.tsx +462 -12
  49. package/registry/badge.json +1 -0
  50. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +39 -44
  51. package/registry/bottom-nav/files/src/lib/kit.tsx +462 -12
  52. package/registry/bottom-nav.json +5 -0
  53. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +357 -300
  54. package/registry/bottom-sheet/files/src/lib/kit.tsx +462 -12
  55. package/registry/bottom-sheet.json +5 -1
  56. package/registry/bullet.json +1 -0
  57. package/registry/button/files/src/components/ui/button.tsx +23 -34
  58. package/registry/button/files/src/lib/kit.tsx +462 -12
  59. package/registry/button-3d/files/src/components/ui/button-3d.tsx +6 -9
  60. package/registry/button-3d/files/src/lib/kit.tsx +462 -12
  61. package/registry/button-3d.json +1 -0
  62. package/registry/button.json +1 -0
  63. package/registry/calendar/files/src/components/ui/calendar.tsx +77 -79
  64. package/registry/calendar/files/src/lib/kit.tsx +462 -12
  65. package/registry/calendar.json +1 -0
  66. package/registry/card/files/src/components/ui/card.tsx +7 -6
  67. package/registry/card/files/src/lib/kit.tsx +462 -12
  68. package/registry/card-stack.json +1 -0
  69. package/registry/card.json +1 -0
  70. package/registry/carousel.json +1 -0
  71. package/registry/chat.json +7 -0
  72. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  73. package/registry/checkbox/files/src/lib/kit.tsx +462 -12
  74. package/registry/checkbox.json +1 -0
  75. package/registry/checkout.json +1 -0
  76. package/registry/chip-group/files/src/components/ui/chip-group.tsx +24 -24
  77. package/registry/chip-group/files/src/lib/kit.tsx +462 -12
  78. package/registry/chip-group.json +1 -0
  79. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  80. package/registry/confetti.json +3 -6
  81. package/registry/coupon/files/src/components/ui/coupon.tsx +47 -52
  82. package/registry/coupon/files/src/components/ui/flip-card.tsx +9 -14
  83. package/registry/coupon/files/src/components/ui/tilt-card.tsx +80 -101
  84. package/registry/coupon/files/src/lib/kit.tsx +462 -12
  85. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  86. package/registry/coupon.json +1 -0
  87. package/registry/cube-pager.json +1 -0
  88. package/registry/dashboard.json +1 -0
  89. package/registry/date-picker/files/src/components/ui/calendar.tsx +77 -79
  90. package/registry/date-picker/files/src/components/ui/date-picker.tsx +64 -71
  91. package/registry/date-picker/files/src/lib/kit.tsx +462 -12
  92. package/registry/date-picker.json +1 -0
  93. package/registry/dialog/files/src/components/ui/dialog.tsx +66 -68
  94. package/registry/dialog/files/src/lib/kit.tsx +462 -12
  95. package/registry/dialog.json +1 -0
  96. package/registry/donut-shop.json +1 -0
  97. package/registry/drawer/files/src/components/ui/drawer.tsx +76 -88
  98. package/registry/drawer/files/src/lib/kit.tsx +462 -12
  99. package/registry/drawer.json +1 -0
  100. package/registry/fab.json +1 -0
  101. package/registry/fintech-app.json +1 -0
  102. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +632 -0
  103. package/registry/flip-calendar/files/src/lib/kit.tsx +672 -0
  104. package/registry/flip-calendar.json +24 -0
  105. package/registry/flip-card/files/src/components/ui/flip-card.tsx +9 -14
  106. package/registry/flip-card/files/src/lib/kit.tsx +462 -12
  107. package/registry/flip-card.json +1 -0
  108. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  109. package/registry/floating-input/files/src/lib/kit.tsx +462 -12
  110. package/registry/floating-input.json +1 -0
  111. package/registry/food-app.json +1 -0
  112. package/registry/gift-card.json +1 -0
  113. package/registry/glass-button.json +5 -0
  114. package/registry/glass-nav-bar.json +1 -0
  115. package/registry/glass-search-bar.json +1 -0
  116. package/registry/glass-segmented.json +1 -0
  117. package/registry/glass-sheet.json +1 -0
  118. package/registry/glass-tab-bar.json +5 -0
  119. package/registry/glass.json +1 -0
  120. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  121. package/registry/grain.json +8 -6
  122. package/registry/inbox.json +1 -0
  123. package/registry/isometric-bar.json +1 -0
  124. package/registry/kit/files/src/lib/kit.tsx +462 -12
  125. package/registry/kit.json +1 -0
  126. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  127. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +29 -16
  128. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +33 -19
  129. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  130. package/registry/liquidglass-tab.json +1 -0
  131. package/registry/list-item/files/src/components/ui/list-item.tsx +43 -43
  132. package/registry/list-item/files/src/lib/kit.tsx +462 -12
  133. package/registry/list-item.json +1 -0
  134. package/registry/loyalty-card.json +1 -0
  135. package/registry/morphing-button-group.json +1 -0
  136. package/registry/number-ticker.json +1 -0
  137. package/registry/offers.json +1 -0
  138. package/registry/onboarding.json +1 -0
  139. package/registry/orb-reveal.json +1 -0
  140. package/registry/otp-input/files/src/components/ui/otp-input.tsx +49 -30
  141. package/registry/otp-input/files/src/lib/kit.tsx +462 -12
  142. package/registry/otp-input.json +1 -0
  143. package/registry/parallax-layers.json +1 -0
  144. package/registry/parallax-scroll.json +1 -0
  145. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  146. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  147. package/registry/password-input/files/src/lib/kit.tsx +462 -12
  148. package/registry/password-input.json +1 -0
  149. package/registry/payment-card/files/src/components/ui/flip-card.tsx +9 -14
  150. package/registry/payment-card/files/src/components/ui/payment-card.tsx +26 -20
  151. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +80 -101
  152. package/registry/payment-card/files/src/lib/kit.tsx +462 -12
  153. package/registry/payment-card.json +1 -0
  154. package/registry/paywall.json +23 -0
  155. package/registry/pixel-bar.json +1 -0
  156. package/registry/polar-area.json +1 -0
  157. package/registry/profile.json +1 -0
  158. package/registry/progress/files/src/components/ui/progress.tsx +31 -29
  159. package/registry/progress/files/src/lib/kit.tsx +462 -12
  160. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +21 -21
  161. package/registry/progress-steps/files/src/lib/kit.tsx +462 -12
  162. package/registry/progress-steps.json +1 -0
  163. package/registry/progress.json +1 -0
  164. package/registry/qr-code-button.json +1 -0
  165. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  166. package/registry/radio-group/files/src/lib/kit.tsx +462 -12
  167. package/registry/radio-group.json +1 -0
  168. package/registry/rating.json +1 -0
  169. package/registry/ring-carousel.json +1 -0
  170. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +832 -0
  171. package/registry/ruler-time-picker/files/src/lib/kit.tsx +672 -0
  172. package/registry/ruler-time-picker.json +26 -0
  173. package/registry/scratch-card.json +1 -0
  174. package/registry/search-bar/files/src/components/ui/search-bar.tsx +28 -28
  175. package/registry/search-bar/files/src/lib/kit.tsx +462 -12
  176. package/registry/search-bar.json +1 -0
  177. package/registry/search-results.json +4 -0
  178. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +11 -17
  179. package/registry/segmented-control/files/src/lib/kit.tsx +462 -12
  180. package/registry/segmented-control.json +1 -0
  181. package/registry/select/files/src/components/ui/select.tsx +107 -104
  182. package/registry/select/files/src/lib/kit.tsx +462 -12
  183. package/registry/select.json +6 -0
  184. package/registry/settings.json +1 -0
  185. package/registry/sign-in.json +1 -0
  186. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  187. package/registry/skeleton/files/src/lib/kit.tsx +462 -12
  188. package/registry/skeleton.json +1 -0
  189. package/registry/slide-to-confirm.json +1 -0
  190. package/registry/slider/files/src/components/ui/slider.tsx +106 -108
  191. package/registry/slider/files/src/lib/kit.tsx +462 -12
  192. package/registry/slider.json +5 -0
  193. package/registry/spin-wheel.json +1 -0
  194. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  195. package/registry/spinner/files/src/lib/kit.tsx +462 -12
  196. package/registry/spinner.json +6 -0
  197. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  198. package/registry/stateful-button/files/src/lib/kit.tsx +462 -12
  199. package/registry/stateful-button.json +5 -0
  200. package/registry/stepper/files/src/components/ui/stepper.tsx +40 -38
  201. package/registry/stepper/files/src/lib/kit.tsx +462 -12
  202. package/registry/stepper.json +1 -0
  203. package/registry/swipe-deck.json +1 -0
  204. package/registry/swipeable-row.json +4 -0
  205. package/registry/switch/files/src/components/ui/switch.tsx +41 -47
  206. package/registry/switch/files/src/lib/kit.tsx +462 -12
  207. package/registry/switch.json +1 -0
  208. package/registry/tabs/files/src/components/ui/tabs.tsx +61 -64
  209. package/registry/tabs/files/src/lib/kit.tsx +462 -12
  210. package/registry/tabs.json +1 -0
  211. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  212. package/registry/text-animation/files/src/lib/kit.tsx +462 -12
  213. package/registry/text-animation.json +7 -0
  214. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  215. package/registry/textarea/files/src/lib/kit.tsx +462 -12
  216. package/registry/textarea.json +1 -0
  217. package/registry/ticket-card.json +1 -0
  218. package/registry/ticket-shape/files/src/lib/kit.tsx +462 -12
  219. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  220. package/registry/ticket-shape.json +1 -0
  221. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +80 -101
  222. package/registry/tilt-card/files/src/lib/kit.tsx +462 -12
  223. package/registry/tilt-card.json +1 -0
  224. package/registry/time-picker/files/src/components/ui/time-picker.tsx +136 -131
  225. package/registry/time-picker/files/src/lib/kit.tsx +462 -12
  226. package/registry/time-picker.json +1 -0
  227. package/registry/timeline/files/src/components/ui/avatar.tsx +89 -74
  228. package/registry/timeline/files/src/components/ui/timeline.tsx +49 -57
  229. package/registry/timeline/files/src/lib/kit.tsx +462 -12
  230. package/registry/timeline-chart.json +1 -0
  231. package/registry/timeline.json +1 -0
  232. package/registry/toast/files/src/components/ui/toast.tsx +543 -465
  233. package/registry/toast/files/src/lib/kit.tsx +462 -12
  234. package/registry/toast.json +7 -5
  235. package/registry/treemap.json +1 -0
  236. package/registry/waterfall.json +1 -0
  237. package/registry/wheel-list.json +1 -0
  238. package/schema/components.schema.json +1 -0
  239. package/registry/alert/files/src/components/ui/button.tsx +0 -303
  240. package/registry/confetti/files/src/lib/kit.tsx +0 -222
  241. package/registry/grain/files/src/lib/kit.tsx +0 -222
  242. package/registry/toast/files/src/components/ui/button.tsx +0 -303
@@ -1,5 +1,5 @@
1
1
  import * as Haptics from 'expo-haptics';
2
- import React, { useEffect, useId, useRef, useState } from 'react';
2
+ import React, { useEffect, useId, useState } from 'react';
3
3
  import {
4
4
  AccessibilityInfo,
5
5
  Animated,
@@ -25,41 +25,41 @@ export type AvatarFallback = 'soft' | 'solid' | 'gradient';
25
25
 
26
26
  export interface AvatarProps {
27
27
  /** Image URL. Initials show while it loads, and stay if it fails. */
28
- src?: string;
28
+ src?: string | undefined;
29
29
  /** Used for the initials, the fallback colour (same name → same colour) and the accessibility label. */
30
- name?: string;
30
+ name?: string | undefined;
31
31
  /** 'xs' 24 · 'sm' 32 · 'md' 40 · 'lg' 56 · 'xl' 72, or any number of points. */
32
- size?: AvatarSize | number;
33
- shape?: AvatarShape;
32
+ size?: AvatarSize | number | undefined;
33
+ shape?: AvatarShape | undefined;
34
34
  /** Presence dot cut into the bottom-right edge. */
35
- status?: AvatarStatus;
35
+ status?: AvatarStatus | undefined;
36
36
  /** Soft pulse behind the status dot (default: on for 'online'). */
37
- pulse?: boolean;
37
+ pulse?: boolean | undefined;
38
38
  /** Ring around the face, separated by a clear gap. */
39
- ring?: AvatarRing;
39
+ ring?: AvatarRing | undefined;
40
40
  /** The story has been watched: the ring cross-fades to a quiet hairline. */
41
- seen?: boolean;
41
+ seen?: boolean | undefined;
42
42
  /** Ring colour override — the first entry is used as a solid colour (default: `accent`). */
43
- ringColors?: string[];
43
+ ringColors?: string[] | undefined;
44
44
  /** Spin a broken story ring — a "story is loading" state. */
45
- ringSpin?: boolean;
45
+ ringSpin?: boolean | undefined;
46
46
  /** Initials look when there is no photo. Default 'soft'. */
47
- fallback?: AvatarFallback;
47
+ fallback?: AvatarFallback | undefined;
48
48
  /** Soft layered shadow under the face (default true for 72pt and up). */
49
- elevated?: boolean;
49
+ elevated?: boolean | undefined;
50
50
  /** Notification count (`true` = dot), top-right. */
51
- badge?: number | boolean;
51
+ badge?: number | boolean | undefined;
52
52
  /** Verified seal with a check, bottom-right (bottom-left when a status dot is shown). */
53
- verified?: boolean;
53
+ verified?: boolean | undefined;
54
54
  /** Colour behind the avatar — the dot, badge and seal cut-outs are punched in it. Default: theme background. */
55
- surface?: string;
55
+ surface?: string | undefined;
56
56
  /** Ring and verified seal colour (default: theme accent). */
57
- accent?: string;
57
+ accent?: string | undefined;
58
58
  /** Makes it a button with a spring press and a light haptic tick. */
59
- onPress?: () => void;
59
+ onPress?: (() => void) | undefined;
60
60
  /** Haptic tick on press (default true). */
61
- haptics?: boolean;
62
- style?: StyleProp<ViewStyle>;
61
+ haptics?: boolean | undefined;
62
+ style?: StyleProp<ViewStyle> | undefined;
63
63
  }
64
64
 
65
65
  const SIZES: Record<AvatarSize, number> = { xs: 24, sm: 32, md: 40, lg: 56, xl: 72 };
@@ -99,7 +99,9 @@ export function colorFromName(name = '') {
99
99
  export function initialsFrom(name = '', max = 2) {
100
100
  const parts = name.trim().split(/\s+/).filter(Boolean);
101
101
  if (!parts.length) return '';
102
- const letters = parts.length === 1 ? [parts[0][0]] : [parts[0][0], parts[parts.length - 1][0]];
102
+ const first = parts[0] ?? '';
103
+ const last = parts[parts.length - 1] ?? '';
104
+ const letters = parts.length === 1 ? [first[0]] : [first[0], last[0]];
103
105
  return letters.slice(0, max).join('').toUpperCase();
104
106
  }
105
107
 
@@ -144,13 +146,13 @@ export function Avatar({
144
146
  // Remember which URL failed so a new `src` gets a fresh try without resetting state in an effect.
145
147
  const [failedSrc, setFailedSrc] = useState<string>();
146
148
  const failed = !!src && failedSrc === src;
147
- const loaded = useRef(new Animated.Value(0)).current;
148
- const press = useRef(new Animated.Value(1)).current;
149
- const spin = useRef(new Animated.Value(0)).current;
150
- const pulseV = useRef(new Animated.Value(0)).current;
149
+ const [loaded] = useState(() => new Animated.Value(0));
150
+ const [press] = useState(() => new Animated.Value(1));
151
+ const [spin] = useState(() => new Animated.Value(0));
152
+ const [pulseV] = useState(() => new Animated.Value(0));
151
153
 
152
154
  const isSeen = seen ?? ring === 'seen';
153
- const seenV = useRef(new Animated.Value(isSeen ? 1 : 0)).current;
155
+ const [seenV] = useState(() => new Animated.Value(isSeen ? 1 : 0));
154
156
 
155
157
  useEffect(() => {
156
158
  loaded.setValue(0);
@@ -248,11 +250,8 @@ export function Avatar({
248
250
  { width: px, height: px, borderRadius: faceRadius, backgroundColor: faceBg },
249
251
  showElevated
250
252
  ? {
251
- shadowColor: '#000',
252
- shadowOpacity: dark ? 0.5 : 0.14,
253
- shadowRadius: px * 0.16,
254
- shadowOffset: { width: 0, height: Math.max(2, px * 0.07) },
255
- elevation: Math.round(px * 0.1),
253
+ // boxShadow scales with the avatar and looks the same on iOS, Android and web.
254
+ boxShadow: `0px ${Math.round(Math.max(2, px * 0.07))}px ${Math.round(px * 0.32)}px rgba(0,0,0,${dark ? 0.5 : 0.14})`,
256
255
  }
257
256
  : null,
258
257
  ]}
@@ -338,7 +337,7 @@ export function Avatar({
338
337
  strokeWidth={width}
339
338
  fill="none"
340
339
  strokeLinecap="round"
341
- strokeDasharray={dashed ? [circ * 0.7, circ * 0.3] : undefined}
340
+ {...(dashed ? { strokeDasharray: [circ * 0.7, circ * 0.3] } : null)}
342
341
  />
343
342
  ) : (
344
343
  <Rect
@@ -571,12 +570,15 @@ const SEAL = (() => {
571
570
  const rr = k % 2 === 0 ? 12 : 10.1;
572
571
  return [12 + rr * Math.cos(a), 12 + rr * Math.sin(a)] as const;
573
572
  });
574
- const mid = (a: readonly number[], b: readonly number[]) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
573
+ type Pt = readonly [number, number];
574
+ const mid = (a: Pt, b: Pt): Pt => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
575
575
  const f = (v: number) => v.toFixed(2);
576
- const start = mid(pts[pts.length - 1], pts[0]);
576
+ // `pts` has 2n points and every index below is taken modulo its length.
577
+ const at = (k: number) => pts[k % pts.length]!;
578
+ const start = mid(at(pts.length - 1), at(0));
577
579
  let d = `M${f(start[0])} ${f(start[1])}`;
578
580
  pts.forEach((pt, i) => {
579
- const m = mid(pt, pts[(i + 1) % pts.length]);
581
+ const m = mid(pt, at(i + 1));
580
582
  d += `Q${f(pt[0])} ${f(pt[1])} ${f(m[0])} ${f(m[1])}`;
581
583
  });
582
584
  return d + 'Z';
@@ -596,20 +598,20 @@ export interface AvatarGroupProps {
596
598
  /** `<Avatar>` elements. The group sets their size, shape and surface. */
597
599
  children: React.ReactNode;
598
600
  /** Show at most this many, then a "+N" chip. */
599
- max?: number;
600
- size?: AvatarSize | number;
601
- shape?: AvatarShape;
601
+ max?: number | undefined;
602
+ size?: AvatarSize | number | undefined;
603
+ shape?: AvatarShape | undefined;
602
604
  /** How far each avatar tucks under the previous one, as a fraction of its size (default 0.28). */
603
- overlap?: number;
605
+ overlap?: number | undefined;
604
606
  /** Real total when it is larger than the children you pass (e.g. 1,284 members). */
605
- total?: number;
607
+ total?: number | undefined;
606
608
  /** Staggered spring-in on mount (skipped with reduce motion). */
607
- animated?: boolean;
609
+ animated?: boolean | undefined;
608
610
  /** Colour of the separating borders — match what the group sits on (default: theme background). */
609
- surface?: string;
611
+ surface?: string | undefined;
610
612
  /** Makes the "+N" chip a button. */
611
- onPressMore?: () => void;
612
- style?: StyleProp<ViewStyle>;
613
+ onPressMore?: (() => void) | undefined;
614
+ style?: StyleProp<ViewStyle> | undefined;
613
615
  }
614
616
 
615
617
  /** Overlapping avatars separated by cut-out borders, with a "+N" chip and a staggered spring-in. */
@@ -625,7 +627,7 @@ export function AvatarGroup({
625
627
  onPressMore,
626
628
  style,
627
629
  }: AvatarGroupProps) {
628
- const { colors, radius, dark, font } = useKit();
630
+ const { colors, radius, dark, font, labels } = useKit();
629
631
  const ff = font.family ? { fontFamily: font.family } : null;
630
632
  const reduceMotion = useReduceMotion();
631
633
  const px = resolveSize(size);
@@ -638,8 +640,13 @@ export function AvatarGroup({
638
640
  const inner = shape === 'circle' ? radius.full : Math.min(px / 2, radius.scale(Math.round(px * 0.3)));
639
641
  const wrapCorner = shape === 'circle' ? radius.full : inner + border;
640
642
 
641
- const anims = useRef<Animated.Value[]>([]).current;
642
- while (anims.length < count) anims.push(new Animated.Value(animated ? 0 : 1));
643
+ // One value per avatar, grown (never shrunk) as children are added; this render already uses the grown list.
644
+ const [pool, setPool] = useState<Animated.Value[]>([]);
645
+ let anims = pool;
646
+ if (pool.length < count) {
647
+ anims = [...pool, ...Array.from({ length: count - pool.length }, () => new Animated.Value(animated ? 0 : 1))];
648
+ setPool(anims);
649
+ }
643
650
 
644
651
  useEffect(() => {
645
652
  if (!animated) return;
@@ -657,35 +664,41 @@ export function AvatarGroup({
657
664
  const names = shown.map((c) => c.props.name).filter(Boolean) as string[];
658
665
  const label =
659
666
  names.length === 0
660
- ? `${total ?? items.length} people`
661
- : `${names.slice(0, 3).join(', ')}${(total ?? items.length) - Math.min(3, names.length) > 0 ? ` and ${(total ?? items.length) - Math.min(3, names.length)} more` : ''}`;
662
-
663
- const wrap = (child: React.ReactNode, i: number, key: React.Key) => (
664
- <Animated.View
665
- key={key}
666
- style={{
667
- marginLeft: i === 0 ? 0 : -px * overlap,
668
- borderRadius: wrapCorner,
669
- padding: border,
670
- backgroundColor: bg,
671
- zIndex: count - i,
672
- opacity: anims[i],
673
- transform: [
674
- { translateX: anims[i].interpolate({ inputRange: [0, 1], outputRange: [-8, 0] }) },
675
- { scale: anims[i].interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) },
676
- ],
677
- }}
678
- >
679
- {child}
680
- </Animated.View>
681
- );
667
+ ? labels.peopleCount(total ?? items.length)
668
+ : (total ?? items.length) - Math.min(3, names.length) > 0
669
+ ? labels.namesAndMore(names.slice(0, 3).join(', '), (total ?? items.length) - Math.min(3, names.length))
670
+ : names.slice(0, 3).join(', ');
671
+
672
+ // `anims` was grown to `count` above, and i < count.
673
+ const wrap = (child: React.ReactNode, i: number, key: React.Key) => {
674
+ const v = anims[i]!;
675
+ return (
676
+ <Animated.View
677
+ key={key}
678
+ style={{
679
+ marginLeft: i === 0 ? 0 : -px * overlap,
680
+ borderRadius: wrapCorner,
681
+ padding: border,
682
+ backgroundColor: bg,
683
+ zIndex: count - i,
684
+ opacity: v,
685
+ transform: [
686
+ { translateX: v.interpolate({ inputRange: [0, 1], outputRange: [-8, 0] }) },
687
+ { scale: v.interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) },
688
+ ],
689
+ }}
690
+ >
691
+ {child}
692
+ </Animated.View>
693
+ );
694
+ };
682
695
 
683
696
  const extraText = extra > 999 ? `${(extra / 1000).toFixed(extra >= 10000 ? 0 : 1).replace(/\.0$/, '')}k` : String(extra);
684
697
  const more =
685
698
  extra > 0 ? (
686
699
  <Pressable
687
700
  accessibilityRole={onPressMore ? 'button' : 'text'}
688
- accessibilityLabel={`${extra} more`}
701
+ accessibilityLabel={labels.moreCount(extra)}
689
702
  disabled={!onPressMore}
690
703
  onPress={onPressMore}
691
704
  hitSlop={px < 44 ? Math.ceil((44 - px) / 2) : undefined}
@@ -774,20 +787,22 @@ function luminance(hex: string) {
774
787
  }
775
788
 
776
789
  function contrast(a: string, b: string) {
777
- const [x, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
790
+ const la = luminance(a);
791
+ const lb = luminance(b);
792
+ const [x, y] = la >= lb ? [la, lb] : [lb, la];
778
793
  return (x + 0.05) / (y + 0.05);
779
794
  }
780
795
 
781
796
  const styles = StyleSheet.create({
782
797
  face: { overflow: 'visible' },
783
798
  clip: { flex: 1, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },
784
- initials: { fontWeight: '400', includeFontPadding: false, textAlign: 'center' },
799
+ initials: { fontWeight: '400', includeFontPadding: false, textAlign: 'center', textAlignVertical: 'center' },
785
800
  inkShadow: { textShadowColor: 'rgba(0,0,0,0.18)', textShadowRadius: 2, textShadowOffset: { width: 0, height: 0.5 } },
786
801
  pulse: { position: 'absolute' },
787
802
  dot: { alignItems: 'center', justifyContent: 'center', overflow: 'hidden' },
788
803
  badge: { position: 'absolute', alignItems: 'center', justifyContent: 'center' },
789
- badgeText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false },
804
+ badgeText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false, textAlignVertical: 'center' },
790
805
  group: { flexDirection: 'row', alignItems: 'center' },
791
806
  more: { alignItems: 'center', justifyContent: 'center' },
792
- moreText: { fontWeight: '400', letterSpacing: -0.2 },
807
+ moreText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false, textAlignVertical: 'center' },
793
808
  });