@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,38 +1,31 @@
1
- import React, { useEffect, useRef } from 'react'
2
- import {
3
- TouchableOpacity,
4
- Animated,
5
- View,
6
- Text,
7
- StyleSheet,
8
- ViewStyle,
9
- Platform,
10
- Easing,
11
- } from 'react-native'
1
+ import React from 'react'
2
+ import { TouchableOpacity, View, StyleSheet, ViewStyle } from 'react-native'
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ interpolateColor,
6
+ } from 'react-native-reanimated'
12
7
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
8
  import { useTheme } from '../../theme'
14
9
  import { s, vs, ms, mvs } from '../../utils/scaling'
15
10
  import { renderIcon } from '../../utils/icons'
16
-
17
- const nativeDriver = Platform.OS !== 'web'
11
+ import { usePressScale } from '../../utils/usePressScale'
12
+ import { useColorTransition } from '../../utils/useColorTransition'
13
+ import { PRESS_SCALE } from '../../utils/animations'
18
14
 
19
15
  export interface ChipProps {
20
16
  label: string
21
17
  selected?: boolean
22
18
  onPress?: () => void
23
- /** JSX icon rendered before the label. */
24
19
  icon?: React.ReactNode
25
- /** Icon name from @expo/vector-icons resolved automatically. */
26
20
  iconName?: string
27
21
  style?: ViewStyle
22
+ accessibilityLabel?: string
28
23
  }
29
24
 
30
25
  export interface ChipOption {
31
26
  label: string
32
27
  value: string | number
33
- /** Icon name resolved via renderIcon (Feather, AntDesign, etc.). */
34
28
  iconName?: string
35
- /** Icon tint color override. */
36
29
  iconColor?: string
37
30
  disabled?: boolean
38
31
  }
@@ -41,79 +34,50 @@ export interface ChipGroupProps {
41
34
  options: ChipOption[]
42
35
  value?: string | number | (string | number)[]
43
36
  onValueChange?: (value: string | number | (string | number)[]) => void
44
- /** When true, allows selecting multiple chips. `value` and `onValueChange` will use arrays. */
45
37
  multiSelect?: boolean
46
38
  style?: ViewStyle
47
39
  }
48
40
 
49
- export function Chip({ label, selected = false, onPress, icon, iconName, style }: ChipProps) {
41
+ function ChipBase({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }: ChipProps) {
50
42
  const { colors } = useTheme()
51
- const scale = useRef(new Animated.Value(1)).current
52
- const pressAnim = useRef(new Animated.Value(selected ? 1 : 0)).current
43
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
44
+ pressScale: PRESS_SCALE.chip,
45
+ })
46
+ const colorProgress = useColorTransition(selected)
53
47
 
54
- useEffect(() => {
55
- Animated.timing(pressAnim, {
56
- toValue: selected ? 1 : 0,
57
- duration: 150,
58
- easing: Easing.out(Easing.ease),
59
- useNativeDriver: false,
60
- }).start()
61
- }, [selected, pressAnim])
48
+ const surfaceStyle = useAnimatedStyle(() => ({
49
+ backgroundColor: interpolateColor(colorProgress.value, [0, 1], [colors.surface, colors.primary]),
50
+ borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary]),
51
+ }))
62
52
 
63
- const handlePressIn = () => {
64
- Animated.spring(scale, {
65
- toValue: 0.95,
66
- useNativeDriver: nativeDriver,
67
- speed: 40,
68
- bounciness: 0,
69
- }).start()
70
- }
71
-
72
- const handlePressOut = () => {
73
- Animated.spring(scale, {
74
- toValue: 1,
75
- useNativeDriver: nativeDriver,
76
- speed: 40,
77
- bounciness: 4,
78
- }).start()
79
- }
53
+ const textStyle = useAnimatedStyle(() => ({
54
+ color: interpolateColor(colorProgress.value, [0, 1], [colors.foreground, colors.primaryForeground]),
55
+ }))
80
56
 
