@retray-dev/ui-kit 6.1.0 → 7.0.1

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 (321) hide show
  1. package/COMPONENTS.md +447 -13
  2. package/EXAMPLES.md +248 -0
  3. package/README.md +11 -10
  4. package/dist/Accordion.d.mts +28 -0
  5. package/dist/Accordion.d.ts +28 -0
  6. package/dist/Accordion.js +340 -0
  7. package/dist/Accordion.mjs +6 -0
  8. package/dist/AlertBanner.d.mts +16 -0
  9. package/dist/AlertBanner.d.ts +16 -0
  10. package/dist/AlertBanner.js +247 -0
  11. package/dist/AlertBanner.mjs +5 -0
  12. package/dist/Avatar.d.mts +20 -0
  13. package/dist/Avatar.d.ts +20 -0
  14. package/dist/Avatar.js +234 -0
  15. package/dist/Avatar.mjs +3 -0
  16. package/dist/Badge.d.mts +26 -0
  17. package/dist/Badge.d.ts +26 -0
  18. package/dist/Badge.js +247 -0
  19. package/dist/Badge.mjs +4 -0
  20. package/dist/Button.d.mts +25 -0
  21. package/dist/Button.d.ts +25 -0
  22. package/dist/Button.js +414 -0
  23. package/dist/Button.mjs +8 -0
  24. package/dist/ButtonGroup.d.mts +26 -0
  25. package/dist/ButtonGroup.d.ts +26 -0
  26. package/dist/ButtonGroup.js +52 -0
  27. package/dist/ButtonGroup.mjs +2 -0
  28. package/dist/Card.d.mts +39 -0
  29. package/dist/Card.d.ts +39 -0
  30. package/dist/Card.js +329 -0
  31. package/dist/Card.mjs +7 -0
  32. package/dist/CategoryStrip.d.mts +26 -0
  33. package/dist/CategoryStrip.d.ts +26 -0
  34. package/dist/CategoryStrip.js +396 -0
  35. package/dist/CategoryStrip.mjs +9 -0
  36. package/dist/Checkbox.d.mts +14 -0
  37. package/dist/Checkbox.d.ts +14 -0
  38. package/dist/Checkbox.js +304 -0
  39. package/dist/Checkbox.mjs +7 -0
  40. package/dist/Chip.d.mts +31 -0
  41. package/dist/Chip.d.ts +31 -0
  42. package/dist/Chip.js +370 -0
  43. package/dist/Chip.mjs +8 -0
  44. package/dist/ConfirmDialog.d.mts +15 -0
  45. package/dist/ConfirmDialog.d.ts +15 -0
  46. package/dist/ConfirmDialog.js +530 -0
  47. package/dist/ConfirmDialog.mjs +9 -0
  48. package/dist/CurrencyDisplay.d.mts +24 -0
  49. package/dist/CurrencyDisplay.d.ts +24 -0
  50. package/dist/CurrencyDisplay.js +189 -0
  51. package/dist/CurrencyDisplay.mjs +3 -0
  52. package/dist/CurrencyInput.d.mts +26 -0
  53. package/dist/CurrencyInput.d.ts +26 -0
  54. package/dist/CurrencyInput.js +404 -0
  55. package/dist/CurrencyInput.mjs +7 -0
  56. package/dist/DetailRow.d.mts +32 -0
  57. package/dist/DetailRow.d.ts +32 -0
  58. package/dist/DetailRow.js +275 -0
  59. package/dist/DetailRow.mjs +4 -0
  60. package/dist/EmptyState.d.mts +27 -0
  61. package/dist/EmptyState.d.ts +27 -0
  62. package/dist/EmptyState.js +503 -0
  63. package/dist/EmptyState.mjs +9 -0
  64. package/dist/Form.d.mts +52 -0
  65. package/dist/Form.d.ts +52 -0
  66. package/dist/Form.js +204 -0
  67. package/dist/Form.mjs +3 -0
  68. package/dist/IconButton.d.mts +22 -0
  69. package/dist/IconButton.d.ts +22 -0
  70. package/dist/IconButton.js +383 -0
  71. package/dist/IconButton.mjs +7 -0
  72. package/dist/Input.d.mts +23 -0
  73. package/dist/Input.d.ts +23 -0
  74. package/dist/Input.js +351 -0
  75. package/dist/Input.mjs +6 -0
  76. package/dist/LabelValue.d.mts +16 -0
  77. package/dist/LabelValue.d.ts +16 -0
  78. package/dist/LabelValue.js +225 -0
  79. package/dist/LabelValue.mjs +4 -0
  80. package/dist/ListGroup.d.mts +34 -0
  81. package/dist/ListGroup.d.ts +34 -0
  82. package/dist/ListGroup.js +217 -0
  83. package/dist/ListGroup.mjs +4 -0
  84. package/dist/ListItem.d.mts +64 -0
  85. package/dist/ListItem.d.ts +64 -0
  86. package/dist/ListItem.js +430 -0
  87. package/dist/ListItem.mjs +8 -0
  88. package/dist/MediaCard.d.mts +39 -0
  89. package/dist/MediaCard.d.ts +39 -0
  90. package/dist/MediaCard.js +427 -0
  91. package/dist/MediaCard.mjs +8 -0
  92. package/dist/MenuGroup.d.mts +34 -0
  93. package/dist/MenuGroup.d.ts +34 -0
  94. package/dist/MenuGroup.js +217 -0
  95. package/dist/MenuGroup.mjs +4 -0
  96. package/dist/MenuItem.d.mts +48 -0
  97. package/dist/MenuItem.d.ts +48 -0
  98. package/dist/MenuItem.js +403 -0
  99. package/dist/MenuItem.mjs +8 -0
  100. package/dist/MonthPicker.d.mts +20 -0
  101. package/dist/MonthPicker.d.ts +20 -0
  102. package/dist/MonthPicker.js +234 -0
  103. package/dist/MonthPicker.mjs +4 -0
  104. package/dist/Pressable.d.mts +34 -0
  105. package/dist/Pressable.d.ts +34 -0
  106. package/dist/Pressable.js +132 -0
  107. package/dist/Pressable.mjs +4 -0
  108. package/dist/Progress.d.mts +14 -0
  109. package/dist/Progress.d.ts +14 -0
  110. package/dist/Progress.js +191 -0
  111. package/dist/Progress.mjs +4 -0
  112. package/dist/RadioGroup.d.mts +19 -0
  113. package/dist/RadioGroup.d.ts +19 -0
  114. package/dist/RadioGroup.js +341 -0
  115. package/dist/RadioGroup.mjs +7 -0
  116. package/dist/Select.d.mts +22 -0
  117. package/dist/Select.d.ts +22 -0
  118. package/dist/Select.js +441 -0
  119. package/dist/Select.mjs +6 -0
  120. package/dist/Separator.d.mts +10 -0
  121. package/dist/Separator.d.ts +10 -0
  122. package/dist/Separator.js +156 -0
  123. package/dist/Separator.mjs +2 -0
  124. package/dist/Sheet.d.mts +81 -0
  125. package/dist/Sheet.d.ts +81 -0
  126. package/dist/Sheet.js +340 -0
  127. package/dist/Sheet.mjs +4 -0
  128. package/dist/Skeleton.d.mts +17 -0
  129. package/dist/Skeleton.d.ts +17 -0
  130. package/dist/Skeleton.js +205 -0
  131. package/dist/Skeleton.mjs +4 -0
  132. package/dist/Slider.d.mts +20 -0
  133. package/dist/Slider.d.ts +20 -0
  134. package/dist/Slider.js +232 -0
  135. package/dist/Slider.mjs +4 -0
  136. package/dist/Spinner.d.mts +12 -0
  137. package/dist/Spinner.d.ts +12 -0
  138. package/dist/Spinner.js +172 -0
  139. package/dist/Spinner.mjs +3 -0
  140. package/dist/Switch.d.mts +13 -0
  141. package/dist/Switch.d.ts +13 -0
  142. package/dist/Switch.js +261 -0
  143. package/dist/Switch.mjs +5 -0
  144. package/dist/Tabs.d.mts +27 -0
  145. package/dist/Tabs.d.ts +27 -0
  146. package/dist/Tabs.js +389 -0
  147. package/dist/Tabs.mjs +6 -0
  148. package/dist/Text.d.mts +12 -0
  149. package/dist/Text.d.ts +12 -0
  150. package/dist/Text.js +311 -0
  151. package/dist/Text.mjs +4 -0
  152. package/dist/Textarea.d.mts +16 -0
  153. package/dist/Textarea.d.ts +16 -0
  154. package/dist/Textarea.js +333 -0
  155. package/dist/Textarea.mjs +6 -0
  156. package/dist/Toast.d.mts +47 -0
  157. package/dist/Toast.d.ts +47 -0
  158. package/dist/Toast.js +185 -0
  159. package/dist/Toast.mjs +3 -0
  160. package/dist/Toggle.d.mts +33 -0
  161. package/dist/Toggle.d.ts +33 -0
  162. package/dist/Toggle.js +397 -0
  163. package/dist/Toggle.mjs +8 -0
  164. package/dist/VirtualList.d.mts +19 -0
  165. package/dist/VirtualList.d.ts +19 -0
  166. package/dist/VirtualList.js +38 -0
  167. package/dist/VirtualList.mjs +1 -0
  168. package/dist/chunk-2CE3TQVY.mjs +11 -0
  169. package/dist/chunk-2UYENBLV.mjs +49 -0
  170. package/dist/chunk-3BBOZ3OQ.mjs +41 -0
  171. package/dist/chunk-5IKW3VNC.mjs +43 -0
  172. package/dist/chunk-63357L2X.mjs +51 -0
  173. package/dist/chunk-6LQYY7HC.mjs +127 -0
  174. package/dist/chunk-6Q64UFIA.mjs +71 -0
  175. package/dist/chunk-76PFOSM2.mjs +41 -0
  176. package/dist/chunk-7H2OR44A.mjs +14 -0
  177. package/dist/chunk-A4MDAP7G.mjs +42 -0
  178. package/dist/chunk-AU2VDY4P.mjs +190 -0
  179. package/dist/chunk-BRKYVJVV.mjs +60 -0
  180. package/dist/chunk-CRYBX2CM.mjs +146 -0
  181. package/dist/chunk-DITNP6PL.mjs +106 -0
  182. package/dist/chunk-FTLJOUOQ.mjs +97 -0
  183. package/dist/chunk-GCWOGZYL.mjs +104 -0
  184. package/dist/chunk-GNGLDL6Z.mjs +60 -0
  185. package/dist/chunk-GPOUINK5.mjs +148 -0
  186. package/dist/chunk-HSPSMN6U.mjs +115 -0
  187. package/dist/chunk-IRRY3CRZ.mjs +82 -0
  188. package/dist/chunk-JB67UOB5.mjs +92 -0
  189. package/dist/chunk-JBLL7U3U.mjs +64 -0
  190. package/dist/chunk-KWCPOM6W.mjs +136 -0
  191. package/dist/chunk-KZJRQOIU.mjs +64 -0
  192. package/dist/chunk-L7E7TVEZ.mjs +145 -0
  193. package/dist/chunk-LG4DO3DK.mjs +174 -0
  194. package/dist/chunk-LWG526VX.mjs +139 -0
  195. package/dist/chunk-MN7OG7IY.mjs +96 -0
  196. package/dist/chunk-MX6HRKMI.mjs +29 -0
  197. package/dist/chunk-NC5ZTR2Y.mjs +32 -0
  198. package/dist/chunk-NQGVLMWG.mjs +90 -0
  199. package/dist/chunk-QCNARS3X.mjs +46 -0
  200. package/dist/chunk-QXGYKWI7.mjs +134 -0
  201. package/dist/chunk-QY3X2UYR.mjs +191 -0
  202. package/dist/chunk-RKLHUDZS.mjs +92 -0
  203. package/dist/chunk-RMMK64W5.mjs +54 -0
  204. package/dist/chunk-RR2VQLKE.mjs +190 -0
  205. package/dist/chunk-RTC3CFXF.mjs +29 -0
  206. package/dist/chunk-SBZYEV4S.mjs +61 -0
  207. package/dist/chunk-SOA2Z4RB.mjs +82 -0
  208. package/dist/chunk-SOYNZDVY.mjs +151 -0
  209. package/dist/chunk-T7XZ7H7Y.mjs +57 -0
  210. package/dist/chunk-TAJ2PQ2O.mjs +163 -0
  211. package/dist/chunk-U4N7WF4Z.mjs +108 -0
  212. package/dist/chunk-URDE3EUU.mjs +132 -0
  213. package/dist/chunk-URLL5JBR.mjs +245 -0
  214. package/dist/chunk-XDMN67KV.mjs +59 -0
  215. package/dist/chunk-Y6MXOREN.mjs +120 -0
  216. package/dist/chunk-YZJAFS4P.mjs +131 -0
  217. package/dist/index.d.mts +94 -852
  218. package/dist/index.d.ts +94 -852
  219. package/dist/index.js +1387 -942
  220. package/dist/index.mjs +50 -3844
  221. package/package.json +23 -14
  222. package/src/assets/fonts/Sohne-Bold.otf +0 -0
  223. package/src/assets/fonts/Sohne-BoldItalic.otf +0 -0
  224. package/src/assets/fonts/Sohne-ExtraBold.otf +0 -0
  225. package/src/assets/fonts/Sohne-ExtraBoldItalic.otf +0 -0
  226. package/src/assets/fonts/Sohne-ExtraLight.otf +0 -0
  227. package/src/assets/fonts/Sohne-ExtraLightItalic.otf +0 -0
  228. package/src/assets/fonts/Sohne-Italic.otf +0 -0
  229. package/src/assets/fonts/Sohne-Light.otf +0 -0
  230. package/src/assets/fonts/Sohne-LightItalic.otf +0 -0
  231. package/src/assets/fonts/Sohne-Medium.otf +0 -0
  232. package/src/assets/fonts/Sohne-MediumItalic.otf +0 -0
  233. package/src/assets/fonts/Sohne-Regular.otf +0 -0
  234. package/src/assets/fonts/Sohne-SemiBold.otf +0 -0
  235. package/src/assets/fonts/Sohne-SemiBoldItalic.otf +0 -0
  236. package/src/assets/fonts/SohneMono-Bold.otf +0 -0
  237. package/src/assets/fonts/SohneMono-BoldItalic.otf +0 -0
  238. package/src/assets/fonts/SohneMono-ExtraBold.otf +0 -0
  239. package/src/assets/fonts/SohneMono-ExtraBoldItalic.otf +0 -0
  240. package/src/assets/fonts/SohneMono-ExtraLight.otf +0 -0
  241. package/src/assets/fonts/SohneMono-ExtraLightItalic.otf +0 -0
  242. package/src/assets/fonts/SohneMono-Italic.otf +0 -0
  243. package/src/assets/fonts/SohneMono-Light.otf +0 -0
  244. package/src/assets/fonts/SohneMono-LightItalic.otf +0 -0
  245. package/src/assets/fonts/SohneMono-Medium.otf +0 -0
  246. package/src/assets/fonts/SohneMono-MediumItalic.otf +0 -0
  247. package/src/assets/fonts/SohneMono-Regular.otf +0 -0
  248. package/src/assets/fonts/SohneMono-SemiBold.otf +0 -0
  249. package/src/assets/fonts/SohneMono-SemiBoldItalic.otf +0 -0
  250. package/src/components/Accordion/Accordion.tsx +13 -15
  251. package/src/components/AlertBanner/AlertBanner.tsx +33 -12
  252. package/src/components/Avatar/Avatar.tsx +4 -2
  253. package/src/components/Badge/Badge.tsx +4 -2
  254. package/src/components/Button/Button.tsx +30 -29
  255. package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
  256. package/src/components/Card/Card.tsx +36 -65
  257. package/src/components/CategoryStrip/CategoryStrip.tsx +68 -58
  258. package/src/components/Checkbox/Checkbox.tsx +41 -55
  259. package/src/components/Chip/Chip.tsx +49 -84
  260. package/src/components/ConfirmDialog/ConfirmDialog.tsx +2 -2
  261. package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +4 -2
  262. package/src/components/CurrencyInput/CurrencyInput.tsx +2 -2
  263. package/src/components/DetailRow/DetailRow.tsx +9 -7
  264. package/src/components/EmptyState/EmptyState.tsx +2 -2
  265. package/src/components/Form/Form.tsx +149 -0
  266. package/src/components/Form/index.ts +1 -0
  267. package/src/components/IconButton/IconButton.tsx +24 -20
  268. package/src/components/Input/Input.tsx +63 -50
  269. package/src/components/LabelValue/LabelValue.tsx +6 -4
  270. package/src/components/ListGroup/ListGroup.tsx +145 -0
  271. package/src/components/ListGroup/index.ts +1 -0
  272. package/src/components/ListItem/ListItem.tsx +30 -43
  273. package/src/components/MediaCard/MediaCard.tsx +31 -29
  274. package/src/components/MenuGroup/MenuGroup.tsx +145 -0
  275. package/src/components/MenuGroup/index.ts +1 -0
  276. package/src/components/MenuItem/MenuItem.tsx +29 -40
  277. package/src/components/MonthPicker/MonthPicker.tsx +14 -4
  278. package/src/components/Pressable/Pressable.tsx +27 -46
  279. package/src/components/Progress/Progress.tsx +21 -12
  280. package/src/components/RadioGroup/RadioGroup.tsx +55 -32
  281. package/src/components/Select/Select.tsx +23 -21
  282. package/src/components/Separator/Separator.tsx +1 -3
  283. package/src/components/Sheet/Sheet.tsx +85 -18
  284. package/src/components/Skeleton/Skeleton.tsx +25 -14
  285. package/src/components/Slider/Slider.tsx +13 -3
  286. package/src/components/Spinner/Spinner.tsx +1 -1
  287. package/src/components/Switch/Switch.tsx +70 -52
  288. package/src/components/Tabs/Tabs.tsx +59 -47
  289. package/src/components/Text/Text.tsx +3 -1
  290. package/src/components/Textarea/Textarea.tsx +44 -23
  291. package/src/components/Toast/Toast.tsx +6 -6
  292. package/src/components/Toggle/Toggle.tsx +86 -68
  293. package/src/components/VirtualList/VirtualList.tsx +60 -0
  294. package/src/components/VirtualList/index.ts +1 -0
  295. package/src/fonts.ts +38 -20
  296. package/src/index.ts +5 -1
  297. package/src/theme/colors.ts +53 -39
  298. package/src/theme/types.ts +3 -0
  299. package/src/tokens.ts +49 -39
  300. package/src/utils/animations.ts +58 -0
  301. package/src/utils/icons.ts +47 -20
  302. package/src/utils/useColorTransition.ts +40 -0
  303. package/src/utils/usePressScale.ts +75 -0
  304. package/src/assets/fonts/Poppins-Black.ttf +0 -0
  305. package/src/assets/fonts/Poppins-BlackItalic.ttf +0 -0
  306. package/src/assets/fonts/Poppins-Bold.ttf +0 -0
  307. package/src/assets/fonts/Poppins-BoldItalic.ttf +0 -0
  308. package/src/assets/fonts/Poppins-ExtraBold.ttf +0 -0
  309. package/src/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
  310. package/src/assets/fonts/Poppins-ExtraLight.ttf +0 -0
  311. package/src/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
  312. package/src/assets/fonts/Poppins-Italic.ttf +0 -0
  313. package/src/assets/fonts/Poppins-Light.ttf +0 -0
  314. package/src/assets/fonts/Poppins-LightItalic.ttf +0 -0
  315. package/src/assets/fonts/Poppins-Medium.ttf +0 -0
  316. package/src/assets/fonts/Poppins-MediumItalic.ttf +0 -0
  317. package/src/assets/fonts/Poppins-Regular.ttf +0 -0
  318. package/src/assets/fonts/Poppins-SemiBold.ttf +0 -0
  319. package/src/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
  320. package/src/assets/fonts/Poppins-Thin.ttf +0 -0
  321. package/src/assets/fonts/Poppins-ThinItalic.ttf +0 -0
