@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
@@ -4,6 +4,7 @@
4
4
  "title": "Scratch Card",
5
5
  "description": "A scratch-to-win card: your finger rubs a shimmering foil away (silver, gold, holo, rose or your own) and whatever is underneath shows through — cashback, a prize, a whole Coupon. Once enough is scratched the rest clears with a haptic tap; progress, controlled reveal and reset included.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg",
9
10
  "expo-haptics"
@@ -86,7 +86,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
86
86
  size = 'md',
87
87
  loading = false,
88
88
  showCancel,
89
- cancelLabel = 'Cancel',
89
+ cancelLabel: cancelLabelProp,
90
90
  onCancel,
91
91
  onClear,
92
92
  accent,
@@ -102,14 +102,16 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
102
102
  onChangeText,
103
103
  onFocus,
104
104
  onBlur,
105
- placeholder = 'Search',
105
+ placeholder: placeholderProp,
106
106
  editable = true,
107
107
  autoFocus,
108
108
  ...props
109
109
  },
110
110
  ref
111
111
  ) {
112
- const { colors, radius, font } = useKit();
112
+ const { colors, radius, font, labels } = useKit();
113
+ const cancelLabel = cancelLabelProp ?? labels.cancel;
114
+ const placeholder = placeholderProp ?? labels.search;
113
115
  const reduce = useReduceMotion();
114
116
  const ff = font.family ? { fontFamily: font.family } : null;
115
117
  const s = SIZES[size];
@@ -131,17 +133,17 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
131
133
  const cancelShown = cancelEnabled && (focused || text.length > 0);
132
134
 
133
135
  // JS driver: layout (widths) and colours.
134
- const focusJS = useRef(new Animated.Value(0)).current;
135
- const cancelJS = useRef(new Animated.Value(0)).current;
136
- const openJS = useRef(new Animated.Value(open ? 1 : 0)).current;
136
+ const [focusJS] = useState(() => new Animated.Value(0));
137
+ const [cancelJS] = useState(() => new Animated.Value(0));
138
+ const [openJS] = useState(() => new Animated.Value(open ? 1 : 0));
137
139
  // Native driver: opacity and transforms, on different views.
138
- const halo = useRef(new Animated.Value(0)).current;
139
- const cancelN = useRef(new Animated.Value(0)).current;
140
- const openN = useRef(new Animated.Value(open ? 1 : 0)).current;
141
- const icon = useRef(new Animated.Value(1)).current;
142
- const clear = useRef(new Animated.Value(text ? 1 : 0)).current;
143
- const busy = useRef(new Animated.Value(loading ? 1 : 0)).current;
144
- const spin = useRef(new Animated.Value(0)).current;
140
+ const [halo] = useState(() => new Animated.Value(0));
141
+ const [cancelN] = useState(() => new Animated.Value(0));
142
+ const [openN] = useState(() => new Animated.Value(open ? 1 : 0));
143
+ const [icon] = useState(() => new Animated.Value(1));
144
+ const [clear] = useState(() => new Animated.Value(text ? 1 : 0));
145
+ const [busy] = useState(() => new Animated.Value(loading ? 1 : 0));
146
+ const [spin] = useState(() => new Animated.Value(0));
145
147
 
146
148
  useEffect(() => {
147
149
  const to = focused ? 1 : 0;
@@ -199,11 +201,11 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
199
201
 
200
202
  // The panel closes a beat after blur so a tap on a suggestion still lands (web blurs on mousedown).
201
203
  const [panelOpen, setPanelOpen] = useState(false);
204
+ if (focused && !panelOpen) setPanelOpen(true);
202
205
  const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
203
206
  useEffect(() => {
204
207
  if (closeTimer.current) clearTimeout(closeTimer.current);
205
- if (focused) setPanelOpen(true);
206
- else closeTimer.current = setTimeout(() => setPanelOpen(false), 160);
208
+ if (!focused) closeTimer.current = setTimeout(() => setPanelOpen(false), 160);
207
209
  return () => {
208
210
  if (closeTimer.current) clearTimeout(closeTimer.current);
209
211
  };
@@ -330,7 +332,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
330
332
  <Pressable
331
333
  hitSlop={12}
332
334
  accessibilityRole="button"
333
- accessibilityLabel="Clear search"
335
+ accessibilityLabel={labels.clearSearch}
334
336
  onPress={() => {
335
337
  setText('');
336
338
  onClear?.();
@@ -434,16 +436,18 @@ function SuggestionPanel({
434
436
  reduce: boolean;
435
437
  onSelect: (item: SearchSuggestion) => void;
436
438
  }) {
437
- const { colors, radius, font } = useKit();
439
+ const { colors, radius, font, shadow } = useKit();
438
440
  const ff = font.family ? { fontFamily: font.family } : null;
439
441
  const [mounted, setMounted] = useState(open);
440
- const enter = useRef(new Animated.Value(0)).current;
441
- const last = useRef(items);
442
- if (items.length) last.current = items;
442
+ if (open && !mounted) setMounted(true);
443
+ const [enter] = useState(() => new Animated.Value(0));
444
+ // Keeps showing the last suggestions while the panel fades out after they empty.
445
+ const [lastItems, setLastItems] = useState(items);
446
+ if (items.length && items !== lastItems) setLastItems(items);
447
+ const shownItems = items.length ? items : lastItems;
443
448
 
444
449
  useEffect(() => {
445
450
  if (open) {
446
- setMounted(true);
447
451
  if (reduce) Animated.timing(enter, { toValue: 1, duration: 150, useNativeDriver: true }).start();
448
452
  else Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();
449
453
  } else {
@@ -459,6 +463,7 @@ function SuggestionPanel({
459
463
  accessibilityRole="list"
460
464
  style={[
461
465
  styles.panel,
466
+ shadow('md'),
462
467
  {
463
468
  backgroundColor: colors.popover,
464
469
  borderColor: colors.border,
@@ -471,7 +476,7 @@ function SuggestionPanel({
471
476
  },
472
477
  ]}
473
478
  >
474
- {last.current.map((item) => (
479
+ {shownItems.map((item) => (
475
480
  <Pressable
476
481
  key={item.key}
477
482
  accessibilityRole="button"
@@ -565,18 +570,13 @@ const styles = StyleSheet.create({
565
570
  clearDot: { alignItems: 'center', justifyContent: 'center' },
566
571
  trailing: { paddingLeft: 2, alignItems: 'center', justifyContent: 'center' },
567
572
  cancel: { position: 'absolute', left: 0, top: 0, bottom: 0, justifyContent: 'center', paddingLeft: 14 },
568
- cancelText: { fontWeight: '400' },
573
+ cancelText: { fontWeight: '400', includeFontPadding: false, textAlignVertical: 'center' },
569
574
  measure: { position: 'absolute', opacity: 0, left: 0, top: 0 },
570
575
  panel: {
571
576
  marginTop: 10,
572
577
  paddingVertical: 6,
573
578
  borderWidth: StyleSheet.hairlineWidth * 2,
574
579
  overflow: 'hidden',
575
- shadowColor: '#000',
576
- shadowOpacity: 0.08,
577
- shadowRadius: 16,
578
- shadowOffset: { width: 0, height: 8 },
579
- elevation: 4,
580
580
  },
581
581
  option: { flexDirection: 'row', alignItems: 'center', gap: 12, minHeight: 44, paddingHorizontal: 12 },
582
582
  optionIcon: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center' },
@@ -1,5 +1,14 @@
1
- import React, { createContext, useContext, useMemo } from 'react';
2
- import { Platform, useColorScheme, type TextStyle } from 'react-native';
1
+ import React, { createContext, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ PanResponder,
4
+ Platform,
5
+ useColorScheme,
6
+ useWindowDimensions,
7
+ type GestureResponderHandlers,
8
+ type PanResponderCallbacks,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ } from 'react-native';
3
12
 
4
13
  /**
5
14
  * ExpoKit design tokens. Every component reads them through useKit(), so one
@@ -8,10 +17,10 @@ import { Platform, useColorScheme, type TextStyle } from 'react-native';
8
17
  */
9
18
 
10
19
  /**
11
- * Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule).
12
- * The CLI won't keep an edited kit.tsx older than the component being added needs.
20
+ * Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule,
21
+ * 3: useLayout / shadow, 4: labels, 5: visible labels such as Cancel / Done / Today, AM / PM, usePanResponder / useLatest). The CLI won't keep an edited kit.tsx older than the component being added needs.
13
22
  */
14
- export const KIT_VERSION = 2;
23
+ export const KIT_VERSION = 5;
15
24
 
16
25
  export type ColorScheme = 'light' | 'dark';
17
26
 
@@ -58,10 +67,300 @@ export interface Theme {
58
67
  };
59
68
  /** 4pt spacing scale: space(4) === 16. */
60
69
  space: (n: number) => number;
61
- font: { family?: string; mono: string };
70
+ /** The same soft shadow on iOS, Android and web: `style={[styles.card, shadow('md')]}`. */
71
+ shadow: (level: ShadowLevel, color?: string) => ViewStyle;
72
+ font: { family?: string | undefined; mono: string };
62
73
  text: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', TextStyle>;
74
+ /** Text the kit shows or speaks on its own (buttons, placeholders, screen-reader names). Translate with <KitProvider labels={...}>. */
75
+ labels: Labels;
63
76
  }
64
77
 
78
+ /**
79
+ * Every string the kit shows or speaks on its own: button text and placeholders (Cancel, Done, Today,
80
+ * Search…) and what screen readers say (close buttons, grabbers, hints). A prop on a component, like
81
+ * `cancelLabel` or `placeholder`, still wins over these.
82
+ * Pass a partial map to <KitProvider labels={...}>; keys you leave out stay English.
83
+ * Functions get the dynamic part, so word order can change per language.
84
+ */
85
+ export interface Labels {
86
+ close: string;
87
+ dismiss: string;
88
+ closeSheet: string;
89
+ sheetGrabber: string;
90
+ sheetResizeHint: string;
91
+ sheetResizeOrCloseHint: string;
92
+ navigationDrawer: string;
93
+ closeDrawer: string;
94
+ closeMenu: string;
95
+ switchAccount: string;
96
+ openActions: string;
97
+ closeActions: string;
98
+ action: string;
99
+ loading: string;
100
+ clearSearch: string;
101
+ cancelSearch: string;
102
+ clearField: (field: string) => string;
103
+ back: string;
104
+ previous: string;
105
+ next: string;
106
+ goToToday: string;
107
+ goToSlide: (n: number) => string;
108
+ showAllCards: string;
109
+ expandNotification: string;
110
+ collapseNotification: string;
111
+ moreCount: (n: number) => string;
112
+ peopleCount: (n: number) => string;
113
+ namesAndMore: (names: string, n: number) => string;
114
+ newCount: (badge: string) => string;
115
+ remove: (item: string) => string;
116
+ selectedCount: (n: number) => string;
117
+ noneSelected: string;
118
+ notSet: string;
119
+ date: string;
120
+ time: string;
121
+ hour: string;
122
+ minute: string;
123
+ hours: (n: string) => string;
124
+ minutes: (n: number) => string;
125
+ booked: string;
126
+ unavailable: string;
127
+ timeDialHint: string;
128
+ flipCalendarHint: string;
129
+ increase: string;
130
+ decrease: string;
131
+ increaseItem: (item: string) => string;
132
+ decreaseItem: (item: string) => string;
133
+ range: string;
134
+ rangeMinimum: (name: string) => string;
135
+ rangeMaximum: (name: string) => string;
136
+ ratingStars: string;
137
+ ratingHearts: string;
138
+ showPassword: string;
139
+ hidePassword: string;
140
+ passwordStrength: (level: string) => string;
141
+ ruleMet: (rule: string) => string;
142
+ ruleUnmet: (rule: string) => string;
143
+ stepOf: (step: number, total: number, label: string) => string;
144
+ allStepsComplete: string;
145
+ stepCurrent: string;
146
+ stepCompleted: string;
147
+ stepNeedsAttention: string;
148
+ stepNotStarted: string;
149
+ goBackToStep: string;
150
+ activateCodeField: string;
151
+ qrCodeFor: (value: string) => string;
152
+ undoStroke: string;
153
+ signaturePad: string;
154
+ signaturePadSigned: string;
155
+ flipToBack: string;
156
+ flipToFront: string;
157
+ revealHint: string;
158
+ confirmHint: string;
159
+ prizeWheel: string;
160
+ spinHint: string;
161
+ prizes: (n: number, list: string) => string;
162
+ orbReveal: string;
163
+ orbBack: string;
164
+ // Visible text
165
+ cancel: string;
166
+ done: string;
167
+ doneCount: (n: number) => string;
168
+ doneDays: (n: number) => string;
169
+ clear: string;
170
+ continue: string;
171
+ delete: string;
172
+ failed: string;
173
+ search: string;
174
+ noResults: string;
175
+ noOptions: string;
176
+ nothingMatches: (query: string) => string;
177
+ selectPlaceholder: string;
178
+ selectDate: string;
179
+ selectDates: string;
180
+ pickStartDate: string;
181
+ pickEndDate: string;
182
+ selectTime: string;
183
+ setTime: (time: string) => string;
184
+ /** Clock periods, shown on 12-hour pickers and times. */
185
+ am: string;
186
+ pm: string;
187
+ /** Read for the AM / PM column. */
188
+ amPm: string;
189
+ morning: string;
190
+ afternoon: string;
191
+ evening: string;
192
+ slotsOpen: (n: number) => string;
193
+ full: string;
194
+ today: string;
195
+ tomorrow: string;
196
+ nextMonday: string;
197
+ inAWeek: string;
198
+ inAMonth: string;
199
+ next7Days: string;
200
+ nextWeekend: string;
201
+ thisMonth: string;
202
+ last7Days: string;
203
+ last30Days: string;
204
+ chooseOneOrMore: string;
205
+ /** After a rolling count, as in "2 selected". */
206
+ selectedSuffix: string;
207
+ chooseMonth: string;
208
+ chooseYear: string;
209
+ backToDays: string;
210
+ dayAndWeek: (day: number, week: number) => string;
211
+ daysLeft: (n: number) => string;
212
+ /** Too short, Weak, Okay, Strong, Very strong. */
213
+ passwordLevels: [string, string, string, string, string];
214
+ passwordsMatch: string;
215
+ passwordsDontMatch: string;
216
+ showQrCode: string;
217
+ copy: string;
218
+ copied: string;
219
+ slideToConfirm: string;
220
+ confirmed: string;
221
+ like: string;
222
+ nope: string;
223
+ swipeRight: string;
224
+ swipeLeft: string;
225
+ }
226
+
227
+ export const defaultLabels: Labels = {
228
+ close: 'Close',
229
+ dismiss: 'Dismiss',
230
+ closeSheet: 'Close sheet',
231
+ sheetGrabber: 'Sheet grabber',
232
+ sheetResizeHint: 'Swipe up or down to resize',
233
+ sheetResizeOrCloseHint: 'Swipe up or down to resize or close',
234
+ navigationDrawer: 'Navigation drawer',
235
+ closeDrawer: 'Close drawer',
236
+ closeMenu: 'Close menu',
237
+ switchAccount: 'Switch account',
238
+ openActions: 'Open actions',
239
+ closeActions: 'Close actions',
240
+ action: 'Action',
241
+ loading: 'Loading',
242
+ clearSearch: 'Clear search',
243
+ cancelSearch: 'Cancel search',
244
+ clearField: (field) => `Clear ${field}`,
245
+ back: 'Back',
246
+ previous: 'Previous',
247
+ next: 'Next',
248
+ goToToday: 'Go to today',
249
+ goToSlide: (n) => `Go to slide ${n}`,
250
+ showAllCards: 'Shows all cards',
251
+ expandNotification: 'Expands the notification',
252
+ collapseNotification: 'Collapses the notification',
253
+ moreCount: (n) => `${n} more`,
254
+ peopleCount: (n) => `${n} people`,
255
+ namesAndMore: (names, n) => `${names} and ${n} more`,
256
+ newCount: (badge) => `${badge} new`,
257
+ remove: (item) => `Remove ${item}`,
258
+ selectedCount: (n) => `${n} selected`,
259
+ noneSelected: 'None selected',
260
+ notSet: 'Not set',
261
+ date: 'Date',
262
+ time: 'Time',
263
+ hour: 'Hour',
264
+ minute: 'Minute',
265
+ hours: (n) => `${n} hours`,
266
+ minutes: (n) => `${n} minutes`,
267
+ booked: 'booked',
268
+ unavailable: 'unavailable',
269
+ timeDialHint: 'Drag around the dial, or swipe up or down to adjust.',
270
+ flipCalendarHint: 'Swipe up to turn to the next day, down for the previous one.',
271
+ increase: 'Increase',
272
+ decrease: 'Decrease',
273
+ increaseItem: (item) => `Increase ${item}`,
274
+ decreaseItem: (item) => `Decrease ${item}`,
275
+ range: 'Range',
276
+ rangeMinimum: (name) => `${name} minimum`,
277
+ rangeMaximum: (name) => `${name} maximum`,
278
+ ratingStars: 'Rating, stars',
279
+ ratingHearts: 'Rating, hearts',
280
+ showPassword: 'Show password',
281
+ hidePassword: 'Hide password',
282
+ passwordStrength: (level) => `Password strength: ${level}`,
283
+ ruleMet: (rule) => `${rule}: done`,
284
+ ruleUnmet: (rule) => `${rule}: not yet`,
285
+ stepOf: (step, total, label) => `Step ${step} of ${total}, ${label}`,
286
+ allStepsComplete: 'All steps complete',
287
+ stepCurrent: 'current',
288
+ stepCompleted: 'completed',
289
+ stepNeedsAttention: 'needs attention',
290
+ stepNotStarted: 'not started',
291
+ goBackToStep: 'Goes back to this step',
292
+ activateCodeField: 'Activates the code field',
293
+ qrCodeFor: (value) => `QR code for ${value}`,
294
+ undoStroke: 'Undo last stroke',
295
+ signaturePad: 'Signature pad, draw your signature',
296
+ signaturePadSigned: 'Signature pad, signed',
297
+ flipToBack: 'Tap to flip to the back.',
298
+ flipToFront: 'Showing the back. Tap to flip to the front.',
299
+ revealHint: 'Double-tap to reveal',
300
+ confirmHint: 'Double tap to confirm',
301
+ prizeWheel: 'Prize wheel',
302
+ spinHint: 'Spins the wheel for a prize',
303
+ prizes: (n, list) => `${n} prizes: ${list}`,
304
+ orbReveal: 'Reveal',
305
+ orbBack: 'Glass orb, drag down to go back',
306
+ cancel: 'Cancel',
307
+ done: 'Done',
308
+ doneCount: (n) => `Done · ${n}`,
309
+ doneDays: (n) => `Done · ${n} days`,
310
+ clear: 'Clear',
311
+ continue: 'Continue',
312
+ delete: 'Delete',
313
+ failed: 'Failed',
314
+ search: 'Search',
315
+ noResults: 'No results',
316
+ noOptions: 'No options',
317
+ nothingMatches: (query) => `Nothing matches “${query}”.`,
318
+ selectPlaceholder: 'Select…',
319
+ selectDate: 'Select a date',
320
+ selectDates: 'Select dates',
321
+ pickStartDate: 'Pick a start date',
322
+ pickEndDate: 'pick an end date',
323
+ selectTime: 'Select a time',
324
+ setTime: (time) => `Set ${time}`,
325
+ am: 'AM',
326
+ pm: 'PM',
327
+ amPm: 'AM or PM',
328
+ morning: 'Morning',
329
+ afternoon: 'Afternoon',
330
+ evening: 'Evening',
331
+ slotsOpen: (n) => `${n} open`,
332
+ full: 'Full',
333
+ today: 'Today',
334
+ tomorrow: 'Tomorrow',
335
+ nextMonday: 'Next Monday',
336
+ inAWeek: 'In a week',
337
+ inAMonth: 'In a month',
338
+ next7Days: 'Next 7 days',
339
+ nextWeekend: 'Next weekend',
340
+ thisMonth: 'This month',
341
+ last7Days: 'Last 7 days',
342
+ last30Days: 'Last 30 days',
343
+ chooseOneOrMore: 'Choose one or more',
344
+ selectedSuffix: 'selected',
345
+ chooseMonth: 'choose month',
346
+ chooseYear: 'choose year',
347
+ backToDays: 'back to days',
348
+ dayAndWeek: (day, week) => `Day ${day} · Week ${week}`,
349
+ daysLeft: (n) => `${n} left`,
350
+ passwordLevels: ['Too short', 'Weak', 'Okay', 'Strong', 'Very strong'],
351
+ passwordsMatch: 'Passwords match',
352
+ passwordsDontMatch: "Passwords don't match",
353
+ showQrCode: 'Show QR Code',
354
+ copy: 'Copy',
355
+ copied: 'Copied',
356
+ slideToConfirm: 'Slide to confirm',
357
+ confirmed: 'Confirmed',
358
+ like: 'LIKE',
359
+ nope: 'NOPE',
360
+ swipeRight: 'Right',
361
+ swipeLeft: 'Left',
362
+ };
363
+
65
364
  const light: Colors = {
66
365
  background: '#ffffff',
67
366
  foreground: '#09090b',
@@ -134,6 +433,7 @@ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
134
433
  capsule: (h) => Math.round((h / 2) * Math.min(1, r / 12)),
135
434
  },
136
435
  space: (n) => n * 4,
436
+ shadow: (level, color) => shadowFor(level, scheme === 'dark', color),
137
437
  font: {
138
438
  family: overrides?.fontFamily,
139
439
  mono: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'ui-monospace, SFMono-Regular, Menlo, monospace' })!,
@@ -146,20 +446,27 @@ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
146
446
  xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
147
447
  xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
148
448
  },
449
+ labels: overrides?.labels ? { ...defaultLabels, ...overrides.labels } : defaultLabels,
149
450
  };
150
451
  }
151
452
 
152
453
  export interface ThemeOverrides {
153
454
  /** Brand colour used for focus rings, selected states and accent buttons. */
154
- accent?: string;
455
+ accent?: string | undefined;
456
+
155
457
  /** Base corner radius in points (default 12). */
156
- radius?: number;
157
- fontFamily?: string;
458
+ radius?: number | undefined;
459
+
460
+ fontFamily?: string | undefined;
461
+
158
462
  /** Override any token per scheme. */
159
- colors?: Partial<Record<ColorScheme, Partial<Colors>>>;
463
+ colors?: Partial<Record<ColorScheme, Partial<Colors>>> | undefined;
464
+
465
+ /** Screen-reader strings in your language, e.g. { close: "बंद करें" }. Keys you leave out stay English. */
466
+ labels?: Partial<Labels> | undefined;
160
467
  }
161
468
 
162
- const KitContext = createContext<{ scheme?: ColorScheme; overrides?: ThemeOverrides } | null>(null);
469
+ const KitContext = createContext<{ scheme?: ColorScheme | undefined; overrides?: ThemeOverrides | undefined } | null>(null);
163
470
 
164
471
  export function KitProvider({
165
472
  scheme,
@@ -169,7 +476,7 @@ export function KitProvider({
169
476
  const value = useMemo(
170
477
  () => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
171
478
  // eslint-disable-next-line react-hooks/exhaustive-deps
172
- [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors]
479
+ [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors, overrides.labels]
173
480
  );
174
481
  return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
175
482
  }
@@ -181,6 +488,101 @@ export function useKit(): Theme {
181
488
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
489
  }
183
490
 
491
+ export type ShadowLevel = 'none' | 'sm' | 'md' | 'lg' | 'xl';
492
+
493
+ const SHADOWS: Record<Exclude<ShadowLevel, 'none'>, [y: number, blur: number, opacity: number]> = {
494
+ sm: [1, 3, 0.08],
495
+ md: [4, 14, 0.1],
496
+ lg: [10, 30, 0.14],
497
+ xl: [18, 48, 0.2],
498
+ };
499
+
500
+ /**
501
+ * A shadow as `boxShadow`, which React Native (0.76+) draws identically on iOS, Android and web —
502
+ * unlike `elevation`, whose Android shadow looks nothing like the iOS one. Darker in dark mode so
503
+ * it still reads on near-black. `color` is a #rrggbb tint (default black).
504
+ */
505
+ export function shadowFor(level: ShadowLevel, dark: boolean, color = '#000000'): ViewStyle {
506
+ if (level === 'none') return {};
507
+ const [y, blur, opacity] = SHADOWS[level];
508
+ const a = Math.min(0.6, dark ? opacity * 2.8 : opacity);
509
+ const full = color.length === 4 ? '#' + [...color.slice(1)].map((c) => c + c).join('') : color;
510
+ const n = parseInt(full.slice(1, 7), 16);
511
+ const [r, g, b] = Number.isNaN(n) ? [0, 0, 0] : [(n >> 16) & 255, (n >> 8) & 255, n & 255];
512
+ return { boxShadow: `0px ${y}px ${blur}px rgba(${r},${g},${b},${+a.toFixed(3)})` };
513
+ }
514
+
515
+ /**
516
+ * Screen size classes, shared by every component so phones, foldables and tablets all get the
517
+ * right layout without any work from you. `compact` — phones (any orientation); `medium` — open
518
+ * foldables (Galaxy Z Fold, Pixel Fold), Surface Duo and small tablets; `expanded` — iPad and
519
+ * larger. Decided by the screen's short side, so a phone on its side stays a phone.
520
+ */
521
+ export type SizeClass = 'compact' | 'medium' | 'expanded';
522
+
523
+ export const breakpoints = { medium: 600, expanded: 840 };
524
+
525
+ /** Widest a sheet / picker / popover card and a screen's content column get on big screens. */
526
+ export const layoutLimits = { sheet: 560, content: 720, gutter: 24 };
527
+
528
+ export function sizeClassOf(shortSide: number): SizeClass {
529
+ if (!(shortSide >= breakpoints.medium)) return 'compact';
530
+ return shortSide >= breakpoints.expanded ? 'expanded' : 'medium';
531
+ }
532
+
533
+ export interface Layout {
534
+ width: number;
535
+ height: number;
536
+ size: SizeClass;
537
+ /** Foldable, tablet or desktop — anything bigger than a phone. */
538
+ wide: boolean;
539
+ landscape: boolean;
540
+ fontScale: number;
541
+ /** Width for a bottom sheet, picker or action sheet: the full screen on a portrait phone, a centred card on anything wider. */
542
+ sheetWidth: number;
543
+ /** Width for a screen's main column (forms, feeds, blocks): full on phones, a readable centred column on tablets. */
544
+ contentWidth: number;
545
+ /** Grid columns that fit the current width: 1 phone, 2 foldable / landscape phone, 3 tablet. */
546
+ columns: number;
547
+ /** A value per size class; a missing one falls back to the next smaller: `pick({ compact: 16, expanded: 24 })`. */
548
+ pick: <T>(values: { compact: T; medium?: T; expanded?: T }) => T;
549
+ }
550
+
551
+ const finite = (n: number) => (Number.isFinite(n) && n > 0 ? n : 0);
552
+
553
+ /** The layout for a window of `width` × `height` points (the pure part of useLayout). */
554
+ export function layoutFor(rawWidth: number, rawHeight: number, fontScale = 1): Layout {
555
+ const width = finite(rawWidth);
556
+ const height = finite(rawHeight);
557
+ const size = sizeClassOf(Math.min(width, height || width));
558
+ const roomy = width >= breakpoints.medium;
559
+ const { sheet, content, gutter } = layoutLimits;
560
+ return {
561
+ width,
562
+ height,
563
+ size,
564
+ wide: size !== 'compact',
565
+ landscape: width > height,
566
+ fontScale,
567
+ sheetWidth: roomy ? Math.min(sheet, width - gutter * 2) : width,
568
+ contentWidth: roomy ? Math.min(content, width - gutter * 2) : width,
569
+ columns: width >= breakpoints.expanded + 160 ? 3 : roomy ? 2 : 1,
570
+ pick: (values) =>
571
+ (size === 'expanded' ? values.expanded ?? values.medium : size === 'medium' ? values.medium : undefined) ??
572
+ values.compact,
573
+ };
574
+ }
575
+
576
+ /**
577
+ * The current screen's layout; re-renders on rotation, split view, Stage Manager and fold / unfold.
578
+ * Components use it so they look right on every device; use it in your own screens the same way:
579
+ * `const { contentWidth } = useLayout()` → `<View style={{ width: '100%', maxWidth: contentWidth, alignSelf: 'center' }}>`.
580
+ */
581
+ export function useLayout(): Layout {
582
+ const { width, height, fontScale } = useWindowDimensions();
583
+ return useMemo(() => layoutFor(width, height, fontScale), [width, height, fontScale]);
584
+ }
585
+
184
586
  /**
185
587
  * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
588
  * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
@@ -220,3 +622,51 @@ export function isTextNode(node: React.ReactNode): node is string | number | (st
220
622
  if (typeof node === 'string' || typeof node === 'number') return true;
221
623
  return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
222
624
  }
625
+
626
+ /**
627
+ * A ref that always holds the latest `value`, for gesture and timer callbacks that must not go stale.
628
+ * Read `.current` only inside callbacks and effects, never while rendering.
629
+ */
630
+ export function useLatest<T>(value: T): React.MutableRefObject<T> {
631
+ const ref = useRef(value);
632
+ useLayoutEffect(() => {
633
+ ref.current = value;
634
+ });
635
+ return ref;
636
+ }
637
+
638
+ const panKeys = [
639
+ 'onStartShouldSetPanResponder',
640
+ 'onStartShouldSetPanResponderCapture',
641
+ 'onMoveShouldSetPanResponder',
642
+ 'onMoveShouldSetPanResponderCapture',
643
+ 'onPanResponderGrant',
644
+ 'onPanResponderStart',
645
+ 'onPanResponderMove',
646
+ 'onPanResponderEnd',
647
+ 'onPanResponderRelease',
648
+ 'onPanResponderReject',
649
+ 'onPanResponderTerminate',
650
+ ] as const;
651
+
652
+ /**
653
+ * PanResponder handlers that always call the latest `config`, created once. Write the callbacks inline:
654
+ * they may read refs and props freely, and nothing is recreated between renders, so a drag in progress
655
+ * is never cut off. Spread the result: `<View {...pan} />`.
656
+ */
657
+ export function usePanResponder(config: PanResponderCallbacks): GestureResponderHandlers {
658
+ const latest = useLatest(config);
659
+ const [handlers] = useState(() => {
660
+ const forward: Record<string, (...args: never[]) => unknown> = {};
661
+ for (const key of panKeys) {
662
+ forward[key] = (...args) => (latest.current[key] as ((...a: never[]) => unknown) | undefined)?.(...args);
663
+ }
664
+ // React Native's defaults when these are left out are `true`, so keep them when the config has none.
665
+ forward.onPanResponderTerminationRequest = (...args) =>
666
+ (latest.current.onPanResponderTerminationRequest as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
667
+ forward.onShouldBlockNativeResponder = (...args) =>
668
+ (latest.current.onShouldBlockNativeResponder as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
669
+ return PanResponder.create(forward as PanResponderCallbacks).panHandlers;
670
+ });
671
+ return handlers;
672
+ }
@@ -4,6 +4,7 @@
4
4
  "title": "Search Bar",
5
5
  "description": "Search field with a soft focus halo: the magnifier pops on focus, Cancel slides in as the field springs narrower, the clear button scales in and a spinning arc shows loading. Suggestions drop down in an animated panel with recent / trending icons and the matching text in bold; a trailing slot fits a filter or mic button. The minimal variant expands from a round icon.",
6
6
  "tier": "free",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg"
9
10
  ],