@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
@@ -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,7 @@
4
4
  "title": "Floating Input",
5
5
  "description": "The label starts as the placeholder and glides onto the border when you focus — a soft accent halo blooms around the field, errors shake in with an icon, valid values pop a spring check. Prefix/suffix, clear button, counter and three variants whose indicator grows from the centre.",
6
6
  "tier": "free",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg"
9
10
  ],
@@ -4,6 +4,7 @@
4
4
  "title": "Food ordering app",
5
5
  "description": "A complete Expo Router app for a donut shop: onboarding once, sign in, a rolling menu, coupons, bag with checkout, profile and settings with a working light / dark switch.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg",
9
10
  "expo-haptics",
@@ -4,6 +4,7 @@
4
4
  "title": "Gift Card",
5
5
  "description": "A gift card on any photo — the amount, a note and who it's for on the front; tap to flip it and read the redeem code off a brushed-silver panel, with a copy button. Tilts toward your finger with a moving reflection; redeemed state.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg"
9
10
  ],
@@ -4,6 +4,11 @@
4
4
  "title": "Glass Button",
5
5
  "description": "Liquid Glass buttons and toolbars. GlassButton: label, icon or both in three sizes (icon-only is a circle), plain monochrome glass or tinted glass for the primary action, a spring press and a light haptic — real interactive Liquid Glass on iOS 26, blur everywhere else. GlassToolbar: icon buttons in one glass capsule with hairline separators, or a floating cluster of glass circles that melt into each other on iOS 26, with an active state for tools and toggles.",
6
6
  "tier": "pro",
