@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,73 +1,37 @@
1
- import React, { useRef } from 'react'
2
- import { View, Text, TouchableOpacity, Animated, StyleSheet, ViewStyle, TextStyle, Platform } from 'react-native'
1
+ import React from 'react'
2
+ import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, TextStyle } from 'react-native'
3
+ import Animated from 'react-native-reanimated'
3
4
  import { impactLight } from '../../utils/haptics'
4
5
  import { useTheme } from '../../theme'
5
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
6
7
  import { RADIUS } from '../../tokens'
7
-
8
- const nativeDriver = Platform.OS !== 'web'
8
+ import { usePressScale } from '../../utils/usePressScale'
9
+ import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
9
10
 
10
11
  export type CardVariant = 'elevated' | 'outlined' | 'filled'
11
12
 
12
13
  export interface CardProps {
13
14
  children: React.ReactNode
14
- /** Visual style variant. `'elevated'` (default) has shadow, `'outlined'` has border only, `'filled'` uses accent background. */
15
15
  variant?: CardVariant
16
- /** Makes the card tappable. Adds press animation and haptic feedback. */
17
16
  onPress?: () => void
18
17
  style?: ViewStyle
18
+ accessibilityLabel?: string
19
19
  }
20
20
 
21
- export interface CardHeaderProps {
22
- children: React.ReactNode
23
- style?: ViewStyle
24
- }
25
-
26
- export interface CardTitleProps {
27
- children: React.ReactNode
28
- style?: TextStyle
29
- }
21
+ export interface CardHeaderProps { children: React.ReactNode; style?: ViewStyle }
22
+ export interface CardTitleProps { children: React.ReactNode; style?: TextStyle }
23
+ export interface CardDescriptionProps { children: React.ReactNode; style?: TextStyle }
24
+ export interface CardContentProps { children: React.ReactNode; style?: ViewStyle }
25
+ export interface CardFooterProps { children: React.ReactNode; style?: ViewStyle }
30
26
 
31
- export interface CardDescriptionProps {
32
- children: React.ReactNode
33
- style?: TextStyle
34
- }
35
-
36
- export interface CardContentProps {
37
- children: React.ReactNode
38
- style?: ViewStyle
39
- }
40
-
41
- export interface CardFooterProps {
42
- children: React.ReactNode
43
- style?: ViewStyle
44
- }
45
-
46
- export function Card({ children, variant = 'elevated', onPress, style }: CardProps) {
27
+ export function Card({ children, variant = 'elevated', onPress, style, accessibilityLabel }: CardProps) {
47
28
  const { colors } = useTheme()
48
- const scale = useRef(new Animated.Value(1)).current
49
-
50
- const handlePressIn = () => {
51
- if (!onPress) return
52
- Animated.spring(scale, {
53
- toValue: 0.98,
54
- useNativeDriver: nativeDriver,
55
- stiffness: 400,
56
- damping: 30,
57
- mass: 1.0,
58
- }).start()
59
- }
60
-
61
- const handlePressOut = () => {
62
- if (!onPress) return
63
- Animated.spring(scale, {
64
- toValue: 1,
65
- useNativeDriver: nativeDriver,
66
- stiffness: 250,
67
- damping: 24,
68
- mass: 1.0,
69
- }).start()
70
- }
29
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
30
+ pressScale: PRESS_SCALE.card,
31
+ pressInSpring: SPRINGS.surfacePressIn,
32
+ pressOutSpring: SPRINGS.surfacePressOut,
33
+ disabled: !onPress,
34
+ })
71
35
 
