@expokit/cli 0.4.4 → 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 (239) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +310 -3
  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/glass-search-bar.json +1 -1
  12. package/registry/_docs/grain.json +6 -1
  13. package/registry/_docs/isometric-bar.json +6 -0
  14. package/registry/_docs/liquidglass-tab.json +1 -1
  15. package/registry/_docs/password-input.json +1 -1
  16. package/registry/_docs/pixel-bar.json +6 -0
  17. package/registry/_docs/polar-area.json +6 -0
  18. package/registry/_docs/qr-code-button.json +9 -3
  19. package/registry/_docs/scratch-card.json +7 -1
  20. package/registry/_docs/search-bar.json +1 -1
  21. package/registry/_docs/select.json +2 -2
  22. package/registry/_docs/slide-to-confirm.json +2 -2
  23. package/registry/_docs/spinner.json +1 -1
  24. package/registry/_docs/stateful-button.json +2 -2
  25. package/registry/_docs/swipe-deck.json +1 -1
  26. package/registry/_docs/time-picker.json +2 -2
  27. package/registry/_docs/timeline-chart.json +6 -0
  28. package/registry/_docs/toast.json +15 -3
  29. package/registry/_docs/treemap.json +6 -0
  30. package/registry/_docs/waterfall.json +6 -0
  31. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  32. package/registry/accordion/files/src/lib/kit.tsx +363 -11
  33. package/registry/accordion.json +1 -0
  34. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +43 -48
  35. package/registry/action-sheet/files/src/lib/kit.tsx +363 -11
  36. package/registry/action-sheet.json +1 -0
  37. package/registry/alert/files/src/components/ui/alert.tsx +19 -12
  38. package/registry/alert/files/src/lib/kit.tsx +363 -11
  39. package/registry/alert.json +3 -4
  40. package/registry/assistant-app.json +1 -0
  41. package/registry/avatar/files/src/components/ui/avatar.tsx +84 -66
  42. package/registry/avatar/files/src/lib/kit.tsx +363 -11
  43. package/registry/avatar.json +1 -0
  44. package/registry/badge/files/src/components/ui/badge.tsx +34 -34
  45. package/registry/badge/files/src/lib/kit.tsx +363 -11
  46. package/registry/badge.json +1 -0
  47. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +28 -27
  48. package/registry/bottom-nav/files/src/lib/kit.tsx +363 -11
  49. package/registry/bottom-nav.json +5 -0
  50. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +353 -289
  51. package/registry/bottom-sheet/files/src/lib/kit.tsx +363 -11
  52. package/registry/bottom-sheet.json +5 -1
  53. package/registry/bullet.json +1 -0
  54. package/registry/button/files/src/components/ui/button.tsx +18 -16
  55. package/registry/button/files/src/lib/kit.tsx +363 -11
  56. package/registry/button-3d/files/src/components/ui/button-3d.tsx +2 -2
  57. package/registry/button-3d/files/src/lib/kit.tsx +363 -11
  58. package/registry/button-3d.json +1 -0
  59. package/registry/button.json +1 -0
  60. package/registry/calendar/files/src/components/ui/calendar.tsx +74 -76
  61. package/registry/calendar/files/src/lib/kit.tsx +363 -11
  62. package/registry/calendar.json +1 -0
  63. package/registry/card/files/src/components/ui/card.tsx +3 -3
  64. package/registry/card/files/src/lib/kit.tsx +363 -11
  65. package/registry/card-stack.json +1 -0
  66. package/registry/card.json +1 -0
  67. package/registry/carousel.json +1 -0
  68. package/registry/chat.json +7 -0
  69. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  70. package/registry/checkbox/files/src/lib/kit.tsx +363 -11
  71. package/registry/checkbox.json +1 -0
  72. package/registry/checkout.json +1 -0
  73. package/registry/chip-group/files/src/components/ui/chip-group.tsx +22 -22
  74. package/registry/chip-group/files/src/lib/kit.tsx +363 -11
  75. package/registry/chip-group.json +1 -0
  76. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  77. package/registry/confetti.json +3 -6
  78. package/registry/coupon/files/src/components/ui/coupon.tsx +42 -42
  79. package/registry/coupon/files/src/components/ui/flip-card.tsx +6 -5
  80. package/registry/coupon/files/src/components/ui/tilt-card.tsx +76 -91
  81. package/registry/coupon/files/src/lib/kit.tsx +363 -11
  82. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  83. package/registry/coupon.json +1 -0
  84. package/registry/cube-pager.json +1 -0
  85. package/registry/dashboard.json +1 -0
  86. package/registry/date-picker/files/src/components/ui/calendar.tsx +74 -76
  87. package/registry/date-picker/files/src/components/ui/date-picker.tsx +55 -56
  88. package/registry/date-picker/files/src/lib/kit.tsx +363 -11
  89. package/registry/date-picker.json +1 -0
  90. package/registry/dialog/files/src/components/ui/dialog.tsx +60 -56
  91. package/registry/dialog/files/src/lib/kit.tsx +363 -11
  92. package/registry/dialog.json +1 -0
  93. package/registry/donut-shop.json +1 -0
  94. package/registry/drawer/files/src/components/ui/drawer.tsx +73 -69
  95. package/registry/drawer/files/src/lib/kit.tsx +363 -11
  96. package/registry/drawer.json +1 -0
  97. package/registry/fab.json +1 -0
  98. package/registry/fintech-app.json +1 -0
  99. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +92 -86
  100. package/registry/flip-calendar/files/src/lib/kit.tsx +363 -11
  101. package/registry/flip-calendar.json +7 -0
  102. package/registry/flip-card/files/src/components/ui/flip-card.tsx +6 -5
  103. package/registry/flip-card/files/src/lib/kit.tsx +363 -11
  104. package/registry/flip-card.json +1 -0
  105. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  106. package/registry/floating-input/files/src/lib/kit.tsx +363 -11
  107. package/registry/floating-input.json +1 -0
  108. package/registry/food-app.json +1 -0
  109. package/registry/gift-card.json +1 -0
  110. package/registry/glass-button.json +5 -0
  111. package/registry/glass-nav-bar.json +1 -0
  112. package/registry/glass-search-bar.json +1 -0
  113. package/registry/glass-segmented.json +1 -0
  114. package/registry/glass-sheet.json +1 -0
  115. package/registry/glass-tab-bar.json +5 -0
  116. package/registry/glass.json +1 -0
  117. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  118. package/registry/grain.json +8 -6
  119. package/registry/inbox.json +1 -0
  120. package/registry/isometric-bar.json +1 -0
  121. package/registry/kit/files/src/lib/kit.tsx +363 -11
  122. package/registry/kit.json +1 -0
  123. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  124. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +23 -9
  125. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +2 -2
  126. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  127. package/registry/liquidglass-tab.json +1 -0
  128. package/registry/list-item/files/src/components/ui/list-item.tsx +42 -42
  129. package/registry/list-item/files/src/lib/kit.tsx +363 -11
  130. package/registry/list-item.json +1 -0
  131. package/registry/loyalty-card.json +1 -0
  132. package/registry/morphing-button-group.json +1 -0
  133. package/registry/number-ticker.json +1 -0
  134. package/registry/offers.json +1 -0
  135. package/registry/onboarding.json +1 -0
  136. package/registry/orb-reveal.json +1 -0
  137. package/registry/otp-input/files/src/components/ui/otp-input.tsx +48 -29
  138. package/registry/otp-input/files/src/lib/kit.tsx +363 -11
  139. package/registry/otp-input.json +1 -0
  140. package/registry/parallax-layers.json +1 -0
  141. package/registry/parallax-scroll.json +1 -0
  142. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  143. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  144. package/registry/password-input/files/src/lib/kit.tsx +363 -11
  145. package/registry/password-input.json +1 -0
  146. package/registry/payment-card/files/src/components/ui/flip-card.tsx +6 -5
  147. package/registry/payment-card/files/src/components/ui/payment-card.tsx +17 -17
  148. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +76 -91
  149. package/registry/payment-card/files/src/lib/kit.tsx +363 -11
  150. package/registry/payment-card.json +1 -0
  151. package/registry/paywall.json +1 -0
  152. package/registry/pixel-bar.json +1 -0
  153. package/registry/polar-area.json +1 -0
  154. package/registry/profile.json +1 -0
  155. package/registry/progress/files/src/components/ui/progress.tsx +29 -29
  156. package/registry/progress/files/src/lib/kit.tsx +363 -11
  157. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +20 -20
  158. package/registry/progress-steps/files/src/lib/kit.tsx +363 -11
  159. package/registry/progress-steps.json +1 -0
  160. package/registry/progress.json +1 -0
  161. package/registry/qr-code-button.json +1 -0
  162. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  163. package/registry/radio-group/files/src/lib/kit.tsx +363 -11
  164. package/registry/radio-group.json +1 -0
  165. package/registry/rating.json +1 -0
  166. package/registry/ring-carousel.json +1 -0
  167. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +98 -82
  168. package/registry/ruler-time-picker/files/src/lib/kit.tsx +363 -11
  169. package/registry/ruler-time-picker.json +7 -0
  170. package/registry/scratch-card.json +1 -0
  171. package/registry/search-bar/files/src/components/ui/search-bar.tsx +25 -21
  172. package/registry/search-bar/files/src/lib/kit.tsx +363 -11
  173. package/registry/search-bar.json +1 -0
  174. package/registry/search-results.json +4 -0
  175. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +8 -7
  176. package/registry/segmented-control/files/src/lib/kit.tsx +363 -11
  177. package/registry/segmented-control.json +1 -0
  178. package/registry/select/files/src/components/ui/select.tsx +93 -88
  179. package/registry/select/files/src/lib/kit.tsx +363 -11
  180. package/registry/select.json +6 -0
  181. package/registry/settings.json +1 -0
  182. package/registry/sign-in.json +1 -0
  183. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  184. package/registry/skeleton/files/src/lib/kit.tsx +363 -11
  185. package/registry/skeleton.json +1 -0
  186. package/registry/slide-to-confirm.json +1 -0
  187. package/registry/slider/files/src/components/ui/slider.tsx +103 -96
  188. package/registry/slider/files/src/lib/kit.tsx +363 -11
  189. package/registry/slider.json +5 -0
  190. package/registry/spin-wheel.json +1 -0
  191. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  192. package/registry/spinner/files/src/lib/kit.tsx +363 -11
  193. package/registry/spinner.json +6 -0
  194. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  195. package/registry/stateful-button/files/src/lib/kit.tsx +363 -11
  196. package/registry/stateful-button.json +5 -0
  197. package/registry/stepper/files/src/components/ui/stepper.tsx +38 -36
  198. package/registry/stepper/files/src/lib/kit.tsx +363 -11
  199. package/registry/stepper.json +1 -0
  200. package/registry/swipe-deck.json +1 -0
  201. package/registry/swipeable-row.json +4 -0
  202. package/registry/switch/files/src/components/ui/switch.tsx +39 -42
  203. package/registry/switch/files/src/lib/kit.tsx +363 -11
  204. package/registry/switch.json +1 -0
  205. package/registry/tabs/files/src/components/ui/tabs.tsx +59 -62
  206. package/registry/tabs/files/src/lib/kit.tsx +363 -11
  207. package/registry/tabs.json +1 -0
  208. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  209. package/registry/text-animation/files/src/lib/kit.tsx +363 -11
  210. package/registry/text-animation.json +7 -0
  211. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  212. package/registry/textarea/files/src/lib/kit.tsx +363 -11
  213. package/registry/textarea.json +1 -0
  214. package/registry/ticket-card.json +1 -0
  215. package/registry/ticket-shape/files/src/lib/kit.tsx +363 -11
  216. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  217. package/registry/ticket-shape.json +1 -0
  218. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +76 -91
  219. package/registry/tilt-card/files/src/lib/kit.tsx +363 -11
  220. package/registry/tilt-card.json +1 -0
  221. package/registry/time-picker/files/src/components/ui/time-picker.tsx +122 -119
  222. package/registry/time-picker/files/src/lib/kit.tsx +363 -11
  223. package/registry/time-picker.json +1 -0
  224. package/registry/timeline/files/src/components/ui/avatar.tsx +84 -66
  225. package/registry/timeline/files/src/components/ui/timeline.tsx +44 -41
  226. package/registry/timeline/files/src/lib/kit.tsx +363 -11
  227. package/registry/timeline-chart.json +1 -0
  228. package/registry/timeline.json +1 -0
  229. package/registry/toast/files/src/components/ui/toast.tsx +541 -463
  230. package/registry/toast/files/src/lib/kit.tsx +363 -11
  231. package/registry/toast.json +7 -5
  232. package/registry/treemap.json +1 -0
  233. package/registry/waterfall.json +1 -0
  234. package/registry/wheel-list.json +1 -0
  235. package/schema/components.schema.json +1 -0
  236. package/registry/alert/files/src/components/ui/button.tsx +0 -290
  237. package/registry/confetti/files/src/lib/kit.tsx +0 -320
  238. package/registry/grain/files/src/lib/kit.tsx +0 -320
  239. package/registry/toast/files/src/components/ui/button.tsx +0 -290