7
+ "aliases": [
8
+ "liquid-glass-button",
9
+ "glass-toolbar",
10
+ "floating-controls"
11
+ ],
7
12
  "dependencies": [
8
13
  "expo-glass-effect",
9
14
  "expo-blur",
@@ -4,6 +4,7 @@
4
4
  "title": "Glass Nav Bar",
5
5
  "description": "The iOS 26 top bar in Liquid Glass: a centred title, a round glass back button and round glass actions that melt together on iOS 26. Wire it to a ScrollView with useGlassNavScroll() and the bar starts transparent, its glass fading in as content slides under it, while an optional large title shrinks and collapses into the bar — all on the native driver. Real Liquid Glass on iOS 26, blur on older iOS, Android and web.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-glass-effect",
9
10
  "expo-blur",
@@ -4,6 +4,7 @@
4
4
  "title": "Glass Search Bar",
5
5
  "description": "iOS 26 floating search pill on Liquid Glass. The pill swells a touch on focus, a clear button pops in with text, and an optional round glass cancel button (or a Cancel capsule) splits off beside it — grouped, so on iOS 26 the two melt together as it springs in. Leading / trailing slots fit a custom icon or a mic button. Real Liquid Glass on iOS 26, blur or frosted glass everywhere else.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-glass-effect",
9
10
  "expo-blur",
@@ -4,6 +4,7 @@
4
4
  "title": "Glass Segmented Control",
5
5
  "description": "A Liquid Glass segmented control: a glass track with a brighter glass thumb that springs to the new segment, stretching in the direction of travel before it settles like a drop. Press the thumb and drag it across the segments — it follows your finger, ticks as it crosses each one and snaps to the nearest on release. Native-driver motion, Reduce Motion aware, monochrome unless you give it an accent.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-glass-effect",
9
10
  "expo-blur",
@@ -4,6 +4,7 @@
4
4
  "title": "Glass Sheet",
5
5
  "description": "iOS 26 floating glass sheet. Detached by default — it floats inset from the screen edges with big rounded corners — or docked edge-to-edge. It springs up over a fading dim, follows your finger from the grabber or the content, settles on the snap point projected from your release velocity (a fling down dismisses) with a haptic tick, and sizes itself to its content when no snap points are given; taller content scrolls. Real Liquid Glass on iOS 26, material blur on older iOS and web, a readable frosted surface on Android.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-glass-effect",
9
10
  "expo-blur",
@@ -4,6 +4,11 @@
4
4
  "title": "Glass Tab Bar",
5
5
  "description": "A floating Liquid Glass tab bar like iOS 26: a glass pill whose selected tab sits in a lighter bubble that springs between tabs, stretching as it travels and settling when it lands. Icons lift when selected, red count badges, optional labels and a separate circular trailing button (search) grouped with the bar so the two melt together on iOS 26. Real Liquid Glass on iOS 26, blur everywhere else; native-driver motion that respects Reduce Motion, selection haptics and a tablist for screen readers.",
6
6
  "tier": "pro",
7
+ "aliases": [
8
+ "liquid-glass-tabs",
9
+ "floating-tab-bar",
10
+ "ios-26-tab-bar"
11
+ ],
7
12
  "dependencies": [
8
13
  "expo-glass-effect",
9
14
  "expo-blur",
@@ -4,6 +4,7 @@
4
4
  "title": "Glass",
5
5
  "description": "Liquid Glass for every device: real iOS 26 Liquid Glass (interactive, mergeable with GlassGroup), system material blur on older iOS, real blur on Android 12+ inside GlassProvider / GlassBackdrop (a frosted surface otherwise) and backdrop blur on web — with a specular rim, hairline edge and soft shadow wherever the glass is emulated. The base every glass component is built on.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-glass-effect",
9
10
  "expo-blur",
@@ -18,24 +18,24 @@ export type GrainPalette =
18
18
 
19
19
  export interface GrainGradientProps {
20
20
  /** A built-in colour set. */
21
- palette?: GrainPalette;
21
+ palette?: GrainPalette | undefined;
22
22
  /** Your own colours: the first is the base, the rest become soft blobs (up to 4). Overrides `palette`. */
23
- colors?: string[];
23
+ colors?: string[] | undefined;
24
24
  /** Strength of the film grain, 0–1. */
25
- grain?: number;
25
+ grain?: number | undefined;
26
26
  /** Shuffle where the blobs sit. */
27
- seed?: number;
28
- style?: StyleProp<ViewStyle>;
29
- children?: React.ReactNode;
27
+ seed?: number | undefined;
28
+ style?: StyleProp<ViewStyle> | undefined;
29
+ children?: React.ReactNode | undefined;
30
30
  }
31
31
 
32
32
  export interface GrainOverlayProps {
33
33
  /** Strength of the grain, 0–1. */
34
- grain?: number;
35
- style?: StyleProp<ViewStyle>;
34
+ grain?: number | undefined;
35
+ style?: StyleProp<ViewStyle> | undefined;
36
36
  }
37
37
 
38
- const PALETTES: Record<GrainPalette, string[]> = {
38
+ const PALETTES: Record<GrainPalette, [string, ...string[]]> = {
39
39
  sunset: ['#2a1238', '#ff6a3d', '#ff3d8b', '#ffc15e', '#7b2ff7'],
40
40
  ocean: ['#04203a', '#0fb5c9', '#2f6bff', '#7ce7d6', '#123f8f'],
41
41
  aurora: ['#0b1324', '#2ee6a6', '#7b5cff', '#1fa2ff', '#b8ff6b'],
@@ -54,7 +54,8 @@ const PALETTES: Record<GrainPalette, string[]> = {
54
54
  export const GRAIN_PALETTES = Object.keys(PALETTES) as GrainPalette[];
55
55
 
56
56
  // Where the blobs sit, in percent of the box: [cx, cy, r].
57
- const SPOTS: [number, number, number][][] = [
57
+ type Spot = [cx: number, cy: number, r: number];
58
+ const SPOTS: [Spot[], ...Spot[][]] = [
58
59
  [[18, 22, 62], [86, 30, 58], [30, 88, 64], [82, 86, 52]],
59
60
  [[80, 16, 60], [14, 52, 58], [62, 70, 66], [24, 96, 48]],
60
61
  [[50, 8, 64], [8, 78, 60], [92, 62, 58], [46, 50, 40]],
@@ -79,8 +80,9 @@ export function GrainOverlay({ grain = 0.55, style }: GrainOverlayProps) {
79
80
  */
80
81
  export function GrainGradient({ palette = 'sunset', colors, grain = 0.55, seed = 0, style, children }: GrainGradientProps) {
81
82
  const id = useId().replace(/[^a-zA-Z0-9]/g, '');
82
- const [base, ...blobs] = colors && colors.length ? colors : PALETTES[palette];
83
- const spots = SPOTS[Math.abs(seed) % SPOTS.length];
83
+ // Both sources are non-empty, so the fallback only satisfies the type checker.
84
+ const [base = PALETTES[palette][0], ...blobs] = colors && colors.length ? colors : PALETTES[palette];
85
+ const spots = SPOTS[Math.abs(seed) % SPOTS.length] ?? SPOTS[0];
84
86
  return (
85
87
  <View style={[styles.root, { backgroundColor: base }, style]}>
86
88
  <Svg style={StyleSheet.absoluteFill} viewBox="0 0 100 100" preserveAspectRatio="none" pointerEvents="none">
@@ -98,8 +100,8 @@ export function GrainGradient({ palette = 'sunset', colors, grain = 0.55, seed =
98
100
  ))}
99
101
  </Defs>
100
102
  <Rect x={0} y={0} width={100} height={100} fill={`url(#base${id})`} />
101
- {blobs.slice(0, 4).map((_, i) => (
102
- <Circle key={i} cx={spots[i][0]} cy={spots[i][1]} r={spots[i][2]} fill={`url(#b${i}${id})`} />
103
+ {spots.slice(0, Math.min(blobs.length, 4)).map(([cx, cy, r], i) => (
104
+ <Circle key={i} cx={cx} cy={cy} r={r} fill={`url(#b${i}${id})`} />
103
105
  ))}
104
106
  </Svg>
105
107
  <GrainOverlay grain={grain} />
@@ -4,14 +4,16 @@
4
4
  "title": "Grain Gradient",
5
5
  "description": "A soft mesh-style gradient — a base colour with blurred colour blobs — finished with real film grain. Twelve palettes or your own colours. Use it as a screen or card background, or behind glass so there is something to refract. GrainOverlay adds the same grain to anything, photos included.",
6
6
  "tier": "free",
7
+ "aliases": [
8
+ "grain-gradient",
9
+ "gradient",
10
+ "mesh-gradient",
11
+ "noise"
12
+ ],
7
13
  "dependencies": [
8
14
  "react-native-svg"
9
15
  ],
10
- "registryDependencies": [
11
- "kit"
12
- ],
13
- "sharedFiles": [
14
- "lib/kit.tsx"
15
- ],
16
+ "registryDependencies": [],
17
+ "sharedFiles": [],
16
18
  "filesDir": "grain/files"
17
19
  }
@@ -4,6 +4,7 @@
4
4
  "title": "Inbox",
5
5
  "description": "A messaging inbox: search, All / Unread / Mentions filter chips with counts, swipeable rows to archive, delete or mark read, unread count badges and a friendly empty state.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg",
9
10
  "expo-haptics"
@@ -4,6 +4,7 @@
4
4
  "title": "Isometric Bars",
5
5
  "description": "3D bars with depth, shaded sides and lids; the tallest bar can be highlighted. Morphs between datasets. Built on expo-charts.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "expo-charts",
9
10
  "react-native-svg"