@oxyhq/bloom 0.26.1 → 0.28.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 (300) hide show
  1. package/lib/commonjs/animated-check/AnimatedCheck.js +162 -0
  2. package/lib/commonjs/animated-check/AnimatedCheck.js.map +1 -0
  3. package/lib/commonjs/animated-check/index.js +13 -0
  4. package/lib/commonjs/animated-check/index.js.map +1 -0
  5. package/lib/commonjs/avatar/Avatar.js +67 -96
  6. package/lib/commonjs/avatar/Avatar.js.map +1 -1
  7. package/lib/commonjs/avatar/AvatarRing.js +183 -0
  8. package/lib/commonjs/avatar/AvatarRing.js.map +1 -0
  9. package/lib/commonjs/avatar/index.js.map +1 -1
  10. package/lib/commonjs/avatar/squircle-path.js +14 -0
  11. package/lib/commonjs/avatar/squircle-path.js.map +1 -0
  12. package/lib/commonjs/avatar/svg-module.js +15 -0
  13. package/lib/commonjs/avatar/svg-module.js.map +1 -0
  14. package/lib/commonjs/dialog/placement.js +9 -7
  15. package/lib/commonjs/dialog/placement.js.map +1 -1
  16. package/lib/commonjs/fill/index.js.map +1 -1
  17. package/lib/commonjs/hooks/index.js +20 -0
  18. package/lib/commonjs/hooks/index.js.map +1 -1
  19. package/lib/commonjs/hooks/useGutters.js +106 -0
  20. package/lib/commonjs/hooks/useGutters.js.map +1 -0
  21. package/lib/commonjs/hooks/useHaptics.js +69 -0
  22. package/lib/commonjs/hooks/useHaptics.js.map +1 -0
  23. package/lib/commonjs/hooks/useInteractionState.js +8 -2
  24. package/lib/commonjs/hooks/useInteractionState.js.map +1 -1
  25. package/lib/commonjs/index.js +155 -63
  26. package/lib/commonjs/index.js.map +1 -1
  27. package/lib/commonjs/index.web.js +155 -63
  28. package/lib/commonjs/index.web.js.map +1 -1
  29. package/lib/commonjs/media-inset-border/MediaInsetBorder.js +50 -0
  30. package/lib/commonjs/media-inset-border/MediaInsetBorder.js.map +1 -0
  31. package/lib/commonjs/media-inset-border/index.js +13 -0
  32. package/lib/commonjs/media-inset-border/index.js.map +1 -0
  33. package/lib/commonjs/motion/ScreenTransition.js +46 -0
  34. package/lib/commonjs/motion/ScreenTransition.js.map +1 -0
  35. package/lib/commonjs/motion/index.js +32 -0
  36. package/lib/commonjs/motion/index.js.map +1 -0
  37. package/lib/commonjs/motion/motion.js +93 -0
  38. package/lib/commonjs/motion/motion.js.map +1 -0
  39. package/lib/commonjs/pressable-scale/PressableScale.js +75 -0
  40. package/lib/commonjs/pressable-scale/PressableScale.js.map +1 -0
  41. package/lib/commonjs/pressable-scale/index.js +13 -0
  42. package/lib/commonjs/pressable-scale/index.js.map +1 -0
  43. package/lib/commonjs/pressable-with-hover/PressableWithHover.js +36 -0
  44. package/lib/commonjs/pressable-with-hover/PressableWithHover.js.map +1 -0
  45. package/lib/commonjs/pressable-with-hover/index.js +13 -0
  46. package/lib/commonjs/pressable-with-hover/index.js.map +1 -0
  47. package/lib/commonjs/styles/breakpoints.js +24 -0
  48. package/lib/commonjs/styles/breakpoints.js.map +1 -0
  49. package/lib/commonjs/styles/index.js +7 -0
  50. package/lib/commonjs/styles/index.js.map +1 -1
  51. package/lib/commonjs/subtle-hover/SubtleHover.js +69 -0
  52. package/lib/commonjs/subtle-hover/SubtleHover.js.map +1 -0
  53. package/lib/commonjs/subtle-hover/index.js +13 -0
  54. package/lib/commonjs/subtle-hover/index.js.map +1 -0
  55. package/lib/commonjs/theme/build-theme.js +2 -0
  56. package/lib/commonjs/theme/build-theme.js.map +1 -1
  57. package/lib/commonjs/theme/gradients.js +39 -0
  58. package/lib/commonjs/theme/gradients.js.map +1 -0
  59. package/lib/commonjs/theme/index.js +7 -0
  60. package/lib/commonjs/theme/index.js.map +1 -1
  61. package/lib/module/animated-check/AnimatedCheck.js +157 -0
  62. package/lib/module/animated-check/AnimatedCheck.js.map +1 -0
  63. package/lib/module/animated-check/index.js +4 -0
  64. package/lib/module/animated-check/index.js.map +1 -0
  65. package/lib/module/avatar/Avatar.js +66 -96
  66. package/lib/module/avatar/Avatar.js.map +1 -1
  67. package/lib/module/avatar/AvatarRing.js +177 -0
  68. package/lib/module/avatar/AvatarRing.js.map +1 -0
  69. package/lib/module/avatar/index.js.map +1 -1
  70. package/lib/module/avatar/squircle-path.js +10 -0
  71. package/lib/module/avatar/squircle-path.js.map +1 -0
  72. package/lib/module/avatar/svg-module.js +11 -0
  73. package/lib/module/avatar/svg-module.js.map +1 -0
  74. package/lib/module/dialog/placement.js +9 -7
  75. package/lib/module/dialog/placement.js.map +1 -1
  76. package/lib/module/fill/index.js.map +1 -1
  77. package/lib/module/hooks/index.js +2 -0
  78. package/lib/module/hooks/index.js.map +1 -1
  79. package/lib/module/hooks/useGutters.js +103 -0
  80. package/lib/module/hooks/useGutters.js.map +1 -0
  81. package/lib/module/hooks/useHaptics.js +65 -0
  82. package/lib/module/hooks/useHaptics.js.map +1 -0
  83. package/lib/module/hooks/useInteractionState.js +10 -3
  84. package/lib/module/hooks/useInteractionState.js.map +1 -1
  85. package/lib/module/index.js +10 -1
  86. package/lib/module/index.js.map +1 -1
  87. package/lib/module/index.web.js +10 -1
  88. package/lib/module/index.web.js.map +1 -1
  89. package/lib/module/media-inset-border/MediaInsetBorder.js +47 -0
  90. package/lib/module/media-inset-border/MediaInsetBorder.js.map +1 -0
  91. package/lib/module/media-inset-border/index.js +4 -0
  92. package/lib/module/media-inset-border/index.js.map +1 -0
  93. package/lib/module/motion/ScreenTransition.js +40 -0
  94. package/lib/module/motion/ScreenTransition.js.map +1 -0
  95. package/lib/module/motion/index.js +5 -0
  96. package/lib/module/motion/index.js.map +1 -0
  97. package/lib/module/motion/motion.js +87 -0
  98. package/lib/module/motion/motion.js.map +1 -0
  99. package/lib/module/pressable-scale/PressableScale.js +72 -0
  100. package/lib/module/pressable-scale/PressableScale.js.map +1 -0
  101. package/lib/module/pressable-scale/index.js +4 -0
  102. package/lib/module/pressable-scale/index.js.map +1 -0
  103. package/lib/module/pressable-with-hover/PressableWithHover.js +31 -0
  104. package/lib/module/pressable-with-hover/PressableWithHover.js.map +1 -0
  105. package/lib/module/pressable-with-hover/index.js +4 -0
  106. package/lib/module/pressable-with-hover/index.js.map +1 -0
  107. package/lib/module/styles/breakpoints.js +20 -0
  108. package/lib/module/styles/breakpoints.js.map +1 -0
  109. package/lib/module/styles/index.js +1 -0
  110. package/lib/module/styles/index.js.map +1 -1
  111. package/lib/module/subtle-hover/SubtleHover.js +66 -0
  112. package/lib/module/subtle-hover/SubtleHover.js.map +1 -0
  113. package/lib/module/subtle-hover/index.js +4 -0
  114. package/lib/module/subtle-hover/index.js.map +1 -0
  115. package/lib/module/theme/build-theme.js +2 -0
  116. package/lib/module/theme/build-theme.js.map +1 -1
  117. package/lib/module/theme/gradients.js +35 -0
  118. package/lib/module/theme/gradients.js.map +1 -0
  119. package/lib/module/theme/index.js +1 -0
  120. package/lib/module/theme/index.js.map +1 -1
  121. package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts +18 -0
  122. package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/animated-check/index.d.ts +3 -0
  124. package/lib/typescript/commonjs/animated-check/index.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/avatar/Avatar.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/avatar/AvatarRing.d.ts +27 -0
  127. package/lib/typescript/commonjs/avatar/AvatarRing.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/avatar/index.d.ts +1 -1
  129. package/lib/typescript/commonjs/avatar/index.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/avatar/squircle-path.d.ts +8 -0
  131. package/lib/typescript/commonjs/avatar/squircle-path.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/avatar/svg-module.d.ts +8 -0
  133. package/lib/typescript/commonjs/avatar/svg-module.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/avatar/types.d.ts +30 -0
  135. package/lib/typescript/commonjs/avatar/types.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/dialog/placement.d.ts +8 -7
  137. package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/fill/index.d.ts +2 -2
  139. package/lib/typescript/commonjs/fill/index.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/hooks/index.d.ts +4 -0
  141. package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/hooks/useGutters.d.ts +32 -0
  143. package/lib/typescript/commonjs/hooks/useGutters.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/hooks/useHaptics.d.ts +28 -0
  145. package/lib/typescript/commonjs/hooks/useHaptics.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/hooks/useInteractionState.d.ts +6 -0
  147. package/lib/typescript/commonjs/hooks/useInteractionState.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/index.d.ts +16 -0
  149. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/index.web.d.ts +16 -0
  151. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts +26 -0
  153. package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
  154. package/lib/typescript/commonjs/media-inset-border/index.d.ts +3 -0
  155. package/lib/typescript/commonjs/media-inset-border/index.d.ts.map +1 -0
  156. package/lib/typescript/commonjs/motion/ScreenTransition.d.ts +33 -0
  157. package/lib/typescript/commonjs/motion/ScreenTransition.d.ts.map +1 -0
  158. package/lib/typescript/commonjs/motion/index.d.ts +4 -0
  159. package/lib/typescript/commonjs/motion/index.d.ts.map +1 -0
  160. package/lib/typescript/commonjs/motion/motion.d.ts +24 -0
  161. package/lib/typescript/commonjs/motion/motion.d.ts.map +1 -0
  162. package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts +21 -0
  163. package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts.map +1 -0
  164. package/lib/typescript/commonjs/pressable-scale/index.d.ts +3 -0
  165. package/lib/typescript/commonjs/pressable-scale/index.d.ts.map +1 -0
  166. package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts +18 -0
  167. package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
  168. package/lib/typescript/commonjs/pressable-with-hover/index.d.ts +3 -0
  169. package/lib/typescript/commonjs/pressable-with-hover/index.d.ts.map +1 -0
  170. package/lib/typescript/commonjs/styles/breakpoints.d.ts +18 -0
  171. package/lib/typescript/commonjs/styles/breakpoints.d.ts.map +1 -0
  172. package/lib/typescript/commonjs/styles/index.d.ts +2 -0
  173. package/lib/typescript/commonjs/styles/index.d.ts.map +1 -1
  174. package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts +32 -0
  175. package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts.map +1 -0
  176. package/lib/typescript/commonjs/subtle-hover/index.d.ts +3 -0
  177. package/lib/typescript/commonjs/subtle-hover/index.d.ts.map +1 -0
  178. package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/theme/gradients.d.ts +12 -0
  180. package/lib/typescript/commonjs/theme/gradients.d.ts.map +1 -0
  181. package/lib/typescript/commonjs/theme/index.d.ts +2 -1
  182. package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/theme/types.d.ts +11 -0
  184. package/lib/typescript/commonjs/theme/types.d.ts.map +1 -1
  185. package/lib/typescript/module/animated-check/AnimatedCheck.d.ts +18 -0
  186. package/lib/typescript/module/animated-check/AnimatedCheck.d.ts.map +1 -0
  187. package/lib/typescript/module/animated-check/index.d.ts +3 -0
  188. package/lib/typescript/module/animated-check/index.d.ts.map +1 -0
  189. package/lib/typescript/module/avatar/Avatar.d.ts.map +1 -1
  190. package/lib/typescript/module/avatar/AvatarRing.d.ts +27 -0
  191. package/lib/typescript/module/avatar/AvatarRing.d.ts.map +1 -0
  192. package/lib/typescript/module/avatar/index.d.ts +1 -1
  193. package/lib/typescript/module/avatar/index.d.ts.map +1 -1
  194. package/lib/typescript/module/avatar/squircle-path.d.ts +8 -0
  195. package/lib/typescript/module/avatar/squircle-path.d.ts.map +1 -0
  196. package/lib/typescript/module/avatar/svg-module.d.ts +8 -0
  197. package/lib/typescript/module/avatar/svg-module.d.ts.map +1 -0
  198. package/lib/typescript/module/avatar/types.d.ts +30 -0
  199. package/lib/typescript/module/avatar/types.d.ts.map +1 -1
  200. package/lib/typescript/module/dialog/placement.d.ts +8 -7
  201. package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
  202. package/lib/typescript/module/fill/index.d.ts +2 -2
  203. package/lib/typescript/module/fill/index.d.ts.map +1 -1
  204. package/lib/typescript/module/hooks/index.d.ts +4 -0
  205. package/lib/typescript/module/hooks/index.d.ts.map +1 -1
  206. package/lib/typescript/module/hooks/useGutters.d.ts +32 -0
  207. package/lib/typescript/module/hooks/useGutters.d.ts.map +1 -0
  208. package/lib/typescript/module/hooks/useHaptics.d.ts +28 -0
  209. package/lib/typescript/module/hooks/useHaptics.d.ts.map +1 -0
  210. package/lib/typescript/module/hooks/useInteractionState.d.ts +6 -0
  211. package/lib/typescript/module/hooks/useInteractionState.d.ts.map +1 -1
  212. package/lib/typescript/module/index.d.ts +16 -0
  213. package/lib/typescript/module/index.d.ts.map +1 -1
  214. package/lib/typescript/module/index.web.d.ts +16 -0
  215. package/lib/typescript/module/index.web.d.ts.map +1 -1
  216. package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts +26 -0
  217. package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
  218. package/lib/typescript/module/media-inset-border/index.d.ts +3 -0
  219. package/lib/typescript/module/media-inset-border/index.d.ts.map +1 -0
  220. package/lib/typescript/module/motion/ScreenTransition.d.ts +33 -0
  221. package/lib/typescript/module/motion/ScreenTransition.d.ts.map +1 -0
  222. package/lib/typescript/module/motion/index.d.ts +4 -0
  223. package/lib/typescript/module/motion/index.d.ts.map +1 -0
  224. package/lib/typescript/module/motion/motion.d.ts +24 -0
  225. package/lib/typescript/module/motion/motion.d.ts.map +1 -0
  226. package/lib/typescript/module/pressable-scale/PressableScale.d.ts +21 -0
  227. package/lib/typescript/module/pressable-scale/PressableScale.d.ts.map +1 -0
  228. package/lib/typescript/module/pressable-scale/index.d.ts +3 -0
  229. package/lib/typescript/module/pressable-scale/index.d.ts.map +1 -0
  230. package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts +18 -0
  231. package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
  232. package/lib/typescript/module/pressable-with-hover/index.d.ts +3 -0
  233. package/lib/typescript/module/pressable-with-hover/index.d.ts.map +1 -0
  234. package/lib/typescript/module/styles/breakpoints.d.ts +18 -0
  235. package/lib/typescript/module/styles/breakpoints.d.ts.map +1 -0
  236. package/lib/typescript/module/styles/index.d.ts +2 -0
  237. package/lib/typescript/module/styles/index.d.ts.map +1 -1
  238. package/lib/typescript/module/subtle-hover/SubtleHover.d.ts +32 -0
  239. package/lib/typescript/module/subtle-hover/SubtleHover.d.ts.map +1 -0
  240. package/lib/typescript/module/subtle-hover/index.d.ts +3 -0
  241. package/lib/typescript/module/subtle-hover/index.d.ts.map +1 -0
  242. package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
  243. package/lib/typescript/module/theme/gradients.d.ts +12 -0
  244. package/lib/typescript/module/theme/gradients.d.ts.map +1 -0
  245. package/lib/typescript/module/theme/index.d.ts +2 -1
  246. package/lib/typescript/module/theme/index.d.ts.map +1 -1
  247. package/lib/typescript/module/theme/types.d.ts +11 -0
  248. package/lib/typescript/module/theme/types.d.ts.map +1 -1
  249. package/package.json +73 -1
  250. package/src/__tests__/AnimatedCheck.test.tsx +31 -0
  251. package/src/__tests__/MediaInsetBorder.test.tsx +36 -0
  252. package/src/__tests__/Motion.test.tsx +58 -0
  253. package/src/__tests__/PressableScale.test.tsx +29 -0
  254. package/src/__tests__/PressableWithHover.test.tsx +48 -0
  255. package/src/__tests__/SubtleHover.test.tsx +42 -0
  256. package/src/__tests__/theme-gradients.test.ts +31 -0
  257. package/src/__tests__/theme.test.ts +3 -0
  258. package/src/__tests__/useGutters.test.ts +58 -0
  259. package/src/__tests__/useHaptics.test.tsx +58 -0
  260. package/src/__tests__/useInteractionState.test.ts +27 -0
  261. package/src/animated-check/AnimatedCheck.stories.tsx +60 -0
  262. package/src/animated-check/AnimatedCheck.tsx +173 -0
  263. package/src/animated-check/index.ts +2 -0
  264. package/src/avatar/Avatar.stories.tsx +95 -0
  265. package/src/avatar/Avatar.tsx +61 -110
  266. package/src/avatar/AvatarRing.tsx +170 -0
  267. package/src/avatar/index.ts +6 -1
  268. package/src/avatar/squircle-path.ts +11 -0
  269. package/src/avatar/svg-module.ts +11 -0
  270. package/src/avatar/types.ts +32 -0
  271. package/src/dialog/placement.ts +10 -7
  272. package/src/fill/index.tsx +2 -2
  273. package/src/hooks/index.ts +4 -0
  274. package/src/hooks/useGutters.ts +99 -0
  275. package/src/hooks/useHaptics.tsx +69 -0
  276. package/src/hooks/useInteractionState.ts +8 -2
  277. package/src/index.ts +20 -0
  278. package/src/index.web.ts +20 -0
  279. package/src/media-inset-border/MediaInsetBorder.stories.tsx +60 -0
  280. package/src/media-inset-border/MediaInsetBorder.tsx +72 -0
  281. package/src/media-inset-border/index.ts +2 -0
  282. package/src/motion/Motion.stories.tsx +99 -0
  283. package/src/motion/ScreenTransition.tsx +71 -0
  284. package/src/motion/index.ts +3 -0
  285. package/src/motion/motion.ts +75 -0
  286. package/src/pressable-scale/PressableScale.stories.tsx +71 -0
  287. package/src/pressable-scale/PressableScale.tsx +103 -0
  288. package/src/pressable-scale/index.ts +2 -0
  289. package/src/pressable-with-hover/PressableWithHover.stories.tsx +43 -0
  290. package/src/pressable-with-hover/PressableWithHover.tsx +43 -0
  291. package/src/pressable-with-hover/index.ts +2 -0
  292. package/src/styles/breakpoints.ts +18 -0
  293. package/src/styles/index.ts +2 -0
  294. package/src/subtle-hover/SubtleHover.stories.tsx +72 -0
  295. package/src/subtle-hover/SubtleHover.tsx +90 -0
  296. package/src/subtle-hover/index.ts +2 -0
  297. package/src/theme/build-theme.ts +2 -0
  298. package/src/theme/gradients.ts +61 -0
  299. package/src/theme/index.ts +2 -1
  300. package/src/theme/types.ts +13 -0
