@expokit/cli 0.2.0 → 0.4.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 (232) hide show
  1. package/README.md +20 -2
  2. package/bin/expo-kit.js +219 -44
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +48 -19
  5. package/package.json +9 -4
  6. package/registry/_docs/accordion.json +16 -0
  7. package/registry/_docs/action-sheet.json +28 -0
  8. package/registry/_docs/alert.json +28 -0
  9. package/registry/_docs/assistant-app.json +47 -0
  10. package/registry/_docs/avatar.json +25 -1
  11. package/registry/_docs/badge.json +36 -5
  12. package/registry/_docs/bottom-nav.json +30 -6
  13. package/registry/_docs/bottom-sheet.json +28 -0
  14. package/registry/_docs/bullet.json +3 -0
  15. package/registry/_docs/button-3d.json +18 -2
  16. package/registry/_docs/button.json +16 -0
  17. package/registry/_docs/calendar.json +21 -5
  18. package/registry/_docs/card-stack.json +16 -0
  19. package/registry/_docs/card.json +20 -0
  20. package/registry/_docs/carousel.json +20 -0
  21. package/registry/_docs/chat.json +184 -0
  22. package/registry/_docs/checkbox.json +16 -0
  23. package/registry/_docs/checkout.json +10 -1
  24. package/registry/_docs/chip-group.json +16 -0
  25. package/registry/_docs/confetti.json +23 -3
  26. package/registry/_docs/coupon.json +16 -0
  27. package/registry/_docs/cube-pager.json +16 -0
  28. package/registry/_docs/dashboard.json +3 -0
  29. package/registry/_docs/date-picker.json +24 -4
  30. package/registry/_docs/dialog.json +24 -0
  31. package/registry/_docs/donut-shop.json +88 -0
  32. package/registry/_docs/drawer.json +32 -4
  33. package/registry/_docs/fab.json +24 -0
  34. package/registry/_docs/fintech-app.json +49 -0
  35. package/registry/_docs/flip-card.json +16 -0
  36. package/registry/_docs/floating-input.json +25 -5
  37. package/registry/_docs/food-app.json +47 -0
  38. package/registry/_docs/gift-card.json +16 -0
  39. package/registry/_docs/glass-button.json +24 -0
  40. package/registry/_docs/glass-nav-bar.json +24 -0
  41. package/registry/_docs/glass-search-bar.json +24 -0
  42. package/registry/_docs/glass-segmented.json +28 -0
  43. package/registry/_docs/glass-sheet.json +28 -0
  44. package/registry/_docs/glass-tab-bar.json +24 -0
  45. package/registry/_docs/glass.json +17 -1
  46. package/registry/_docs/grain.json +80 -0
  47. package/registry/_docs/inbox.json +3 -0
  48. package/registry/_docs/isometric-bar.json +3 -0
  49. package/registry/_docs/kit.json +3 -0
  50. package/registry/_docs/liquidglass-tab.json +3 -0
  51. package/registry/_docs/list-item.json +16 -0
  52. package/registry/_docs/loyalty-card.json +16 -0
  53. package/registry/_docs/morphing-button-group.json +96 -0
  54. package/registry/_docs/number-ticker.json +16 -0
  55. package/registry/_docs/offers.json +3 -0
  56. package/registry/_docs/onboarding.json +3 -0
  57. package/registry/_docs/orb-reveal.json +132 -0
  58. package/registry/_docs/otp-input.json +28 -4
  59. package/registry/_docs/parallax-layers.json +20 -0
  60. package/registry/_docs/parallax-scroll.json +24 -0
  61. package/registry/_docs/password-input.json +23 -3
  62. package/registry/_docs/payment-card.json +16 -0
  63. package/registry/_docs/pixel-bar.json +3 -0
  64. package/registry/_docs/polar-area.json +3 -0
  65. package/registry/_docs/profile.json +3 -0
  66. package/registry/_docs/progress-steps.json +24 -0
  67. package/registry/_docs/progress.json +20 -0
  68. package/registry/_docs/qr-code-button.json +122 -0
  69. package/registry/_docs/radio-group.json +20 -0
  70. package/registry/_docs/rating.json +24 -0
  71. package/registry/_docs/ring-carousel.json +20 -0
  72. package/registry/_docs/scratch-card.json +20 -0
  73. package/registry/_docs/search-bar.json +23 -3
  74. package/registry/_docs/search-results.json +3 -0
  75. package/registry/_docs/segmented-control.json +20 -0
  76. package/registry/_docs/select.json +33 -8
  77. package/registry/_docs/settings.json +9 -0
  78. package/registry/_docs/sign-in.json +3 -0
  79. package/registry/_docs/skeleton.json +22 -2
  80. package/registry/_docs/slide-to-confirm.json +24 -0
  81. package/registry/_docs/slider.json +27 -7
  82. package/registry/_docs/spin-wheel.json +24 -0
  83. package/registry/_docs/spinner.json +41 -11
  84. package/registry/_docs/stateful-button.json +184 -0
  85. package/registry/_docs/stepper.json +23 -7
  86. package/registry/_docs/swipe-deck.json +16 -0
  87. package/registry/_docs/swipeable-row.json +24 -1
  88. package/registry/_docs/switch.json +20 -0
  89. package/registry/_docs/tabs.json +24 -0
  90. package/registry/_docs/text-animation.json +141 -0
  91. package/registry/_docs/textarea.json +18 -2
  92. package/registry/_docs/ticket-card.json +20 -0
  93. package/registry/_docs/ticket-shape.json +4 -1
  94. package/registry/_docs/tilt-card.json +20 -4
  95. package/registry/_docs/time-picker.json +22 -6
  96. package/registry/_docs/timeline-chart.json +3 -0
  97. package/registry/_docs/timeline.json +18 -2
  98. package/registry/_docs/toast.json +32 -8
  99. package/registry/_docs/treemap.json +3 -0
  100. package/registry/_docs/waterfall.json +3 -0
  101. package/registry/_docs/wheel-list.json +20 -0
  102. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  103. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  104. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  105. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  106. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  107. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  108. package/registry/alert/files/src/lib/kit.tsx +8 -5
  109. package/registry/assistant-app.json +49 -0
  110. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  111. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  112. package/registry/badge/files/src/components/ui/badge.tsx +345 -21
  113. package/registry/badge/files/src/lib/kit.tsx +8 -5
  114. package/registry/badge.json +1 -1
  115. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  116. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  117. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  118. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  119. package/registry/button/files/src/components/ui/button.tsx +4 -3
  120. package/registry/button/files/src/lib/kit.tsx +8 -5
  121. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  122. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  123. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  124. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  125. package/registry/card/files/src/components/ui/card.tsx +8 -6
  126. package/registry/card/files/src/lib/kit.tsx +8 -5
  127. package/registry/chat.json +18 -0
  128. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  129. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  130. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  131. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  132. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  133. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  134. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  135. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  136. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  137. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  138. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  139. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  140. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  141. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  142. package/registry/donut-shop.json +18 -0
  143. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  144. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  145. package/registry/fintech-app.json +57 -0
  146. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  147. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  148. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  149. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  150. package/registry/food-app.json +55 -0
  151. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  152. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  153. package/registry/grain.json +17 -0
  154. package/registry/kit/files/src/lib/kit.tsx +8 -5
  155. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  156. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  157. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  158. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  159. package/registry/morphing-button-group.json +18 -0
  160. package/registry/orb-reveal.json +20 -0
  161. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  162. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  163. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  164. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  165. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  166. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  167. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  168. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  169. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  170. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  171. package/registry/progress/files/src/lib/kit.tsx +8 -5
  172. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  173. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  174. package/registry/qr-code-button.json +18 -0
  175. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  176. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  177. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  178. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  179. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  180. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  181. package/registry/select/files/src/components/ui/select.tsx +3 -3
  182. package/registry/select/files/src/lib/kit.tsx +8 -5
  183. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  184. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  185. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  186. package/registry/slider/files/src/lib/kit.tsx +8 -5
  187. package/registry/spinner/files/src/components/ui/spinner.tsx +452 -34
  188. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  189. package/registry/spinner.json +1 -1
  190. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
  191. package/registry/{pagination-dots → stateful-button}/files/src/lib/kit.tsx +8 -5
  192. package/registry/stateful-button.json +18 -0
  193. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  194. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  195. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  196. package/registry/switch/files/src/lib/kit.tsx +8 -5
  197. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  198. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  199. package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
  200. package/registry/text-animation/files/src/lib/kit.tsx +222 -0
  201. package/registry/text-animation.json +15 -0
  202. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  203. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  204. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  205. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  206. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  207. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  208. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  209. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  210. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  211. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  212. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  213. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  214. package/registry/toast/files/src/lib/kit.tsx +8 -5
  215. package/schema/components.schema.json +1 -1
  216. package/registry/_docs/color-swatch.json +0 -66
  217. package/registry/_docs/kpi-card.json +0 -11
  218. package/registry/_docs/like-button.json +0 -72
  219. package/registry/_docs/morph-button.json +0 -108
  220. package/registry/_docs/pagination-dots.json +0 -88
  221. package/registry/_docs/shimmer-button.json +0 -78
  222. package/registry/_docs/tag-input.json +0 -128
  223. package/registry/color-swatch.json +0 -17
  224. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  225. package/registry/kpi-card.json +0 -14
  226. package/registry/like-button.json +0 -18
  227. package/registry/morph-button.json +0 -18
  228. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  229. package/registry/pagination-dots.json +0 -15
  230. package/registry/shimmer-button.json +0 -17
  231. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  232. package/registry/tag-input.json +0 -17