@@ -1,7 +1,13 @@
1
- import React, { useRef, useState, useEffect } from 'react'
2
- import { View, Animated, StyleSheet, ViewStyle } from 'react-native'
1
+ import React, { useEffect, useState } from 'react'
2
+ import { View, StyleSheet, ViewStyle } from 'react-native'
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withSpring,
7
+ } from 'react-native-reanimated'
3
8
  import { useTheme } from '../../theme'
4
9
  import { vs } from '../../utils/scaling'
10
+ import { SPRINGS } from '../../utils/animations'
5
11
 
6
12
  export type ProgressVariant = 'default' | 'success' | 'warning' | 'destructive'
7
13
 
@@ -10,23 +16,23 @@ export interface ProgressProps {
10
16
  max?: number
11
17
  variant?: ProgressVariant
12
18
  style?: ViewStyle
19
+ accessibilityLabel?: string
13
20
  }
14
21
 
15
- export function Progress({ value = 0, max = 100, variant = 'default', style }: ProgressProps) {
22
+ export function Progress({ value = 0, max = 100, variant = 'default', style, accessibilityLabel }: ProgressProps) {
16
23
  const { colors } = useTheme()
17
24
  const percent = Math.min(Math.max((value / max) * 100, 0), 100)
18
25
  const [trackWidth, setTrackWidth] = useState(0)
19
- const animatedWidth = useRef(new Animated.Value(0)).current
26
+ const animatedWidth = useSharedValue(0)
20
27
 
21
28
  useEffect(() => {
22
29
  if (trackWidth === 0) return
23
- Animated.spring(animatedWidth, {
24
- toValue: (percent / 100) * trackWidth,
25
- useNativeDriver: false,
26
- speed: 20,
27
- bounciness: 0,
28
- }).start()
29
- }, [percent, trackWidth])
30
+ animatedWidth.value = withSpring((percent / 100) * trackWidth, SPRINGS.glide)
31
+ }, [percent, trackWidth, animatedWidth])
32
+
33
+ const indicatorAnimatedStyle = useAnimatedStyle(() => ({
34
+ width: animatedWidth.value,
35
+ }))
30
36
 