@@ -67,36 +67,36 @@ export interface BottomNavItem {
67
67
  */
68
68
  icon: BottomNavIconName | ((props: BottomNavIconProps) => React.ReactNode);
69
69
  /** `true` shows a dot; a number shows a count (99+ above 99). */
70
- badge?: boolean | number;
70
+ badge?: boolean | number | undefined;
71
71
  /**
72
72
  * A primary action (e.g. Create) instead of a destination: drawn as a solid accent button and never
73
73
  * becomes the active tab. Pressing it calls `onAction`.
74
74
  */
75
- action?: boolean;
76
- accessibilityLabel?: string;
75
+ action?: boolean | undefined;
76
+ accessibilityLabel?: string | undefined;
77
77
  }
78
78
 
79
79
  export interface BottomNavProps {
80
80
  items: BottomNavItem[];
81
- value?: string;
82
- defaultValue?: string;
83
- onValueChange?: (key: string) => void;
81
+ value?: string | undefined;
82
+ defaultValue?: string | undefined;
83
+ onValueChange?: ((key: string) => void) | undefined;
84
84
  /** Called with the item's key when an `action` item is pressed. */
85
- onAction?: (key: string) => void;
86
- variant?: BottomNavVariant;
85
+ onAction?: ((key: string) => void) | undefined;
86
+ variant?: BottomNavVariant | undefined;
87
87
  /** Active colour and the action button fill. */
88
- accent?: string;
88
+ accent?: string | undefined;
89
89
  /** Opt-in two-colour gradient for the action button (default: none — a solid accent fill). */
90
- gradient?: [string, string];
90
+ gradient?: [string, string] | undefined;
91
91
  /** Show labels. `floating` and `pill` show only the active label; the others show all. */
92
- showLabels?: boolean;
92
+ showLabels?: boolean | undefined;
93
93
  /** Extra bottom padding, e.g. `useSafeAreaInsets().bottom` when the bar sits at the screen edge. */
94
- bottomInset?: number;
94
+ bottomInset?: number | undefined;
95
95
  /** Show the iOS home-indicator line (default: on for `minimal`). */
96
- homeIndicator?: boolean;
96
+ homeIndicator?: boolean | undefined;
97
97
  /** Selection tick on tab change, light impact on the action button (default true). */
98
- haptics?: boolean;
99
- style?: StyleProp<ViewStyle>;
98
+ haptics?: boolean | undefined;
99
+ style?: StyleProp<ViewStyle> | undefined;
100
100
  }