@@ -0,0 +1,113 @@
1
+ import React, { useId } from 'react';
2
+ import { Image, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
3
+ import Svg, { Circle, Defs, LinearGradient, RadialGradient, Rect, Stop } from 'react-native-svg';
4
+
5
+ export type GrainPalette =
6
+ | 'sunset'
7
+ | 'ocean'
8
+ | 'aurora'
9
+ | 'dusk'
10
+ | 'peach'
11
+ | 'graphite'
12
+ | 'ember'
13
+ | 'mint'
14
+ | 'lavender'
15
+ | 'candy'
16
+ | 'forest'
17
+ | 'noir';
18
+
19
+ export interface GrainGradientProps {
20
+ /** A built-in colour set. */
21
+ palette?: GrainPalette;
22
+ /** Your own colours: the first is the base, the rest become soft blobs (up to 4). Overrides `palette`. */
23
+ colors?: string[];
24
+ /** Strength of the film grain, 0–1. */
25
+ grain?: number;
26
+ /** Shuffle where the blobs sit. */
27
+ seed?: number;
28
+ style?: StyleProp<ViewStyle>;
29
+ children?: React.ReactNode;
30
+ }
31
+
32
+ export interface GrainOverlayProps {
33
+ /** Strength of the grain, 0–1. */
34
+ grain?: number;
35
+ style?: StyleProp<ViewStyle>;
36
+ }
37
+
38
+ const PALETTES: Record<GrainPalette, string[]> = {
39
+ sunset: ['#2a1238', '#ff6a3d', '#ff3d8b', '#ffc15e', '#7b2ff7'],
40
+ ocean: ['#04203a', '#0fb5c9', '#2f6bff', '#7ce7d6', '#123f8f'],
41
+ aurora: ['#0b1324', '#2ee6a6', '#7b5cff', '#1fa2ff', '#b8ff6b'],
42
+ dusk: ['#1a1433', '#ff7aa8', '#6d5dfc', '#ffb38a', '#2b2a6b'],
43
+ peach: ['#ffe9dc', '#ffb48a', '#ff8fab', '#ffd6a5', '#c7b8ff'],
44
+ graphite: ['#16171b', '#3b3f4a', '#6b6f7d', '#2a2d35', '#8a8f9e'],
45
+ ember: ['#1c0806', '#ff4e1f', '#ff9a3c', '#b3122e', '#ffd36e'],
46
+ mint: ['#e6fff6', '#7ff0c8', '#58c7ff', '#c4ffde', '#9bf2a8'],
47
+ lavender: ['#efe9ff', '#b9a2ff', '#ff9ed8', '#a8c8ff', '#e0c3ff'],
48
+ candy: ['#2b0a3d', '#ff5fa2', '#4dd8ff', '#ffe066', '#a05bff'],
49
+ forest: ['#071a12', '#1f8a5b', '#7ccf6b', '#0f5c4a', '#d4e66b'],
50
+ noir: ['#050506', '#2b2b33', '#5b4bff', '#121216', '#ff3d6e'],
51
+ };
52
+
53
+ /** Every built-in palette, in order. */
54
+ export const GRAIN_PALETTES = Object.keys(PALETTES) as GrainPalette[];
55
+
56
+ // Where the blobs sit, in percent of the box: [cx, cy, r].
57
+ const SPOTS: [number, number, number][][] = [
58
+ [[18, 22, 62], [86, 30, 58], [30, 88, 64], [82, 86, 52]],
59
+ [[80, 16, 60], [14, 52, 58], [62, 70, 66], [24, 96, 48]],
60
+ [[50, 8, 64], [8, 78, 60], [92, 62, 58], [46, 50, 40]],
61
+ ];
62
+
63
+ // 128×128 tiling noise (grey speckle with varying alpha), drawn at 2× so each speck is one device pixel or so.
64
+ const NOISE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAQAAABpN6lAAAAyMklEQVR42k2dwZXdOAwEeVYADEABMACeFBpDY2a0UNX647fPa3s88yWRINBoNKDWrtWf3mab+2rX08/7p2fuvue6nrHup6+rjXav9+97vn+a75/mc7X368+1730/93r/fPrpz9j34etrvP+fq++x+/v589xnnFF/8ir1SXu+15jtOvd6v/95r/F+xrXm+zn3+xPv19/vfT+7vV+t69ZPPfd57+29xljv57X+Xuk67/0+9anvd7/fdde/13ft8X7l/b49Wv3+XpXnen/2qSe53k9977jtunTfPMp7S3Xx9yPfj7reW34/+Fz8qhuom3p/9Mx3We66UD1me7+r1eO/X3kX5X7G+7DvIr4XrM94v/L+6mvu92/16/1zr0d/byqPu3urJaiHeb/+3uZ7N3x6fx97vH+7699qM2obamPau0Dv/9/PXHX1UYu+ayHeJauNen+9D75qyUYt/nu1u676/undkDPqSqfutrb+XbnxPtb7IfWPtWavFdTjXmUF78O+l9isJ4/xPnSrPb9Prej7va12fby3/O7oe0tlK6N+7v2393K1A0/dRC1cffbAOuqh6hbrtt9Hr8V4l5lHPzzw+1hlVbUko/7ltQ7/dtfS/n3u+yDvDp+yk5v7rc+869qrbLasmTt7sINV+88TrfqZd0nfn+NCdel331wVDbpW+P2B+v+oh29lkKNu+F2g9/veh63HfG+wFmBhHXVcytBax8CxiNrNw4OVbb1fqX3A3k4t17t/tWetHrkWlYNSC1rfUVa0y9jfhXpv9soS9+OSl22NusP3uhzhd9PKUrjjp7a0lw0vrrA4PHVXLNpd21zHrtW+vz9WDz/4kDohZWqsd12yaYyvRZTxvTv8rvWjsdYxqZPZ63brUepnz41nwCreW60bv/ExZbi9fr17Ub6kTH/UgtWiPDzwxj/U76uWppaW4/d+Tq9db/7se7/4m7r6xVLX9TrGXxZQnzPxbnWIu16l1UOzCR6hOrLv870Pxv69j1Entx601r8MpswHu9j+GnWma21xLHXD72Vf863bfc8zFlQ3yUOfWpYy3bucW1lLubWyifcs4wyf2jusq8wVl1Q3XSZdOzQbJ1pnXP/6Pk797HtvuEO3rP7DwdYG1IPxu15m4Kuug3fAj9QSvE51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';
65
+
66
+ /** Film grain to lay over anything: a photo, a gradient, a flat colour. Fills its parent; never takes touches. */
67
+ export function GrainOverlay({ grain = 0.55, style }: GrainOverlayProps) {
68
+ return (
69
+ <View pointerEvents="none" style={[StyleSheet.absoluteFill, { opacity: Math.max(0, Math.min(1, grain)) }, style]}>
70
+ <Image source={{ uri: NOISE, scale: 2 }} alt="" resizeMode="repeat" style={StyleSheet.absoluteFill} accessibilityIgnoresInvertColors />
71
+ </View>
72
+ );
73
+ }
74
+
75
+ /**
76
+ * A soft mesh-style gradient — a base colour with blurred colour blobs — finished with real film
77
+ * grain. Use it as a screen or card background, or behind glass so there is something to refract.
78
+ * Pure react-native-svg plus a tiny tiling noise image: no native setup.
79
+ */
80
+ export function GrainGradient({ palette = 'sunset', colors, grain = 0.55, seed = 0, style, children }: GrainGradientProps) {
81
+ const id = useId().replace(/[^a-zA-Z0-9]/g, '');
82
+ const [base, ...blobs] = colors && colors.length ? colors : PALETTES[palette];
83
+ const spots = SPOTS[Math.abs(seed) % SPOTS.length];
84
+ return (
85
+ <View style={[styles.root, { backgroundColor: base }, style]}>
86
+ <Svg style={StyleSheet.absoluteFill} viewBox="0 0 100 100" preserveAspectRatio="none" pointerEvents="none">
87
+ <Defs>
88
+ <LinearGradient id={`base${id}`} x1="0" y1="0" x2="1" y2="1">
89
+ <Stop offset="0" stopColor={base} stopOpacity={1} />
90
+ <Stop offset="1" stopColor={blobs[blobs.length - 1] ?? base} stopOpacity={0.35} />
91
+ </LinearGradient>
92
+ {blobs.slice(0, 4).map((c, i) => (
93
+ <RadialGradient key={i} id={`b${i}${id}`} cx="50%" cy="50%" r="50%">
94
+ <Stop offset="0" stopColor={c} stopOpacity={0.95} />
95
+ <Stop offset="0.45" stopColor={c} stopOpacity={0.55} />
96
+ <Stop offset="1" stopColor={c} stopOpacity={0} />
97
+ </RadialGradient>
98
+ ))}
99
+ </Defs>
100
+ <Rect x={0} y={0} width={100} height={100} fill={`url(#base${id})`} />
101
+ {blobs.slice(0, 4).map((_, i) => (
102
+ <Circle key={i} cx={spots[i][0]} cy={spots[i][1]} r={spots[i][2]} fill={`url(#b${i}${id})`} />
103
+ ))}
104
+ </Svg>
105
+ <GrainOverlay grain={grain} />
106
+ {children}
107
+ </View>
108
+ );
109
+ }
110
+
111
+ const styles = StyleSheet.create({
112
+ root: { overflow: 'hidden' },
113
+ });
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "grain",
3
+ "type": "component",
4
+ "title": "Grain Gradient",
5
+ "description": "A soft mesh-style gradient — a base colour with blurred colour blobs — finished with real film grain. Twelve palettes or your own colours. Use it as a screen or card background, or behind glass so there is something to refract. GrainOverlay adds the same grain to anything, photos included.",
6
+ "tier": "free",
7
+ "dependencies": [
8
+ "react-native-svg"
9
+ ],
10
+ "registryDependencies": [
11
+ "kit"
12
+ ],
13
+ "sharedFiles": [
14
+ "lib/kit.tsx"
15
+ ],
16
+ "filesDir": "grain/files"
17
+ }
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -44,7 +44,7 @@ export function GlassTabBar({ state, navigation, insets }: BottomTabBarProps) {
44
44
  Animated.spring(bubbleX, {
45
45
  toValue: activeIndex * itemW,
46
46
  useNativeDriver: true,
47
- damping: 18,
47
+ damping: 29,
48
48
  stiffness: 240,
49
49
  mass: 0.8,
50
50
  }).start();
@@ -33,7 +33,7 @@ export function setTabBarCollapsed(next: boolean) {
33
33
  Animated.spring(tabBarCollapse, {
34
34
  toValue: next ? 1 : 0,
35
35
  useNativeDriver: false,
36
- damping: 20,
36
+ damping: 29,
37
37
  stiffness: 220,
38
38
  mass: 0.9,
39
39
  }).start();
@@ -140,7 +140,8 @@ export function ListItem({
140
140
  accessibilityLabel,
141
141
  style,
142
142
  }: ListItemProps) {
143
- const { colors, radius } = useKit();
143
+ const { colors, radius, font } = useKit();
144
+ const ff = font.family ? { fontFamily: font.family } : null;
144
145
  const tint = accent ?? colors.accent;
145
146
  const s = SIZES[size];
146
147
  const pressable = !!(onPress || onLongPress);
@@ -192,18 +193,18 @@ export function ListItem({
192
193
  ) : null}
193
194
  <View style={styles.body}>
194
195
  <View style={styles.titleRow}>
195
- <Text numberOfLines={1} style={[styles.title, { fontSize: s.title, color: titleColor }, titleStyle]}>
196
+ <Text numberOfLines={1} style={[styles.title, ff, { fontSize: s.title, color: titleColor }, titleStyle]}>
196
197
  {title}
197
198
  </Text>
198
- {meta ? <Text style={[styles.meta, { color: colors.mutedForeground }]}>{meta}</Text> : null}
199
+ {meta ? <Text style={[styles.meta, ff, { color: colors.mutedForeground }]}>{meta}</Text> : null}
199
200
  </View>
200
201
  {subtitle ? (
201
- <Text numberOfLines={1} style={[styles.sub, { fontSize: s.sub, color: colors.mutedForeground }]}>
202
+ <Text numberOfLines={1} style={[styles.sub, ff, { fontSize: s.sub, color: colors.mutedForeground }]}>
202
203
  {subtitle}
203
204
  </Text>
204
205
  ) : null}
205
206
  {description ? (
206
- <Text numberOfLines={numberOfLines} style={[styles.desc, { fontSize: s.sub, lineHeight: s.sub * 1.36, color: colors.mutedForeground }]}>
207
+ <Text numberOfLines={numberOfLines} style={[styles.desc, ff, { fontSize: s.sub, lineHeight: s.sub * 1.36, color: colors.mutedForeground }]}>
207
208
  {description}
208
209
  </Text>
209
210
  ) : null}
@@ -211,17 +212,17 @@ export function ListItem({
211
212
  {value || badge || checked || trailing !== undefined || showChevron ? (
212
213
  <View style={styles.trailing}>
213
214
  {value ? (
214
- <Text numberOfLines={1} style={[styles.value, { color: colors.mutedForeground }]}>
215
+ <Text numberOfLines={1} style={[styles.value, ff, { color: colors.mutedForeground }]}>
215
216
  {value}
216
217
  </Text>
217
218
  ) : null}
218
219
  {badge === true ? <View style={[styles.dot, { backgroundColor: tint }]} /> : null}
219
220
  {badgeText ? (
220
221
  <View style={[styles.badge, { borderRadius: radius.capsule(22), backgroundColor: tint }]}>
221
- <Text style={[styles.badgeText, { color: readableOn(tint) }]}>{badgeText}</Text>
222
+ <Text style={[styles.badgeText, ff, { color: readableOn(tint) }]}>{badgeText}</Text>
222
223
  </View>
223
224
  ) : null}
224
- {isTextNode(trailing) ? <Text style={[styles.value, { color: colors.mutedForeground }]}>{trailing}</Text> : trailing}
225
+ {isTextNode(trailing) ? <Text style={[styles.value, ff, { color: colors.mutedForeground }]}>{trailing}</Text> : trailing}
225
226
  {checked ? <Check color={tint} /> : null}
226
227
  {showChevron ? <Chevron color={alpha(colors.mutedForeground, 0.7)} /> : null}
227
228
  </View>
@@ -282,11 +283,12 @@ export interface ListGroupProps {
282
283
  * between rows that start past the icon so they line up with the text.
283
284
  */
284
285
  export function ListGroup({ header, footer, background, children, style }: ListGroupProps) {
285
- const { colors, radius } = useKit();
286
+ const { colors, radius, font } = useKit();
287
+ const ff = font.family ? { fontFamily: font.family } : null;
286
288
  return (
287
289
  <View style={[styles.group, style]}>
288
290
  {header ? (
289
- <Text accessibilityRole="header" style={[styles.header, { color: colors.mutedForeground }]}>
291
+ <Text accessibilityRole="header" style={[styles.header, ff, { color: colors.mutedForeground }]}>
290
292
  {header}
291
293
  </Text>
292
294
  ) : null}
@@ -298,7 +300,7 @@ export function ListGroup({ header, footer, background, children, style }: ListG
298
300
  >
299
301
  {withDividers(children)}
300
302
  </View>
301
- {footer ? <Text style={[styles.footer, { color: colors.mutedForeground }]}>{footer}</Text> : null}
303
+ {footer ? <Text style={[styles.footer, ff, { color: colors.mutedForeground }]}>{footer}</Text> : null}
302
304
  </View>
303
305
  );
304
306
  }
@@ -315,16 +317,17 @@ export interface ListSectionProps {
315
317
 
316
318
  /** Plain full-width section: header text, then rows with inset separators. */
317
319
  export function ListSection({ header, footer, dividerAfterLast, children, style }: ListSectionProps) {
318
- const { colors } = useKit();
320
+ const { colors, font } = useKit();
321
+ const ff = font.family ? { fontFamily: font.family } : null;
319
322
  return (
320
323
  <View style={style}>
321
324
  {header ? (
322
- <Text accessibilityRole="header" style={[styles.sectionHeader, { color: colors.mutedForeground }]}>
325
+ <Text accessibilityRole="header" style={[styles.sectionHeader, ff, { color: colors.mutedForeground }]}>
323
326
  {header}
324
327
  </Text>
325
328
  ) : null}
326
329
  {withDividers(children, dividerAfterLast)}
327
- {footer ? <Text style={[styles.footer, { color: colors.mutedForeground }]}>{footer}</Text> : null}
330
+ {footer ? <Text style={[styles.footer, ff, { color: colors.mutedForeground }]}>{footer}</Text> : null}
328
331
  </View>
329
332
  );
330
333
  }
@@ -341,7 +344,8 @@ function withDividers(children: React.ReactNode, last = false) {
341
344
  }
342
345
 
343
346
  function AvatarCircle({ src, name = '', color, online, size, ring }: ListItemAvatar & { size: number; ring: string }) {
344
- const { colors } = useKit();
347
+ const { colors, font } = useKit();
348
+ const ff = font.family ? { fontFamily: font.family } : null;
345
349
  const [failed, setFailed] = useState(false);
346
350
  const initials = name
347
351
  .split(/\s+/)
@@ -356,7 +360,7 @@ function AvatarCircle({ src, name = '', color, online, size, ring }: ListItemAva
356
360
  return (
357
361
  <View style={{ width: size, height: size }}>
358
362
  <View style={[styles.avatar, { width: size, height: size, borderRadius: size / 2, backgroundColor: bg }]}>
359
- <Text style={{ color: readableOn(bg), fontSize: size * 0.38, fontWeight: '400' }}>{initials}</Text>
363
+ <Text style={[{ color: readableOn(bg), fontSize: size * 0.38, fontWeight: '400' }, ff]}>{initials}</Text>
360
364
  {src && !failed ? (
361
365
  <Image source={{ uri: src }} onError={() => setFailed(true)} style={[StyleSheet.absoluteFill, { borderRadius: size / 2 }]} />
362
366
  ) : null}
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "morphing-button-group",
3
+ "type": "component",
4
+ "title": "Morphing Button Group",
5
+ "description": "A row of buttons that morphs between sets: one button splits into two, two join into one, and a button that changes its label cross-fades it while it stretches. Ships with SignatureSheet, the pattern it was made for — a \"Sign\" pill that grows into a signing card whose Cancel splits into Reset + Confirm once you draw, and Confirm stretches into \"Confirm Signature\" before the card glides back down.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics"
10
+ ],
11
+ "registryDependencies": [
12
+ "kit"
13
+ ],
14
+ "sharedFiles": [
15
+ "lib/kit.tsx"
16
+ ],
17
+ "filesDir": null
18
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "orb-reveal",
3
+ "type": "component",
4
+ "title": "Orb Reveal",
5
+ "description": "An onboarding hero built around a thick liquid-glass orb. Hold it and it swells while a deep blue smile of light glows through it; drag it up and it shrinks into a glass button, showing your title through the lens on the way — magnified in the middle, mirrored around the bevel, with a rainbow fringe — then glass marbles fountain out of it into a heart. Drag it back down and the marbles float away.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "@shopify/react-native-skia",
9
+ "react-native-reanimated",
10
+ "react-native-worklets",
11
+ "expo-haptics"
12
+ ],
13
+ "registryDependencies": [
14
+ "kit"
15
+ ],
16
+ "sharedFiles": [
17
+ "lib/kit.tsx"
18
+ ],
19
+ "filesDir": null
20
+ }
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -84,7 +84,7 @@ export function OtpInput({
84
84
  style,
85
85
  }: OtpInputProps) {
86
86
  const { colors } = useKit();
87
- const input = useRef<TextInput>(null);
87
+ const input = useRef<ComponentRef<typeof TextInput>>(null);
88
88
  const [inner, setInner] = useState('');
89
89
  const [focused, setFocused] = useState(false);
90
90
  const [width, setWidth] = useState(0);
@@ -202,7 +202,8 @@ function Cell({
202
202
  w: number;
203
203
  h: number;
204
204
  }) {
205
- const { colors, radius } = useKit();
205
+ const { colors, radius, font: kitFont } = useKit();
206
+ const ff = kitFont.family ? { fontFamily: kitFont.family } : null;
206
207
  const filled = !!char;
207
208
  // Native driver: digit pop, caret blink, halo, success pulse.
208
209
  const pop = useRef(new Animated.Value(filled ? 1 : 0)).current;
@@ -305,7 +306,7 @@ function Cell({
305
306
  {secure ? (
306
307
  <View style={{ width: dot, height: dot, borderRadius: dot / 2, backgroundColor: fg }} />
307
308
  ) : (
308
- <Text style={[styles.char, { color: fg, fontSize: font, lineHeight: Math.round(font * 1.2) }]}>{char}</Text>
309
+ <Text style={[styles.char, ff, { color: fg, fontSize: font, lineHeight: Math.round(font * 1.2) }]}>{char}</Text>
309
310
  )}
310
311
  </Animated.View>
311
312
  ) : active ? (
@@ -318,7 +319,8 @@ function Cell({
318
319
 
319
320
  /** Helper / error / success line that fades and slides in whenever its text changes. */
320
321
  function Message({ text, kind }: { text: string; kind: CellState }) {
321
- const { colors } = useKit();
322
+ const { colors, font } = useKit();
323
+ const ff = font.family ? { fontFamily: font.family } : null;
322
324
  const enter = useRef(new Animated.Value(0)).current;
323
325
  const draw = useRef(new Animated.Value(0)).current;
324
326
 
@@ -359,7 +361,7 @@ function Message({ text, kind }: { text: string; kind: CellState }) {
359
361
  />
360
362
  </Svg>
361
363
  ) : null}
362
- <Text style={[styles.messageText, { color }]}>{text}</Text>
364
+ <Text style={[styles.messageText, ff, { color }]}>{text}</Text>
363
365
  </Animated.View>
364
366
  );
365
367
  }
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
1
+ import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -99,7 +99,7 @@ const NO_WEB_OUTLINE = Platform.OS === 'web' ? ({ outlineStyle: 'none', outlineW
99
99
  const AUTOFILL_CSS = `@keyframes ekAutofillOn{from{}to{}}@keyframes ekAutofillOff{from{}to{}}
100
100
  input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus{animation:ekAutofillOn 1ms;-webkit-text-fill-color:currentColor!important;caret-color:currentColor;transition:background-color 99999s 0s,color 99999s 0s!important}
101
101
  input:not(:-webkit-autofill){animation:ekAutofillOff 1ms}`;
102
- function useWebAutofill(inputRef: React.RefObject<TextInput | null>, onChange: (filled: boolean) => void) {
102
+ function useWebAutofill(inputRef: React.RefObject<ComponentRef<typeof TextInput> | null>, onChange: (filled: boolean) => void) {
103
103
  useEffect(() => {
104
104
  if (Platform.OS !== 'web' || typeof document === 'undefined') return;
105
105
  if (!document.getElementById('expokit-autofill')) {
@@ -126,7 +126,7 @@ function useWebAutofill(inputRef: React.RefObject<TextInput | null>, onChange: (
126
126
  * cutting a notch in the outline (Material-style). Focus draws a soft halo, errors shake,
127
127
  * success pops a check — all spring-driven.
128
128
  */
129
- export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function FloatingInput(
129
+ export const FloatingInput = forwardRef<ComponentRef<typeof TextInput>, FloatingInputProps>(function FloatingInput(
130
130
  {
131
131
  label,
132
132
  floatingLabel = true,
@@ -169,7 +169,7 @@ export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function
169
169
  const ff = font.family ? { fontFamily: font.family } : null;
170
170
  const S = FLOATING_INPUT_SIZES[size];
171
171
  // The user's border colour only replaces the resting one; numeric height / radius drive the layout maths.
172
- const { borderColor: userBorder, ...userField } = StyleSheet.flatten(fieldStyle) ?? {};
172
+ const { borderColor: userBorder, ...userField } = flattenView(fieldStyle);
173
173
  const userLabelColor = StyleSheet.flatten(labelStyleProp)?.color;
174
174
  const HEIGHT = typeof userField.height === 'number' ? userField.height : S.height;
175
175
  // Floated size stays relative to the resting size, so a bigger label floats bigger at the same ratio.
@@ -179,8 +179,8 @@ export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function
179
179
  const slot = S.icon + 10; // room for the clear button / check
180
180
  const tint = accent ?? colors.accent;
181
181
  const bg = surface ?? colors.background;
182
- const inputRef = useRef<TextInput>(null);
183
- useImperativeHandle(ref, () => inputRef.current as TextInput);
182
+ const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
183
+ useImperativeHandle(ref, () => inputRef.current as ComponentRef<typeof TextInput>);
184
184
 
185
185
  const [focused, setFocused] = useState(false);
186
186
  const [inner, setInner] = useState(defaultValue ?? '');
@@ -500,6 +500,16 @@ function FieldMessage({ text, tone, color, style }: { text: string; tone: Messag
500
500
  );
501
501
  }
502
502
 
503
+ /**
504
+ * The user's field style as one object. Not StyleSheet.flatten: with TypeScript 6 its RN 0.88 signature
505
+ * rejects a plain StyleProp<ViewStyle> once expo/types adds web-only keys to ViewStyle.
506
+ */
507
+ function flattenView(style: StyleProp<ViewStyle>): ViewStyle {
508
+ if (!style || typeof style !== 'object') return {};
509
+ if (Array.isArray(style)) return Object.assign({}, ...style.map((s) => flattenView(s as StyleProp<ViewStyle>)));
510
+ return style as ViewStyle;
511
+ }
512
+
503
513
  const styles = StyleSheet.create({
504
514
  disabled: { opacity: 0.5 },
505
515
  halo: { position: 'absolute', top: -HALO, left: -HALO, right: -HALO, bottom: -HALO },
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { forwardRef, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import { Animated, Pressable, StyleSheet, Text, View, type TextInput } from 'react-native';
3
3
  import Svg, { Circle, Path } from 'react-native-svg';
4
4
 
@@ -50,7 +50,7 @@ export function scorePassword(pw: string) {
50
50
  * Floating-label password field with an eye / eye-off toggle that cross-fades, a spring-filled
51
51
  * strength meter, an optional requirements checklist and a confirm-password mode.
52
52
  */
53
- export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function PasswordInput(
53
+ export const PasswordInput = forwardRef<ComponentRef<typeof TextInput>, PasswordInputProps>(function PasswordInput(
54
54
  {
55
55
  strength = true,
56
56
  score = scorePassword,
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -121,7 +121,7 @@ export function FlipCard({
121
121
  borderRadius: radius,
122
122
  opacity,
123
123
  transform: [{ perspective }, { [axis]: turn } as never, { scale: lift }],
124
- }) as Animated.WithAnimatedObject<ViewStyle>;
124
+ }) as Animated.WithAnimatedValue<ViewStyle>;
125
125
 
126
126
  // The shadow squeezes along the turning axis as the card goes edge-on (see SQUEEZE) and fades a little.
127
127
  const squeeze = p.interpolate(SQUEEZE);