@retray-dev/ui-kit 6.2.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 (317) hide show
  1. package/COMPONENTS.md +444 -10
  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 -873
  218. package/dist/index.d.ts +94 -873
  219. package/dist/index.js +751 -357
  220. package/dist/index.mjs +50 -3895
  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 +3 -3
  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 +10 -11
  255. package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
  256. package/src/components/Card/Card.tsx +17 -34
  257. package/src/components/CategoryStrip/CategoryStrip.tsx +24 -21
  258. package/src/components/Checkbox/Checkbox.tsx +11 -6
  259. package/src/components/Chip/Chip.tsx +17 -15
  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 +4 -2
  268. package/src/components/Input/Input.tsx +27 -31
  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 +9 -10
  273. package/src/components/MediaCard/MediaCard.tsx +7 -5
  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 +7 -9
  277. package/src/components/MonthPicker/MonthPicker.tsx +2 -2
  278. package/src/components/RadioGroup/RadioGroup.tsx +11 -14
  279. package/src/components/Select/Select.tsx +6 -6
  280. package/src/components/Separator/Separator.tsx +1 -3
  281. package/src/components/Sheet/Sheet.tsx +81 -17
  282. package/src/components/Skeleton/Skeleton.tsx +1 -1
  283. package/src/components/Slider/Slider.tsx +2 -2
  284. package/src/components/Spinner/Spinner.tsx +1 -1
  285. package/src/components/Switch/Switch.tsx +28 -5
  286. package/src/components/Tabs/Tabs.tsx +22 -18
  287. package/src/components/Text/Text.tsx +3 -1
  288. package/src/components/Textarea/Textarea.tsx +18 -14
  289. package/src/components/Toast/Toast.tsx +6 -6
  290. package/src/components/Toggle/Toggle.tsx +47 -23
  291. package/src/components/VirtualList/VirtualList.tsx +60 -0
  292. package/src/components/VirtualList/index.ts +1 -0
  293. package/src/fonts.ts +38 -20
  294. package/src/index.ts +5 -1
  295. package/src/theme/colors.ts +53 -39
  296. package/src/theme/types.ts +3 -0
  297. package/src/tokens.ts +49 -39
  298. package/src/utils/icons.ts +47 -20
  299. package/src/utils/usePressScale.ts +2 -0
  300. package/src/assets/fonts/Poppins-Black.ttf +0 -0
  301. package/src/assets/fonts/Poppins-BlackItalic.ttf +0 -0
  302. package/src/assets/fonts/Poppins-Bold.ttf +0 -0
  303. package/src/assets/fonts/Poppins-BoldItalic.ttf +0 -0
  304. package/src/assets/fonts/Poppins-ExtraBold.ttf +0 -0
  305. package/src/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
  306. package/src/assets/fonts/Poppins-ExtraLight.ttf +0 -0
  307. package/src/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
  308. package/src/assets/fonts/Poppins-Italic.ttf +0 -0
  309. package/src/assets/fonts/Poppins-Light.ttf +0 -0
  310. package/src/assets/fonts/Poppins-LightItalic.ttf +0 -0
  311. package/src/assets/fonts/Poppins-Medium.ttf +0 -0
  312. package/src/assets/fonts/Poppins-MediumItalic.ttf +0 -0
  313. package/src/assets/fonts/Poppins-Regular.ttf +0 -0
  314. package/src/assets/fonts/Poppins-SemiBold.ttf +0 -0
  315. package/src/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
  316. package/src/assets/fonts/Poppins-Thin.ttf +0 -0
  317. package/src/assets/fonts/Poppins-ThinItalic.ttf +0 -0
@@ -46,8 +46,11 @@ export function Checkbox({
46
46
  }))
47
47
 