@@ -0,0 +1,99 @@
1
+ import { useMemo } from 'react';
2
+ import { useWindowDimensions } from 'react-native';
3
+
4
+ import { BREAKPOINTS } from '../styles/breakpoints';
5
+ import { space } from '../styles/tokens';
6
+
7
+ /**
8
+ * A named gutter size, or `0` for no padding on that edge.
9
+ *
10
+ * - `'compact'` — tight gutter (dense lists, chips).
11
+ * - `'base'` — the default page gutter.
12
+ * - `'wide'` — roomy gutter (marketing / hero surfaces).
13
+ */
14
+ export type Gutter = 'compact' | 'base' | 'wide' | 0;
15
+
16
+ /** Resolved edge padding in px — one value per side, ready to spread onto a style. */
17
+ export interface Gutters {
18
+ paddingTop: number;
19
+ paddingRight: number;
20
+ paddingBottom: number;
21
+ paddingLeft: number;
22
+ }
23
+
24
+ /** The active viewport tier: the widest matched breakpoint, or `'base'` below `sm`. */
25
+ type GutterTier = 'base' | 'sm' | 'md' | 'lg' | 'xl';
26
+
27
+ /**
28
+ * Per-size gutter padding (px) at each viewport tier. Padding steps up around
29
+ * tablet width (`md`), mirroring the design-system scale: phones get a tighter
30
+ * gutter, wider screens a roomier one. Values come from the shared `space`
31
+ * tokens so gutters stay on the spacing grid.
32
+ */
33
+ const GUTTER_SCALE: Record<Exclude<Gutter, 0>, Record<GutterTier, number>> = {
34
+ compact: { base: space.sm, sm: space.sm, md: space.md, lg: space.md, xl: space.md },
35
+ base: { base: space.lg, sm: space.lg, md: space.xl, lg: space.xl, xl: space.xl },
36
+ wide: { base: space.xl, sm: space.xl, md: space._3xl, lg: space._3xl, xl: space._3xl },
37
+ };
38
+
39
+ /** Widest-first breakpoint table so the first match wins. */
40
+ const TIERS: ReadonlyArray<readonly [Exclude<GutterTier, 'base'>, number]> = [
41
+ ['xl', BREAKPOINTS.xl],
42
+ ['lg', BREAKPOINTS.lg],
43
+ ['md', BREAKPOINTS.md],
44
+ ['sm', BREAKPOINTS.sm],
45
+ ];
46
+
47
+ function resolveTier(width: number): GutterTier {
48
+ for (const [tier, minWidth] of TIERS) {
49
+ if (width >= minWidth) return tier;
50
+ }
51
+ return 'base';
52
+ }
53
+
54
+ function resolveEdge(size: Gutter, tier: GutterTier): number {
55
+ return size === 0 ? 0 : GUTTER_SCALE[size][tier];
56
+ }
57
+
58
+ /**
59
+ * Expand a 1/2/4-arg CSS-shorthand gutter list into `[top, right, bottom, left]`.
60
+ * `??` (not `||`) so an explicit `0` is preserved rather than falling through.
61
+ */
62
+ function normalizeShorthand(gutters: Gutter[]): [Gutter, Gutter, Gutter, Gutter] {
63
+ const top = gutters[0] ?? 0;
64
+ const right = gutters[1] ?? top;
65
+ const bottom = gutters[2] ?? top;
66
+ const left = gutters[3] ?? right;
67
+ return [top, right, bottom, left];
68
+ }
69
+
70
+ /**
71
+ * Breakpoint-aware gutter padding. Give it CSS-shorthand gutter sizes and it
72
+ * returns resolved `padding*` values for the current viewport width, recomputed
73
+ * as the window crosses a breakpoint.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * const gutters = useGutters(['base']); // uniform
78
+ * const gutters = useGutters([0, 'wide']); // no vertical, wide horizontal
79
+ * const gutters = useGutters(['compact', 'base', 0, 'base']); // t / r / b / l
80
+ * <View style={gutters} />
81
+ * ```
82
+ */
83
+ export function useGutters(gutters: [Gutter]): Gutters;
84
+ export function useGutters(gutters: [Gutter, Gutter]): Gutters;
85
+ export function useGutters(gutters: [Gutter, Gutter, Gutter, Gutter]): Gutters;
86
+ export function useGutters(gutters: Gutter[]): Gutters {
87
+ const { width } = useWindowDimensions();
88
+ const [top, right, bottom, left] = normalizeShorthand(gutters);
89
+
90
+ return useMemo<Gutters>(() => {
91
+ const tier = resolveTier(width);
92
+ return {
93
+ paddingTop: resolveEdge(top, tier),
94
+ paddingRight: resolveEdge(right, tier),
95
+ paddingBottom: resolveEdge(bottom, tier),
96
+ paddingLeft: resolveEdge(left, tier),
97
+ };
98
+ }, [width, top, right, bottom, left]);
99
+ }
@@ -0,0 +1,69 @@
1
+ import React, { createContext, useCallback, useContext, useMemo } from 'react';
2
+ import { Platform } from 'react-native';
3
+
4
+ import { lazyRequire } from '../utils/lazy-require';
5
+
6
+ /**
7
+ * Lazily loads `expo-haptics`, returning `null` when it is not installed. The
8
+ * dependency is an optional peer, so calls degrade to a no-op when absent.
9
+ */
10
+ const getHapticsModule = lazyRequire<typeof import('expo-haptics')>('expo-haptics');
11
+
12
+ /** Impact strength requested by a caller. */
13
+ export type HapticStrength = 'light' | 'medium' | 'heavy';
14
+
15
+ interface BloomHapticsContextValue {
16
+ enabled: boolean;
17
+ }
18
+
19
+ const BloomHapticsContext = createContext<BloomHapticsContextValue>({ enabled: true });
20
+
21
+ export interface BloomHapticsProviderProps {
22
+ /** Globally enable or disable haptics for the subtree. Defaults to `true`. */
23
+ enabled?: boolean;
24
+ children?: React.ReactNode;
25
+ }
26
+
27
+ /**
28
+ * Optional provider that toggles haptic feedback for its subtree — e.g. to honour
29
+ * a user "reduce haptics" preference. `useHaptics` defaults to enabled when no
30
+ * provider is present.
31
+ */
32
+ export function BloomHapticsProvider({ enabled = true, children }: BloomHapticsProviderProps) {
33
+ const value = useMemo(() => ({ enabled }), [enabled]);
34
+ return <BloomHapticsContext.Provider value={value}>{children}</BloomHapticsContext.Provider>;
35
+ }
36
+
37
+ BloomHapticsProvider.displayName = 'BloomHapticsProvider';
38
+
39
+ /**
40
+ * Returns a function that fires a haptic impact. The returned callback no-ops on
41
+ * web, when haptics are disabled via {@link BloomHapticsProvider}, or when the
42
+ * optional `expo-haptics` peer is not installed.
43
+ *
44
+ * `strength` defaults to `'light'`. Android is clamped to the light impact style
45
+ * regardless of `strength` — its medium/heavy motors read as too strong for UI
46
+ * feedback (tuned to match iOS perceived intensity).
47
+ */
48
+ export function useHaptics(): (strength?: HapticStrength) => void {
49
+ const { enabled } = useContext(BloomHapticsContext);
50
+
51
+ return useCallback(
52
+ (strength: HapticStrength = 'light') => {
53
+ if (!enabled || Platform.OS === 'web') return;
54
+
55
+ const haptics = getHapticsModule();
56
+ if (!haptics) return;
57
+
58
+ const { ImpactFeedbackStyle } = haptics;
59
+ let style = ImpactFeedbackStyle.Light;
60
+ if (Platform.OS !== 'android') {
61
+ if (strength === 'medium') style = ImpactFeedbackStyle.Medium;
62
+ else if (strength === 'heavy') style = ImpactFeedbackStyle.Heavy;
63
+ }
64
+
65
+ void haptics.impactAsync(style);
66
+ },
67
+ [enabled],
68
+ );
69
+ }
@@ -1,5 +1,11 @@
1
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useMemo, useState } from 'react';
2
2
 