81
57
  const handlePress = () => {
82
58
  hapticSelection()
83
59
  onPress?.()
84
60
  }
85
61
 
86
- const backgroundColor = pressAnim.interpolate({
87
- inputRange: [0, 1],
88
- outputRange: [colors.surface, colors.primary],
89
- })
90
-
91
- const textColor = pressAnim.interpolate({
92
- inputRange: [0, 1],
93
- outputRange: [colors.foreground, colors.primaryForeground],
94
- })
95
-
96
- const borderColor = pressAnim.interpolate({
97
- inputRange: [0, 1],
98
- outputRange: [colors.border, colors.primary],
99
- })
100
-
101
62
  const resolvedIcon = iconName
102
63
  ? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground)
103
64
  : icon
104
65
 
105
66
  return (
106
- <Animated.View style={[styles.wrapper, { transform: [{ scale }] }, style]}>
67
+ <Animated.View style={[styles.wrapper, scaleStyle, style]} {...hoverHandlers}>
107
68
  <TouchableOpacity
108
69
  onPress={handlePress}
109
- onPressIn={handlePressIn}
110
- onPressOut={handlePressOut}
70
+ onPressIn={onPressIn}
71
+ onPressOut={onPressOut}
111
72
  activeOpacity={1}
112
73
  touchSoundDisabled={true}
74
+ accessibilityRole="button"
75
+ accessibilityLabel={accessibilityLabel ?? label}
76
+ accessibilityState={{ selected }}
113
77
  >
114
- <Animated.View style={[styles.chip, { backgroundColor, borderColor }]}>
78
+ <Animated.View style={[styles.chip, surfaceStyle]}>
115
79
  {resolvedIcon ? <View style={styles.chipIcon}>{resolvedIcon}</View> : null}
116
- <Animated.Text style={[styles.label, { color: textColor }]} allowFontScaling={true}>
80
+ <Animated.Text style={[styles.label, textStyle]} allowFontScaling={true}>
117
81
  {label}
118
82
  </Animated.Text>
119
83
  </Animated.View>
@@ -122,33 +86,24 @@ export function Chip({ label, selected = false, onPress, icon, iconName, style }
122
86
  )
123
87
  }
124
88
 
89
+ export const Chip = React.memo(ChipBase)
90
+
125
91
  export function ChipGroup({ options, value, onValueChange, multiSelect = false, style }: ChipGroupProps) {
126
92
  const handlePress = (optionValue: string | number) => {
127
93
  if (!multiSelect) {
128
94
  onValueChange?.(optionValue)
129
95
  return
130
96
  }
131
-
132
- // Multiselect logic
133
97
  const currentArray = Array.isArray(value) ? value : value ? [value] : []
134
98
  const isSelected = currentArray.includes(optionValue)
135
-
136
- let newArray: (string | number)[]
137
- if (isSelected) {
138
- // Remove from selection
139
- newArray = currentArray.filter((v) => v !== optionValue)
140
- } else {
141
- // Add to selection
142
- newArray = [...currentArray, optionValue]
143
- }
144
-
99
+ const newArray: (string | number)[] = isSelected
100
+ ? currentArray.filter((v) => v !== optionValue)
101
+ : [...currentArray, optionValue]
145
102
  onValueChange?.(newArray)
146
103
  }
147
104
 
148
105
  const isSelected = (optionValue: string | number): boolean => {
149
- if (Array.isArray(value)) {
150
- return value.includes(optionValue)
151
- }
106
+ if (Array.isArray(value)) return value.includes(optionValue)
152
107
  return optionValue === value
153
108
  }
154
109
 
@@ -161,7 +116,11 @@ export function ChipGroup({ options, value, onValueChange, multiSelect = false,
161
116
  selected={isSelected(opt.value)}
162
117
  onPress={opt.disabled ? undefined : () => handlePress(opt.value)}
163
118
  iconName={opt.iconName}
164
- style={opt.disabled ? { opacity: 0.4 } : undefined}
119
+ // AUDIT FIX: was style={{ opacity: 0.4 }} with no accessibilityState.
120
+ // Now passes disabled state to the Chip's TouchableOpacity via onPress=undefined
121
+ // and adds explicit accessibility state for screen readers.
122
+ style={opt.disabled ? styles.chipDisabled : undefined}
123
+ accessibilityLabel={opt.disabled ? `${opt.label}, unavailable` : opt.label}
165
124
  />
166
125
  ))}