31
37
  const indicatorColor =
32
38
  variant === 'success' ? colors.success
@@ -38,9 +44,12 @@ export function Progress({ value = 0, max = 100, variant = 'default', style }: P
38
44
  <View
39
45
  style={[styles.track, { backgroundColor: colors.surface }, style]}
40
46
  onLayout={(e) => setTrackWidth(e.nativeEvent.layout.width)}
47
+ accessibilityRole="progressbar"
48
+ accessibilityLabel={accessibilityLabel}
49
+ accessibilityValue={{ min: 0, max: 100, now: Math.round(percent) }}
41
50
  >
42
51
  <Animated.View
43
- style={[styles.indicator, { width: animatedWidth, backgroundColor: indicatorColor }]}
52
+ style={[styles.indicator, { backgroundColor: indicatorColor }, indicatorAnimatedStyle]}
44
53
  />
45
54
  </View>
46
55
  )
@@ -1,10 +1,18 @@
1
- import React, { useRef } from 'react'
2
- import { TouchableOpacity, Animated, View, Text, StyleSheet, ViewStyle, Platform } from 'react-native'
1
+ import React from 'react'
2
+ import { TouchableOpacity, View, Text, StyleSheet, ViewStyle } from 'react-native'
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withSpring,
7
+ interpolateColor,
8
+ } from 'react-native-reanimated'
9
+ import { useEffect } from 'react'
3
10
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
4
-
5
- const nativeDriver = Platform.OS !== 'web'
6
11
  import { useTheme } from '../../theme'
