@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
+ }
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import React, { useEffect, useId, useMemo, useState } from 'react';
2
2
  import { Animated, Image, PixelRatio, StyleSheet, Text, View, type ImageSourcePropType, type StyleProp, type ViewStyle } from 'react-native';
3
3
  import Svg, { Circle, ClipPath, Defs, G, Line, LinearGradient, Path, Pattern, Rect, Stop } from 'react-native-svg';
4
4
 
@@ -28,13 +28,13 @@ export interface TicketRect {
28
28
  export interface TicketGeometry {
29
29
  width: number;
30
30
  height: number;
31
- corners?: TicketCorners;
31
+ corners?: TicketCorners | undefined;
32
32
  /** Round bites cut into a side — e.g. both ends of a tear line. */
33
- notches?: readonly TicketNotch[];
33
+ notches?: readonly TicketNotch[] | undefined;
34
34
  /** Pattern per side (default straight). */
35
- edges?: Partial<Record<TicketSide, TicketEdge>>;
35
+ edges?: Partial<Record<TicketSide, TicketEdge | undefined>> | undefined;
36
36
  /** Bite radius, scallop depth or tooth height of the edge patterns (default 5). */
37
- edgeSize?: number;
37
+ edgeSize?: number | undefined;
38
38
  }
39
39
 
40
40
  const SIDES: readonly TicketSide[] = ['top', 'right', 'bottom', 'left'];
@@ -140,20 +140,25 @@ export function ticketSafeRects(g: TicketGeometry, offset: { x: number; y: numbe
140
140
  // Each round hole is covered by a few stepped rects that hug its curve, so almost nothing
141
141
  // around it is left without light.
142
142
  const holes: [number, number, number, number][] = [];
143
- const STEPS = [0, 0.45, 0.75, 1];
143
+ // Consecutive pairs of [0, 0.45, 0.75, 1].
144
+ const STEPS: readonly (readonly [number, number])[] = [
145
+ [0, 0.45],
146
+ [0.45, 0.75],
147
+ [0.75, 1],
148
+ ];
144
149
  /** A quarter disc of radius r at corner (cx, cy), opening toward (dx, dy) = ±1. */
145
150
  const quarter = (cx: number, cy: number, r: number, dx: number, dy: number) => {
146
- for (let i = 0; i < STEPS.length - 1; i++) {
147
- const reach = r * Math.sqrt(1 - STEPS[i] * STEPS[i]);
148
- const [a, b] = [cy + dy * r * STEPS[i], cy + dy * r * STEPS[i + 1]];
151
+ for (const [s0, s1] of STEPS) {
152
+ const reach = r * Math.sqrt(1 - s0 * s0);
153
+ const [a, b] = [cy + dy * r * s0, cy + dy * r * s1];
149
154
  holes.push([Math.min(cx, cx + dx * reach), Math.min(a, b), Math.max(cx, cx + dx * reach), Math.max(a, b)]);
150
155
  }
151
156
  };
152
157
  /** The same with x and y swapped (steps run along x). */
153
158
  const quarterX = (cx: number, cy: number, r: number, dx: number, dy: number) => {
154
- for (let i = 0; i < STEPS.length - 1; i++) {
155
- const reach = r * Math.sqrt(1 - STEPS[i] * STEPS[i]);
156
- const [a, b] = [cx + dx * r * STEPS[i], cx + dx * r * STEPS[i + 1]];
159
+ for (const [s0, s1] of STEPS) {
160
+ const reach = r * Math.sqrt(1 - s0 * s0);
161
+ const [a, b] = [cx + dx * r * s0, cx + dx * r * s1];
157
162
  holes.push([Math.min(a, b), Math.min(cy, cy + dy * reach), Math.max(a, b), Math.max(cy, cy + dy * reach)]);
158
163
  }
159
164
  };
@@ -191,25 +196,25 @@ export function ticketSafeRects(g: TicketGeometry, offset: { x: number; y: numbe
191
196
  for (const hole of holes) [hole[0], hole[1], hole[2], hole[3]] = [down(hole[0]), down(hole[1]), up(hole[2]), up(hole[3])];
192
197
 
193
198
  const cuts = (lo: number, hi: number, more: number[]) => [...new Set([lo, hi, ...more])].filter((v) => v >= lo && v <= hi).sort((a, b) => a - b);
199
+ /** Each neighbouring pair of sorted cuts: [v0, v1], [v1, v2], … */
200
+ const bands = (v: number[]) => v.slice(1).map((b, i): [number, number] => [v[i] ?? b, b]);
194
201
  const xs = cuts(near(x0), near(x1), holes.flatMap(([a, , c]) => [a, c]));
195
202
  const ys = cuts(near(y0), near(y1), holes.flatMap(([, b, , d]) => [b, d]));
196
203
  const rows: { y: number; h: number; spans: [number, number][] }[] = [];
197
204
  // Every cut is on the pixel grid, so anything thinner than half a pixel is a rounding twin; a
198
205
  // one-pixel row (where two stepped holes overlap) must stay, or the reflection shows a gap there.
199
206
  const thin = 0.5 / px;
200
- for (let j = 0; j < ys.length - 1; j++) {
201
- const [ya, yb] = [ys[j], ys[j + 1]];
207
+ for (const [ya, yb] of bands(ys)) {
202
208
  if (yb - ya < thin) continue;
203
209
  const spans: [number, number][] = [];
204
- for (let i = 0; i < xs.length - 1; i++) {
205
- const [xa, xb] = [xs[i], xs[i + 1]];
210
+ for (const [xa, xb] of bands(xs)) {
206
211
  if (xb - xa < thin || holes.some(([hx0, hy0, hx1, hy1]) => xa < hx1 && xb > hx0 && ya < hy1 && yb > hy0)) continue;
207
212
  const last = spans[spans.length - 1];
208
213
  if (last && Math.abs(last[1] - xa) < 0.01) last[1] = xb;
209
214
  else spans.push([xa, xb]);
210
215
  }
211
216
  const prev = rows[rows.length - 1];
212
- const same = prev && prev.spans.length === spans.length && prev.spans.every((s, i) => s[0] === spans[i][0] && s[1] === spans[i][1]);
217
+ const same = prev && prev.spans.length === spans.length && prev.spans.every((s, i) => s[0] === spans[i]?.[0] && s[1] === spans[i]?.[1]);
213
218
  if (prev && same && Math.abs(prev.y + prev.h - ya) < 0.01) prev.h += yb - ya;
214
219
  else rows.push({ y: ya, h: yb - ya, spans });
215
220
  }
@@ -231,8 +236,8 @@ function random(seed: string) {
231
236
  const CONFETTI = ['#ffd23f', '#ff5fa2', '#3ddcff', '#7dff7a', '#ffffff'];
232
237
 
233
238
  /** A rubber stamp ("USED", "CHECKED IN") that thumps down whenever its text changes. */
234
- export function TicketStamp({ text, color, size = 17, style }: { text: string; color: string; size?: number; style?: StyleProp<ViewStyle> }) {
235
- const thump = useRef(new Animated.Value(0)).current;
239
+ export function TicketStamp({ text, color, size = 17, style }: { text: string; color: string; size?: number | undefined; style?: StyleProp<ViewStyle> | undefined }) {
240
+ const [thump] = useState(() => new Animated.Value(0));
236
241
  useEffect(() => {
237
242
  thump.setValue(0);
238
243
  Animated.spring(thump, { toValue: 1, ...motion.bouncy, useNativeDriver: true }).start();
@@ -258,12 +263,17 @@ export function TicketStamp({ text, color, size = 17, style }: { text: string; c
258
263
  }
259
264
 
260
265
  /** A QR-style matrix (finder squares, timing lines, data) drawn as one path — a look-alike, not a real code. */
261
- export function QrLike({ code, size, color = '#111111' }: { code: string; size: number; color?: string }) {
266
+ export function QrLike({ code, size, color = '#111111' }: { code: string; size: number; color?: string | undefined }) {
262
267
  const d = useMemo(() => {
263
268
  const N = 25;
264
269
  const next = random(code + '|qr');
265
270
  const finder = (x: number, y: number) => {
266
- for (const [fx, fy] of [[0, 0], [N - 7, 0], [0, N - 7]]) {
271
+ const corners: [number, number][] = [
272
+ [0, 0],
273
+ [N - 7, 0],
274
+ [0, N - 7],
275
+ ];
276
+ for (const [fx, fy] of corners) {
267
277
  const [u, v] = [x - fx, y - fy];
268
278
  if (u >= -1 && u <= 7 && v >= -1 && v <= 7) {
269
279
  if (u < 0 || v < 0 || u > 6 || v > 6) return 0; // the white ring around a finder
@@ -289,7 +299,7 @@ export function QrLike({ code, size, color = '#111111' }: { code: string; size:
289
299
  }
290
300
 
291
301
  /** A Code 128-style barcode drawn as one path — a look-alike. `vertical` turns it on its side. */
292
- export function BarcodeLike({ code, width, height, color = '#111111', vertical = false }: { code: string; width: number; height: number; color?: string; vertical?: boolean }) {
302
+ export function BarcodeLike({ code, width, height, color = '#111111', vertical = false }: { code: string; width: number; height: number; color?: string | undefined; vertical?: boolean | undefined }) {
293
303
  const { d, units } = useMemo(() => {
294
304
  const next = random(code + '|bars');
295
305
  const bars: [number, number][] = [];
@@ -314,39 +324,39 @@ export function BarcodeLike({ code, width, height, color = '#111111', vertical =
314
324
  }
315
325
 
316
326
  export interface TicketShapeProps extends Omit<TicketGeometry, 'width' | 'height'> {
317
- children?: React.ReactNode;
327
+ children?: React.ReactNode | undefined;
318
328
  /** A colour, or 2+ colours for a gradient (default white). */
319
- fill?: string | readonly string[];
329
+ fill?: string | readonly string[] | undefined;
320
330
  /** Gradient direction in degrees: 0 runs left → right, 90 top → bottom (default 125). */
321
- angle?: number;
331
+ angle?: number | undefined;
322
332
  /** Photo painted inside the outline (cover), over the fill (`require()` or `{ uri }`). */
323
- image?: ImageSourcePropType;
324
- imageOpacity?: number;
333
+ image?: ImageSourcePropType | undefined;
334
+ imageOpacity?: number | undefined;
325
335
  /** Darkens the paint toward the bottom (0 – 1) so white text reads on a photo — inside the outline only. */
326
- shade?: number;
336
+ shade?: number | undefined;
327
337
  /** Texture over the fill. */
328
- pattern?: TicketPattern;
338
+ pattern?: TicketPattern | undefined;
329
339
  /** Pattern ink (default: white or black at low opacity, whichever suits the fill; confetti is multicolour). */
330
- patternColor?: string;
340
+ patternColor?: string | undefined;
331
341
  /** Keeps the confetti scatter the same on every render (e.g. the coupon code). */
332
- seed?: string;
342
+ seed?: string | undefined;
333
343
  /** Outline colour. */
334
- stroke?: string;
335
- strokeWidth?: number;
344
+ stroke?: string | undefined;
345
+ strokeWidth?: number | undefined;
336
346
  /** Dash the outline — a "cut here" coupon. */
337
- dashed?: boolean;
347
+ dashed?: boolean | undefined;
338
348
  /** Dashed tear line along this side. */
339
- tear?: TicketSide;
340
- tearColor?: string;
349
+ tear?: TicketSide | undefined;
350
+ tearColor?: string | undefined;
341
351
  /**
342
352
  * Paint as one piece of a bigger ticket: that ticket's rect in this shape's points. Gradients,
343
353
  * patterns and photos then line up across the pieces.
344
354
  */
345
- frame?: TicketRect;
355
+ frame?: TicketRect | undefined;
346
356
  /** Reports the measured size after layout. */
347
- onSize?: (size: { width: number; height: number }) => void;
357
+ onSize?: ((size: { width: number; height: number }) => void) | undefined;
348
358
  /** Padding, width, margins… — the shape takes its size from this box. */
349
- style?: StyleProp<ViewStyle>;
359
+ style?: StyleProp<ViewStyle> | undefined;
350
360
  }
351
361
 
352
362
  /**
@@ -381,7 +391,7 @@ export function TicketShape({
381
391
  const id = 'ts' + useId().replace(/[^A-Za-z0-9_-]/g, '');
382
392
  const { w, h } = size;
383
393
  const colors = typeof fill === 'string' ? [fill] : fill.length ? fill : ['#ffffff'];
384
- const ink = readableOn(colors[Math.floor((colors.length - 1) / 2)]);
394
+ const ink = readableOn(colors[Math.floor((colors.length - 1) / 2)] ?? '#ffffff');
385
395
  const d = ticketPath({ width: w, height: h, corners, notches, edges, edgeSize });
386
396
  const f = frameProp ?? { x: 0, y: 0, width: w, height: h };
387
397
 
@@ -395,7 +405,7 @@ export function TicketShape({
395
405
  const clip = `url(#${id}-clip)`;
396
406
  const corner = cornerList(corners);
397
407
  const [s0, s1] = tear === 'top' || tear === 'bottom' ? (tear === 'top' ? [corner[0], corner[1]] : [corner[3], corner[2]]) : tear === 'left' ? [corner[0], corner[3]] : [corner[1], corner[2]];
398
- const inset = [Math.abs(s0) + 7, Math.abs(s1) + 7];
408
+ const inset: [number, number] = [Math.abs(s0) + 7, Math.abs(s1) + 7];
399
409
  const tearLine =
400
410
  tear === 'top' || tear === 'bottom'
401
411
  ? { x1: inset[0], x2: w - inset[1], y1: tear === 'top' ? 0.75 : h - 0.75, y2: tear === 'top' ? 0.75 : h - 0.75 }
@@ -419,7 +429,7 @@ export function TicketShape({
419
429
  const x = f.x + rnd() * f.width;
420
430
  const y = f.y + rnd() * f.height;
421
431
  const turn = Math.round(rnd() * 180);
422
- const color = patternColor ?? CONFETTI[Math.floor(rnd() * CONFETTI.length)];
432
+ const color = patternColor ?? CONFETTI[Math.floor(rnd() * CONFETTI.length)] ?? '#ffffff';
423
433
  const o = 0.35 + rnd() * 0.4;
424
434
  return rnd() < 0.55 ? (
425
435
  <Rect key={i} x={x - 1.6} y={y - 4.5} width={3.2} height={9} rx={1} fill={color} opacity={o} transform={`rotate(${turn} ${r2(x)} ${r2(y)})`} />
@@ -446,7 +456,7 @@ export function TicketShape({
446
456
  ))}
447
457
  </LinearGradient>
448
458
  ) : null;
449
- const fillPath = <Path d={d} fill={colors.length > 1 ? `url(#${id}-fill)` : colors[0]} />;
459
+ const fillPath = <Path d={d} fill={colors.length > 1 ? `url(#${id}-fill)` : (colors[0] ?? '#ffffff')} />;
450
460
  const overlayDefs = (
451
461
  <>
452
462
  <ClipPath id={`${id}-clip`}>
@@ -482,7 +492,7 @@ export function TicketShape({
482
492
  <>
483
493
  {shade > 0 ? <Rect width={w} height={h} fill={`url(#${id}-shade)`} clipPath={clip} /> : null}
484
494
  {texture ? <G clipPath={clip}>{texture}</G> : null}
485
- {stroke ? <Path d={d} fill="none" stroke={stroke} strokeWidth={strokeWidth} strokeDasharray={dashed ? [7, 5] : undefined} strokeLinecap="round" /> : null}
495
+ {stroke ? <Path d={d} fill="none" stroke={stroke} strokeWidth={strokeWidth} {...(dashed ? { strokeDasharray: [7, 5] } : null)} strokeLinecap="round" /> : null}
486
496
  {tear ? <Line {...tearLine} stroke={tearColor ?? alpha(ink, 0.28)} strokeWidth={1.6} strokeDasharray={[6, 5]} strokeLinecap="round" /> : null}
487
497
  </>
488
498
  );
@@ -4,6 +4,7 @@
4
4
  "title": "Coupon",
5
5
  "description": "Coupons people actually want to tap — a vivid value panel (sunburst, confetti, waves…) joined to a details panel by a perforated tear line with real cut-outs. Ten themes, five outlines (ticket, stamp, receipt, scallop, cut-out), copyable code, Apply button, live countdown, applied / used / expired / locked states, a light sweep, and an optional 3D tilt with a holo sheen that flips over to the T&C.",
6
6
  "tier": "free",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg"
9
10
  ],
@@ -4,6 +4,7 @@
4
4
  "title": "Cube Pager",
5
5
  "description": "Swipeable pages that turn like the faces of a cube — from the outside, the inside, or spinning in place. Follows your finger, carries flick momentum, rubber-bands at the ends and shades faces as they turn away. Great for stories and onboarding.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [],
8
9
  "registryDependencies": [
9
10
  "kit"
@@ -4,6 +4,7 @@
4
4
  "title": "Dashboard",
5
5
  "description": "A finance / analytics home screen: a collapsing “Overview” large title with bell and avatar, time-of-day greeting, Day / Week / Month switcher, a revenue area chart that rises in on every period change and scrubs under your finger while the headline number rolls, KPI tiles with sparklines, springy quick actions and recent transactions — plus skeleton loading and pull-to-refresh.",
6
6
  "tier": "pro",
7
+ "aliases": [],
7
8
  "dependencies": [
8
9
  "react-native-svg",
9
10
  "expo-haptics"