@expokit/cli 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +313 -4
  4. package/package.json +1 -1
  5. package/registry/_docs/action-sheet.json +1 -1
  6. package/registry/_docs/alert.json +6 -0
  7. package/registry/_docs/bottom-sheet.json +6 -0
  8. package/registry/_docs/bullet.json +6 -0
  9. package/registry/_docs/dialog.json +1 -1
  10. package/registry/_docs/drawer.json +3 -3
  11. package/registry/_docs/flip-calendar.json +106 -0
  12. package/registry/_docs/glass-search-bar.json +1 -1
  13. package/registry/_docs/grain.json +6 -1
  14. package/registry/_docs/isometric-bar.json +6 -0
  15. package/registry/_docs/liquidglass-tab.json +1 -1
  16. package/registry/_docs/password-input.json +1 -1
  17. package/registry/_docs/paywall.json +144 -0
  18. package/registry/_docs/pixel-bar.json +6 -0
  19. package/registry/_docs/polar-area.json +6 -0
  20. package/registry/_docs/qr-code-button.json +9 -3
  21. package/registry/_docs/ruler-time-picker.json +147 -0
  22. package/registry/_docs/scratch-card.json +7 -1
  23. package/registry/_docs/search-bar.json +1 -1
  24. package/registry/_docs/select.json +2 -2
  25. package/registry/_docs/slide-to-confirm.json +2 -2
  26. package/registry/_docs/spinner.json +1 -1
  27. package/registry/_docs/stateful-button.json +2 -2
  28. package/registry/_docs/swipe-deck.json +1 -1
  29. package/registry/_docs/time-picker.json +2 -2
  30. package/registry/_docs/timeline-chart.json +6 -0
  31. package/registry/_docs/toast.json +15 -3
  32. package/registry/_docs/treemap.json +6 -0
  33. package/registry/_docs/waterfall.json +6 -0
  34. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  35. package/registry/accordion/files/src/lib/kit.tsx +462 -12
  36. package/registry/accordion.json +1 -0
  37. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +50 -58
  38. package/registry/action-sheet/files/src/lib/kit.tsx +462 -12
  39. package/registry/action-sheet.json +1 -0
  40. package/registry/alert/files/src/components/ui/alert.tsx +20 -13
  41. package/registry/alert/files/src/lib/kit.tsx +462 -12
  42. package/registry/alert.json +3 -4
  43. package/registry/assistant-app.json +1 -0
  44. package/registry/avatar/files/src/components/ui/avatar.tsx +89 -74
  45. package/registry/avatar/files/src/lib/kit.tsx +462 -12
  46. package/registry/avatar.json +1 -0
  47. package/registry/badge/files/src/components/ui/badge.tsx +36 -36
  48. package/registry/badge/files/src/lib/kit.tsx +462 -12
  49. package/registry/badge.json +1 -0
  50. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +39 -44
  51. package/registry/bottom-nav/files/src/lib/kit.tsx +462 -12
  52. package/registry/bottom-nav.json +5 -0
  53. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +357 -300
  54. package/registry/bottom-sheet/files/src/lib/kit.tsx +462 -12
  55. package/registry/bottom-sheet.json +5 -1
  56. package/registry/bullet.json +1 -0
  57. package/registry/button/files/src/components/ui/button.tsx +23 -34
  58. package/registry/button/files/src/lib/kit.tsx +462 -12
  59. package/registry/button-3d/files/src/components/ui/button-3d.tsx +6 -9
  60. package/registry/button-3d/files/src/lib/kit.tsx +462 -12
  61. package/registry/button-3d.json +1 -0
  62. package/registry/button.json +1 -0
  63. package/registry/calendar/files/src/components/ui/calendar.tsx +77 -79
  64. package/registry/calendar/files/src/lib/kit.tsx +462 -12
  65. package/registry/calendar.json +1 -0
  66. package/registry/card/files/src/components/ui/card.tsx +7 -6
  67. package/registry/card/files/src/lib/kit.tsx +462 -12
  68. package/registry/card-stack.json +1 -0
  69. package/registry/card.json +1 -0
  70. package/registry/carousel.json +1 -0
  71. package/registry/chat.json +7 -0
  72. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  73. package/registry/checkbox/files/src/lib/kit.tsx +462 -12
  74. package/registry/checkbox.json +1 -0
  75. package/registry/checkout.json +1 -0
  76. package/registry/chip-group/files/src/components/ui/chip-group.tsx +24 -24
  77. package/registry/chip-group/files/src/lib/kit.tsx +462 -12
  78. package/registry/chip-group.json +1 -0
  79. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  80. package/registry/confetti.json +3 -6
  81. package/registry/coupon/files/src/components/ui/coupon.tsx +47 -52
  82. package/registry/coupon/files/src/components/ui/flip-card.tsx +9 -14
  83. package/registry/coupon/files/src/components/ui/tilt-card.tsx +80 -101
  84. package/registry/coupon/files/src/lib/kit.tsx +462 -12
  85. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  86. package/registry/coupon.json +1 -0
  87. package/registry/cube-pager.json +1 -0
  88. package/registry/dashboard.json +1 -0
  89. package/registry/date-picker/files/src/components/ui/calendar.tsx +77 -79
  90. package/registry/date-picker/files/src/components/ui/date-picker.tsx +64 -71
  91. package/registry/date-picker/files/src/lib/kit.tsx +462 -12
  92. package/registry/date-picker.json +1 -0
  93. package/registry/dialog/files/src/components/ui/dialog.tsx +66 -68
  94. package/registry/dialog/files/src/lib/kit.tsx +462 -12
  95. package/registry/dialog.json +1 -0
  96. package/registry/donut-shop.json +1 -0
  97. package/registry/drawer/files/src/components/ui/drawer.tsx +76 -88
  98. package/registry/drawer/files/src/lib/kit.tsx +462 -12
  99. package/registry/drawer.json +1 -0
  100. package/registry/fab.json +1 -0
  101. package/registry/fintech-app.json +1 -0
  102. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +632 -0
  103. package/registry/flip-calendar/files/src/lib/kit.tsx +672 -0
  104. package/registry/flip-calendar.json +24 -0
  105. package/registry/flip-card/files/src/components/ui/flip-card.tsx +9 -14
  106. package/registry/flip-card/files/src/lib/kit.tsx +462 -12
  107. package/registry/flip-card.json +1 -0
  108. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  109. package/registry/floating-input/files/src/lib/kit.tsx +462 -12
  110. package/registry/floating-input.json +1 -0
  111. package/registry/food-app.json +1 -0
  112. package/registry/gift-card.json +1 -0
  113. package/registry/glass-button.json +5 -0
  114. package/registry/glass-nav-bar.json +1 -0
  115. package/registry/glass-search-bar.json +1 -0
  116. package/registry/glass-segmented.json +1 -0
  117. package/registry/glass-sheet.json +1 -0
  118. package/registry/glass-tab-bar.json +5 -0
  119. package/registry/glass.json +1 -0
  120. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  121. package/registry/grain.json +8 -6
  122. package/registry/inbox.json +1 -0
  123. package/registry/isometric-bar.json +1 -0
  124. package/registry/kit/files/src/lib/kit.tsx +462 -12
  125. package/registry/kit.json +1 -0
  126. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  127. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +29 -16
  128. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +33 -19
  129. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  130. package/registry/liquidglass-tab.json +1 -0
  131. package/registry/list-item/files/src/components/ui/list-item.tsx +43 -43
  132. package/registry/list-item/files/src/lib/kit.tsx +462 -12
  133. package/registry/list-item.json +1 -0
  134. package/registry/loyalty-card.json +1 -0
  135. package/registry/morphing-button-group.json +1 -0
  136. package/registry/number-ticker.json +1 -0
  137. package/registry/offers.json +1 -0
  138. package/registry/onboarding.json +1 -0
  139. package/registry/orb-reveal.json +1 -0
  140. package/registry/otp-input/files/src/components/ui/otp-input.tsx +49 -30
  141. package/registry/otp-input/files/src/lib/kit.tsx +462 -12
  142. package/registry/otp-input.json +1 -0
  143. package/registry/parallax-layers.json +1 -0
  144. package/registry/parallax-scroll.json +1 -0
  145. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  146. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  147. package/registry/password-input/files/src/lib/kit.tsx +462 -12
  148. package/registry/password-input.json +1 -0
  149. package/registry/payment-card/files/src/components/ui/flip-card.tsx +9 -14
  150. package/registry/payment-card/files/src/components/ui/payment-card.tsx +26 -20
  151. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +80 -101
  152. package/registry/payment-card/files/src/lib/kit.tsx +462 -12
  153. package/registry/payment-card.json +1 -0
  154. package/registry/paywall.json +23 -0
  155. package/registry/pixel-bar.json +1 -0
  156. package/registry/polar-area.json +1 -0
  157. package/registry/profile.json +1 -0
  158. package/registry/progress/files/src/components/ui/progress.tsx +31 -29
  159. package/registry/progress/files/src/lib/kit.tsx +462 -12
  160. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +21 -21
  161. package/registry/progress-steps/files/src/lib/kit.tsx +462 -12
  162. package/registry/progress-steps.json +1 -0
  163. package/registry/progress.json +1 -0
  164. package/registry/qr-code-button.json +1 -0
  165. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  166. package/registry/radio-group/files/src/lib/kit.tsx +462 -12
  167. package/registry/radio-group.json +1 -0
  168. package/registry/rating.json +1 -0
  169. package/registry/ring-carousel.json +1 -0
  170. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +832 -0
  171. package/registry/ruler-time-picker/files/src/lib/kit.tsx +672 -0
  172. package/registry/ruler-time-picker.json +26 -0
  173. package/registry/scratch-card.json +1 -0
  174. package/registry/search-bar/files/src/components/ui/search-bar.tsx +28 -28
  175. package/registry/search-bar/files/src/lib/kit.tsx +462 -12
  176. package/registry/search-bar.json +1 -0
  177. package/registry/search-results.json +4 -0
  178. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +11 -17
  179. package/registry/segmented-control/files/src/lib/kit.tsx +462 -12
  180. package/registry/segmented-control.json +1 -0
  181. package/registry/select/files/src/components/ui/select.tsx +107 -104
  182. package/registry/select/files/src/lib/kit.tsx +462 -12
  183. package/registry/select.json +6 -0
  184. package/registry/settings.json +1 -0
  185. package/registry/sign-in.json +1 -0
  186. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  187. package/registry/skeleton/files/src/lib/kit.tsx +462 -12
  188. package/registry/skeleton.json +1 -0
  189. package/registry/slide-to-confirm.json +1 -0
  190. package/registry/slider/files/src/components/ui/slider.tsx +106 -108
  191. package/registry/slider/files/src/lib/kit.tsx +462 -12
  192. package/registry/slider.json +5 -0
  193. package/registry/spin-wheel.json +1 -0
  194. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  195. package/registry/spinner/files/src/lib/kit.tsx +462 -12
  196. package/registry/spinner.json +6 -0
  197. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  198. package/registry/stateful-button/files/src/lib/kit.tsx +462 -12
  199. package/registry/stateful-button.json +5 -0
  200. package/registry/stepper/files/src/components/ui/stepper.tsx +40 -38
  201. package/registry/stepper/files/src/lib/kit.tsx +462 -12
  202. package/registry/stepper.json +1 -0
  203. package/registry/swipe-deck.json +1 -0
  204. package/registry/swipeable-row.json +4 -0
  205. package/registry/switch/files/src/components/ui/switch.tsx +41 -47
  206. package/registry/switch/files/src/lib/kit.tsx +462 -12
  207. package/registry/switch.json +1 -0
  208. package/registry/tabs/files/src/components/ui/tabs.tsx +61 -64
  209. package/registry/tabs/files/src/lib/kit.tsx +462 -12
  210. package/registry/tabs.json +1 -0
  211. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  212. package/registry/text-animation/files/src/lib/kit.tsx +462 -12
  213. package/registry/text-animation.json +7 -0
  214. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  215. package/registry/textarea/files/src/lib/kit.tsx +462 -12
  216. package/registry/textarea.json +1 -0
  217. package/registry/ticket-card.json +1 -0
  218. package/registry/ticket-shape/files/src/lib/kit.tsx +462 -12
  219. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  220. package/registry/ticket-shape.json +1 -0
  221. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +80 -101
  222. package/registry/tilt-card/files/src/lib/kit.tsx +462 -12
  223. package/registry/tilt-card.json +1 -0
  224. package/registry/time-picker/files/src/components/ui/time-picker.tsx +136 -131
  225. package/registry/time-picker/files/src/lib/kit.tsx +462 -12
  226. package/registry/time-picker.json +1 -0
  227. package/registry/timeline/files/src/components/ui/avatar.tsx +89 -74
  228. package/registry/timeline/files/src/components/ui/timeline.tsx +49 -57
  229. package/registry/timeline/files/src/lib/kit.tsx +462 -12
  230. package/registry/timeline-chart.json +1 -0
  231. package/registry/timeline.json +1 -0
  232. package/registry/toast/files/src/components/ui/toast.tsx +543 -465
  233. package/registry/toast/files/src/lib/kit.tsx +462 -12
  234. package/registry/toast.json +7 -5
  235. package/registry/treemap.json +1 -0
  236. package/registry/waterfall.json +1 -0
  237. package/registry/wheel-list.json +1 -0
  238. package/schema/components.schema.json +1 -0
  239. package/registry/alert/files/src/components/ui/button.tsx +0 -303
  240. package/registry/confetti/files/src/lib/kit.tsx +0 -222
  241. package/registry/grain/files/src/lib/kit.tsx +0 -222
  242. package/registry/toast/files/src/components/ui/button.tsx +0 -303
