@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,5 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import {
3
+ Animated,
3
4
  Image,
4
5
  Pressable,
5
6
  StyleSheet,
@@ -25,38 +26,38 @@ export interface PaymentCardProps {
25
26
  /** Expiry, e.g. "09/29". */
26
27
  expiry: string;
27
28
  /** Shown on the back. */
28
- cvv?: string;
29
- brand?: PaymentCardBrand;
29
+ cvv?: string | undefined;
30
+ brand?: PaymentCardBrand | undefined;
30
31
  /** Issuer name, top-left. */
31
- bank?: string;
32
+ bank?: string | undefined;
32
33
  /** Small label top-right, e.g. "Debit", "World Elite". */
33
- label?: string;
34
+ label?: string | undefined;
34
35
  /** Card art — a photo or texture (`require('./card.jpg')` or `{ uri }`). Or any element drawn as the art, e.g. `<GrainGradient palette=\"ocean\" style={{ flex: 1 }} />`. Default: brushed graphite. */
35
- image?: ImageSourcePropType | React.ReactElement;
36
+ image?: ImageSourcePropType | React.ReactElement | undefined;
36
37
  /** `light` text for dark art (default), `dark` text for pale art. */
37
- tone?: 'light' | 'dark';
38
+ tone?: 'light' | 'dark' | undefined;
38
39
  /** EMV chip finish. */
39
- chip?: 'gold' | 'silver';
40
+ chip?: 'gold' | 'silver' | undefined;
40
41
  /** Show only the last 4 digits (default true). */
41
- masked?: boolean;
42
+ masked?: boolean | undefined;
42
43
  /** Controlled side: true shows the back. */
43
- flipped?: boolean;
44
- defaultFlipped?: boolean;
45
- onFlip?: (flipped: boolean) => void;
44
+ flipped?: boolean | undefined;
45
+ defaultFlipped?: boolean | undefined;
46
+ onFlip?: ((flipped: boolean) => void) | undefined;
46
47
  /** Tilt toward the finger (default true). */
47
- tilt?: boolean;
48
+ tilt?: boolean | undefined;
48
49
  /**
49
50
  * Tap flips the card (default true). `false` makes it a still card — no tilt, no taps — for use
50
51
  * inside something tappable (e.g. a CardStack); a controlled `flipped` still turns it.
51
52
  */
52
- flippable?: boolean;
53
+ flippable?: boolean | undefined;
53
54
  /** Frozen cards are frosted over with a label and ignore taps. */
54
- frozen?: boolean;
55
+ frozen?: boolean | undefined;
55
56
  /** Corner radius (default 16 — a real card's corner at phone size). */
56
- radius?: number;
57
+ radius?: number | undefined;
57
58
  /** Every built-in string, for translation ({bank} and {last4} are filled in). */
58
- labels?: PaymentCardLabels;
59
- style?: StyleProp<ViewStyle>;
59
+ labels?: PaymentCardLabels | undefined;
60
+ style?: StyleProp<ViewStyle> | undefined;
60
61
  }
61
62
 
62
63
  export interface PaymentCardLabels {
@@ -227,7 +228,7 @@ export function PaymentCard({
227
228
  labels,
228
229
  style,
229
230
  }: PaymentCardProps) {
230
- const { font } = useKit();
231
+ const { font, shadow } = useKit();
231
232
  const ff = font.family ? { fontFamily: font.family } : null;
232
233
  const controlled = flippedProp !== undefined;
233
234
  const [own, setOwn] = useState(defaultFlipped);
@@ -378,7 +379,12 @@ export function PaymentCard({
378
379
  {card}
379
380
  </TiltCard>
380
381
  ) : (
381
- <Pressable onPress={toggle} accessibilityRole="button" accessibilityState={{ disabled: frozen }} accessibilityLabel={a11y} style={[styles.flat, { borderRadius: radius }, style]}>
382
+ <Pressable onPress={toggle} accessibilityRole="button" accessibilityState={{ disabled: frozen }} accessibilityLabel={a11y} style={[{ borderRadius: radius }, style]}>
383
+ {/* boxShadow on a plate that narrows with the flip, so the shadow (same on iOS and Android) never shows as an empty box edge-on. */}
384
+ <Animated.View
385
+ pointerEvents="none"
386
+ style={[StyleSheet.absoluteFill, styles.plate, { borderRadius: radius, transform: [{ scaleX: flip.shadowScaleX }] }, shadow('lg')]}
387
+ />
382
388
  {card}
383
389
  </Pressable>
384
390
  );
@@ -398,5 +404,5 @@ const styles = StyleSheet.create({
398
404
  signature: { flex: 1, borderRadius: 3, backgroundColor: '#eef0f4', overflow: 'hidden' },
399
405
  cvv: { width: 58, borderRadius: 3, backgroundColor: '#ffffff', alignItems: 'center', justifyContent: 'center' },
400
406
  backFoot: { position: 'absolute', left: 18, right: 18, bottom: 16, flexDirection: 'row', alignItems: 'flex-end', gap: 12 },
401
- flat: { shadowColor: '#000', shadowOpacity: 0.3, shadowRadius: 18, shadowOffset: { width: 0, height: 10 }, elevation: 8 },
407
+ plate: { backgroundColor: '#1c1c20' },
402
408
  });
@@ -4,7 +4,6 @@ import {
4
4
  Animated,
5
5
  Easing,
6
6
  Image,
7
- PanResponder,
8
7
  StyleSheet,
9
8
  View,
10
9
  type ImageSourcePropType,
@@ -14,7 +13,7 @@ import {
14
13
  } from 'react-native';
15
14
  import Svg, { Defs, LinearGradient, RadialGradient, Rect, Stop } from 'react-native-svg';
16
15
 
17
- import { motion } from '@/lib/kit';
16
+ import { motion, usePanResponder } from '@/lib/kit';
18
17
 
19
18
  /** `holo` adds a rainbow foil sheen that shifts as the card tilts (always faintly visible). */
20
19
  export type TiltCardGlare = 'soft' | 'sharp' | 'holo' | 'none';
@@ -28,58 +27,58 @@ export interface TiltRect {
28
27
  }
29
28
 
30
29
  export interface TiltCardProps {
31
- children?: React.ReactNode;
30
+ children?: React.ReactNode | undefined;
32
31
  /** Photo that fills the card behind the content (`require()` or `{ uri }`). */
33
- image?: ImageSourcePropType;
32
+ image?: ImageSourcePropType | undefined;
34
33
  /** Darkens the bottom of `image` so white text on it stays readable: 0 off … 1 strong (default 0.6). */
35
- shade?: number;
34
+ shade?: number | undefined;
36
35
  /** Maximum tilt in degrees at the card's edge (default 12). */
37
- maxTilt?: number;
36
+ maxTilt?: number | undefined;
38
37
  /** Scale while touched (default 1.03). */
39
- pressScale?: number;
38
+ pressScale?: number | undefined;
40
39
  /** Light reflection that slides across the card as it tilts. */
41
- glare?: TiltCardGlare;
40
+ glare?: TiltCardGlare | undefined;
42
41
  /**
43
42
  * Where the reflection may fall, in card points — for cards with cut-outs (ticket notches,
44
43
  * perforated edges), so light never shows over a hole. Default: the whole card.
45
44
  */
46
- glareClip?: TiltRect[];
45
+ glareClip?: TiltRect[] | undefined;
47
46
  /** Depth of the 3D effect — smaller is more dramatic (default 800). */
48
- perspective?: number;
47
+ perspective?: number | undefined;
49
48
  /** Slowly sway on its own while untouched, so the card reads as 3D at rest. */
50
- idle?: boolean;
49
+ idle?: boolean | undefined;
51
50
  /** Corner radius (default 20). */
52
- radius?: number;
51
+ radius?: number | undefined;
53
52
  /** Soft shadow under the card that shifts as it tilts (default true). */
54
- shadow?: boolean;
53
+ shadow?: boolean | undefined;
55
54
  /**
56
55
  * The part of the card that casts the shadow, in card points — e.g. leave out a ticket stub that's
57
56
  * being torn off, so the plate never shows where it was. Default: the whole card.
58
57
  */
59
- shadowRect?: TiltRect;
58
+ shadowRect?: TiltRect | undefined;
60
59
  /**
61
60
  * Narrows the shadow sideways (1 = full width, 0 = none) — for content that turns edge-on inside
62
61
  * the card, like a FlipCard mid-flip, so the shadow plate never shows past it.
63
62
  */
64
- shadowScaleX?: Animated.Value | Animated.AnimatedInterpolation<number>;
63
+ shadowScaleX?: Animated.Value | Animated.AnimatedInterpolation<number> | undefined;
65
64
  /** Turn touch tilting off (the card still renders, e.g. while a parent list scrolls). */
66
- disabled?: boolean;
65
+ disabled?: boolean | undefined;
67
66
  /** Called with a light tap (not after a drag). */
68
- onPress?: () => void;
69
- accessibilityLabel?: string;
67
+ onPress?: (() => void) | undefined;
68
+ accessibilityLabel?: string | undefined;
70
69
  /** Width, height, margins — applied to the card's outer box. */
71
- style?: StyleProp<ViewStyle>;
70
+ style?: StyleProp<ViewStyle> | undefined;
72
71
  }
73
72
 
74
73
  export interface TiltLayerProps {
75
- children?: React.ReactNode;
74
+ children?: React.ReactNode | undefined;
76
75
  /**
77
76
  * How far above the card this layer floats: 0 sits on the card, 1 floats well above it
78
77
  * and slides furthest as the card tilts (default 0.5). Negative sinks below the surface.
79
78
  */
80
- depth?: number;
79
+ depth?: number | undefined;
81
80
  /** Layers fill the card by default; position content inside, or override here. */
82
- style?: StyleProp<ViewStyle>;
81
+ style?: StyleProp<ViewStyle> | undefined;
83
82
  }
84
83
 
85
84
  export interface TiltState {
@@ -109,7 +108,8 @@ const PAINT = 96;
109
108
  // pinks, violets and cyans stay clean when blended over dark cards (yellow and green turn olive).
110
109
  const SMOOTH = Easing.bezier(0.32, 0.72, 0, 1);
111
110
 
112
- const HOLO = Array.from({ length: 37 }, (_, i) => ['#ff6bd6', '#b38cff', '#6fb6ff', '#5ff0ff', '#a7ffe0', '#ffc2ef'][i % 6]);
111
+ const HOLO_CYCLE = ['#ff6bd6', '#b38cff', '#6fb6ff', '#5ff0ff', '#a7ffe0', '#ffc2ef'];
112
+ const HOLO = Array.from({ length: 37 }, (_, i) => HOLO_CYCLE[i % HOLO_CYCLE.length] ?? '#ff6bd6');
113
113
 
114
114
  function GlarePaint({ variant }: { variant: 'soft' | 'sharp' }) {
115
115
  const sharp = variant === 'sharp';
@@ -262,21 +262,15 @@ export function TiltCard({
262
262
  }: TiltCardProps) {
263
263
  const reduce = useReduceMotion();
264
264
  const [size, setSize] = useState({ w: 0, h: 0 });
265
- const sizeRef = useRef(size);
266
- sizeRef.current = size;
267
265
 
268
266
  // -1 … 1 across the card; the card leans toward the finger.
269
- const tx = useRef(new Animated.Value(0)).current;
270
- const ty = useRef(new Animated.Value(0)).current;
271
- const press = useRef(new Animated.Value(0)).current;
267
+ const [tx] = useState(() => new Animated.Value(0));
268
+ const [ty] = useState(() => new Animated.Value(0));
269
+ const [press] = useState(() => new Animated.Value(0));
272
270
  const touching = useRef(false);
273
271
  const origin = useRef({ x: 0, y: 0 });
274
272
  const moved = useRef(false);
275
273
  const viewRef = useRef<ComponentRef<typeof View>>(null);
276
- const disabledRef = useRef(disabled);
277
- disabledRef.current = disabled;
278
- const reduceRef = useRef(reduce);
279
- reduceRef.current = reduce;
280
274
 
281
275
  // Idle sway: a slow figure-eight while nobody is touching.
282
276
  const idleLoop = useRef<Animated.CompositeAnimation | null>(null);
@@ -310,7 +304,7 @@ export function TiltCard({
310
304
  }, [idle, reduce]);
311
305
 
312
306
  const aim = (pageX: number, pageY: number) => {
313
- const { w, h } = sizeRef.current;
307
+ const { w, h } = size;
314
308
  if (!w || !h) return;
315
309
  tx.setValue(Math.max(-1, Math.min(1, ((pageX - origin.current.x) / w) * 2 - 1)));
316
310
  ty.setValue(Math.max(-1, Math.min(1, ((pageY - origin.current.y) / h) * 2 - 1)));
@@ -318,7 +312,7 @@ export function TiltCard({
318
312
 
319
313
  const release = (tapped: boolean) => {
320
314
  touching.current = false;
321
- if (tapped && !moved.current) onPressRef.current?.();
315
+ if (tapped && !moved.current) onPress?.();
322
316
  if (reduce) {
323
317
  // The card never leaves flat, so only the light has to settle: a short ease, no spring.
324
318
  const settle = (v: Animated.Value) => Animated.timing(v, { toValue: 0, duration: 150, easing: SMOOTH, useNativeDriver: true });
@@ -345,57 +339,48 @@ export function TiltCard({
345
339
  if (finished && !touching.current) startIdle();
346
340
  });
347
341
  };
348
- // Read the latest onPress at release time, so the responder never has to be rebuilt mid-gesture.
349
- const onPressRef = useRef(onPress);
350
- onPressRef.current = onPress;
351
- const releaseRef = useRef(release);
352
- releaseRef.current = release;
353
-
354
- const responder = useMemo(
355
- () =>
356
- PanResponder.create({
357
- onStartShouldSetPanResponder: () => !disabledRef.current,
358
- // Only a real drag takes the touch from a button inside the card — a tap's tiny jitter must not.
359
- onMoveShouldSetPanResponder: (_e, g) => !disabledRef.current && Math.abs(g.dx) + Math.abs(g.dy) > 6,
360
- // Let a parent ScrollView take over scrolls that start on the card (on Android the native
361
- // scroll view can only do that when the card doesn't block it).
362
- onPanResponderTerminationRequest: () => true,
363
- onShouldBlockNativeResponder: () => false,
364
- onPanResponderGrant: (e) => {
365
- touching.current = true;
366
- moved.current = false;
367
- idleLoop.current?.stop();
368
- const { pageX, pageY, locationX, locationY } = e.nativeEvent;
369
- // locationX is relative to the touched child, so measure the card itself and correct
370
- // once the measurement arrives.
371
- origin.current = { x: pageX - locationX, y: pageY - locationY };
372
- viewRef.current?.measure((_x, _y, _w, _h, px, py) => {
373
- if (typeof px === 'number' && typeof py === 'number') origin.current = { x: px, y: py };
374
- });
375
- aim(pageX, pageY);
376
- if (reduceRef.current) {
377
- Animated.timing(press, { toValue: 1, duration: 150, easing: SMOOTH, useNativeDriver: true }).start();
378
- } else {
379
- Animated.spring(press, {
380
- toValue: 1,
381
- ...motion.snappy,
382
- useNativeDriver: true,
383
- }).start();
384
- }
385
- },
386
- onPanResponderMove: (e, g) => {
387
- if (Math.abs(g.dx) + Math.abs(g.dy) > 6) moved.current = true;
388
- aim(e.nativeEvent.pageX, e.nativeEvent.pageY);
389
- },
390
- onPanResponderRelease: () => releaseRef.current(true),
391
- onPanResponderTerminate: () => releaseRef.current(false),
392
- // React Native grants before asking the current responder to let go; when a child keeps its
393
- // gesture (e.g. a ticket stub being torn), the grant is rejected and no release follows.
394
- onPanResponderReject: () => releaseRef.current(false),
395
- }),
396
- // eslint-disable-next-line react-hooks/exhaustive-deps
397
- []
398
- );
342
+ // usePanResponder always calls the latest callbacks, so they read props and state directly and
343
+ // the responder is never rebuilt mid-gesture.
344
+ const pan = usePanResponder({
345
+ onStartShouldSetPanResponder: () => !disabled,
346
+ // Only a real drag takes the touch from a button inside the card — a tap's tiny jitter must not.
347
+ onMoveShouldSetPanResponder: (_e, g) => !disabled && Math.abs(g.dx) + Math.abs(g.dy) > 6,
348
+ // Let a parent ScrollView take over scrolls that start on the card (on Android the native
349
+ // scroll view can only do that when the card doesn't block it).
350
+ onPanResponderTerminationRequest: () => true,
351
+ onShouldBlockNativeResponder: () => false,
352
+ onPanResponderGrant: (e) => {
353
+ touching.current = true;
354
+ moved.current = false;
355
+ idleLoop.current?.stop();
356
+ const { pageX, pageY, locationX, locationY } = e.nativeEvent;
357
+ // locationX is relative to the touched child, so measure the card itself and correct
358
+ // once the measurement arrives.
359
+ origin.current = { x: pageX - locationX, y: pageY - locationY };
360
+ viewRef.current?.measure((_x, _y, _w, _h, px, py) => {
361
+ if (typeof px === 'number' && typeof py === 'number') origin.current = { x: px, y: py };
362
+ });
363
+ aim(pageX, pageY);
364
+ if (reduce) {
365
+ Animated.timing(press, { toValue: 1, duration: 150, easing: SMOOTH, useNativeDriver: true }).start();
366
+ } else {
367
+ Animated.spring(press, {
368
+ toValue: 1,
369
+ ...motion.snappy,
370
+ useNativeDriver: true,
371
+ }).start();
372
+ }
373
+ },
374
+ onPanResponderMove: (e, g) => {
375
+ if (Math.abs(g.dx) + Math.abs(g.dy) > 6) moved.current = true;
376
+ aim(e.nativeEvent.pageX, e.nativeEvent.pageY);
377
+ },
378
+ onPanResponderRelease: () => release(true),
379
+ onPanResponderTerminate: () => release(false),
380
+ // React Native grants before asking the current responder to let go; when a child keeps its
381
+ // gesture (e.g. a ticket stub being torn), the grant is rejected and no release follows.
382
+ onPanResponderReject: () => release(false),
383
+ });
399
384
 
400
385
  const onLayout = (e: LayoutChangeEvent) => {
401
386
  const { width, height } = e.nativeEvent.layout;
@@ -469,24 +454,21 @@ export function TiltCard({
469
454
  ) : null}
470
455
  {/* The card sits in its own (non-collapsing) layer: iOS intersects sibling layers in 3D, so a
471
456
  tilted card next to the flat shadow plate would be sliced by it along a diagonal. */}
472
- {/* Android draws siblings by elevation, so the wrapper (not the card inside it) carries the
473
- elevation that keeps it above the plate; with no background it casts no shadow itself. */}
474
- <View collapsable={false} style={{ elevation: shadow ? 15 : 0 }}>
457
+ {/* The plate's shadow is a boxShadow (no elevation), so on Android too the card simply paints above it. */}
458
+ <View collapsable={false}>
475
459
  <Animated.View
476
460
  ref={viewRef}
477
- {...responder.panHandlers}
461
+ {...pan}
478
462
  accessible={!!onPress || !!accessibilityLabel}
479
463
  accessibilityRole={onPress ? 'button' : undefined}
480
464
  accessibilityLabel={accessibilityLabel}
481
465
  // Screen readers activate the card through an action, not synthesized touches.
482
466
  accessibilityActions={onPress ? [{ name: 'activate' }] : undefined}
483
467
  onAccessibilityAction={(e) => {
484
- if (e.nativeEvent.actionName === 'activate') onPressRef.current?.();
468
+ if (e.nativeEvent.actionName === 'activate') onPress?.();
485
469
  }}
486
- // elevation (no background → no extra shadow) keeps the card above its shadow plate on Android.
487
470
  style={{
488
471
  borderRadius: radius,
489
- elevation: shadow ? 15 : 0,
490
472
  transform: [{ perspective }, { rotateX }, { rotateY }, { scale }],
491
473
  }}
492
474
  >
@@ -578,12 +560,12 @@ export function TiltLayer({ children, depth = 0.5, style }: TiltLayerProps) {
578
560
  }
579
561
 
580
562
  export interface TiltFoilProps {
581
- children?: React.ReactNode;
563
+ children?: React.ReactNode | undefined;
582
564
  /** Corner radius (default 8). */
583
- radius?: number;
565
+ radius?: number | undefined;
584
566
  /** Base metal under the rainbow (default silver). */
585
- base?: string;
586
- style?: StyleProp<ViewStyle>;
567
+ base?: string | undefined;
568
+ style?: StyleProp<ViewStyle> | undefined;
587
569
  }
588
570
 
589
571
  /**
@@ -665,11 +647,8 @@ const styles = StyleSheet.create({
665
647
  right: 0,
666
648
  bottom: 0,
667
649
  backgroundColor: '#000',
668
- shadowColor: '#000',
669
- shadowOpacity: 0.45,
670
- shadowRadius: 24,
671
- shadowOffset: { width: 0, height: 16 },
672
- elevation: 14,
650
+ // boxShadow draws the same soft shadow on iOS, Android and web.
651
+ boxShadow: '0px 16px 48px rgba(0,0,0,0.45)',
673
652
  },
674
653
  clip: { overflow: 'hidden' },
675
654
  window: { position: 'absolute', overflow: 'hidden' },