@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,48 @@
1
+ import React from 'react';
2
+ import { Text } from 'react-native';
3
+ import { fireEvent, render } from '@testing-library/react-native';
4
+
5
+ import { PressableWithHover } from '../pressable-with-hover';
6
+
7
+ describe('PressableWithHover', () => {
8
+ it('carries a displayName', () => {
9
+ expect(PressableWithHover.displayName).toBe('PressableWithHover');
10
+ });
11
+
12
+ it('renders its children', () => {
13
+ const { getByText } = render(
14
+ <PressableWithHover
15
+ accessibilityLabel="Row"
16
+ style={{ backgroundColor: 'white' }}
17
+ hoverStyle={{ backgroundColor: 'gray' }}
18
+ >
19
+ <Text>Row</Text>
20
+ </PressableWithHover>,
21
+ );
22
+ expect(getByText('Row')).toBeTruthy();
23
+ });
24
+
25
+ it('merges hoverStyle only while hovered', () => {
26
+ const { getByTestId } = render(
27
+ <PressableWithHover
28
+ testID="row"
29
+ style={{ backgroundColor: 'white' }}
30
+ hoverStyle={{ backgroundColor: 'gray' }}
31
+ >
32
+ <Text>Row</Text>
33
+ </PressableWithHover>,
34
+ );
35
+
36
+ const row = getByTestId('row');
37
+ expect(row.props.style).toEqual({ backgroundColor: 'white' });
38
+
39
+ fireEvent(row, 'hoverIn');
40
+ expect(getByTestId('row').props.style).toEqual([
41
+ { backgroundColor: 'white' },
42
+ { backgroundColor: 'gray' },
43
+ ]);
44
+
45
+ fireEvent(row, 'hoverOut');
46
+ expect(getByTestId('row').props.style).toEqual({ backgroundColor: 'white' });
47
+ });
48
+ });
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+
4
+ import { BloomThemeProvider } from '../theme/BloomThemeProvider';
5
+ import { SubtleHover } from '../subtle-hover';
6
+
7
+ // Default test Platform.OS is 'ios' (see __mocks__/react-native.ts), so the
8
+ // web-only overlay is suppressed unless `native` is opted in.
9
+ function renderWithTheme(ui: React.ReactElement) {
10
+ return render(
11
+ <BloomThemeProvider mode="light" colorPreset="teal">
12
+ {ui}
13
+ </BloomThemeProvider>,
14
+ );
15
+ }
16
+
17
+ describe('SubtleHover', () => {
18
+ it('carries a displayName', () => {
19
+ expect(SubtleHover.displayName).toBe('SubtleHover');
20
+ });
21
+
22
+ // `renderWithTheme` wraps the subject in the provider's flex View, so the
23
+ // overlay's presence is read from that wrapper's `children` rather than the
24
+ // root being null.
25
+ it('renders nothing on native by default', () => {
26
+ const tree = renderWithTheme(<SubtleHover hover />).toJSON();
27
+ expect(tree).not.toBeNull();
28
+ expect(tree?.children).toBeNull();
29
+ });
30
+
31
+ it('renders the overlay on native when opted in', () => {
32
+ const tree = renderWithTheme(<SubtleHover hover native />).toJSON();
33
+ expect(tree?.children).not.toBeNull();
34
+ });
35
+
36
+ it('accepts an opacity override and a style', () => {
37
+ const tree = renderWithTheme(
38
+ <SubtleHover hover native opacity={0.9} style={{ borderRadius: 12 }} />,
39
+ ).toJSON();
40
+ expect(tree?.children).not.toBeNull();
41
+ });
42
+ });
@@ -0,0 +1,31 @@
1
+ import { buildTheme } from '../theme/build-theme';
2
+ import { THEME_GRADIENTS } from '../theme/gradients';
3
+
4
+ describe('theme gradients', () => {
5
+ it('are exposed on the built theme', () => {
6
+ const theme = buildTheme('teal', 'light');
7
+ expect(theme.gradients).toBe(THEME_GRADIENTS);
8
+ expect(Object.keys(theme.gradients).length).toBeGreaterThan(0);
9
+ });
10
+
11
+ it('every gradient has ordered [offset, color] stops', () => {
12
+ for (const [name, gradient] of Object.entries(THEME_GRADIENTS)) {
13
+ expect(name).toBeTruthy();
14
+ expect(Array.isArray(gradient.values)).toBe(true);
15
+ expect(gradient.values.length).toBeGreaterThanOrEqual(2);
16
+
17
+ for (const [offset, color] of gradient.values) {
18
+ expect(typeof offset).toBe('number');
19
+ expect(offset).toBeGreaterThanOrEqual(0);
20
+ expect(offset).toBeLessThanOrEqual(1);
21
+ expect(color).toMatch(/^#[0-9a-f]{6}$/i);
22
+ }
23
+ }
24
+ });
25
+
26
+ it('are identical across light and dark mode (theme-agnostic)', () => {
27
+ expect(buildTheme('teal', 'dark').gradients).toBe(
28
+ buildTheme('teal', 'light').gradients,
29
+ );
30
+ });
31
+ });
@@ -5,6 +5,7 @@ import {
5
5
  hexToAppColorName,
6
6
  } from '../theme/color-presets';
7
7
  import { buildTheme } from '../theme/build-theme';
8
+ import { THEME_GRADIENTS } from '../theme/gradients';
8
9
  import type { Theme, ThemeColors, ThemeMode } from '../theme/types';
9
10
 
10
11
  describe('Theme system', () => {
@@ -111,6 +112,7 @@ describe('Theme system', () => {
111
112
  isDark: false,
112
113
  isLight: true,
113
114
  colors: createMockColors(),
115
+ gradients: THEME_GRADIENTS,
114
116
  };
115
117
  expect(lightTheme.isDark).toBe(false);
116
118
  expect(lightTheme.isLight).toBe(true);
@@ -120,6 +122,7 @@ describe('Theme system', () => {
120
122
  isDark: true,
121
123
  isLight: false,
122
124
  colors: createMockColors(),
125
+ gradients: THEME_GRADIENTS,
123
126
  };
124
127
  expect(darkTheme.isDark).toBe(true);
125
128
  expect(darkTheme.isLight).toBe(false);
@@ -0,0 +1,58 @@
1
+ import { renderHook } from '@testing-library/react-native';
2
+
3
+ import { useGutters } from '../hooks/useGutters';
4
+ import { space } from '../styles/tokens';
5
+
6
+ // The RN mock reports a 375px-wide window (below the `sm` 640 breakpoint), so
7
+ // every case resolves to the `base` tier.
8
+
9
+ describe('useGutters', () => {
10
+ it('expands a single size to all four edges', () => {
11
+ const { result } = renderHook(() => useGutters(['base']));
12
+ expect(result.current).toEqual({
13
+ paddingTop: space.lg,
14
+ paddingRight: space.lg,
15
+ paddingBottom: space.lg,
16
+ paddingLeft: space.lg,
17
+ });
18
+ });
19
+
20
+ it('treats a two-value list as [vertical, horizontal]', () => {
21
+ const { result } = renderHook(() => useGutters([0, 'wide']));
22
+ expect(result.current).toEqual({
23
+ paddingTop: 0,
24
+ paddingRight: space.xl,
25
+ paddingBottom: 0,
26
+ paddingLeft: space.xl,
27
+ });
28
+ });
29
+
30
+ it('maps a four-value list to [top, right, bottom, left]', () => {
31
+ const { result } = renderHook(() =>
32
+ useGutters(['compact', 'base', 0, 'wide']),
33
+ );
34
+ expect(result.current).toEqual({
35
+ paddingTop: space.sm,
36
+ paddingRight: space.lg,
37
+ paddingBottom: 0,
38
+ paddingLeft: space.xl,
39
+ });
40
+ });
41
+
42
+ it('returns zero padding for a `0` gutter', () => {
43
+ const { result } = renderHook(() => useGutters([0]));
44
+ expect(result.current).toEqual({
45
+ paddingTop: 0,
46
+ paddingRight: 0,
47
+ paddingBottom: 0,
48
+ paddingLeft: 0,
49
+ });
50
+ });
51
+
52
+ it('keeps a stable object identity across re-renders at the same width', () => {
53
+ const { result, rerender } = renderHook(() => useGutters(['base']));
54
+ const first = result.current;
55
+ rerender({});
56
+ expect(result.current).toBe(first);
57
+ });
58
+ });
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { act, renderHook } from '@testing-library/react-native';
4
+ import { ImpactFeedbackStyle, impactAsync } from 'expo-haptics';
5
+
6
+ import { BloomHapticsProvider, useHaptics } from '../hooks/useHaptics';
7
+
8
+ const originalOS = Platform.OS;
9
+
10
+ afterEach(() => {
11
+ Platform.OS = originalOS;
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ describe('useHaptics', () => {
16
+ it('returns a callback', () => {
17
+ const { result } = renderHook(() => useHaptics());
18
+ expect(typeof result.current).toBe('function');
19
+ });
20
+
21
+ it('fires the mapped impact style on iOS', () => {
22
+ Platform.OS = 'ios';
23
+ const { result } = renderHook(() => useHaptics());
24
+ act(() => result.current('heavy'));
25
+ expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Heavy);
26
+ });
27
+
28
+ it('defaults to the light impact', () => {
29
+ Platform.OS = 'ios';
30
+ const { result } = renderHook(() => useHaptics());
31
+ act(() => result.current());
32
+ expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Light);
33
+ });
34
+
35
+ it('clamps to the light impact on Android regardless of strength', () => {
36
+ Platform.OS = 'android';
37
+ const { result } = renderHook(() => useHaptics());
38
+ act(() => result.current('heavy'));
39
+ expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Light);
40
+ });
41
+
42
+ it('no-ops on web', () => {
43
+ Platform.OS = 'web';
44
+ const { result } = renderHook(() => useHaptics());
45
+ act(() => result.current('medium'));
46
+ expect(impactAsync).not.toHaveBeenCalled();
47
+ });
48
+
49
+ it('no-ops when disabled via BloomHapticsProvider', () => {
50
+ Platform.OS = 'ios';
51
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
52
+ <BloomHapticsProvider enabled={false}>{children}</BloomHapticsProvider>
53
+ );
54
+ const { result } = renderHook(() => useHaptics(), { wrapper });
55
+ act(() => result.current('medium'));
56
+ expect(impactAsync).not.toHaveBeenCalled();
57
+ });
58
+ });
@@ -0,0 +1,27 @@
1
+ import { act, renderHook } from '@testing-library/react-native';
2
+
3
+ import { useInteractionState } from '../hooks/useInteractionState';
4
+
5
+ describe('useInteractionState', () => {
6
+ it('starts inactive and toggles on onIn/onOut', () => {
7
+ const { result } = renderHook(() => useInteractionState());
8
+ expect(result.current.state).toBe(false);
9
+
10
+ act(() => result.current.onIn());
11
+ expect(result.current.state).toBe(true);
12
+
13
+ act(() => result.current.onOut());
14
+ expect(result.current.state).toBe(false);
15
+ });
16
+
17
+ it('keeps handler identities stable across renders', () => {
18
+ const { result, rerender } = renderHook(() => useInteractionState());
19
+ const firstOnIn = result.current.onIn;
20
+ const firstOnOut = result.current.onOut;
21
+
22
+ rerender({});
23
+
24
+ expect(result.current.onIn).toBe(firstOnIn);
25
+ expect(result.current.onOut).toBe(firstOnOut);
26
+ });
27
+ });
@@ -0,0 +1,60 @@
1
+ import React, { useRef } from 'react';
2
+ import { Pressable, Text, View } from 'react-native';
3
+ import type { Meta, StoryObj } from '@storybook/react-vite';
4
+
5
+ import { AnimatedCheck, type AnimatedCheckRef } from './index';
6
+
7
+ const meta: Meta<typeof AnimatedCheck> = {
8
+ title: 'Components/AnimatedCheck',
9
+ component: AnimatedCheck,
10
+ argTypes: {
11
+ size: { control: { type: 'range', min: 16, max: 128, step: 4 } },
12
+ color: { control: 'color' },
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof AnimatedCheck>;
19
+
20
+ export const Playable: Story = {
21
+ args: { size: 64 },
22
+ render: (args) => {
23
+ function Demo() {
24
+ const ref = useRef<AnimatedCheckRef>(null);
25
+ return (
26
+ <View style={{ gap: 16, alignItems: 'center' }}>
27
+ <AnimatedCheck ref={ref} {...args} />
28
+ <Pressable
29
+ accessibilityLabel="Play"
30
+ onPress={() => ref.current?.play()}
31
+ style={{
32
+ paddingVertical: 8,
33
+ paddingHorizontal: 16,
34
+ borderRadius: 8,
35
+ backgroundColor: '#111827',
36
+ }}
37
+ >
38
+ <Text style={{ color: 'white', fontWeight: '600' }}>Play</Text>
39
+ </Pressable>
40
+ </View>
41
+ );
42
+ }
43
+ return <Demo />;
44
+ },
45
+ };
46
+
47
+ export const CustomColor: Story = {
48
+ args: { size: 80, color: '#6366f1' },
49
+ render: (args) => {
50
+ function Demo() {
51
+ const ref = useRef<AnimatedCheckRef>(null);
52
+ return (
53
+ <Pressable accessibilityLabel="Replay" onPress={() => ref.current?.play()}>
54
+ <AnimatedCheck ref={ref} {...args} />
55
+ </Pressable>
56
+ );
57
+ }
58
+ return <Demo />;
59
+ },
60
+ };
@@ -0,0 +1,173 @@
1
+ import React, { forwardRef, useCallback, useImperativeHandle, useMemo } from 'react';
2
+ import { StyleSheet, Text } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ useAnimatedProps,
6
+ useSharedValue,
7
+ withDelay,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+
11
+ import { getSvgModule } from '../avatar/svg-module';
12
+ import { useTheme } from '../theme';
13
+
14
+ /** The resolved `react-native-svg` module (non-null branch). */
15
+ type SvgModule = NonNullable<ReturnType<typeof getSvgModule>>;
16
+
17
+ /** Square viewBox the ring + check are drawn on. */
18
+ const VIEW_BOX = 52;
19
+ /** Ring radius within the viewBox. */
20
+ const CIRCLE_RADIUS = 24;
21
+ /** Ring circumference (2·π·r ≈ 150.8) rounded up so the dash fully hides it. */
22
+ const CIRCLE_LENGTH = 166;
23
+ /** Approximate path length of the check stroke. */
24
+ const CHECK_LENGTH = 48;
25
+ /** The check glyph, drawn on the 52×52 canvas. */
26
+ const CHECK_PATH = 'M14.1 27.2l7.1 7.2 16.7-16.8';
27
+ const STROKE_WIDTH = 4;
28
+ /** Ring draw-on duration (ms). */
29
+ const CIRCLE_DURATION = 500;
30
+ /** Check draw-on duration (ms), started once the ring completes. */
31
+ const CHECK_DURATION = 300;
32
+
33
+ export interface AnimatedCheckRef {
34
+ /** Replays the draw-on animation from the start. */
35
+ play: () => void;
36
+ }
37
+
38
+ export interface AnimatedCheckProps {
39
+ /** Width and height in px. Defaults to `24`. */
40
+ size?: number;
41
+ /** Stroke color of the ring and check. Defaults to the theme success color. */
42
+ color?: string;
43
+ }
44
+
45
+ // `Animated.createAnimatedComponent` must be called once per underlying
46
+ // component (calling it per-render would remount and break the animation).
47
+ // react-native-svg is a lazy/optional peer, so we build the animated wrappers
48
+ // the first time it resolves and cache them at module scope.
49
+ function buildAnimatedSvg(svg: SvgModule) {
50
+ return {
51
+ Circle: Animated.createAnimatedComponent(svg.Circle),
52
+ Path: Animated.createAnimatedComponent(svg.Path),
53
+ };
54
+ }
55
+
56
+ let animatedSvgCache: ReturnType<typeof buildAnimatedSvg> | null = null;
57
+
58
+ function getAnimatedSvg(svg: SvgModule): ReturnType<typeof buildAnimatedSvg> {
59
+ if (!animatedSvgCache) animatedSvgCache = buildAnimatedSvg(svg);
60
+ return animatedSvgCache;
61
+ }
62
+
63
+ interface AnimatedCheckSvgProps {
64
+ size: number;
65
+ color: string;
66
+ svg: SvgModule;
67
+ }
68
+
69
+ /** The animated SVG implementation, rendered only when react-native-svg exists. */
70
+ const AnimatedCheckSvg = forwardRef<AnimatedCheckRef, AnimatedCheckSvgProps>(
71
+ function AnimatedCheckSvg({ size, color, svg }, ref) {
72
+ const { default: Svg } = svg;
73
+ const { Circle, Path } = getAnimatedSvg(svg);
74
+
75
+ const circleProgress = useSharedValue(0);
76
+ const checkProgress = useSharedValue(0);
77
+
78
+ const circleAnimatedProps = useAnimatedProps(() => ({
79
+ strokeDashoffset: CIRCLE_LENGTH - circleProgress.value * CIRCLE_LENGTH,
80
+ }));
81
+ const checkAnimatedProps = useAnimatedProps(() => ({
82
+ strokeDashoffset: CHECK_LENGTH - CHECK_LENGTH * checkProgress.value,
83
+ }));
84
+
85
+ const play = useCallback(() => {
86
+ circleProgress.value = 0;
87
+ checkProgress.value = 0;
88
+ circleProgress.value = withTiming(1, {
89
+ duration: CIRCLE_DURATION,
90
+ easing: Easing.linear,
91
+ });
92
+ checkProgress.value = withDelay(
93
+ CIRCLE_DURATION,
94
+ withTiming(1, { duration: CHECK_DURATION, easing: Easing.linear }),
95
+ );
96
+ }, [circleProgress, checkProgress]);
97
+
98
+ useImperativeHandle(ref, () => ({ play }), [play]);
99
+
100
+ return (
101
+ <Svg fill="none" viewBox={`0 0 ${VIEW_BOX} ${VIEW_BOX}`} width={size} height={size}>
102
+ <Circle
103
+ animatedProps={circleAnimatedProps}
104
+ cx={VIEW_BOX / 2}
105
+ cy={VIEW_BOX / 2}
106
+ r={CIRCLE_RADIUS}
107
+ fill="none"
108
+ stroke={color}
109
+ strokeWidth={STROKE_WIDTH}
110
+ strokeDasharray={CIRCLE_LENGTH}
111
+ />
112
+ <Path
113
+ animatedProps={checkAnimatedProps}
114
+ d={CHECK_PATH}
115
+ stroke={color}
116
+ strokeWidth={STROKE_WIDTH}
117
+ strokeDasharray={CHECK_LENGTH}
118
+ />
119
+ </Svg>
120
+ );
121
+ },
122
+ );
123
+
124
+ interface StaticCheckProps {
125
+ size: number;
126
+ color: string;
127
+ }
128
+
129
+ /**
130
+ * Static fallback rendered when react-native-svg is unavailable (e.g. a web
131
+ * bundle that omits the optional peer). Draws a plain Unicode check so the
132
+ * component still communicates "done"; `play()` is a no-op.
133
+ */
134
+ const StaticCheck = forwardRef<AnimatedCheckRef, StaticCheckProps>(
135
+ function StaticCheck({ size, color }, ref) {
136
+ useImperativeHandle(ref, () => ({ play: () => undefined }), []);
137
+ return (
138
+ <Text
139
+ accessibilityElementsHidden
140
+ style={[styles.staticCheck, { width: size, fontSize: size * 0.8, lineHeight: size, color }]}
141
+ >
142
+ {'✓'}
143
+ </Text>
144
+ );
145
+ },
146
+ );
147
+
148
+ /**
149
+ * A draw-on checkmark: a ring strokes on, then the check strokes in. Expose a
150
+ * ref and call `ref.current?.play()` to (re)play — e.g. on a successful submit.
151
+ * Ported from Bluesky's `AnimatedCheck`.
152
+ */
153
+ export const AnimatedCheck = forwardRef<AnimatedCheckRef, AnimatedCheckProps>(
154
+ function AnimatedCheck({ size = 24, color }, ref) {
155
+ const theme = useTheme();
156
+ const resolvedColor = color ?? theme.colors.success;
157
+ const svg = useMemo(() => getSvgModule(), []);
158
+
159
+ if (!svg) {
160
+ return <StaticCheck ref={ref} size={size} color={resolvedColor} />;
161
+ }
162
+ return <AnimatedCheckSvg ref={ref} size={size} color={resolvedColor} svg={svg} />;
163
+ },
164
+ );
165
+
166
+ AnimatedCheck.displayName = 'AnimatedCheck';
167
+
168
+ const styles = StyleSheet.create({
169
+ staticCheck: {
170
+ textAlign: 'center',
171
+ fontWeight: '700',
172
+ },
173
+ });
@@ -0,0 +1,2 @@
1
+ export { AnimatedCheck } from './AnimatedCheck';
2
+ export type { AnimatedCheckProps, AnimatedCheckRef } from './AnimatedCheck';
@@ -19,6 +19,7 @@ const meta: Meta<typeof Avatar> = {
19
19
  verified: { control: 'boolean' },
20
20
  live: { control: 'boolean' },
21
21
  hideLiveBadge: { control: 'boolean' },
22
+ ring: { control: 'object' },
22
23
  },
23
24
  };
24
25
 
@@ -29,6 +30,13 @@ type Story = StoryObj<typeof Avatar>;
29
30
  const SAMPLE_URI =
30
31
  'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop';
31
32
 
33
+ // Sample Instagram-stories-style gradient, defined locally in the story only —
34
+ // Bloom ships no such palette (it stays brand-neutral). Consumers supply their
35
+ // own ring colors via the `ring` prop.
36
+ const STORY_GRADIENT = ['#F58529', '#DD2A7B', '#8134AF', '#515BD4'];
37
+ // Muted gray used by IG for an already-seen story.
38
+ const SEEN_GRAY = '#C7C7CC';
39
+
32
40
  export const Basic: Story = {
33
41
  args: { size: 64, name: 'Nate Isern' },
34
42
  };
@@ -118,3 +126,90 @@ export const LiveGallery: Story = {
118
126
  ),
119
127
  name: 'Live (gallery)',
120
128
  };
129
+
130
+ export const GradientRing: Story = {
131
+ args: {
132
+ size: 72,
133
+ uri: SAMPLE_URI,
134
+ ring: { colors: STORY_GRADIENT, width: 3, gap: 3 },
135
+ },
136
+ name: 'Gradient ring (stories)',
137
+ };
138
+
139
+ export const SeenVsUnseen: Story = {
140
+ render: () => (
141
+ <View style={{ flexDirection: 'row', gap: 20, alignItems: 'center' }}>
142
+ {/* Unseen → gradient ring. */}
143
+ <Avatar
144
+ size={72}
145
+ uri={SAMPLE_URI}
146
+ ring={{ colors: STORY_GRADIENT, width: 3, gap: 3 }}
147
+ />
148
+ {/* Seen → muted solid gray ring. */}
149
+ <Avatar
150
+ size={72}
151
+ uri={SAMPLE_URI}
152
+ ring={{ colors: SEEN_GRAY, width: 3, gap: 3 }}
153
+ />
154
+ </View>
155
+ ),
156
+ name: 'Seen vs unseen',
157
+ };
158
+
159
+ export const RingWithGap: Story = {
160
+ render: () => (
161
+ <View style={{ flexDirection: 'row', gap: 24, alignItems: 'center' }}>
162
+ {/* gap 0 → overlays the edge, footprint unchanged. */}
163
+ <Avatar size={72} uri={SAMPLE_URI} ring={{ colors: STORY_GRADIENT, width: 3, gap: 0 }} />
164
+ {/* gap 4 → sits outside the avatar, footprint grows. */}
165
+ <Avatar size={72} uri={SAMPLE_URI} ring={{ colors: STORY_GRADIENT, width: 3, gap: 4 }} />
166
+ {/* gap 8 → wider clearance. */}
167
+ <Avatar size={72} uri={SAMPLE_URI} ring={{ colors: STORY_GRADIENT, width: 4, gap: 8 }} />
168
+ </View>
169
+ ),
170
+ name: 'Ring with gap',
171
+ };
172
+
173
+ export const RingSquircle: Story = {
174
+ args: {
175
+ size: 72,
176
+ uri: SAMPLE_URI,
177
+ shape: 'squircle',
178
+ ring: { colors: STORY_GRADIENT, width: 3, gap: 3 },
179
+ },
180
+ name: 'Gradient ring (squircle)',
181
+ };
182
+
183
+ export const SolidRing: Story = {
184
+ render: () => (
185
+ <View style={{ flexDirection: 'row', gap: 20, alignItems: 'center' }}>
186
+ <Avatar size={72} uri={SAMPLE_URI} ring={{ colors: '#1A73E8', width: 3 }} />
187
+ <Avatar size={72} uri={SAMPLE_URI} shape="squircle" ring={{ colors: '#1A73E8', width: 3 }} />
188
+ <Avatar size={72} uri={SAMPLE_URI} ring={{ colors: '#1A73E8', width: 3, gap: 4 }} />
189
+ </View>
190
+ ),
191
+ name: 'Solid ring',
192
+ };
193
+
194
+ export const GradientDirections: Story = {
195
+ render: () => (
196
+ <View style={{ flexDirection: 'row', gap: 20, alignItems: 'center' }}>
197
+ <Avatar
198
+ size={72}
199
+ uri={SAMPLE_URI}
200
+ ring={{ colors: STORY_GRADIENT, width: 3, gap: 3, gradientDirection: 'diagonal' }}
201
+ />
202
+ <Avatar
203
+ size={72}
204
+ uri={SAMPLE_URI}
205
+ ring={{ colors: STORY_GRADIENT, width: 3, gap: 3, gradientDirection: 'horizontal' }}
206
+ />
207
+ <Avatar
208
+ size={72}
209
+ uri={SAMPLE_URI}
210
+ ring={{ colors: STORY_GRADIENT, width: 3, gap: 3, gradientDirection: 'vertical' }}
211
+ />
212
+ </View>
213
+ ),
214
+ name: 'Gradient directions',
215
+ };