@@ -1,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"
@@ -3,7 +3,6 @@ import {
3
3
  AccessibilityInfo,
4
4
  Animated,
5
5
  Easing,
6
- PanResponder,
7
6
  Pressable,
8
7
  StyleSheet,
9
8
  Text,
@@ -14,7 +13,7 @@ import {
14
13
  } from 'react-native';
15
14
  import Svg, { Path } from 'react-native-svg';
16
15
 
17
- import { alpha, motion, readableOn, useKit } from '@/lib/kit';
16
+ import { alpha, motion, readableOn, useKit, usePanResponder } from '@/lib/kit';
18
17
 
19
18
  export type CalendarMode = 'single' | 'range' | 'multiple';
20
19
 
@@ -30,65 +29,65 @@ export type CalendarMarks = Record<string, string | string[]>;
30
29
 
31
30
  export interface CalendarBaseProps {
32
31
  /** Controlled visible month (any date inside it). */
33
- month?: Date;
32
+ month?: Date | undefined;
34
33
  /** Month shown first (default: the selected date, else today). */
35
- defaultMonth?: Date;
36
- onMonthChange?: (month: Date) => void;
34
+ defaultMonth?: Date | undefined;
35
+ onMonthChange?: ((month: Date) => void) | undefined;
37
36
  /** 0 = Sunday, 1 = Monday. */
38
- weekStartsOn?: 0 | 1;
37
+ weekStartsOn?: 0 | 1 | undefined;
39
38
  /** BCP 47 locale for month and weekday names (default: device locale). */
40
- locale?: string;
39
+ locale?: string | undefined;
41
40
  /** Earliest selectable day. */
42
- minDate?: Date;
41
+ minDate?: Date | undefined;
43
42
  /** Latest selectable day. */
44
- maxDate?: Date;
43
+ maxDate?: Date | undefined;
45
44
  /** Return true to disable a day (booked, holidays…). */
46
- isDateDisabled?: (date: Date) => boolean;
45
+ isDateDisabled?: ((date: Date) => boolean) | undefined;
47
46
  /** Coloured dots under dates: `{ [toDateKey(date)]: '#22c55e' | ['#22c55e', '#f59e0b'] }`. */
48
- markedDates?: CalendarMarks;
47
+ markedDates?: CalendarMarks | undefined;
49
48
  /** Fill whole days with a colour (birthdays, shifts, people…): `{ [toDateKey(date)]: '#f472b6' }`. */
50
- highlightedDates?: Record<string, string>;
49
+ highlightedDates?: Record<string, string> | undefined;
51
50
  /** `tiles` puts every day on a soft tile; `minimal` shows bare numbers. */
52
- variant?: 'tiles' | 'minimal';
51
+ variant?: 'tiles' | 'minimal' | undefined;
53
52
  /** Weekday header: `narrow` (M T W) or `short` (Mon Tue Wed). */
54
- weekdayFormat?: 'narrow' | 'short';
53
+ weekdayFormat?: 'narrow' | 'short' | undefined;
55
54
  /** Show a "Today" pill in the header while another month is on screen. */
56
- showTodayButton?: boolean;
55
+ showTodayButton?: boolean | undefined;
57
56
  /** Show the faded trailing / leading days of the neighbouring months. */
58
- showOutsideDays?: boolean;
57
+ showOutsideDays?: boolean | undefined;
59
58
  /** Ring and tint today's date. */
60
- showToday?: boolean;
59
+ showToday?: boolean | undefined;
61
60
  /** Always render six week rows so the height never changes between months. */
62
- fixedWeeks?: boolean;
61
+ fixedWeeks?: boolean | undefined;
63
62
  /** Tap the month title to jump through a month / year grid. */
64
- monthPicker?: boolean;
63
+ monthPicker?: boolean | undefined;
65
64
  /** Most dates selectable in `multiple` mode. */
66
- max?: number;
65
+ max?: number | undefined;
67
66
  /** Selection and today colour (default: theme accent). */
68
- accent?: string;
69
- style?: StyleProp<ViewStyle>;
67
+ accent?: string | undefined;
68
+ style?: StyleProp<ViewStyle> | undefined;
70
69
  }
71
70
 
72
71
  export interface CalendarSingleProps {
73
- mode?: 'single';
74
- value?: Date | null;
75
- defaultValue?: Date | null;
76
- onChange?: (date: Date) => void;
72
+ mode?: 'single' | undefined;
73
+ value?: Date | null | undefined;
74
+ defaultValue?: Date | null | undefined;
75
+ onChange?: ((date: Date) => void) | undefined;
77
76
  }
78
77
 
79
78
  export interface CalendarRangeProps {
80
79
  mode: 'range';
81
- value?: DateRange | null;
82
- defaultValue?: DateRange | null;
80
+ value?: DateRange | null | undefined;
81
+ defaultValue?: DateRange | null | undefined;
83
82
  /** Fires on every tap: first with `{ start, end: null }`, then with both ends. */
84
- onChange?: (range: DateRange) => void;
83
+ onChange?: ((range: DateRange) => void) | undefined;
85
84
  }
86
85
 
87
86
  export interface CalendarMultipleProps {
88
87
  mode: 'multiple';
89
- value?: Date[];
90
- defaultValue?: Date[];
91
- onChange?: (dates: Date[]) => void;
88
+ value?: Date[] | undefined;
89
+ defaultValue?: Date[] | undefined;
90
+ onChange?: ((dates: Date[]) => void) | undefined;
92
91
  }
93
92
 
94
93
  export type CalendarProps = CalendarBaseProps & (CalendarSingleProps | CalendarRangeProps | CalendarMultipleProps);
@@ -161,11 +160,11 @@ function useFormatters(locale?: string) {
161
160
  }
162
161
  };