7
12
  import { s, vs, ms, mvs } from '../../utils/scaling'
13
+ import { usePressScale } from '../../utils/usePressScale'
14
+ import { useColorTransition } from '../../utils/useColorTransition'
15
+ import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
8
16
 
9
17
  export interface RadioOption {
10
18
  label: string
@@ -18,6 +26,7 @@ export interface RadioGroupProps {
18
26
  onValueChange?: (value: string) => void
19
27
  orientation?: 'vertical' | 'horizontal'
20
28
  style?: ViewStyle
29
+ accessibilityLabel?: string
21
30
  }
22
31
 
23
32
  function RadioItem({
@@ -30,49 +39,54 @@ function RadioItem({
30
39
  onSelect: () => void
31
40
  }) {
32
41
  const { colors } = useTheme()
33
- const scale = useRef(new Animated.Value(1)).current
42
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
43
+ pressScale: PRESS_SCALE.button,
44
+ disabled: option.disabled,
45
+ })
46
+ const colorProgress = useColorTransition(selected)
47
+
48
+ const dotScale = useSharedValue(selected ? 1 : 0)
49
+ useEffect(() => {
50
+ dotScale.value = withSpring(selected ? 1 : 0, SPRINGS.elastic)
51
+ }, [selected, dotScale])
34
52
 
35
- const handlePressIn = () => {
36
- if (option.disabled) return
37
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
38
- }
53
+ const radioStyle = useAnimatedStyle(() => ({
54
+ borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary]),
55
+ }))
39
56
 