72
36
  const handlePress = () => {
73
37
  if (!onPress) return
@@ -78,11 +42,14 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
78
42
  const variantStyle: ViewStyle = {
79
43
  elevated: {
80
44
  backgroundColor: colors.card,
81
- borderColor: colors.border,
45
+ // AUDIT FIX: removed borderColor — shadow is the depth signal; a border on
46
+ // top of a shadow creates redundant double-framing that reads as "heavy"
47
+ // rather than "elevated". borderWidth: 0 overrides the base style's borderWidth: 1.
48
+ borderWidth: 0,
82
49
  shadowColor: '#000',
83
- shadowOffset: { width: 0, height: 6 },
84
- shadowOpacity: 0.10,
85
- shadowRadius: 16,
50
+ shadowOffset: { width: 0, height: 4 },
51
+ shadowOpacity: 0.09,
52
+ shadowRadius: 14,
86
53
  elevation: 4,
87
54
  },
88
55
  outlined: {
@@ -107,13 +74,15 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
107
74
 
108
75
  if (onPress) {
109
76
  return (
110
- <Animated.View style={{ transform: [{ scale }] }}>
77
+ <Animated.View style={animatedStyle} {...hoverHandlers}>
111
78
  <TouchableOpacity
112
79
  onPress={handlePress}
113
- onPressIn={handlePressIn}
114
- onPressOut={handlePressOut}
80
+ onPressIn={onPressIn}
81
+ onPressOut={onPressOut}
115
82
  activeOpacity={1}
116
83
  touchSoundDisabled={true}
84
+ accessibilityRole="button"
85
+ accessibilityLabel={accessibilityLabel}
117
86
  >
118
87
  {cardContent}
119
88
  </TouchableOpacity>
@@ -136,7 +105,7 @@ export function CardTitle({ children, style }: CardTitleProps) {
136
105
  export function CardDescription({ children, style }: CardDescriptionProps) {
137
106
  const { colors } = useTheme()
138
107
  return (
139
- <Text style={[styles.description, { color: colors.foregroundMuted }, style]} allowFontScaling={true}>{children}</Text>
108
+ <Text style={[styles.description, { color: colors.foregroundSubtle }, style]} allowFontScaling={true}>{children}</Text>
140
109
  )
141
110
  }
142
111
 
@@ -150,7 +119,7 @@ export function CardFooter({ children, style }: CardFooterProps) {
150
119
 
151
120
  const styles = StyleSheet.create({
152
121
  card: {
153
- borderRadius: RADIUS.md, // 14px — Airbnb property card spec
122
+ borderRadius: RADIUS.md,
154
123
  borderWidth: 1,
155
124
  },
156
125
  header: {
@@ -159,12 +128,14 @@ const styles = StyleSheet.create({
159
128
  gap: vs(4),
160
129
  },
161
130
  title: {
162
- fontFamily: 'Poppins-SemiBold',
131
+ fontFamily: 'Sohne-SemiBold',
163
132
  fontSize: ms(16),
164
133
  lineHeight: mvs(22),
165
134
  },
135
+ // AUDIT FIX: was foregroundMuted (2.2:1 fail) — description text now uses
136
+ // foregroundSubtle (5.9:1 ✓) which is readable while still visually secondary.
166
137
  description: {
167
- fontFamily: 'Poppins-Regular',
138
+ fontFamily: 'Sohne-Regular',
168
139
  fontSize: ms(13),
169
140
  lineHeight: mvs(18),
170
141
  },
@@ -1,22 +1,25 @@
1
- import React, { useRef } from 'react'
1
+ import React, { useCallback } from 'react'
2
2
  import {
3
3
  ScrollView,
4
4
  TouchableOpacity,
5
- Animated,
6
5
  Text,
7
6
  View,
8
7
  StyleSheet,
9
8
  ViewStyle,
10
- Platform,
11
9
  } from 'react-native'
10
+ import Animated, {
11
+ useAnimatedStyle,
12
+ interpolateColor,
13
+ } from 'react-native-reanimated'
12
14
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
15
  import { useTheme } from '../../theme'
14
16
  import { s, vs, ms } from '../../utils/scaling'
15
17
  import { renderIcon } from '../../utils/icons'
18
+ import { usePressScale } from '../../utils/usePressScale'
19
+ import { useColorTransition } from '../../utils/useColorTransition'
20
+ import { PRESS_SCALE } from '../../utils/animations'
16
21
  import { RADIUS } from '../../tokens'
17
22
 
18
- const nativeDriver = Platform.OS !== 'web'
19
-
20
23
  export interface CategoryItem {
21
24
  label: string
22
25
  value: string
@@ -36,68 +39,69 @@ export interface CategoryStripProps {
36
39
  style?: ViewStyle
37
40
  /** Style applied to each pill item. */
38
41
  itemStyle?: ViewStyle
42
+ accessibilityLabel?: string
39
43
  }
40
44
 
41
- function CategoryChip({
45
+ const CategoryChip = React.memo(function CategoryChip({
42
46
  item,
43
47
  selected,
44
- onPress,
48
+ onSelect,
45
49
  }: {
46
50
  item: CategoryItem
47
51
  selected: boolean
48
- onPress: () => void
52
+ onSelect: (value: string) => void
49
53
  }) {
50
54
  const { colors } = useTheme()
51
- const scale = useRef(new Animated.Value(1)).current
52
-
53
- const handlePressIn = () => {
54
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
55
- }
55
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
56
+ pressScale: PRESS_SCALE.chip,
57
+ })
58
+ const progress = useColorTransition(selected)
56
59
 
57
- const handlePressOut = () => {
58
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
59
- }
60
+ const surfaceStyle = useAnimatedStyle(() => ({
61
+ backgroundColor: interpolateColor(progress.value, [0, 1], [colors.surface, colors.primary]),
62
+ borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
63
+ }))
60
64
 
61
- const bgColor = selected ? colors.primary : colors.surface
62
- const textColor = selected ? colors.primaryForeground : colors.foregroundSubtle
63
- const borderColor = selected ? colors.primary : colors.border
65
+ const textColorStyle = useAnimatedStyle(() => ({
66
+ color: interpolateColor(progress.value, [0, 1], [colors.foregroundSubtle, colors.primaryForeground]),
67
+ }))
64
68
 
69
+ // Static color for icon — icon families take a static color prop, not animated.
70
+ const iconColor = selected ? colors.primaryForeground : colors.foregroundSubtle
65
71
  const resolvedIcon =
66
72
  typeof item.icon === 'string'
67
- ? renderIcon(item.icon, 16, textColor)
73
+ ? renderIcon(item.icon, 16, iconColor)
68
74
  : item.icon ?? null
69
75
 
70
76
  return (
71
- <Animated.View style={{ transform: [{ scale }] }}>
77
+ <Animated.View style={scaleStyle} {...hoverHandlers}>
72
78
  <TouchableOpacity
73
- style={[
74
- styles.chip,
75
- {
76
- backgroundColor: bgColor,
77
- borderColor,
78
- },
79
- ]}
80
- onPress={onPress}
81
- onPressIn={handlePressIn}
82
- onPressOut={handlePressOut}
79
+ onPress={() => onSelect(item.value)}
80
+ onPressIn={onPressIn}
81
+ onPressOut={onPressOut}
83
82
  activeOpacity={1}
84
83
  touchSoundDisabled={true}
84
+ accessibilityRole="button"
85
+ accessibilityLabel={item.label}
86
+ accessibilityState={{ selected }}
85
87
  >
86
- {resolvedIcon && <View style={styles.chipIcon}>{resolvedIcon}</View>}
87
- <Text style={[styles.chipLabel, { color: textColor }]} allowFontScaling={true}>
88
- {item.label}
89
- </Text>
90
- {item.badge !== undefined && item.badge > 0 && (
91
- <View style={[styles.chipBadge, { backgroundColor: colors.primary }]}>
92
- <Text style={[styles.chipBadgeText, { color: colors.primaryForeground }]}>
93
- {Math.min(item.badge, 99)}
94
- </Text>
95
- </View>
96
- )}
88
+ <Animated.View style={[styles.chip, surfaceStyle]}>
89
+ {resolvedIcon && <View style={styles.chipIcon}>{resolvedIcon}</View>}
90
+ <Animated.Text style={[styles.chipLabel, textColorStyle]} allowFontScaling={true}>
91
+ {item.label}
92
+ </Animated.Text>
93
+ {item.badge !== undefined && item.badge > 0 && (
94
+ <View style={[styles.chipBadge, { backgroundColor: colors.primary }]}>
95
+ <Text style={[styles.chipBadgeText, { color: colors.primaryForeground }]}>
96
+ {Math.min(item.badge, 99)}
97
+ </Text>
98
+ </View>
99
+ )}
100
+ </Animated.View>
97
101
  </TouchableOpacity>
98
102
  </Animated.View>
99
103
  )
100
- }
104
+ })
101
105
 
102
106
  export function CategoryStrip({
103
107
  categories,
@@ -106,21 +110,25 @@ export function CategoryStrip({
106
110
  multiSelect = false,
107
111
  style,
108
112
  itemStyle,
113
+ accessibilityLabel,
109
114
  }: CategoryStripProps) {
110
115
  const selected = Array.isArray(value) ? value : value ? [value] : []
111
116
 
112
- const handlePress = (v: string) => {
113
- hapticSelection()
114
- if (multiSelect) {
115
- const current = Array.isArray(value) ? value : value ? [value] : []
116
- const next = current.includes(v)
117
- ? current.filter((x) => x !== v)
118
- : [...current, v]
119
- onValueChange?.(next)
120
- } else {
121
- onValueChange?.(v === value ? '' : v)
122
- }
123
- }
117
+ const handlePress = useCallback(
118
+ (v: string) => {
119
+ hapticSelection()
120
+ if (multiSelect) {
121
+ const current = Array.isArray(value) ? value : value ? [value] : []
122
+ const next = current.includes(v)
123
+ ? current.filter((x) => x !== v)
124
+ : [...current, v]
125
+ onValueChange?.(next)
126
+ } else {
127
+ onValueChange?.(v === value ? '' : v)
128
+ }
129
+ },
130
+ [multiSelect, value, onValueChange],
131
+ )
124
132
 
125
133
  return (
126
134
  <ScrollView
@@ -128,13 +136,15 @@ export function CategoryStrip({
128
136
  showsHorizontalScrollIndicator={false}
129
137
  contentContainerStyle={[styles.container, style]}
130
138
  style={styles.scroll}
139
+ accessibilityRole={multiSelect ? undefined : 'radiogroup'}
140
+ accessibilityLabel={accessibilityLabel}
131
141
  >
132
142
  {categories.map((cat) => (
133
143
  <View key={cat.value} style={itemStyle}>
134
144
  <CategoryChip
135
145
  item={cat}
136
146
  selected={selected.includes(cat.value)}
137
- onPress={() => handlePress(cat.value)}
147
+ onSelect={handlePress}
138
148
  />
139
149
  </View>
140
150
  ))}
@@ -166,7 +176,7 @@ const styles = StyleSheet.create({
166
176
  justifyContent: 'center',
167
177
  },
168
178
  chipLabel: {
169
- fontFamily: 'Poppins-Medium',
179
+ fontFamily: 'Sohne-Medium',
170
180
  fontSize: ms(13),
171
181
  },
172
182
  chipBadge: {
@@ -178,7 +188,7 @@ const styles = StyleSheet.create({
178
188
  justifyContent: 'center',
179
189
  },
180
190
  chipBadgeText: {
181
- fontFamily: 'Poppins-Bold',
191
+ fontFamily: 'Sohne-Bold',
182
192
  fontSize: ms(9),
183
193
  lineHeight: 14,
184
194
  },
@@ -1,10 +1,16 @@
1
- import React, { useRef, useEffect } 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
+ interpolateColor,
6
+ withTiming,
7
+ } from 'react-native-reanimated'
3
8
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
4
-
5
- const nativeDriver = Platform.OS !== 'web'
6
9
  import { useTheme } from '../../theme'
7
10
  import { s, vs, ms, mvs } from '../../utils/scaling'
11
+ import { usePressScale } from '../../utils/usePressScale'
12
+ import { useColorTransition } from '../../utils/useColorTransition'
13
+ import { PRESS_SCALE, TIMINGS, EASINGS } from '../../utils/animations'
8
14
 
9
15
  export interface CheckboxProps {
10
16
  checked?: boolean
@@ -12,6 +18,7 @@ export interface CheckboxProps {
12
18
  label?: string
13
19
  disabled?: boolean
14
20
  style?: ViewStyle
21
+ accessibilityLabel?: string
15
22
  }
16
23
 
17
24
  export function Checkbox({
@@ -20,78 +27,53 @@ export function Checkbox({
20
27
  label,
21
28
  disabled,
22
29
  style,
30
+ accessibilityLabel,
23
31
  }: CheckboxProps) {
24
32
  const { colors } = useTheme()
25
- const scale = useRef(new Animated.Value(1)).current
26
- const bgOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current
27
- const checkOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current
28
-
29
- useEffect(() => {
30
- Animated.parallel([
31
- Animated.timing(bgOpacity, {
32
- toValue: checked ? 1 : 0,
33
- duration: 150,
34
- useNativeDriver: false,
35
- }),
36
- Animated.timing(checkOpacity, {
37
- toValue: checked ? 1 : 0,
38
- duration: 120,
39
- useNativeDriver: false,
40
- }),
41
- ]).start()
42
- }, [checked, bgOpacity, checkOpacity])
43
-
44
- const borderColor = bgOpacity.interpolate({
45
- inputRange: [0, 1],
46
- outputRange: [colors.border, colors.primary],
33
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
34
+ pressScale: PRESS_SCALE.button,
35
+ disabled,
47
36
  })
37
+ const progress = useColorTransition(checked)
48
38
 
49
- const backgroundColor = bgOpacity.interpolate({
50
- inputRange: [0, 1],
51
- outputRange: ['transparent', colors.primary],
52
- })
39
+ const boxStyle = useAnimatedStyle(() => ({
40
+ borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
41
+ backgroundColor: interpolateColor(progress.value, [0, 1], ['transparent', colors.primary]),
42
+ }))
53
43
 
54
- const handlePressIn = () => {
55
- if (disabled) return
56
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
57
- }
58
-
59
- const handlePressOut = () => {
60
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
61
- }
44
+ const checkStyle = useAnimatedStyle(() => ({
45
+ opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
46
+ }))
62
47
 
63
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.
64
52
  <TouchableOpacity
65
- style={[styles.row, style]}
53
+ style={[styles.row, disabled && styles.rowDisabled, style]}
66
54
  onPress={() => {
67
55
  hapticSelection()
68
56
  onCheckedChange?.(!checked)
69
57
  }}
70
- onPressIn={handlePressIn}
71
- onPressOut={handlePressOut}
58
+ onPressIn={onPressIn}
59
+ onPressOut={onPressOut}
72
60
  disabled={disabled}
73
61
  activeOpacity={1}
74
62
  touchSoundDisabled={true}
63
+ accessibilityRole="checkbox"
64
+ accessibilityLabel={accessibilityLabel ?? label}
65
+ accessibilityState={{ checked, disabled: !!disabled }}
75
66
  >
76
- <Animated.View style={{ transform: [{ scale }] }}>
77
- <Animated.View
78
- style={[
79
- styles.box,
80
- {
81
- borderColor,
82
- backgroundColor,
83
- opacity: disabled ? 0.45 : 1,
84
- },
85
- ]}
86
- >
87
- <Animated.View style={{ opacity: checkOpacity }}>
67
+ <Animated.View style={scaleStyle}>
68
+ <Animated.View style={[styles.box, boxStyle]}>
69
+ <Animated.View style={checkStyle}>
88
70
  <View style={[styles.checkmark, { borderColor: colors.primaryForeground }]} />
89
71
  </Animated.View>
90
72
  </Animated.View>
91
73
  </Animated.View>
92
74
  {label ? (
93
75
  <Text
94
- style={[styles.label, { color: disabled ? colors.foregroundMuted : colors.foreground }]}
76
+ style={[styles.label, { color: colors.foreground }]}
95
77
  allowFontScaling={true}
96
78
  >
97
79
  {label}
@@ -107,6 +89,10 @@ const styles = StyleSheet.create({
107
89
  alignItems: 'center',
108
90
  gap: s(12),
109
91
  },
92
+ // AUDIT FIX: was inline opacity on the box only
93
+ rowDisabled: {
94
+ opacity: 0.45,
95
+ },
110
96
  box: {
111
97
  width: s(24),
112
98
  height: s(24),
@@ -123,7 +109,7 @@ const styles = StyleSheet.create({
123
109
  transform: [{ rotate: '-45deg' }, { translateY: -1 }],
124
110
  },
125
111
  label: {
126
- fontFamily: 'Poppins-Regular',
112
+ fontFamily: 'Sohne-Regular',
127
113
  fontSize: ms(14),
128
114
  lineHeight: mvs(20),
129
115
  },