163
162
  return {
164
- title: make({ month: 'long', year: 'numeric' }, (d) => `${EN_MONTHS[d.getMonth()]} ${d.getFullYear()}`),
165
- month: make({ month: 'short' }, (d) => EN_MONTHS[d.getMonth()].slice(0, 3)),
166
- weekday: make({ weekday: 'short' }, (d) => EN_DAYS[d.getDay()]),
167
- weekdayNarrow: make({ weekday: 'narrow' }, (d) => EN_DAYS[d.getDay()][0]),
168
- weekdayLong: make({ weekday: 'long' }, (d) => EN_DAYS[d.getDay()]),
163
+ title: make({ month: 'long', year: 'numeric' }, (d) => `${EN_MONTHS[d.getMonth()] ?? ''} ${d.getFullYear()}`),
164
+ month: make({ month: 'short' }, (d) => (EN_MONTHS[d.getMonth()] ?? '').slice(0, 3)),
165
+ weekday: make({ weekday: 'short' }, (d) => EN_DAYS[d.getDay()] ?? ''),
166
+ weekdayNarrow: make({ weekday: 'narrow' }, (d) => (EN_DAYS[d.getDay()] ?? '').slice(0, 1)),
167
+ weekdayLong: make({ weekday: 'long' }, (d) => EN_DAYS[d.getDay()] ?? ''),
169
168
  full: make({ weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }, (d) => d.toDateString()),
170
169
  };