48
48
  return (
49
+ // AUDIT FIX: opacity was applied only to the box, leaving the label at full
50
+ // opacity when disabled — a contradictory visual signal. Now the entire row
51
+ // dims uniformly so label and control communicate the same disabled state.
49
52
  <TouchableOpacity
50
- style={[styles.row, style]}
53
+ style={[styles.row, disabled && styles.rowDisabled, style]}
51
54
  onPress={() => {
52
55
  hapticSelection()
53
56
  onCheckedChange?.(!checked)
@@ -62,9 +65,7 @@ export function Checkbox({
62
65
  accessibilityState={{ checked, disabled: !!disabled }}
63
66
  >
64
67
  <Animated.View style={scaleStyle}>
65
- <Animated.View
66
- style={[styles.box, { opacity: disabled ? 0.45 : 1 }, boxStyle]}
67
- >
68
+ <Animated.View style={[styles.box, boxStyle]}>
68
69
  <Animated.View style={checkStyle}>
69
70
  <View style={[styles.checkmark, { borderColor: colors.primaryForeground }]} />
70
71
  </Animated.View>
@@ -72,7 +73,7 @@ export function Checkbox({
72
73
  </Animated.View>
73
74
  {label ? (
74
75
  <Text
75
- style={[styles.label, { color: disabled ? colors.foregroundMuted : colors.foreground }]}
76
+ style={[styles.label, { color: colors.foreground }]}
76
77
  allowFontScaling={true}
77
78
  >
78
79
  {label}
@@ -88,6 +89,10 @@ const styles = StyleSheet.create({
88
89
  alignItems: 'center',
89
90
  gap: s(12),
90
91
  },
92
+ // AUDIT FIX: was inline opacity on the box only
93
+ rowDisabled: {
94
+ opacity: 0.45,
95
+ },
91
96
  box: {
92
97
  width: s(24),
93
98
  height: s(24),
@@ -104,7 +109,7 @@ const styles = StyleSheet.create({
104
109
  transform: [{ rotate: '-45deg' }, { translateY: -1 }],
105
110
  },
106
111
  label: {
107
- fontFamily: 'Poppins-Regular',
112
+ fontFamily: 'Sohne-Regular',
108
113
  fontSize: ms(14),
109
114
  lineHeight: mvs(20),
110
115
  },
@@ -16,9 +16,7 @@ export interface ChipProps {
16
16
  label: string
17
17
  selected?: boolean
18
18
  onPress?: () => void
19
- /** JSX icon rendered before the label. */
20
19
  icon?: React.ReactNode
21
- /** Icon name from @expo/vector-icons resolved automatically. */
22
20
  iconName?: string
23
21
  style?: ViewStyle
24
22
  accessibilityLabel?: string
@@ -27,9 +25,7 @@ export interface ChipProps {
27
25
  export interface ChipOption {
28
26
  label: string
29
27
  value: string | number
30
- /** Icon name resolved via renderIcon (Feather, AntDesign, etc.). */
31
28
  iconName?: string
32
- /** Icon tint color override. */
33
29
  iconColor?: string
34
30
  disabled?: boolean
35
31
  }
@@ -38,12 +34,11 @@ export interface ChipGroupProps {
38
34
  options: ChipOption[]
39
35
  value?: string | number | (string | number)[]
40
36
  onValueChange?: (value: string | number | (string | number)[]) => void
41
- /** When true, allows selecting multiple chips. `value` and `onValueChange` will use arrays. */
42
37
  multiSelect?: boolean
43
38
  style?: ViewStyle
44
39
  }
45
40
 
46
- export function Chip({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }: ChipProps) {
41
+ function ChipBase({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }: ChipProps) {
47
42
  const { colors } = useTheme()
48
43
  const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
49
44
  pressScale: PRESS_SCALE.chip,
@@ -91,27 +86,24 @@ export function Chip({ label, selected = false, onPress, icon, iconName, style,
91
86
  )
92
87
  }
93
88
 
89
+ export const Chip = React.memo(ChipBase)
90
+
94
91
  export function ChipGroup({ options, value, onValueChange, multiSelect = false, style }: ChipGroupProps) {
95
92
  const handlePress = (optionValue: string | number) => {
96
93
  if (!multiSelect) {
97
94
  onValueChange?.(optionValue)
98
95
  return
99
96
  }
100
-
101
97
  const currentArray = Array.isArray(value) ? value : value ? [value] : []
102
98
  const isSelected = currentArray.includes(optionValue)
103
-
104
99
  const newArray: (string | number)[] = isSelected
105
100
  ? currentArray.filter((v) => v !== optionValue)
106
101
  : [...currentArray, optionValue]
107
-
108
102
  onValueChange?.(newArray)
109
103
  }
110
104
 
111
105
  const isSelected = (optionValue: string | number): boolean => {
112
- if (Array.isArray(value)) {
113
- return value.includes(optionValue)
114
- }
106
+ if (Array.isArray(value)) return value.includes(optionValue)
115
107
  return optionValue === value
116
108
  }
117
109
 
@@ -124,7 +116,11 @@ export function ChipGroup({ options, value, onValueChange, multiSelect = false,
124
116
  selected={isSelected(opt.value)}
125
117
  onPress={opt.disabled ? undefined : () => handlePress(opt.value)}
126
118
  iconName={opt.iconName}
127
- 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}
128
124
  />
129
125
  ))}
130
126
  </View>
@@ -136,19 +132,25 @@ const styles = StyleSheet.create({
136
132
  chip: {
137
133
  borderRadius: 999,
138
134
  paddingHorizontal: s(14),
139
- 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,
140
139
  borderWidth: 1,
141
140
  alignItems: 'center',
142
141
  justifyContent: 'center',
143
142
  flexDirection: 'row',
144
143
  gap: s(5),
145
144
  },
145
+ chipDisabled: {
146
+ opacity: 0.4,
147
+ },
146
148
  chipIcon: {
147
149
  alignItems: 'center',
148
150
  justifyContent: 'center',
149
151
  },
150
152
  label: {
151
- fontFamily: 'Poppins-Medium',
153
+ fontFamily: 'Sohne-Medium',
152
154
  fontSize: ms(13),
153
155
  lineHeight: mvs(18),
154
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'
@@ -44,7 +44,7 @@ const sizeMap: Record<IconButtonSize, { container: number; icon: number }> = {
44
44
  lg: { container: s(52), icon: 24 },
45
45
  }
46
46
 
47
- export function IconButton({
47
+ function IconButtonBase({
48
48
  iconName,
49
49
  icon,
50
50
  iconColor,
@@ -151,6 +151,8 @@ export function IconButton({
151
151
  )
152
152
  }
153
153
 
154
+ export const IconButton = React.memo(IconButtonBase)
155
+
154
156
  const styles = StyleSheet.create({
155
157
  wrapper: {
156
158
  alignSelf: 'flex-start',
@@ -182,7 +184,7 @@ const styles = StyleSheet.create({
182
184
  paddingHorizontal: 3,
183
185
  },
184
186
  badgeText: {
185
- fontFamily: 'Poppins-Bold',
187
+ fontFamily: 'Sohne-Bold',
186
188
  fontSize: ms(9),
187
189
  lineHeight: 14,
188
190
  },
@@ -3,6 +3,7 @@ import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, TextStyle
3
3
  import Animated, {
4
4
  useAnimatedStyle,
5
5
  interpolateColor,
6
+ interpolate,
6
7
  } from 'react-native-reanimated'
7
8
  import { AntDesign } from '@expo/vector-icons'
8
9
  import { useTheme } from '../../theme'
@@ -11,46 +12,26 @@ import { renderIcon } from '../../utils/icons'
11
12
  import { useColorTransition } from '../../utils/useColorTransition'
12
13
  import { TIMINGS } from '../../utils/animations'
13
14
 
14
- const webInputResetStyle: any =
15
+ const webInputResetStyle: Record<string, unknown> =
15
16
  Platform.OS === 'web'
16
17
  ? { outlineStyle: 'none', outlineWidth: 0, outlineColor: 'transparent', boxShadow: 'none' }
17
18
  : {}
18
19
 
19
20
  export interface InputProps extends TextInputProps {
20
21
  label?: string
21
- /** Red helper text below the input; also changes border to `destructive` color. Takes priority over `hint`. */
22
22
  error?: string
23
- /** Helper text shown below the input when there is no error. */
24
23
  hint?: string
25
- /** Disabled visual state — dimmed appearance, not editable. Also sets `editable={false}`. */
26
24
  disabled?: boolean
27
- /** Text or component rendered before the input text. */
28
25
  prefix?: React.ReactNode
29
- /** Text or component rendered after the input text. */
30
26
  suffix?: React.ReactNode
31
- /** Style applied to prefix text if prefix is a string. */
32
27
  prefixStyle?: TextStyle
33
- /** Style applied to suffix text if suffix is a string. */
34
28
  suffixStyle?: TextStyle
35
- /**
36
- * Icon name from `@expo/vector-icons` rendered before the input text.
37
- * See https://icons.expo.fyi. Takes precedence over `prefix`.
38
- */
39
29
  prefixIcon?: string
40
- /**
41
- * Icon name from `@expo/vector-icons` rendered after the input text.
42
- * See https://icons.expo.fyi. Takes precedence over `suffix` (unless `type="password"`).
43
- */
44
30
  suffixIcon?: string
45
- /** Override the resolved prefix icon color. Defaults to `mutedForeground`. */
46
31
  prefixIconColor?: string
47
- /** Override the resolved suffix icon color. Defaults to `mutedForeground`. */
48
32
  suffixIconColor?: string
49
- /** Input type. When set to \`'password'\`, shows a toggle button to reveal/hide text. */
50
33
  type?: 'text' | 'password'
51
- /** Style for the outer container \`View\`. Use \`style\` (from \`TextInputProps\`) to style the \`TextInput\` itself. */
52
34
  containerStyle?: ViewStyle
53
- /** Style for the inner border wrapper (overrides padding, etc). */
54
35
  inputWrapperStyle?: ViewStyle
55
36
  }
56
37
 
@@ -59,7 +40,6 @@ export function Input({ label, error, hint, disabled, prefix, suffix, prefixStyl
59
40
  const [focused, setFocused] = useState(false)
60
41
  const [showPassword, setShowPassword] = useState(false)
61
42
 
62
- // Asymmetric durations — focus snaps in, blurs out subtly. Runs on UI thread.
63
43
  const focusProgress = useColorTransition(focused, {
64
44
  duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration,
65
45
  })
@@ -75,6 +55,8 @@ export function Input({ label, error, hint, disabled, prefix, suffix, prefixStyl
75
55
  const effectiveSuffix: React.ReactNode = isPassword && !suffix && !suffixIcon ? (
76
56
  <TouchableOpacity
77
57
  onPress={() => setShowPassword(!showPassword)}
58
+ // AUDIT FIX: was padding: s(4) → ~28px target (below 44px minimum).
59
+ // padding: s(12) with negative margin keeps visual size but expands hit area.
78
60
  style={styles.passwordToggle}
79
61
  activeOpacity={0.6}
80
62
  accessibilityRole="button"
@@ -86,10 +68,16 @@ export function Input({ label, error, hint, disabled, prefix, suffix, prefixStyl
86
68
  ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted)
87
69
  : suffix
88
70
 
89
- const borderColorStyle = useAnimatedStyle(() => ({
71
+ // Border drawn on an absolute overlay so the 1px→2px weight change never
72
+ // resizes the layout box (which would reflow content / shift the interface).
73
+ // Wrapper keeps borderWidth: 0; overlay grows inward and is non-interactive.
74
+ const borderAnimStyle = useAnimatedStyle(() => ({
90
75
  borderColor: error
91
76
  ? colors.destructive
92
77
  : interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
78
+ borderWidth: error
79
+ ? 2
80
+ : interpolate(focusProgress.value, [0, 1], [1, 2]),
93
81
  }))
94
82
 
95
83
  return (
@@ -99,10 +87,10 @@ export function Input({ label, error, hint, disabled, prefix, suffix, prefixStyl
99
87
  style={[
100
88
  styles.inputWrapper,
101
89
  { backgroundColor: isDisabled ? colors.surface : colors.background },
102
- borderColorStyle,
103
90
  inputWrapperStyle,
104
91
  ]}
105
92
  >
93
+ <Animated.View style={[styles.borderOverlay, borderAnimStyle]} pointerEvents="none" />
106
94
  {effectivePrefix ? (
107
95
  typeof effectivePrefix === 'string' ? (
108
96
  <Text style={[styles.prefixText, { color: colors.foregroundMuted }, prefixStyle]} allowFontScaling={true}>
@@ -168,20 +156,25 @@ const styles = StyleSheet.create({
168
156
  opacity: 0.6,
169
157
  },
170
158
  label: {
171
- fontFamily: 'Poppins-Medium',
159
+ fontFamily: 'Sohne-Medium',
172
160
  fontSize: ms(14),
173
161
  },
174
162
  inputWrapper: {
175
163
  flexDirection: 'row',
176
164
  alignItems: 'center',
177
- borderWidth: 2,
165
+ // Border lives on borderOverlay (absolute) so its 1px→2px focus change
166
+ // never resizes this box. Wrapper itself carries no border.
178
167
  borderRadius: 8,
179
168
  paddingHorizontal: s(14),
180
169
  paddingVertical: vs(11),
181
170
  minHeight: 48,
182
171
  },
172
+ borderOverlay: {
173
+ ...StyleSheet.absoluteFillObject,
174
+ borderRadius: 8,
175
+ },
183
176
  input: {
184
- fontFamily: 'Poppins-Regular',
177
+ fontFamily: 'Sohne-Regular',
185
178
  flex: 1,
186
179
  fontSize: ms(16),
187
180
  paddingVertical: vs(2),
@@ -191,7 +184,7 @@ const styles = StyleSheet.create({
191
184
  marginRight: s(8),
192
185
  },
193
186
  prefixText: {
194
- fontFamily: 'Poppins-Regular',
187
+ fontFamily: 'Sohne-Regular',
195
188
  fontSize: ms(15),
196
189
  marginRight: s(8),
197
190
  },
@@ -199,15 +192,18 @@ const styles = StyleSheet.create({
199
192
  marginLeft: s(8),
200
193
  },
201
194
  suffixText: {
202
- fontFamily: 'Poppins-Regular',
195
+ fontFamily: 'Sohne-Regular',
203
196
  fontSize: ms(15),
204
197
  marginLeft: s(8),
205
198
  },
199
+ // AUDIT FIX: was padding: s(4) → ~28px tap target. Now 12px padding → ~44px.
200
+ // Negative margin compensates so the visual icon position is unchanged.
206
201
  passwordToggle: {
207
- padding: s(4),
202
+ padding: s(12),
203
+ margin: -s(8),
208
204
  },
209
205
  helperText: {
210
- fontFamily: 'Poppins-Regular',
206
+ fontFamily: 'Sohne-Regular',
211
207
  fontSize: ms(13),
212
208
  },
213
209
  })
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } 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 interface LabelValueProps {
@@ -14,7 +14,7 @@ export interface LabelValueProps {
14
14
  style?: ViewStyle
15
15
  }
16
16
 
17
- export function LabelValue({ label, value, iconName, iconColor, style }: LabelValueProps) {
17
+ function LabelValueBase({ label, value, iconName, iconColor, style }: LabelValueProps) {
18
18
  const { colors } = useTheme()
19
19
 
20
20
  const resolvedIcon = iconName
@@ -40,6 +40,8 @@ export function LabelValue({ label, value, iconName, iconColor, style }: LabelVa
40
40
  )
41
41
  }
42
42
 
43
+ export const LabelValue = React.memo(LabelValueBase)
44
+
43
45
  const styles = StyleSheet.create({
44
46
  container: {
45
47
  flexDirection: 'row',
@@ -57,12 +59,12 @@ const styles = StyleSheet.create({
57
59
  justifyContent: 'center',
58
60
  },
59
61
  label: {
60
- fontFamily: 'Poppins-Regular',
62
+ fontFamily: 'Sohne-Regular',
61
63
  fontSize: ms(13),
62
64
  lineHeight: mvs(18),
63
65
  },
64
66
  value: {
65
- fontFamily: 'Poppins-Medium',
67
+ fontFamily: 'Sohne-Medium',
66
68
  fontSize: ms(14),
67
69
  lineHeight: mvs(20),
68
70
  textAlign: 'right',