167
126
  </View>
@@ -173,19 +132,25 @@ const styles = StyleSheet.create({
173
132
  chip: {
174
133
  borderRadius: 999,
175
134
  paddingHorizontal: s(14),
176
- paddingVertical: vs(5),
135
+ // AUDIT FIX: was vs(5) → ~28px total height — below WCAG 44px tap target.
136
+ // vs(10) → ~44px total height meets WCAG 2.5.5 (AAA) minimum.
137
+ paddingVertical: vs(10),
138
+ minHeight: 44,
177
139
  borderWidth: 1,
178
140
  alignItems: 'center',
179
141
  justifyContent: 'center',
180
142
  flexDirection: 'row',
181
143
  gap: s(5),
182
144
  },
145
+ chipDisabled: {
146
+ opacity: 0.4,
147
+ },
183
148
  chipIcon: {
184
149
  alignItems: 'center',
185
150
  justifyContent: 'center',
186
151
  },
187
152
  label: {
188
- fontFamily: 'Poppins-Medium',
153
+ fontFamily: 'Sohne-Medium',
189
154
  fontSize: ms(13),
190
155
  lineHeight: mvs(18),
191
156
  },
@@ -120,12 +120,12 @@ const styles = StyleSheet.create({
120
120
  gap: vs(12),
121
121
  },
122
122
  title: {
123
- fontFamily: 'Poppins-SemiBold',
123
+ fontFamily: 'Sohne-SemiBold',
124
124
  fontSize: ms(18),
125
125
  lineHeight: mvs(26),
126
126
  },
127
127
  description: {
128
- fontFamily: 'Poppins-Regular',
128
+ fontFamily: 'Sohne-Regular',
129
129
  fontSize: ms(15),
130
130
  lineHeight: mvs(22),
131
131
  },
@@ -49,7 +49,7 @@ function formatValue(value: number | string, prefix: string, showDecimals: boole
49
49
  return `${sign}${prefix}${intPart}`
50
50
  }
51
51
 
52
- export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, textColor, variant, autoScale, maxFontSize, style }: CurrencyDisplayProps) {
52
+ function CurrencyDisplayBase({ value, prefix = '$', showDecimals = false, textColor, variant, autoScale, maxFontSize, style }: CurrencyDisplayProps) {
53
53
  const { colors } = useTheme()
54
54
  const formatted = formatValue(value, prefix, showDecimals)
55
55
  const baseFontSize = variant ? variantFontSize[variant] : ms(56)
@@ -71,12 +71,14 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
71
71
  )
72
72
  }
73
73
 
74
+ export const CurrencyDisplay = React.memo(CurrencyDisplayBase)
75
+
74
76
  const styles = StyleSheet.create({
75
77
  container: {
76
78
  alignSelf: 'flex-start',
77
79
  },
78
80
  amount: {
79
- fontFamily: 'Poppins-Bold',
81
+ fontFamily: 'Sohne-Bold',
80
82
  includeFontPadding: false,
81
83
  textAlignVertical: 'top',
82
84
  },
@@ -59,8 +59,8 @@ export function CurrencyInput({
59
59
  }
60
60
 
61
61
  const inputStyle: TextStyle = size === 'large'
62
- ? { fontFamily: 'Poppins-Regular', fontSize: ms(36) }
63
- : { fontFamily: 'Poppins-Regular' }
62
+ ? { fontFamily: 'Sohne-Regular', fontSize: ms(36) }
63
+ : { fontFamily: 'Sohne-Regular' }
64
64
 
65
65
  const dollarIcon = renderIcon('dollar-sign', size === 'large' ? 24 : 16, colors.foregroundMuted)
66
66
 
@@ -1,17 +1,17 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
- import { s, vs, ms, mvs } from '../../utils/scaling'
4
+ import { s, ms, mvs } from '../../utils/scaling'
5
5
  import { renderIcon } from '../../utils/icons'
6
6
 
7
7
  export type DetailRowSeparator = 'dotted' | 'solid' | 'dashed' | 'none'
8
8
  export type DetailRowLabelWeight = 'normal' | 'medium' | 'semibold' | 'bold'
9
9
 
10
10
  const weightMap: Record<DetailRowLabelWeight, string> = {
11
- normal: 'Poppins-Regular',
12
- medium: 'Poppins-Medium',
13
- semibold: 'Poppins-SemiBold',
14
- bold: 'Poppins-Bold',
11
+ normal: 'Sohne-Regular',
12
+ medium: 'Sohne-Medium',
13
+ semibold: 'Sohne-SemiBold',
14
+ bold: 'Sohne-Bold',
15
15
  }
16
16
 
17
17
  export interface DetailRowProps {
@@ -38,7 +38,7 @@ export interface DetailRowProps {
38
38
  valueStyle?: TextStyle
39
39
  }
40
40
 
41
- export function DetailRow({
41
+ function DetailRowBase({
42
42
  label,
43
43
  value,
44
44
  separator = 'dotted',
@@ -108,6 +108,8 @@ export function DetailRow({
108
108
  )
109
109
  }
110
110
 
111
+ export const DetailRow = React.memo(DetailRowBase)
112
+
111
113
  const styles = StyleSheet.create({
112
114
  row: {
113
115
  flexDirection: 'row',
@@ -138,7 +140,7 @@ const styles = StyleSheet.create({
138
140
  flexShrink: 0,
139
141
  },
140
142
  valueText: {
141
- fontFamily: 'Poppins-SemiBold',
143
+ fontFamily: 'Sohne-SemiBold',
142
144
  fontSize: ms(13),
143
145
  lineHeight: mvs(18),
144
146
  },
@@ -109,7 +109,7 @@ const styles = StyleSheet.create({
109
109
  marginTop: vs(16),
110
110
  },
111
111
  title: {
112
- fontFamily: 'Poppins-Medium',
112
+ fontFamily: 'Sohne-Medium',
113
113
  fontSize: ms(18),
114
114
  textAlign: 'center',
115
115
  },
@@ -118,7 +118,7 @@ const styles = StyleSheet.create({
118
118
  marginTop: vs(10),
119
119
  },
120
120
  description: {
121
- fontFamily: 'Poppins-Regular',
121
+ fontFamily: 'Sohne-Regular',
122
122
  fontSize: ms(14),
123
123
  lineHeight: mvs(20),
124
124
  textAlign: 'center',
@@ -0,0 +1,149 @@
1
+ import React from 'react'
2
+ import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
3
+ import { useTheme } from '../../theme'
4
+ import { s, vs } from '../../utils/scaling'
5
+
6
+ export interface FormProps {
7
+ children: React.ReactNode
8
+ style?: ViewStyle
9
+ }
10
+
11
+ export interface FormFieldProps {
12
+ children: React.ReactNode
13
+ label?: string
14
+ error?: string
15
+ required?: boolean
16
+ style?: ViewStyle
17
+ labelStyle?: TextStyle
18
+ errorStyle?: TextStyle
19
+ }
20
+
21
+ export interface FormSectionProps {
22
+ children: React.ReactNode
23
+ title?: string
24
+ description?: string
25
+ style?: ViewStyle
26
+ }
27
+
28
+ export interface FormFooterProps {
29
+ children: React.ReactNode
30
+ style?: ViewStyle
31
+ }
32
+
33
+ /**
34
+ * Form wrapper with consistent spacing between fields.
35
+ * Use Form.Field for individual inputs with label + error,
36
+ * Form.Section for grouped fields, and Form.Footer for action buttons.
37
+ */
38
+ export function Form({ children, style }: FormProps) {
39
+ return <View style={[styles.form, style]}>{children}</View>
40
+ }
41
+
42
+ /**
43
+ * Wraps a single form input with optional label and error message.
44
+ * Automatically spaces children properly.
45
+ */
46
+ export function FormField({
47
+ children,
48
+ label,
49
+ error,
50
+ required,
51
+ style,
52
+ labelStyle,
53
+ errorStyle,
54
+ }: FormFieldProps) {
55
+ const { colors } = useTheme()
56
+
57
+ return (
58
+ <View style={[styles.field, style]}>
59
+ {label ? (
60
+ <Text style={[styles.label, { color: colors.foreground }, labelStyle]} allowFontScaling={true}>
61
+ {label}
62
+ {required ? <Text style={{ color: colors.destructive }}> *</Text> : null}
63
+ </Text>
64
+ ) : null}
65
+ {children}
66
+ {error ? (
67
+ <Text style={[styles.error, { color: colors.destructive }, errorStyle]} allowFontScaling={true}>
68
+ {error}
69
+ </Text>
70
+ ) : null}
71
+ </View>
72
+ )
73
+ }
74
+
75
+ /**
76
+ * Groups related form fields with optional title and description.
77
+ */
78
+ export function FormSection({ children, title, description, style }: FormSectionProps) {
79
+ const { colors } = useTheme()
80
+
81
+ return (
82
+ <View style={[styles.section, style]}>
83
+ {title ? (
84
+ <View style={styles.sectionHeader}>
85
+ <Text style={[styles.sectionTitle, { color: colors.foreground }]} allowFontScaling={true}>
86
+ {title}
87
+ </Text>
88
+ {description ? (
89
+ <Text style={[styles.sectionDescription, { color: colors.foregroundMuted }]} allowFontScaling={true}>
90
+ {description}
91
+ </Text>
92
+ ) : null}
93
+ </View>
94
+ ) : null}
95
+ {children}
96
+ </View>
97
+ )
98
+ }
99
+
100
+ /**
101
+ * Footer area for submit/cancel buttons or form actions.
102
+ */
103
+ export function FormFooter({ children, style }: FormFooterProps) {
104
+ return <View style={[styles.footer, style]}>{children}</View>
105
+ }
106
+
107
+ Form.Field = FormField
108
+ Form.Section = FormSection
109
+ Form.Footer = FormFooter
110
+
111
+ const styles = StyleSheet.create({
112
+ form: {
113
+ gap: vs(16),
114
+ },
115
+ field: {
116
+ gap: vs(6),
117
+ },
118
+ label: {
119
+ fontFamily: 'Sohne-Medium',
120
+ fontSize: 14,
121
+ lineHeight: 20,
122
+ },
123
+ error: {
124
+ fontFamily: 'Sohne-Regular',
125
+ fontSize: 12,
126
+ lineHeight: 16,
127
+ },
128
+ section: {
129
+ gap: vs(16),
130
+ },
131
+ sectionHeader: {
132
+ gap: vs(4),
133
+ },
134
+ sectionTitle: {
135
+ fontFamily: 'Sohne-SemiBold',
136
+ fontSize: 16,
137
+ lineHeight: 24,
138
+ },
139
+ sectionDescription: {
140
+ fontFamily: 'Sohne-Regular',
141
+ fontSize: 14,
142
+ lineHeight: 20,
143
+ },
144
+ footer: {
145
+ flexDirection: 'row',
146
+ gap: s(12),
147
+ paddingTop: vs(8),
148
+ },
149
+ })
@@ -0,0 +1 @@
1
+ export * from './Form'
@@ -1,21 +1,20 @@
1
- import React, { useRef } from 'react'
1
+ import React from 'react'
2
2
  import {
3
3
  TouchableOpacity,
4
- Animated,
5
4
  ActivityIndicator,
6
5
  StyleSheet,
7
6
  View,
8
7
  Text,
9
8
  TouchableOpacityProps,
10
9
  ViewStyle,
11
- Platform,
12
10
  } from 'react-native'
13
-
14
- const nativeDriver = Platform.OS !== 'web'
11
+ import Animated from 'react-native-reanimated'
15
12
  import { impactLight } from '../../utils/haptics'
16
13
  import { useTheme } from '../../theme'
17
14
  import { s, ms } from '../../utils/scaling'
18
15
  import { renderIcon } from '../../utils/icons'
16
+ import { usePressScale } from '../../utils/usePressScale'
17
+ import { PRESS_SCALE } from '../../utils/animations'
19
18
 
20
19
  // primary: filled primary
21
20
  // secondary: filled surface — icon on neutral bg (Airbnb icon-button-circle)
@@ -45,7 +44,7 @@ const sizeMap: Record<IconButtonSize, { container: number; icon: number }> = {
45
44
  lg: { container: s(52), icon: 24 },
46
45
  }
47
46
 
48
- export function IconButton({
47
+ function IconButtonBase({
49
48
  iconName,
50
49
  icon,
51
50
  iconColor,
@@ -56,20 +55,16 @@ export function IconButton({
56
55
  disabled,
57
56
  style,
58
57
  onPress,
58
+ accessibilityLabel,
59
+ accessibilityHint,
59
60
  ...props
60
61
  }: IconButtonProps) {
61
62
  const { colors } = useTheme()
62
63
  const isDisabled = disabled || loading
63
- const scale = useRef(new Animated.Value(1)).current
64
-
65
- const handlePressIn = () => {
66
- if (isDisabled) return
67
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
68
- }
69
-
70
- const handlePressOut = () => {
71
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
72
- }
64
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
65
+ pressScale: PRESS_SCALE.button,
66
+ disabled: isDisabled,
67
+ })
73
68
 
74
69
  const handlePress: TouchableOpacityProps['onPress'] = (e) => {
75
70
  impactLight()
@@ -109,7 +104,10 @@ export function IconButton({
109
104
  const showCount = typeof badge === 'number' && badge > 0
110
105
 
111
106
  return (
112
- <Animated.View style={[styles.wrapper, { transform: [{ scale }] }]}>
107
+ <Animated.View
108
+ style={[styles.wrapper, animatedStyle]}
109
+ {...hoverHandlers}
110
+ >
113
111
  <TouchableOpacity
114
112
  style={[
115
113
  styles.base,
@@ -122,8 +120,12 @@ export function IconButton({
122
120
  activeOpacity={1}
123
121
  touchSoundDisabled={true}
124
122
  onPress={handlePress}
125
- onPressIn={handlePressIn}
126
- onPressOut={handlePressOut}
123
+ onPressIn={onPressIn}
124
+ onPressOut={onPressOut}
125
+ accessibilityRole="button"
126
+ accessibilityLabel={accessibilityLabel ?? iconName ?? 'icon button'}
127
+ accessibilityHint={accessibilityHint}
128
+ accessibilityState={{ disabled: isDisabled, busy: loading }}
127
129
  {...props}
128
130
  >
129
131
  {loading ? (
@@ -149,6 +151,8 @@ export function IconButton({
149
151
  )
150
152
  }
151
153
 
154
+ export const IconButton = React.memo(IconButtonBase)
155
+
152
156
  const styles = StyleSheet.create({
153
157
  wrapper: {
154
158
  alignSelf: 'flex-start',
@@ -180,7 +184,7 @@ const styles = StyleSheet.create({
180
184
  paddingHorizontal: 3,
181
185
  },
182
186
  badgeText: {
183
- fontFamily: 'Poppins-Bold',
187
+ fontFamily: 'Sohne-Bold',
184
188
  fontSize: ms(9),
185
189
  lineHeight: 14,
186
190
  },