171
170
  }, [locale]);
@@ -206,7 +205,7 @@ export function Calendar(props: CalendarProps) {
206
205
  accent,
207
206
  style,
208
207
  } = props as unknown as FlatProps;
209
- const { colors, radius, font } = useKit();
208
+ const { colors, radius, font, labels } = useKit();
210
209
  const ff = font.family ? { fontFamily: font.family } : null;
211
210
  const tint = accent ?? colors.accent;
212
211
  const fmt = useFormatters(locale);
@@ -244,9 +243,10 @@ export function Calendar(props: CalendarProps) {
244
243
 
245
244
  /* month slide transition */
246
245
  const [width, setWidth] = useState(0);
247
- const slide = useRef(new Animated.Value(1)).current; // native
246
+ const [slide] = useState(() => new Animated.Value(1)); // native
248
247
  const [leaving, setLeaving] = useState<{ month: Date; dir: number } | null>(null);
249
- const dirRef = useRef(1);
248
+ // Which way the last month change went: the incoming grid slides in from that side.
249
+ const [slideDir, setSlideDir] = useState(1);
250
250
  const shownRef = useRef(visible);
251
251
  const visibleKey = monthIndex(visible);
252
252
  useLayoutEffect(() => {
@@ -254,7 +254,7 @@ export function Calendar(props: CalendarProps) {
254
254
  if (monthIndex(prev) === visibleKey) return;
255
255
  shownRef.current = visible;
256
256
  const dir = visibleKey > monthIndex(prev) ? 1 : -1;
257
- dirRef.current = dir;
257
+ setSlideDir(dir);
258
258
  setLeaving({ month: prev, dir });
259
259
  slide.stopAnimation();
260
260
  slide.setValue(0);
@@ -271,7 +271,7 @@ export function Calendar(props: CalendarProps) {
271
271
  /* days / months / years view */
272
272
  const [view, setView] = useState<'days' | 'months' | 'years'>('days');
273
273
  const [pickYear, setPickYear] = useState(visible.getFullYear());
274
- const viewAnim = useRef(new Animated.Value(1)).current; // native
274
+ const [viewAnim] = useState(() => new Animated.Value(1)); // native
275
275
  const [gridH, setGridH] = useState(0);
276
276
  const switchView = (next: 'days' | 'months' | 'years') => {
277
277
  if (next !== 'days' && view === 'days') setPickYear(visible.getFullYear());
@@ -283,7 +283,9 @@ export function Calendar(props: CalendarProps) {
283
283
 
284
284
  /* selection */
285
285
  const stateRef = useRef({ mode, current, value, onChange, max, visible, goTo });
286
- stateRef.current = { mode, current, value, onChange, max, visible, goTo };
286
+ useLayoutEffect(() => {
287
+ stateRef.current = { mode, current, value, onChange, max, visible, goTo };
288
+ });
287
289
  const onDayPress = useCallback((d: Date) => {
288
290
  const s = stateRef.current;
289
291
  let next: CalendarValue;
@@ -305,18 +307,14 @@ export function Calendar(props: CalendarProps) {
305
307
  if (monthIndex(d) !== monthIndex(s.visible)) s.goTo(d);
306
308
  }, []);
307
309
 
308
- const pan = useMemo(
309
- () =>
310
- PanResponder.create({
311
- onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 14 && Math.abs(g.dx) > Math.abs(g.dy) * 1.4,
312
- onPanResponderRelease: (_, g) => {
313
- const s = stateRef.current;
314
- if (g.dx < -40 || g.vx < -0.5) s.goTo(addMonths(s.visible, 1));
315
- else if (g.dx > 40 || g.vx > 0.5) s.goTo(addMonths(s.visible, -1));
316
- },
317
- }),
318
- []
319
- );
310
+ const pan = usePanResponder({
311
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 14 && Math.abs(g.dx) > Math.abs(g.dy) * 1.4,
312
+ onPanResponderRelease: (_, g) => {
313
+ const s = stateRef.current;
314
+ if (g.dx < -40 || g.vx < -0.5) s.goTo(addMonths(s.visible, 1));
315
+ else if (g.dx > 40 || g.vx > 0.5) s.goTo(addMonths(s.visible, -1));
316
+ },
317
+ });
320
318
 
321
319
  const weekdays = useMemo(() => {
322
320
  const base = new Date(2023, 0, 1); // a Sunday
@@ -349,7 +347,7 @@ export function Calendar(props: CalendarProps) {
349
347
 
350
348
  const W = width || 320;
351
349
  // Reduce Motion: months swap with a fade only, no sideways slide.
352
- const dir = reduce ? 0 : dirRef.current;
350
+ const dir = reduce ? 0 : slideDir;
353
351
  const prevDisabled = view === 'days' ? monthIndex(visible) - 1 < minM : view === 'months' ? pickYear * 12 - 1 < minM : false;
354
352
  const nextDisabled = view === 'days' ? monthIndex(visible) + 1 > maxM : view === 'months' ? (pickYear + 1) * 12 > maxM : false;
355
353
  const decadeStart = Math.floor(pickYear / 12) * 12;
@@ -374,7 +372,7 @@ export function Calendar(props: CalendarProps) {
374
372
  onPress={monthPicker ? () => switchView(view === 'days' ? 'months' : view === 'months' ? 'years' : 'days') : undefined}
375
373
  disabled={!monthPicker}
376
374
  accessibilityRole={monthPicker ? 'button' : 'header'}
377
- accessibilityLabel={monthPicker ? `${title}, ${view === 'days' ? 'choose month' : view === 'months' ? 'choose year' : 'back to days'}` : title}
375
+ accessibilityLabel={monthPicker ? `${title}, ${view === 'days' ? labels.chooseMonth : view === 'months' ? labels.chooseYear : labels.backToDays}` : title}
378
376
  hitSlop={6}
379
377
  style={({ pressed }) => [styles.titleBtn, { borderRadius: radius.sm, backgroundColor: pressed ? colors.muted : 'transparent' }]}
380
378
  >
@@ -401,12 +399,12 @@ export function Calendar(props: CalendarProps) {
401
399
  <Pressable
402
400
  onPress={() => goTo(today)}
403
401
  accessibilityRole="button"
404
- accessibilityLabel="Go to today"
402
+ accessibilityLabel={labels.goToToday}
405
403
  hitSlop={4}
406
404
  style={({ pressed }) => [styles.todayBtn, { borderRadius: radius.capsule(32), backgroundColor: pressed ? colors.secondary : colors.muted }]}
407
405
  >
408
406
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.todayText, ff, { color: tint }]}>
409
- Today
407
+ {labels.today}
410
408
  </Text>
411
409
  </Pressable>
412
410
  ) : null}
@@ -429,7 +427,7 @@ export function Calendar(props: CalendarProps) {
429
427
  </Text>
430
428
  ))}
431
429
  </View>
432
- <View {...pan.panHandlers} style={styles.gridClip} onLayout={(e) => setGridH(e.nativeEvent.layout.height)}>
430
+ <View {...pan} style={styles.gridClip} onLayout={(e) => setGridH(e.nativeEvent.layout.height)}>
433
431
  {leaving ? (
434
432
  <Animated.View
435
433
  pointerEvents="none"
@@ -525,16 +523,16 @@ interface MonthGridProps {
525
523
  fixedWeeks: boolean;
526
524
  showOutsideDays: boolean;
527
525
  showToday: boolean;
528
- minDate?: Date;
529
- maxDate?: Date;
530
- isDateDisabled?: (date: Date) => boolean;
531
- markedDates?: CalendarMarks;
532
- highlightedDates?: Record<string, string>;
526
+ minDate?: Date | undefined;
527
+ maxDate?: Date | undefined;
528
+ isDateDisabled?: ((date: Date) => boolean) | undefined;
529
+ markedDates?: CalendarMarks | undefined;
530
+ highlightedDates?: Record<string, string> | undefined;
533
531
  tiles: boolean;
534
- singleKey?: string;
535
- rangeStart?: number;
536
- rangeEnd?: number;
537
- manyKeys?: string;
532
+ singleKey?: string | undefined;
533
+ rangeStart?: number | undefined;
534
+ rangeEnd?: number | undefined;
535
+ manyKeys?: string | undefined;
538
536
  tint: string;
539
537
  fullLabel: (d: Date) => string;
540
538
  reduce: boolean;
@@ -636,8 +634,8 @@ interface DayCellProps {
636
634
  band: 'none' | 'start' | 'mid' | 'end';
637
635
  roundLeft: boolean;
638
636
  roundRight: boolean;
639
- marks?: string | string[];
640
- highlight?: string;
637
+ marks?: string | string[] | undefined;
638
+ highlight?: string | undefined;
641
639
  tile: boolean;
642
640
  tint: string;
643
641
  reduce: boolean;
@@ -665,8 +663,8 @@ const DayCell = memo(function DayCell({
665
663
  const { colors, radius, font } = useKit();
666
664
  const ff = font.family ? { fontFamily: font.family } : null;
667
665
  const r = tileRadius(radius.md);
668
- const pop = useRef(new Animated.Value(selected ? 1 : 0)).current; // native: circle scale + opacity
669
- const bandIn = useRef(new Animated.Value(band === 'none' ? 0 : 1)).current; // native: band opacity
666
+ const [pop] = useState(() => new Animated.Value(selected ? 1 : 0)); // native: circle scale + opacity
667
+ const [bandIn] = useState(() => new Animated.Value(band === 'none' ? 0 : 1)); // native: band opacity
670
668
  useEffect(() => {
671
669
  // Reduce Motion: the selection fills in with a quick fade, no scale pop.
672
670
  if (reduce) Animated.timing(pop, { toValue: selected ? 1 : 0, duration: 150, useNativeDriver: true }).start();
@@ -773,14 +771,14 @@ const DayCell = memo(function DayCell({
773
771
  /* ───────────────────────────────────── bits ───────────────────────────────────── */
774
772
 
775
773
  function NavButton({ dir, disabled, onPress }: { dir: 1 | -1; disabled: boolean; onPress: () => void }) {
776
- const { colors } = useKit();
774
+ const { colors, labels } = useKit();
777
775
  return (
778
776
  <Pressable
779
777
  onPress={onPress}
780
778
  disabled={disabled}
781
779
  hitSlop={4}
782
780
  accessibilityRole="button"
783
- accessibilityLabel={dir < 0 ? 'Previous' : 'Next'}
781
+ accessibilityLabel={dir < 0 ? labels.previous : labels.next}
784
782
  accessibilityState={{ disabled }}
785
783
  style={({ pressed }) => [styles.nav, { backgroundColor: pressed ? colors.secondary : colors.muted, opacity: disabled ? 0.3 : 1 }]}
786
784
  >
@@ -798,7 +796,7 @@ function NavButton({ dir, disabled, onPress }: { dir: 1 | -1; disabled: boolean;
798
796
  }
799
797
 
800
798
  function Chevron({ turned, color, reduce }: { turned: boolean; color: string; reduce: boolean }) {
801
- const turn = useRef(new Animated.Value(turned ? 1 : 0)).current; // native
799
+ const [turn] = useState(() => new Animated.Value(turned ? 1 : 0)); // native
802
800
  useEffect(() => {
803
801
  if (reduce) {
804
802
  turn.setValue(turned ? 1 : 0);
@@ -839,7 +837,7 @@ const styles = StyleSheet.create({
839
837
  navs: { flexDirection: 'row', alignItems: 'center', gap: 6 },
840
838
  nav: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center' },
841
839
  todayBtn: { minHeight: 32, paddingHorizontal: 12, alignItems: 'center', justifyContent: 'center' },
842
- todayText: { fontSize: 13, fontWeight: '400' },
840
+ todayText: { fontSize: 13, fontWeight: '400', includeFontPadding: false, textAlignVertical: 'center' },
843
841
  weekRow: { flexDirection: 'row', marginBottom: 6 },
844
842
  weekday: { flex: 1, textAlign: 'center', fontSize: 13, fontWeight: '400', letterSpacing: 0.2 },
845
843
  gridClip: { overflow: 'hidden' },
@@ -858,7 +856,7 @@ const styles = StyleSheet.create({
858
856
  alignSelf: 'center',
859
857
  },
860
858
  fill: { borderWidth: 0 },
861
- day: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },
859
+ day: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'], includeFontPadding: false, textAlignVertical: 'center' },
862
860
  dayStrong: { fontWeight: '400' },
863
861
  outside: { opacity: 0.4 },
864
862
  disabledText: { opacity: 0.35 },
@@ -867,5 +865,5 @@ const styles = StyleSheet.create({
867
865
  pickGrid: { flexDirection: 'row', flexWrap: 'wrap', alignContent: 'center', paddingTop: 4 },
868
866
  pickCell: { width: '33.333%', height: 56, alignItems: 'center', justifyContent: 'center' },
869
867
  pickInner: { minWidth: 76, minHeight: 40, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', borderWidth: 1.5 },
870
- pickText: { fontSize: 15, fontWeight: '400' },
868
+ pickText: { fontSize: 15, fontWeight: '400', includeFontPadding: false, textAlignVertical: 'center' },
871
869
  });