3
+ /**
4
+ * Tracks a single boolean interaction flag (hovered / focused / pressed).
5
+ *
6
+ * Returns a memoized `{ state, onIn, onOut }` with stable handler identities,
7
+ * suitable for spreading onto Pressable-like components.
8
+ */
3
9
  export function useInteractionState() {
4
10
  const [state, setState] = useState(false);
5
11
 
@@ -10,7 +16,7 @@ export function useInteractionState() {
10
16
  setState(false);
11
17
  }, []);
12
18
 
13
- return { state, onIn, onOut };
19
+ return useMemo(() => ({ state, onIn, onOut }), [state, onIn, onOut]);
14
20
  }
15
21
 
16
22
  /**
package/src/index.ts CHANGED
@@ -40,6 +40,10 @@ export type {
40
40
  export { useInteractionState } from './hooks/useInteractionState';
41
41
  export { useDelayedLoading } from './hooks/useDelayedLoading';
42
42
  export { useThrottledValue } from './hooks/useThrottledValue';
43
+ export { useHaptics, BloomHapticsProvider } from './hooks/useHaptics';
44
+ export type { HapticStrength, BloomHapticsProviderProps } from './hooks/useHaptics';
45
+ export { useGutters } from './hooks/useGutters';
46
+ export type { Gutter, Gutters } from './hooks/useGutters';
43
47
 
44
48
  // Icons
45
49
  export * as Icons from './icons';
@@ -97,12 +101,28 @@ export * as Typography from './typography';
97
101
  export * as Skeleton from './skeleton';
98
102
  export * as Grid from './grid';
99
103
  export { Fill } from './fill';
104
+ export { MediaInsetBorder } from './media-inset-border';
105
+ export type { MediaInsetBorderProps } from './media-inset-border';
100
106
  export { IconCircle } from './icon-circle';
101
107
  export { ConnectionDots } from './connection-dots';
102
108
  export type { ConnectionDotsProps } from './connection-dots';
103
109
  export { BenefitRow, BenefitList } from './benefit-list';
104
110
  export type { BenefitRowProps, BenefitListProps } from './benefit-list';
105
111
 
112
+ // Interaction primitives
113
+ export { PressableScale } from './pressable-scale';
114
+ export type { PressableScaleProps } from './pressable-scale';
115
+ export { PressableWithHover } from './pressable-with-hover';
116
+ export type { PressableWithHoverProps } from './pressable-with-hover';
117
+ export { SubtleHover } from './subtle-hover';
118
+ export type { SubtleHoverProps } from './subtle-hover';
119
+
120
+ // Motion presets (Reanimated enter/exit + directional screen transition)
121
+ export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop, ScreenTransition } from './motion';
122
+ export type { ScreenTransitionProps, ScreenTransitionDirection } from './motion';
123
+ export { AnimatedCheck } from './animated-check';
124
+ export type { AnimatedCheckProps, AnimatedCheckRef } from './animated-check';
125
+
106
126
  // Form components
107
127
  export * from './text-field';
108
128
  export * from './segmented-control';
package/src/index.web.ts CHANGED
@@ -45,6 +45,10 @@ export type {
45
45
  export { useInteractionState } from './hooks/useInteractionState';
46
46
  export { useDelayedLoading } from './hooks/useDelayedLoading';
47
47
  export { useThrottledValue } from './hooks/useThrottledValue';
48
+ export { useHaptics, BloomHapticsProvider } from './hooks/useHaptics';
49
+ export type { HapticStrength, BloomHapticsProviderProps } from './hooks/useHaptics';
50
+ export { useGutters } from './hooks/useGutters';
51
+ export type { Gutter, Gutters } from './hooks/useGutters';
48
52
 
49
53
  // Icons
50
54
  export * as Icons from './icons';
@@ -102,12 +106,28 @@ export * as Typography from './typography';
102
106
  export * as Skeleton from './skeleton';
103
107
  export * as Grid from './grid';
104
108
  export { Fill } from './fill';
109
+ export { MediaInsetBorder } from './media-inset-border';
110
+ export type { MediaInsetBorderProps } from './media-inset-border';
105
111
  export { IconCircle } from './icon-circle';
106
112
  export { ConnectionDots } from './connection-dots/index.web';
107
113
  export type { ConnectionDotsProps } from './connection-dots/index.web';
108
114
  export { BenefitRow, BenefitList } from './benefit-list';
109
115
  export type { BenefitRowProps, BenefitListProps } from './benefit-list';
110
116
 
117
+ // Interaction primitives
118
+ export { PressableScale } from './pressable-scale';
119
+ export type { PressableScaleProps } from './pressable-scale';
120
+ export { PressableWithHover } from './pressable-with-hover';
121
+ export type { PressableWithHoverProps } from './pressable-with-hover';
122
+ export { SubtleHover } from './subtle-hover';
123
+ export type { SubtleHoverProps } from './subtle-hover';
124
+
125
+ // Motion presets (Reanimated enter/exit + directional screen transition)
126
+ export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop, ScreenTransition } from './motion';
127
+ export type { ScreenTransitionProps, ScreenTransitionDirection } from './motion';
128
+ export { AnimatedCheck } from './animated-check';
129
+ export type { AnimatedCheckProps, AnimatedCheckRef } from './animated-check';
130
+
111
131
  // Form components
112
132
  export * from './text-field';
113
133
  export * from './segmented-control';
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { Image, View } from 'react-native';
3
+ import type { Meta, StoryObj } from '@storybook/react-vite';
4
+
5
+ import { MediaInsetBorder } from './index';
6
+
7
+ const meta: Meta<typeof MediaInsetBorder> = {
8
+ title: 'Components/MediaInsetBorder',
9
+ component: MediaInsetBorder,
10
+ argTypes: {
11
+ opaque: { control: 'boolean' },
12
+ },
13
+ };
14
+
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof MediaInsetBorder>;
18
+
19
+ const SAMPLE_URI =
20
+ 'https://images.unsplash.com/photo-1526779259212-939e64788e3c?w=400&q=80';
21
+
22
+ export const OverImage: Story = {
23
+ render: (args) => (
24
+ <View style={{ width: 240, height: 160, borderRadius: 8, overflow: 'hidden' }}>
25
+ <Image source={{ uri: SAMPLE_URI }} style={{ width: '100%', height: '100%' }} />
26
+ <MediaInsetBorder {...args} />
27
+ </View>
28
+ ),
29
+ };
30
+
31
+ export const OverFlatSurface: Story = {
32
+ render: (args) => (
33
+ <View
34
+ style={{
35
+ width: 240,
36
+ height: 160,
37
+ borderRadius: 8,
38
+ backgroundColor: '#e5e7eb',
39
+ }}
40
+ >
41
+ <MediaInsetBorder {...args} />
42
+ </View>
43
+ ),
44
+ };
45
+
46
+ export const Opaque: Story = {
47
+ args: { opaque: true },
48
+ render: (args) => (
49
+ <View
50
+ style={{
51
+ width: 240,
52
+ height: 160,
53
+ borderRadius: 8,
54
+ backgroundColor: '#e5e7eb',
55
+ }}
56
+ >
57
+ <MediaInsetBorder {...args} />
58
+ </View>
59
+ ),
60
+ };
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import {
3
+ PixelRatio,
4
+ Platform,
5
+ StyleSheet,
6
+ type StyleProp,
7
+ type ViewStyle,
8
+ } from 'react-native';
9
+
10
+ import { Fill } from '../fill';
11
+ import { borderRadius } from '../styles/tokens';
12
+ import { useTheme } from '../theme';
13
+
14
+ /**
15
+ * A 1px (hairline) inset border reads too heavy on high-DPI web displays, so we
16
+ * drop to 0.5px there. On native the platform hairline is already correct, and
17
+ * `PixelRatio` is only consulted on web.
18
+ */
19
+ const HAIRLINE_WIDTH =
20
+ Platform.OS === 'web' && PixelRatio.get() > 1 ? 0.5 : StyleSheet.hairlineWidth;
21
+
22
+ export interface MediaInsetBorderProps {
23
+ /**
24
+ * Render a solid, full-contrast border instead of the default softened one.
25
+ * Use where the inset border must line up with adjacent opaque borders, such
26
+ * as external link-preview cards.
27
+ */
28
+ opaque?: boolean;
29
+ /**
30
+ * Extra style merged last. Override `borderRadius` here to match the corner
31
+ * radius of the surrounding media box.
32
+ */
33
+ style?: StyleProp<ViewStyle>;
34
+ children?: React.ReactNode;
35
+ }
36
+
37
+ /**
38
+ * A theme-aware hairline border painted just inside a media box (image, avatar,
39
+ * embed) to separate it from the surrounding background. Absolutely positioned
40
+ * to fill its parent (built on {@link Fill}) and non-interactive.
41
+ */
42
+ export function MediaInsetBorder({ opaque, style, children }: MediaInsetBorderProps) {
43
+ const theme = useTheme();
44
+
45
+ return (
46
+ <Fill
47
+ style={[
48
+ styles.base,
49
+ { borderColor: theme.colors.border },
50
+ !opaque && styles.soft,
51
+ style,
52
+ ]}
53
+ >
54
+ {children}
55
+ </Fill>
56
+ );
57
+ }
58
+
59
+ MediaInsetBorder.displayName = 'MediaInsetBorder';
60
+
61
+ const styles = StyleSheet.create({
62
+ base: {
63
+ borderRadius: borderRadius.sm,
64
+ borderWidth: HAIRLINE_WIDTH,
65
+ pointerEvents: 'none',
66
+ },
67
+ // Non-opaque: same border color at reduced opacity so it reads as a subtle,
68
+ // lower-contrast separator rather than a hard edge.
69
+ soft: {
70
+ opacity: 0.6,
71
+ },
72
+ });
@@ -0,0 +1,2 @@
1
+ export { MediaInsetBorder } from './MediaInsetBorder';
2
+ export type { MediaInsetBorderProps } from './MediaInsetBorder';
@@ -0,0 +1,99 @@
1
+ import React, { useState } from 'react';
2
+ import { Pressable, Text, View } from 'react-native';
3
+ import Animated from 'react-native-reanimated';
4
+ import type { Meta, StoryObj } from '@storybook/react-vite';
5
+
6
+ import { ScaleAndFadeIn, ScaleAndFadeOut, ScreenTransition, ShrinkAndPop } from './index';
7
+
8
+ const meta: Meta = {
9
+ title: 'Motion/Presets',
10
+ };
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj;
15
+
16
+ function Toggle({ children }: { children: React.ReactNode }) {
17
+ const [shown, setShown] = useState(true);
18
+ return (
19
+ <View style={{ gap: 16, alignItems: 'flex-start' }}>
20
+ <Pressable
21
+ accessibilityLabel="Toggle"
22
+ onPress={() => setShown((v) => !v)}
23
+ style={{
24
+ paddingVertical: 8,
25
+ paddingHorizontal: 16,
26
+ borderRadius: 8,
27
+ backgroundColor: '#111827',
28
+ }}
29
+ >
30
+ <Text style={{ color: 'white', fontWeight: '600' }}>{shown ? 'Hide' : 'Show'}</Text>
31
+ </Pressable>
32
+ <View style={{ height: 96, justifyContent: 'center' }}>{shown ? children : null}</View>
33
+ </View>
34
+ );
35
+ }
36
+
37
+ const card = {
38
+ width: 160,
39
+ height: 72,
40
+ borderRadius: 16,
41
+ backgroundColor: '#6366f1',
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ } as const;
45
+
46
+ export const ScaleAndFade: Story = {
47
+ render: () => (
48
+ <Toggle>
49
+ <Animated.View entering={ScaleAndFadeIn} exiting={ScaleAndFadeOut} style={card}>
50
+ <Text style={{ color: 'white', fontWeight: '700' }}>Scale + Fade</Text>
51
+ </Animated.View>
52
+ </Toggle>
53
+ ),
54
+ };
55
+
56
+ export const Pop: Story = {
57
+ render: () => (
58
+ <Toggle>
59
+ <Animated.View entering={ScaleAndFadeIn} exiting={ShrinkAndPop} style={card}>
60
+ <Text style={{ color: 'white', fontWeight: '700' }}>Shrink + Pop</Text>
61
+ </Animated.View>
62
+ </Toggle>
63
+ ),
64
+ };
65
+
66
+ export const Transition: Story = {
67
+ render: () => {
68
+ function Demo() {
69
+ const [forward, setForward] = useState(true);
70
+ return (
71
+ <View style={{ gap: 16, alignItems: 'flex-start' }}>
72
+ <Pressable
73
+ accessibilityLabel="Swap direction"
74
+ onPress={() => setForward((v) => !v)}
75
+ style={{
76
+ paddingVertical: 8,
77
+ paddingHorizontal: 16,
78
+ borderRadius: 8,
79
+ backgroundColor: '#111827',
80
+ }}
81
+ >
82
+ <Text style={{ color: 'white', fontWeight: '600' }}>
83
+ Direction: {forward ? 'forward' : 'backward'}
84
+ </Text>
85
+ </Pressable>
86
+ <ScreenTransition
87
+ key={forward ? 'forward' : 'backward'}
88
+ direction={forward ? 'forward' : 'backward'}
89
+ enabledWeb
90
+ style={card}
91
+ >
92
+ <Text style={{ color: 'white', fontWeight: '700' }}>Screen</Text>
93
+ </ScreenTransition>
94
+ </View>
95
+ );
96
+ }
97
+ return <Demo />;
98
+ },
99
+ };
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import { Platform, type StyleProp, type ViewStyle } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ FadeIn,
6
+ FadeOut,
7
+ SlideInLeft,
8
+ SlideInRight,
9
+ } from 'react-native-reanimated';
10
+
11
+ /** Direction the incoming screen travels. */
12
+ export type ScreenTransitionDirection = 'forward' | 'backward';
13
+
14
+ export interface ScreenTransitionProps {
15
+ /**
16
+ * `'forward'` slides the new screen in from the right, `'backward'` from the
17
+ * left — matching a push / pop navigation gesture.
18
+ */
19
+ direction: ScreenTransitionDirection;
20
+ /**
21
+ * Enable the transition on web. Off by default: Reanimated layout animations
22
+ * are fragile across web bundlers, so web opts in explicitly and gets a
23
+ * lightweight cross-fade rather than a slide.
24
+ */
25
+ enabledWeb?: boolean;
26
+ style?: StyleProp<ViewStyle>;
27
+ children?: React.ReactNode;
28
+ }
29
+
30
+ /** CSS-smooth deceleration for the native slide. */
31
+ const SLIDE_EASING = Easing.out(Easing.exp);
32
+ /** Web cross-fade duration (ms) — "totally vibes based", per the Bluesky original. */
33
+ const WEB_FADE_DURATION = 90;
34
+
35
+ /**
36
+ * Wraps `children` in a Reanimated layout-animated view that slides in
37
+ * directionally on native and (optionally) cross-fades on web. Ported from
38
+ * Bluesky's `ScreenTransition`.
39
+ *
40
+ * On web the slide is intentionally dropped: layout animations there are
41
+ * unreliable, so `enabledWeb` gates a simple fade and the default is no
42
+ * transition at all.
43
+ */
44
+ export function ScreenTransition({
45
+ direction,
46
+ enabledWeb,
47
+ style,
48
+ children,
49
+ }: ScreenTransitionProps) {
50
+ const isWeb = Platform.OS === 'web';
51
+
52
+ const entering = isWeb
53
+ ? enabledWeb
54
+ ? FadeIn.duration(WEB_FADE_DURATION)
55
+ : undefined
56
+ : (direction === 'forward' ? SlideInRight : SlideInLeft).easing(SLIDE_EASING);
57
+
58
+ const exiting = isWeb
59
+ ? enabledWeb
60
+ ? FadeOut.duration(WEB_FADE_DURATION)
61
+ : undefined
62
+ : FadeOut.duration(WEB_FADE_DURATION);
63
+
64
+ return (
65
+ <Animated.View entering={entering} exiting={exiting} style={style}>
66
+ {children}
67
+ </Animated.View>
68
+ );
69
+ }
70
+
71
+ ScreenTransition.displayName = 'ScreenTransition';
@@ -0,0 +1,3 @@
1
+ export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop } from './motion';
2
+ export { ScreenTransition } from './ScreenTransition';
3
+ export type { ScreenTransitionProps, ScreenTransitionDirection } from './ScreenTransition';
@@ -0,0 +1,75 @@
1
+ import {
2
+ withDelay,
3
+ withSequence,
4
+ withTiming,
5
+ type LayoutAnimation,
6
+ } from 'react-native-reanimated';
7
+
8
+ /**
9
+ * Reanimated custom layout-animation presets. Each is a worklet builder
10
+ * compatible with an `Animated.View`'s `entering` / `exiting` prop:
11
+ *
12
+ * ```tsx
13
+ * <Animated.View entering={ScaleAndFadeIn} exiting={ScaleAndFadeOut} />
14
+ * ```
15
+ *
16
+ * Ported from Bluesky's custom-animation set. The `'worklet'` directive is
17
+ * preserved through Bloom's build; the consuming app's Metro + reanimated Babel
18
+ * plugin transforms it so the animation runs on the UI thread (same path the
19
+ * existing `bottom-sheet` gesture worklets rely on).
20
+ */
21
+
22
+ /** Entering: fade in while growing from 70% to full size. */
23
+ export const ScaleAndFadeIn: () => LayoutAnimation = () => {
24
+ 'worklet';
25
+ return {
26
+ animations: {
27
+ opacity: withTiming(1),
28
+ transform: [{ scale: withTiming(1) }],
29
+ },
30
+ initialValues: {
31
+ opacity: 0,
32
+ transform: [{ scale: 0.7 }],
33
+ },
34
+ };
35
+ };
36
+
37
+ /** Exiting counterpart to {@link ScaleAndFadeIn}: fade out while shrinking to 70%. */
38
+ export const ScaleAndFadeOut: () => LayoutAnimation = () => {
39
+ 'worklet';
40
+ return {
41
+ animations: {
42
+ opacity: withTiming(0),
43
+ transform: [{ scale: withTiming(0.7) }],
44
+ },
45
+ initialValues: {
46
+ opacity: 1,
47
+ transform: [{ scale: 1 }],
48
+ },
49
+ };
50
+ };
51
+
52
+ /**
53
+ * Exiting with a brief "pop": the scale dips to 70%, overshoots to 110%, and the
54
+ * element fades out. Good for confirming a tapped or removed item.
55
+ */
56
+ export const ShrinkAndPop: () => LayoutAnimation = () => {
57
+ 'worklet';
58
+ return {
59
+ animations: {
60
+ opacity: withDelay(125, withTiming(0, { duration: 125 })),
61
+ transform: [
62
+ {
63
+ scale: withSequence(
64
+ withTiming(0.7, { duration: 75 }),
65
+ withTiming(1.1, { duration: 150 }),
66
+ ),
67
+ },
68
+ ],
69
+ },
70
+ initialValues: {
71
+ opacity: 1,
72
+ transform: [{ scale: 1 }],
73
+ },
74
+ };
75
+ };