101
101
 
102
102
  // Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
@@ -159,9 +159,9 @@ export function BottomNav({
159
159
  const slot = rowW / n;
160
160
 
161
161
  // Indicator edges (JS driver: they set left / width) and the slot offset (native: spotlight).
162
- const lead = useRef(new Animated.Value(0)).current;
163
- const trail = useRef(new Animated.Value(0)).current;
164
- const x = useRef(new Animated.Value(0)).current;
162
+ const [lead] = useState(() => new Animated.Value(0));
163
+ const [trail] = useState(() => new Animated.Value(0));
164
+ const [x] = useState(() => new Animated.Value(0));
165
165
  const placed = useRef(false);
166
166
  const last = useRef(index);
167
167
 
@@ -327,8 +327,9 @@ export function BottomNav({
327
327
 
328
328
  // ---- centre FAB in its notch (absolute, so its whole face stays inside the bar's bounds) -----
329
329
  let centre: React.ReactNode = null;
330
- if (notched && rowW) {
331
- const item = items[actionIndex];
330
+ const actionItem = notched ? items[actionIndex] : undefined;
331
+ if (actionItem && rowW) {
332
+ const item = actionItem;
332
333
  centre = (
333
334
  <CentreAction
334
335
  item={item}
@@ -449,11 +450,11 @@ interface NavButtonProps {
449
450
  }
450
451
 
451
452
  function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, reduce, onPress }: NavButtonProps) {
452
- const { font } = useKit();
453
+ const { font, labels } = useKit();
453
454
  const ff = font.family ? { fontFamily: font.family } : null;
454
- const press = useRef(new Animated.Value(1)).current; // press squish (native)
455
- const pop = useRef(new Animated.Value(active ? 1 : 0)).current; // active state (native)
456
- const grow = useRef(new Animated.Value(active ? 1 : 0)).current; // pill width (JS — layout)
455
+ const [press] = useState(() => new Animated.Value(1)); // press squish (native)
456
+ const [pop] = useState(() => new Animated.Value(active ? 1 : 0)); // active state (native)
457
+ const [grow] = useState(() => new Animated.Value(active ? 1 : 0)); // pill width (JS — layout)
457
458
  const [labelW, setLabelW] = useState(0);
458
459
 
459
460
  useEffect(() => {
@@ -468,7 +469,7 @@ function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, r
468
469
 
469
470
  const pressTo = (v: number) =>
470
471
  Animated.spring(press, { toValue: v, useNativeDriver: true, ...(v < 1 ? motion.snappy : motion.bouncy) }).start();
471
- const a11y = item.accessibilityLabel ?? (typeof item.badge === 'number' ? `${item.label}, ${item.badge} new` : item.label);
472
+ const a11y = item.accessibilityLabel ?? (typeof item.badge === 'number' ? `${item.label}, ${labels.newCount(String(item.badge))}` : item.label);
472
473
  const pressable = {
473
474
  onPress,
474
475
  onPressIn: () => pressTo(0.88),
@@ -666,7 +667,7 @@ function CentreAction({
666
667
  }) {
667
668
  const { font } = useKit();
668
669
  const ff = font.family ? { fontFamily: font.family } : null;
669
- const press = useRef(new Animated.Value(1)).current;
670
+ const [press] = useState(() => new Animated.Value(1));
670
671
  const pressTo = (v: number) =>
671
672
  Animated.spring(press, { toValue: v, useNativeDriver: true, ...(v < 1 ? motion.snappy : motion.bouncy) }).start();
672
673
  return (
@@ -773,7 +774,7 @@ function FabFace({
773
774
  function Badge({ value, ring, reduce }: { value: true | number; ring: string; reduce: boolean }) {
774
775
  const { colors, radius, font } = useKit();
775
776
  const ff = font.family ? { fontFamily: font.family } : null;
776
- const s = useRef(new Animated.Value(reduce ? 1 : 0.3)).current;
777
+ const [s] = useState(() => new Animated.Value(reduce ? 1 : 0.3));
777
778
  useEffect(() => {
778
779
  if (reduce) {
779
780
  s.setValue(1);
@@ -1,5 +1,14 @@
1
- import React, { createContext, useContext, useMemo } from 'react';
2
- import { Platform, useColorScheme, useWindowDimensions, type TextStyle, type ViewStyle } 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
@@ -9,9 +18,9 @@ import { Platform, useColorScheme, useWindowDimensions, type TextStyle, type Vie
9
18
 
10
19
  /**
11
20
  * Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule,
12
- * 3: useLayout / shadow). The CLI won't keep an edited kit.tsx older than the component being added needs.
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 = 3;
23
+ export const KIT_VERSION = 5;
15
24
 
16
25
  export type ColorScheme = 'light' | 'dark';
17
26
 
@@ -60,10 +69,298 @@ export interface Theme {
60
69
  space: (n: number) => number;
61
70
  /** The same soft shadow on iOS, Android and web: `style={[styles.card, shadow('md')]}`. */
62
71
  shadow: (level: ShadowLevel, color?: string) => ViewStyle;
63
- font: { family?: string; mono: string };
72
+ font: { family?: string | undefined; mono: string };
64
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;
65
76
  }
66
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
+
67
364
  const light: Colors = {
68
365
  background: '#ffffff',
69
366
  foreground: '#09090b',
@@ -149,20 +446,27 @@ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
149
446
  xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
150
447
  xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
151
448
  },
449
+ labels: overrides?.labels ? { ...defaultLabels, ...overrides.labels } : defaultLabels,
152
450
  };
153
451
  }
154
452
 
155
453
  export interface ThemeOverrides {
156
454
  /** Brand colour used for focus rings, selected states and accent buttons. */
157
- accent?: string;
455
+ accent?: string | undefined;
456
+
158
457
  /** Base corner radius in points (default 12). */
159
- radius?: number;
160
- fontFamily?: string;
458
+ radius?: number | undefined;
459
+
460
+ fontFamily?: string | undefined;
461
+
161
462
  /** Override any token per scheme. */
162
- 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;
163
467
  }
164
468
 
165
- const KitContext = createContext<{ scheme?: ColorScheme; overrides?: ThemeOverrides } | null>(null);
469
+ const KitContext = createContext<{ scheme?: ColorScheme | undefined; overrides?: ThemeOverrides | undefined } | null>(null);
166
470
 
167
471
  export function KitProvider({
168
472
  scheme,
@@ -172,7 +476,7 @@ export function KitProvider({
172
476
  const value = useMemo(
173
477
  () => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
174
478
  // eslint-disable-next-line react-hooks/exhaustive-deps
175
- [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors]
479
+ [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors, overrides.labels]
176
480
  );
177
481
  return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
178
482
  }
@@ -318,3 +622,51 @@ export function isTextNode(node: React.ReactNode): node is string | number | (st
318
622
  if (typeof node === 'string' || typeof node === 'number') return true;
319
623
  return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
320
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,11 @@
4
4
  "title": "Bottom Nav",
5
5
  "description": "Bottom navigation in five looks — minimal dot, floating capsule, expanding pill, notched centre FAB and top indicator with a spotlight. A liquid indicator stretches between tabs (leading edge first, trailing edge catching up), icons morph from outline to filled with a spring and a lift, active labels rise in, badges pop, the solid accent action button glows softly, and every change ticks a haptic.",
6
6
  "tier": "free",
7
+ "aliases": [
8
+ "bottom-tabs",
9
+ "tab-bar",
10
+ "bottom-bar"
11
+ ],
7
12
  "dependencies": [
8
13
  "react-native-svg",
9
14
  "expo-haptics"