40
- const handlePressOut = () => {
41
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
42
- }
57
+ const dotStyle = useAnimatedStyle(() => ({
58
+ transform: [{ scale: dotScale.value }],
59
+ opacity: dotScale.value,
60
+ }))
43
61
 
44
62
  return (
63
+ // AUDIT FIX: opacity was applied only to the radio circle, leaving the label
64
+ // at full opacity when disabled. The whole row now dims uniformly so users
65
+ // get a single, consistent disabled signal across the entire item.
45
66
  <TouchableOpacity
46
- style={styles.row}
67
+ style={[styles.row, option.disabled && styles.rowDisabled]}
47
68
  onPress={() => {
48
69
  if (!option.disabled) {
49
70
  hapticSelection()
50
71
  onSelect()
51
72
  }
52
73
  }}
53
- onPressIn={handlePressIn}
54
- onPressOut={handlePressOut}
74
+ onPressIn={onPressIn}
75
+ onPressOut={onPressOut}
55
76
  activeOpacity={1}
56
77
  touchSoundDisabled={true}
57
78
  disabled={option.disabled}
79
+ accessibilityRole="radio"
80
+ accessibilityLabel={option.label}
81
+ accessibilityState={{ checked: selected, disabled: !!option.disabled }}
58
82
  >
59
- <Animated.View
60
- style={[
61
- styles.radio,
62
- {
63
- borderColor: selected ? colors.primary : colors.border,
64
- opacity: option.disabled ? 0.45 : 1,
65
- transform: [{ scale }],
66
- },
67
- ]}
68
- >
69
- {selected ? <View style={[styles.dot, { backgroundColor: colors.primary }]} /> : null}
83
+ <Animated.View style={scaleStyle}>
84
+ <Animated.View style={[styles.radio, radioStyle]}>
85
+ <Animated.View style={[styles.dot, { backgroundColor: colors.primary }, dotStyle]} />
86
+ </Animated.View>
70
87
  </Animated.View>
71
88
  <Text
72
- style={[
73
- styles.label,
74
- { color: option.disabled ? colors.foregroundMuted : colors.foreground },
75
- ]}
89
+ style={[styles.label, { color: colors.foreground }]}
76
90
  allowFontScaling={true}
77
91
  >
78
92
  {option.label}
@@ -87,9 +101,14 @@ export function RadioGroup({
87
101
  onValueChange,
88
102
  orientation = 'vertical',
89
103
  style,
104
+ accessibilityLabel,
90
105
  }: RadioGroupProps) {
91
106
  return (
92
- <View style={[styles.container, orientation === 'horizontal' && styles.horizontal, style]}>
107
+ <View
108
+ style={[styles.container, orientation === 'horizontal' && styles.horizontal, style]}
109
+ accessibilityRole="radiogroup"
110
+ accessibilityLabel={accessibilityLabel}
111
+ >
93
112
  {options.map((option) => (
94
113
  <RadioItem
95
114
  key={option.value}
@@ -115,6 +134,10 @@ const styles = StyleSheet.create({
115
134
  alignItems: 'center',
116
135
  gap: s(12),
117
136
  },
137
+ // AUDIT FIX: was opacity on the inner circle only
138
+ rowDisabled: {
139
+ opacity: 0.45,
140
+ },
118
141
  radio: {
119
142
  width: s(24),
120
143
  height: s(24),
@@ -129,7 +152,7 @@ const styles = StyleSheet.create({
129
152
  borderRadius: s(5),
130
153
  },
131
154
  label: {
132
- fontFamily: 'Poppins-Regular',
155
+ fontFamily: 'Sohne-Regular',
133
156
  fontSize: ms(14),
134
157
  lineHeight: mvs(20),
135
158
  },
@@ -1,15 +1,17 @@
1
1
  import React, { useRef, useState } from 'react'
2
- import { View, Text, TouchableOpacity, Modal, Animated, StyleSheet, ViewStyle, Platform } from 'react-native'
2
+ import { View, Text, TouchableOpacity, Modal, StyleSheet, ViewStyle, Platform } from 'react-native'
3
+ import Animated from 'react-native-reanimated'
3
4
  import { Picker } from '@react-native-picker/picker'
4
5
  import { Entypo } from '@expo/vector-icons'
5
6
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
7
  import { useTheme } from '../../theme'
7
8
  import { s, vs, ms } from '../../utils/scaling'
9
+ import { usePressScale } from '../../utils/usePressScale'
10
+ import { PRESS_SCALE } from '../../utils/animations'
8
11
 
9
12
  const isIOS = Platform.OS === 'ios'
10
13
  const isAndroid = Platform.OS === 'android'
11
14
  const isWeb = Platform.OS === 'web'
12
- const nativeDriver = Platform.OS !== 'web'
13
15
 
14
16
  export interface SelectOption {
15
17
  label: string
@@ -26,6 +28,7 @@ export interface SelectProps {
26
28
  error?: string
27
29
  disabled?: boolean
28
30
  style?: ViewStyle
31
+ accessibilityLabel?: string
29
32
  }
30
33
 
31
34
  export function Select({
@@ -37,24 +40,19 @@ export function Select({
37
40
  error,
38
41
  disabled,
39
42
  style,
43
+ accessibilityLabel,
40
44
  }: SelectProps) {
41
45
  const { colors } = useTheme()
42
- const scale = useRef(new Animated.Value(1)).current
46
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
47
+ pressScale: PRESS_SCALE.button,
48
+ disabled,
49
+ })
43
50
  const [pickerVisible, setPickerVisible] = useState(false)
44
51
  const [pendingValue, setPendingValue] = useState<string | undefined>(value)
45
52
  const pickerRef = useRef<React.ElementRef<typeof Picker>>(null)
46
53
 
47
54
  const selected = options.find((o) => o.value === value)
48
55
 
49
- const handlePressIn = () => {
50
- if (disabled) return
51
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
52
- }
53
-
54
- const handlePressOut = () => {
55
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
56
- }
57
-
58
56
  const handleOpen = () => {
59
57
  if (disabled) return
60
58
  hapticSelection()
@@ -62,7 +60,7 @@ export function Select({
62
60
  setPendingValue(value)
63
61
  setPickerVisible(true)
64
62
  } else if (isAndroid) {
65
- ;(pickerRef.current as any)?.focus()
63
+ ;(pickerRef.current as { focus?: () => void })?.focus?.()
66
64
  }
67
65
  }
68
66
 
@@ -84,7 +82,7 @@ export function Select({
84
82
 
85
83
  {/* Trigger button — shown on iOS and Android only */}
86
84
  {!isWeb ? (
87
- <Animated.View style={{ transform: [{ scale }], opacity: disabled ? 0.45 : 1 }}>
85
+ <Animated.View style={[animatedStyle, { opacity: disabled ? 0.45 : 1 }]}>
88
86
  <TouchableOpacity
89
87
  style={[
90
88
  styles.trigger,
@@ -94,10 +92,14 @@ export function Select({
94
92
  },
95
93
  ]}
96
94
  onPress={handleOpen}
97
- onPressIn={handlePressIn}
98
- onPressOut={handlePressOut}
95
+ onPressIn={onPressIn}
96
+ onPressOut={onPressOut}
99
97
  activeOpacity={1}
100
98
  touchSoundDisabled={true}
99
+ accessibilityRole="combobox"
100
+ accessibilityLabel={accessibilityLabel ?? label}
101
+ accessibilityValue={{ text: selected?.label ?? placeholder }}
102
+ accessibilityState={{ disabled: !!disabled, expanded: pickerVisible }}
101
103
  >
102
104
  <Text
103
105
  style={[
@@ -232,7 +234,7 @@ const styles = StyleSheet.create({
232
234
  gap: vs(8),
233
235
  },
234
236
  label: {
235
- fontFamily: 'Poppins-Medium',
237
+ fontFamily: 'Sohne-Medium',
236
238
  fontSize: ms(13),
237
239
  },
238
240
  trigger: {
@@ -245,7 +247,7 @@ const styles = StyleSheet.create({
245
247
  paddingVertical: vs(11),
246
248
  },
247
249
  triggerText: {
248
- fontFamily: 'Poppins-Regular',
250
+ fontFamily: 'Sohne-Regular',
249
251
  fontSize: ms(15),
250
252
  flex: 1,
251
253
  },
@@ -253,7 +255,7 @@ const styles = StyleSheet.create({
253
255
  marginLeft: s(8),
254
256
  },
255
257
  helperText: {
256
- fontFamily: 'Poppins-Regular',
258
+ fontFamily: 'Sohne-Regular',
257
259
  fontSize: ms(13),
258
260
  },
259
261
  iosBackdrop: {
@@ -274,14 +276,14 @@ const styles = StyleSheet.create({
274
276
  borderBottomWidth: 1,
275
277
  },
276
278
  iosToolbarTitle: {
277
- fontFamily: 'Poppins-SemiBold',
279
+ fontFamily: 'Sohne-SemiBold',
278
280
  fontSize: ms(17),
279
281
  },
280
282
  iosDoneBtn: {
281
283
  padding: s(4),
282
284
  },
283
285
  iosDoneBtnText: {
284
- fontFamily: 'Poppins-SemiBold',
286
+ fontFamily: 'Sohne-SemiBold',
285
287
  fontSize: ms(17),
286
288
  },
287
289
  androidHiddenPicker: {
@@ -14,7 +14,7 @@ export function Separator({ orientation = 'horizontal', style }: SeparatorProps)
14
14
  <View
15
15
  style={[
16
16
  orientation === 'horizontal' ? styles.horizontal : styles.vertical,
17
- { backgroundColor: colors.border },
17
+ { backgroundColor: colors.separator },
18
18
  style,
19
19
  ]}
20
20
  />
@@ -25,11 +25,9 @@ const styles = StyleSheet.create({
25
25
  horizontal: {
26
26
  height: 1,
27
27
  width: '100%',
28
- opacity: 0.7,
29
28
  },
30
29
  vertical: {
31
30
  width: 1,
32
31
  height: '100%',
33
- opacity: 0.7,
34
32
  },
35
33
  })
@@ -25,6 +25,21 @@ export { BottomSheetModalProvider }
25
25
  // Re-export BottomSheetTextInput as SheetTextInput for consumer convenience
26
26
  export { BottomSheetTextInput as SheetTextInput }
27
27
 
28
+ export interface SheetHeaderProps {
29
+ children: React.ReactNode
30
+ style?: ViewStyle
31
+ }
32
+
33
+ export interface SheetContentProps {
34
+ children: React.ReactNode
35
+ style?: ViewStyle
36
+ }
37
+
38
+ export interface SheetFooterProps {
39
+ children: React.ReactNode
40
+ style?: ViewStyle
41
+ }
42
+
28
43
  export interface SheetProps {
29
44
  open: boolean
30
45
  onClose: () => void
@@ -80,6 +95,23 @@ export interface SheetProps {
80
95
  snapPoints?: (string | number)[]
81
96
  }
82
97
 
98
+ export function SheetHeader({ children, style }: SheetHeaderProps) {
99
+ return <View style={[styles.header, style]}>{children}</View>
100
+ }
101
+
102
+ export function SheetContent({ children, style }: SheetContentProps) {
103
+ return <View style={[styles.sheetContent, style]}>{children}</View>
104
+ }
105
+
106
+ export function SheetFooter({ children, style }: SheetFooterProps) {
107
+ const { colors } = useTheme()
108
+ return (
109
+ <View style={[styles.sheetFooter, { backgroundColor: colors.card, borderTopColor: colors.border }, style]}>
110
+ {children}
111
+ </View>
112
+ )
113
+ }
114
+
83
115
  export function Sheet({
84
116
  open,
85
117
  onClose,
@@ -125,21 +157,26 @@ export function Sheet({
125
157
  />
126
158
  ), [])
127
159
 
128
- const renderFooter = useCallback((props: BottomSheetFooterProps) => {
129
- if (!footer) return null
130
- return (
131
- <BottomSheetFooter {...props}>
132
- {footer}
133
- </BottomSheetFooter>
134
- )
135
- }, [footer])
160
+ // Detect compound components in children
161
+ const childArray = React.Children.toArray(children)
162
+ const customHeader = childArray.find((child) => React.isValidElement(child) && child.type === SheetHeader)
163
+ const customContent = childArray.find((child) => React.isValidElement(child) && child.type === SheetContent)
164
+ const customFooter = childArray.find((child) => React.isValidElement(child) && child.type === SheetFooter)
165
+
166
+ // If using compound components, filter them out from main children
167
+ const filteredChildren = customHeader || customContent || customFooter
168
+ ? childArray.filter(
169
+ (child) =>
170
+ !React.isValidElement(child) ||
171
+ (child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
172
+ )
173
+ : children
136
174
 
137
175
  const effectiveSubtitle = subtitle ?? description
176
+ const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader
138
177
 
139
- const showHeader = !!(title || effectiveSubtitle || showCloseButton)
140
-
141
- const headerNode = showHeader ? (
142
- <View style={styles.header}>
178
+ const headerNode = customHeader ? customHeader : (showHeader ? (
179
+ <View style={styles.header} accessibilityRole="header">
143
180
  <View style={styles.headerRow}>
144
181
  {title ? (
145
182
  <Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>
@@ -152,6 +189,9 @@ export function Sheet({
152
189
  style={styles.closeButton}
153
190
  activeOpacity={0.6}
154
191
  touchSoundDisabled={true}
192
+ accessibilityRole="button"
193
+ accessibilityLabel="Close"
194
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
155
195
  >
156
196
  <AntDesign name="close" size={ms(18)} color={colors.foregroundMuted} />
157
197
  </TouchableOpacity>
@@ -163,7 +203,19 @@ export function Sheet({
163
203
  </Text>
164
204
  ) : null}
165
205
  </View>
166
- ) : null
206
+ ) : null)
207
+
208
+ const contentNode = customContent ? customContent : filteredChildren
209
+ const effectiveFooter = customFooter ? customFooter : footer
210
+
211
+ const renderFooter = useCallback((props: BottomSheetFooterProps) => {
212
+ if (!effectiveFooter) return null
213
+ return (
214
+ <BottomSheetFooter {...props}>
215
+ {effectiveFooter}
216
+ </BottomSheetFooter>
217
+ )
218
+ }, [effectiveFooter])
167
219
 
168
220
  const useScroll = scrollable || !!maxHeight
169
221
  const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT
@@ -179,7 +231,7 @@ export function Sheet({
179
231
  maxDynamicContentSize={useDynamicSizing ? effectiveMaxHeight : undefined}
180
232
  onDismiss={onClose}
181
233
  backdropComponent={renderBackdrop}
182
- footerComponent={footer ? renderFooter : undefined}
234
+ footerComponent={effectiveFooter ? renderFooter : undefined}
183
235
  backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
184
236
  handleIndicatorStyle={[styles.handle, { backgroundColor: colors.border }]}
185
237
  enablePanDownToClose
@@ -201,18 +253,22 @@ export function Sheet({
201
253
  persistentScrollbar={isAndroid}
202
254
  >
203
255
  {headerNode}
204
- {children}
256
+ {contentNode}
205
257
  </BottomSheetScrollView>
206
258
  ) : (
207
259
  <BottomSheetView style={[styles.content, contentStyle, style]}>
208
260
  {headerNode}
209
- {children}
261
+ {contentNode}
210
262
  </BottomSheetView>
211
263
  )}
212
264
  </BottomSheetModal>
213
265
  )
214
266
  }
215
267
 
268
+ Sheet.Header = SheetHeader
269
+ Sheet.Content = SheetContent
270
+ Sheet.Footer = SheetFooter
271
+
216
272
  const styles = StyleSheet.create({
217
273
  background: {
218
274
  borderTopLeftRadius: ms(16),
@@ -235,12 +291,12 @@ const styles = StyleSheet.create({
235
291
  justifyContent: 'space-between',
236
292
  },
237
293
  title: {
238
- fontFamily: 'Poppins-SemiBold',
294
+ fontFamily: 'Sohne-SemiBold',
239
295
  fontSize: ms(18),
240
296
  flex: 1,
241
297
  },
242
298
  subtitle: {
243
- fontFamily: 'Poppins-Regular',
299
+ fontFamily: 'Sohne-Regular',
244
300
  fontSize: ms(14),
245
301
  lineHeight: mvs(20),
246
302
  },
@@ -257,4 +313,15 @@ const styles = StyleSheet.create({
257
313
  paddingBottom: vs(32),
258
314
  paddingRight: s(16),
259
315
  },
316
+ sheetContent: {
317
+ gap: vs(16),
318
+ },
319
+ sheetFooter: {
320
+ paddingHorizontal: s(16),
321
+ paddingVertical: vs(16),
322
+ borderTopWidth: 1,
323
+ flexDirection: 'row',
324
+ gap: s(12),
325
+ },
260
326
  })
327
+
@@ -1,8 +1,16 @@
1
- import React, { useEffect, useRef, useState } from 'react'
2
- import { Animated, StyleSheet, View, ViewStyle } from 'react-native'
1
+ import React, { useEffect, useState } from 'react'
2
+ import { StyleSheet, View, ViewStyle } from 'react-native'
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withRepeat,
7
+ withTiming,
8
+ Easing,
9
+ } from 'react-native-reanimated'
3
10
  import { LinearGradient } from 'expo-linear-gradient'
4
11
  import { useTheme } from '../../theme'
5
12
  import { s } from '../../utils/scaling'
13
+ import { TIMINGS } from '../../utils/animations'
6
14
 
7
15
  // circle: circular avatar placeholder text: short line preset base: custom dimensions
8
16
  export type SkeletonPreset = 'base' | 'circle' | 'text'
@@ -27,24 +35,24 @@ export function Skeleton({
27
35
  style,
28
36
  }: SkeletonProps) {
29
37
  const { colors, colorScheme } = useTheme()
30
- const shimmerAnim = useRef(new Animated.Value(0)).current
38
+ const shimmer = useSharedValue(0)
31
39
  const [containerWidth, setContainerWidth] = useState(300)
32
40
 
33
41
  const shimmerHighlight =
34
42
  colorScheme === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.7)'
35
43
 
36
44
  useEffect(() => {
37
- const animation = Animated.loop(
38
- Animated.timing(shimmerAnim, { toValue: 1, duration: 1200, useNativeDriver: true })
45
+ // Repeats indefinitely on the UI thread — zero JS bridge cost per frame.
46
+ shimmer.value = withRepeat(
47
+ withTiming(1, { duration: TIMINGS.shimmer.duration, easing: Easing.linear }),
48
+ -1,
49
+ false,
39
50
  )
40
- animation.start()
41
- return () => animation.stop()
42
- }, [shimmerAnim])
51
+ }, [shimmer])
43
52
 
44
- const translateX = shimmerAnim.interpolate({
45
- inputRange: [0, 1],
46
- outputRange: [-containerWidth, containerWidth],
47
- })
53
+ const shimmerStyle = useAnimatedStyle(() => ({
54
+ transform: [{ translateX: -containerWidth + shimmer.value * (containerWidth * 2) }],
55
+ }))
48
56
 
49
57
  // Resolve dimensions by preset
50
58
  const resolvedWidth: number | string =
@@ -66,12 +74,15 @@ export function Skeleton({
66
74
  <View
67
75
  style={[
68
76
  styles.base,
69
- { width: resolvedWidth as any, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
77
+ { width: resolvedWidth as number | `${number}%`, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
70
78
  style,
71
79
  ]}
72
80
  onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
81
+ accessibilityRole="progressbar"
82
+ accessibilityLabel="Loading"
83
+ accessibilityState={{ busy: true }}
73
84
  >
74
- <Animated.View style={[StyleSheet.absoluteFill, { transform: [{ translateX }] }]}>
85
+ <Animated.View style={[StyleSheet.absoluteFill, shimmerStyle]}>
75
86
  <LinearGradient
76
87
  colors={['transparent', shimmerHighlight, 'transparent']}
77
88
  start={{ x: 0, y: 0 }}
@@ -46,7 +46,17 @@ export function Slider({
46
46
  }
47
47
 
48
48
  return (
49
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel}>
49
+ <View
50
+ style={[styles.wrapper, style]}
51
+ accessibilityRole="adjustable"
52
+ accessibilityLabel={accessibilityLabel ?? label}
53
+ accessibilityValue={{
54
+ min: minimumValue,
55
+ max: maximumValue,
56
+ now: value,
57
+ text: formatValue(value),
58
+ }}
59
+ >
50
60
  {label || showValue ? (
51
61
  <View style={styles.header}>
52
62
  {label ? (
@@ -91,11 +101,11 @@ const styles = StyleSheet.create({
91
101
  alignItems: 'center',
92
102
  },
93
103
  label: {
94
- fontFamily: 'Poppins-Medium',
104
+ fontFamily: 'Sohne-Medium',
95
105
  fontSize: ms(15),
96
106
  },
97
107
  valueText: {
98
- fontFamily: 'Poppins-Medium',
108
+ fontFamily: 'Sohne-Medium',
99
109
  fontSize: ms(14),
100
110
  },
101
111
  slider: {
@@ -49,7 +49,7 @@ const styles = StyleSheet.create({
49
49
  gap: vs(6),
50
50
  },
51
51
  label: {
52
- fontFamily: 'Poppins-Regular',
52
+ fontFamily: 'Sohne-Regular',
53
53
  lineHeight: mvs(18),
54
54
  },
55
55
  })