@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
@@ -25,6 +25,21 @@ export { BottomSheetModalProvider }
25
25
  // Re-export BottomSheetTextInput as SheetTextInput for consumer convenience
26
26
  export { BottomSheetTextInput as SheetTextInput }
27
27
 
28
+ export interface SheetHeaderProps {
29
+ children: React.ReactNode
30
+ style?: ViewStyle
31
+ }
32
+
33
+ export interface SheetContentProps {
34
+ children: React.ReactNode
35
+ style?: ViewStyle
36
+ }
37
+
38
+ export interface SheetFooterProps {
39
+ children: React.ReactNode
40
+ style?: ViewStyle
41
+ }
42
+
28
43
  export interface SheetProps {
29
44
  open: boolean
30
45
  onClose: () => void
@@ -80,6 +95,23 @@ export interface SheetProps {
80
95
  snapPoints?: (string | number)[]
81
96
  }
82
97
 
98
+ export function SheetHeader({ children, style }: SheetHeaderProps) {
99
+ return <View style={[styles.header, style]}>{children}</View>
100
+ }
101
+
102
+ export function SheetContent({ children, style }: SheetContentProps) {
103
+ return <View style={[styles.sheetContent, style]}>{children}</View>
104
+ }
105
+
106
+ export function SheetFooter({ children, style }: SheetFooterProps) {
107
+ const { colors } = useTheme()
108
+ return (
109
+ <View style={[styles.sheetFooter, { backgroundColor: colors.card, borderTopColor: colors.border }, style]}>
110
+ {children}
111
+ </View>
112
+ )
113
+ }
114
+
83
115
  export function Sheet({
84
116
  open,
85
117
  onClose,
@@ -125,20 +157,25 @@ export function Sheet({
125
157
  />
126
158
  ), [])
127
159
 
128
- const renderFooter = useCallback((props: BottomSheetFooterProps) => {
129
- if (!footer) return null
130
- return (
131
- <BottomSheetFooter {...props}>
132
- {footer}
133
- </BottomSheetFooter>
134
- )
135
- }, [footer])
160
+ // Detect compound components in children
161
+ const childArray = React.Children.toArray(children)
162
+ const customHeader = childArray.find((child) => React.isValidElement(child) && child.type === SheetHeader)
163
+ const customContent = childArray.find((child) => React.isValidElement(child) && child.type === SheetContent)
164
+ const customFooter = childArray.find((child) => React.isValidElement(child) && child.type === SheetFooter)
165
+
166
+ // If using compound components, filter them out from main children
167
+ const filteredChildren = customHeader || customContent || customFooter
168
+ ? childArray.filter(
169
+ (child) =>
170
+ !React.isValidElement(child) ||
171
+ (child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
172
+ )
173
+ : children
136
174
 
137
175
  const effectiveSubtitle = subtitle ?? description
176
+ const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader
138
177
 
139
- const showHeader = !!(title || effectiveSubtitle || showCloseButton)
140
-
141
- const headerNode = showHeader ? (
178
+ const headerNode = customHeader ? customHeader : (showHeader ? (
142
179
  <View style={styles.header} accessibilityRole="header">
143
180
  <View style={styles.headerRow}>
144
181
  {title ? (
@@ -166,7 +203,19 @@ export function Sheet({
166
203
  </Text>
167
204
  ) : null}
168
205
  </View>
169
- ) : null
206
+ ) : null)
207
+
208
+ const contentNode = customContent ? customContent : filteredChildren
209
+ const effectiveFooter = customFooter ? customFooter : footer
210
+
211
+ const renderFooter = useCallback((props: BottomSheetFooterProps) => {
212
+ if (!effectiveFooter) return null
213
+ return (
214
+ <BottomSheetFooter {...props}>
215
+ {effectiveFooter}
216
+ </BottomSheetFooter>
217
+ )
218
+ }, [effectiveFooter])
170
219
 
171
220
  const useScroll = scrollable || !!maxHeight
172
221
  const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT
@@ -182,7 +231,7 @@ export function Sheet({
182
231
  maxDynamicContentSize={useDynamicSizing ? effectiveMaxHeight : undefined}
183
232
  onDismiss={onClose}
184
233
  backdropComponent={renderBackdrop}
185
- footerComponent={footer ? renderFooter : undefined}
234
+ footerComponent={effectiveFooter ? renderFooter : undefined}
186
235
  backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
187
236
  handleIndicatorStyle={[styles.handle, { backgroundColor: colors.border }]}
188
237
  enablePanDownToClose
@@ -204,18 +253,22 @@ export function Sheet({
204
253
  persistentScrollbar={isAndroid}
205
254
  >
206
255
  {headerNode}
207
- {children}
256
+ {contentNode}
208
257
  </BottomSheetScrollView>
209
258
  ) : (
210
259
  <BottomSheetView style={[styles.content, contentStyle, style]}>
211
260
  {headerNode}
212
- {children}
261
+ {contentNode}
213
262
  </BottomSheetView>
214
263
  )}
215
264
  </BottomSheetModal>
216
265
  )
217
266
  }
218
267
 
268
+ Sheet.Header = SheetHeader
269
+ Sheet.Content = SheetContent
270
+ Sheet.Footer = SheetFooter
271
+
219
272
  const styles = StyleSheet.create({
220
273
  background: {
221
274
  borderTopLeftRadius: ms(16),
@@ -238,12 +291,12 @@ const styles = StyleSheet.create({
238
291
  justifyContent: 'space-between',
239
292
  },
240
293
  title: {
241
- fontFamily: 'Poppins-SemiBold',
294
+ fontFamily: 'Sohne-SemiBold',
242
295
  fontSize: ms(18),
243
296
  flex: 1,
244
297
  },
245
298
  subtitle: {
246
- fontFamily: 'Poppins-Regular',
299
+ fontFamily: 'Sohne-Regular',
247
300
  fontSize: ms(14),
248
301
  lineHeight: mvs(20),
249
302
  },
@@ -260,4 +313,15 @@ const styles = StyleSheet.create({
260
313
  paddingBottom: vs(32),
261
314
  paddingRight: s(16),
262
315
  },
316
+ sheetContent: {
317
+ gap: vs(16),
318
+ },
319
+ sheetFooter: {
320
+ paddingHorizontal: s(16),
321
+ paddingVertical: vs(16),
322
+ borderTopWidth: 1,
323
+ flexDirection: 'row',
324
+ gap: s(12),
325
+ },
263
326
  })
327
+
@@ -74,7 +74,7 @@ export function Skeleton({
74
74
  <View
75
75
  style={[
76
76
  styles.base,
77
- { width: resolvedWidth as any, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
77
+ { width: resolvedWidth as number | `${number}%`, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
78
78
  style,
79
79
  ]}
80
80
  onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
@@ -101,11 +101,11 @@ const styles = StyleSheet.create({
101
101
  alignItems: 'center',
102
102
  },
103
103
  label: {
104
- fontFamily: 'Poppins-Medium',
104
+ fontFamily: 'Sohne-Medium',
105
105
  fontSize: ms(15),
106
106
  },
107
107
  valueText: {
108
- fontFamily: 'Poppins-Medium',
108
+ fontFamily: 'Sohne-Medium',
109
109
  fontSize: ms(14),
110
110
  },
111
111
  slider: {
@@ -49,7 +49,7 @@ const styles = StyleSheet.create({
49
49
  gap: vs(6),
50
50
  },
51
51
  label: {
52
- fontFamily: 'Poppins-Regular',
52
+ fontFamily: 'Sohne-Regular',
53
53
  lineHeight: mvs(18),
54
54
  },
55
55
  })
@@ -13,12 +13,12 @@ import { useTheme } from '../../theme'
13
13
  import { s } from '../../utils/scaling'
14
14
  import { SPRINGS, TIMINGS, EASINGS } from '../../utils/animations'
15
15
 
16
- const TRACK_WIDTH = s(52)
16
+ const TRACK_WIDTH = s(52)
17
17
  const TRACK_HEIGHT = s(30)
18
- const THUMB_SIZE = s(24)
18
+ const THUMB_SIZE = s(24)
19
19
  const THUMB_OFFSET = s(3)
20
20
  const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
21
- const ICON_SIZE = s(13)
21
+ const ICON_SIZE = s(13)
22
22
 
23
23
  export interface SwitchProps {
24
24
  checked?: boolean
@@ -31,7 +31,6 @@ export interface SwitchProps {
31
31
  export function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }: SwitchProps) {
32
32
  const { colors } = useTheme()
33
33
 
34
- // Single 0→1 progress drives thumb position, track color, and icon crossfade — all UI thread.
35
34
  const progress = useSharedValue(checked ? 1 : 0)
36
35
 
37
36
  useEffect(() => {
@@ -50,6 +49,19 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
50
49
  ),
51
50
  }))
52
51
 
52
+ // AUDIT FIX: the off-state track used surfaceStrong (~#ebebeb in light mode)
53
+ // with no border — nearly invisible on white page/card surfaces. A 1.5px border
54
+ // that fades out as the track fills gives the off state clear visual definition
55
+ // without adding visual weight to the on state.
56
+ const trackBorderStyle = useAnimatedStyle(() => ({
57
+ borderWidth: 1.5,
58
+ borderColor: interpolateColor(
59
+ progress.value,
60
+ [0, 1],
61
+ [colors.border, 'transparent'],
62
+ ),
63
+ }))
64
+
53
65
  const checkIconStyle = useAnimatedStyle(() => ({
54
66
  opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
55
67
  }))
@@ -59,7 +71,7 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
59
71
  }))
60
72
 
61
73
  return (
62
- <View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
74
+ <View style={[{ opacity: disabled ? 0.45 : 1, alignSelf: 'flex-start' }, style]}>
63
75
  <TouchableOpacity
64
76
  onPress={() => {
65
77
  hapticSelection()
@@ -71,8 +83,10 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
71
83
  accessibilityRole="switch"
72
84
  accessibilityLabel={accessibilityLabel}
73
85
  accessibilityState={{ checked, disabled: !!disabled }}
86
+ style={styles.touchable}
74
87
  >
75
88
  <Animated.View style={[styles.track, trackStyle]}>
89
+ <Animated.View style={[styles.trackBorder, trackBorderStyle]} pointerEvents="none" />
76
90
  <Animated.View
77
91
  style={[styles.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]}
78
92
  >
@@ -90,11 +104,18 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
90
104
  }
91
105
 
92
106
  const styles = StyleSheet.create({
107
+ touchable: {
108
+ alignSelf: 'flex-start',
109
+ },
93
110
  track: {
94
111
  width: TRACK_WIDTH,
95
112
  height: TRACK_HEIGHT,
96
113
  borderRadius: TRACK_HEIGHT / 2,
97
114
  },
115
+ trackBorder: {
116
+ ...StyleSheet.absoluteFillObject,
117
+ borderRadius: TRACK_HEIGHT / 2,
118
+ },
98
119
  thumb: {
99
120
  position: 'absolute',
100
121
  top: THUMB_OFFSET,
@@ -112,5 +133,7 @@ const styles = StyleSheet.create({
112
133
  },
113
134
  iconWrapper: {
114
135
  position: 'absolute',
136
+ alignItems: 'center',
137
+ justifyContent: 'center',
115
138
  },
116
139
  })
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect } from 'react'
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react'
2
2
  import { View, TouchableOpacity, Text, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native'
3
3
  import Animated, {
4
4
  useSharedValue,
@@ -17,8 +17,6 @@ export interface TabItem {
17
17
  icon?: React.ReactNode | ((active: boolean) => React.ReactNode)
18
18
  }
19
19
 
20
- // pill: animated sliding pill background (default)
21
- // underline: 2px bottom border on active tab — Airbnb product-tab style
22
20
  export type TabsVariant = 'pill' | 'underline'
23
21
 
24
22
  export interface TabsProps {
@@ -76,13 +74,16 @@ function TabTrigger({
76
74
  <Animated.View style={animatedStyle}>
77
75
  <View style={styles.triggerInner}>
78
76
  {tab.icon ? (
79
- (typeof tab.icon === 'function' ? (tab.icon as any)(isActive) : tab.icon) as React.ReactNode
77
+ typeof tab.icon === 'function' ? tab.icon(isActive) : tab.icon
80
78
  ) : null}
81
79
  <Text
82
80
  style={[
83
81
  styles.triggerLabel,
82
+ // AUDIT FIX: active state now only changes color, never font metrics.
83
+ // Previously: inactive=Regular, active=Medium (pill) or SemiBold+fontSize14 (underline)
84
+ // The weight/size change caused measurable layout reflow every tab switch.
85
+ // Solution: all labels render at SemiBold always; active = foreground, inactive = foregroundMuted.
84
86
  { color: isActive ? colors.foreground : colors.foregroundMuted },
85
- isActive && (isUnderline ? styles.activeTriggerLabelUnderline : styles.activeTriggerLabel),
86
87
  ]}
87
88
  allowFontScaling={true}
88
89
  >
@@ -100,26 +101,27 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
100
101
  const active = value ?? internal
101
102
 
102
103
  const tabLayouts = useRef<Record<string, { x: number; width: number }>>({})
103
- // Shared values drive the pill position on the UI thread — no JS bridge cost on slide.
104
104
  const pillX = useSharedValue(0)
105
105
  const pillWidth = useSharedValue(0)
106
106
  const initialised = useRef(false)
107
107
 
108
- const animatePill = (tabValue: string, animate: boolean) => {
108
+ const animatePill = useCallback((tabValue: string, animate: boolean) => {
109
109
  const layout = tabLayouts.current[tabValue]
110
110
  if (!layout) return
111
111
  if (animate) {
112
+ // eslint-disable-next-line react-hooks/immutability
112
113
  pillX.value = withSpring(layout.x, SPRINGS.glide)
114
+ // eslint-disable-next-line react-hooks/immutability
113
115
  pillWidth.value = withSpring(layout.width, SPRINGS.glide)
114
116
  } else {
115
117
  pillX.value = layout.x
116
118
  pillWidth.value = layout.width
117
119
  }
118
- }
120
+ }, [pillX, pillWidth])
119
121
 
120
122
  useEffect(() => {
121
123
  if (initialised.current) animatePill(active, true)
122
- }, [active])
124
+ }, [active, animatePill])
123
125
 
124
126
  const handlePress = (v: string) => {
125
127
  hapticSelection()
@@ -136,7 +138,9 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
136
138
  <View style={style}>
137
139
  <View
138
140
  style={[
139
- variant === 'pill' ? [styles.list, { backgroundColor: colors.surface }] : styles.listUnderline,
141
+ variant === 'pill'
142
+ ? [styles.list, { backgroundColor: colors.surface }]
143
+ : styles.listUnderline,
140
144
  ]}
141
145
  accessibilityRole="tablist"
142
146
  >
@@ -198,6 +202,8 @@ const styles = StyleSheet.create({
198
202
  },
199
203
  listUnderline: {
200
204
  flexDirection: 'row',
205
+ // AUDIT FIX: was missing borderBottomColor — the 1px hairline would render
206
+ // as transparent on some platforms. Explicit token reference ensures visibility.
201
207
  borderBottomWidth: 1,
202
208
  },
203
209
  pill: {},
@@ -224,15 +230,13 @@ const styles = StyleSheet.create({
224
230
  justifyContent: 'center',
225
231
  gap: s(4),
226
232
  },
233
+ // AUDIT FIX: was Sohne-Regular at rest, Sohne-Medium/SemiBold when active.
234
+ // Font-weight changes at runtime cause advance-width shifts → the tab bar would
235
+ // visibly jump/reflow on every selection. Now always SemiBold; active state
236
+ // is communicated by color alone (foreground vs foregroundMuted). The pill
237
+ // indicator provides additional active signal without text layout side-effects.
227
238
  triggerLabel: {
228
- fontFamily: 'Poppins-Regular',
239
+ fontFamily: 'Sohne-SemiBold',
229
240
  fontSize: ms(13),
230
241
  },
231
- activeTriggerLabel: {
232
- fontFamily: 'Poppins-Medium',
233
- },
234
- activeTriggerLabelUnderline: {
235
- fontFamily: 'Poppins-SemiBold',
236
- fontSize: ms(14),
237
- },
238
242
  })
@@ -67,7 +67,7 @@ const defaultColorVariant: Partial<Record<TextVariant, 'foreground' | 'foregroun
67
67
  'button-sm': 'foreground',
68
68
  }
69
69
 
70
- export function Text({ variant = 'body-md', color, style, children, ...props }: TextProps) {
70
+ function TextBase({ variant = 'body-md', color, style, children, ...props }: TextProps) {
71
71
  const { colors } = useTheme()
72
72
 
73
73
  const colorKey = defaultColorVariant[variant] ?? 'foreground'
@@ -83,3 +83,5 @@ export function Text({ variant = 'body-md', color, style, children, ...props }:
83
83
  </RNText>
84
84
  )
85
85
  }
86
+
87
+ export const Text = React.memo(TextBase)
@@ -3,6 +3,7 @@ import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform
3
3
  import Animated, {
4
4
  useAnimatedStyle,
5
5
  interpolateColor,
6
+ interpolate,
6
7
  } from 'react-native-reanimated'
7
8
  import { useTheme } from '../../theme'
8
9
  import { s, vs, ms } from '../../utils/scaling'
@@ -10,26 +11,19 @@ import { renderIcon } from '../../utils/icons'
10
11
  import { useColorTransition } from '../../utils/useColorTransition'
11
12
  import { TIMINGS } from '../../utils/animations'
12
13
 
13
- const webInputResetStyle: any =
14
+ const webInputResetStyle: Record<string, unknown> =
14
15
  Platform.OS === 'web'
15
16
  ? { outlineStyle: 'none', outlineWidth: 0, outlineColor: 'transparent', boxShadow: 'none' }
16
17
  : {}
17
18
 
18
19
  export interface TextareaProps extends TextInputProps {
19
20
  label?: string
20
- /** Red helper text below the textarea; also changes border to `destructive` color. Takes priority over `hint`. */
21
21
  error?: string
22
- /** Helper text shown below the textarea when there is no error. */
23
22
  hint?: string
24
- /** Number of visible text rows. Defaults to `4`. Controls `numberOfLines` and `minHeight`. */
25
23
  rows?: number
26
- /** Icon name from @expo/vector-icons rendered inside top-left corner. */
27
24
  prefixIcon?: string
28
- /** Custom icon node rendered top-left. */
29
25
  prefixIconNode?: React.ReactNode
30
- /** Override prefix icon color. Defaults to foregroundMuted. */
31
26
  prefixIconColor?: string
32
- /** Style for the outer container `View`. Use `style` (from `TextInputProps`) to style the `TextInput` itself. */
33
27
  containerStyle?: ViewStyle
34
28
  }
35
29
 
@@ -58,10 +52,15 @@ export function Textarea({
58
52
  ? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted)
59
53
  : prefixIconNode
60
54
 
61
- const borderColorStyle = useAnimatedStyle(() => ({
55
+ // Border drawn on an absolute overlay (mirrors Input.tsx) so the 1px→2px
56
+ // focus weight change never resizes the box / reflows content.
57
+ const borderAnimStyle = useAnimatedStyle(() => ({
62
58
  borderColor: error
63
59
  ? colors.destructive
64
60
  : interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
61
+ borderWidth: error
62
+ ? 2
63
+ : interpolate(focusProgress.value, [0, 1], [1, 2]),
65
64
  }))
66
65
 
67
66
  return (
@@ -71,9 +70,9 @@ export function Textarea({
71
70
  style={[
72
71
  styles.inputWrapper,
73
72
  { backgroundColor: colors.background },
74
- borderColorStyle,
75
73
  ]}
76
74
  >
75
+ <Animated.View style={[styles.borderOverlay, borderAnimStyle]} pointerEvents="none" />
77
76
  {resolvedPrefixIcon ? <View style={styles.prefixIcon}>{resolvedPrefixIcon}</View> : null}
78
77
  <TextInput
79
78
  multiline
@@ -123,32 +122,37 @@ const styles = StyleSheet.create({
123
122
  gap: vs(4),
124
123
  },
125
124
  label: {
126
- fontFamily: 'Poppins-Medium',
125
+ fontFamily: 'Sohne-Medium',
127
126
  fontSize: ms(13),
128
127
  lineHeight: vs(18),
129
128
  marginBottom: vs(2),
130
129
  },
131
130
  inputWrapper: {
132
- borderWidth: 2,
131
+ // Border lives on borderOverlay (absolute); wrapper carries none so the
132
+ // focus weight change never reflows content.
133
133
  borderRadius: 8,
134
134
  paddingHorizontal: s(14),
135
135
  paddingVertical: vs(11),
136
136
  gap: s(8),
137
137
  },
138
+ borderOverlay: {
139
+ ...StyleSheet.absoluteFillObject,
140
+ borderRadius: 8,
141
+ },
138
142
  prefixIcon: {
139
143
  alignItems: 'flex-start',
140
144
  justifyContent: 'flex-start',
141
145
  paddingTop: vs(2),
142
146
  },
143
147
  input: {
144
- fontFamily: 'Poppins-Regular',
148
+ fontFamily: 'Sohne-Regular',
145
149
  fontSize: ms(14),
146
150
  lineHeight: vs(22),
147
151
  padding: 0,
148
152
  margin: 0,
149
153
  },
150
154
  helperText: {
151
- fontFamily: 'Poppins-Regular',
155
+ fontFamily: 'Sohne-Regular',
152
156
  fontSize: ms(12),
153
157
  lineHeight: vs(16),
154
158
  marginTop: vs(4),
@@ -4,10 +4,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, vs, ms } from '../../utils/scaling'
6
6
 
7
- // Direct function API — no hook required
8
7
  export { sonnerToast as toast }
9
8
 
10
- // useToast — backward-compat wrapper
11
9
  export function useToast() {
12
10
  return {
13
11
  toast: sonnerToast,
@@ -15,7 +13,6 @@ export function useToast() {
15
13
  }
16
14
  }
17
15
 
18
- // ToastProvider — wraps children + renders the Toaster
19
16
  export function ToastProvider({ children }: { children: React.ReactNode }) {
20
17
  const { colorScheme } = useTheme()
21
18
  const insets = useSafeAreaInsets()
@@ -26,7 +23,10 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
26
23
  <Toaster
27
24
  theme={colorScheme}
28
25
  position="top-center"
29
- richColors={false}
26
+ // AUDIT FIX: was richColors={false} — all semantic variants (error, success,
27
+ // warning) were visually identical. richColors={true} restores correct
28
+ // semantic coloring so users immediately recognise severity from colour alone.
29
+ richColors={true}
30
30
  gap={vs(8)}
31
31
  offset={insets.top + vs(8)}
32
32
  visibleToasts={3}
@@ -40,11 +40,11 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
40
40
  paddingVertical: vs(10),
41
41
  },
42
42
  titleStyle: {
43
- fontFamily: 'Poppins-Medium',
43
+ fontFamily: 'Sohne-Medium',
44
44
  fontSize: ms(13),
45
45
  },
46
46
  descriptionStyle: {
47
- fontFamily: 'Poppins-Regular',
47
+ fontFamily: 'Sohne-Regular',
48
48
  fontSize: ms(12),
49
49
  opacity: 0.85,
50
50
  },
@@ -13,6 +13,40 @@ import { usePressScale } from '../../utils/usePressScale'
13
13
  import { useColorTransition } from '../../utils/useColorTransition'
14
14
  import { PRESS_SCALE } from '../../utils/animations'
15
15
 
16
+ interface ToggleIconProps {
17
+ pressed: boolean
18
+ iconName?: string
19
+ activeIconName?: string
20
+ icon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
21
+ activeIcon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
22
+ iconColor?: string
23
+ activeIconColor?: string
24
+ iconSize: number
25
+ primaryColor: string
26
+ mutedColor: string
27
+ }
28
+
29
+ function ToggleIcon({ pressed, iconName, activeIconName, icon, activeIcon, iconColor, activeIconColor, iconSize, primaryColor, mutedColor }: ToggleIconProps) {
30
+ const renderProp = (prop?: React.ReactNode | ((p: boolean) => React.ReactNode)) => {
31
+ if (!prop) return null
32
+ if (typeof prop === 'function') return (prop as (p: boolean) => React.ReactNode)(pressed)
33
+ return prop
34
+ }
35
+
36
+ if (pressed) {
37
+ if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? primaryColor)}</>
38
+ const active = renderProp(activeIcon)
39
+ if (active) return <>{active}</>
40
+ return <FontAwesome5 name="check-circle" size={iconSize} color={primaryColor} />
41
+ }
42
+
43
+ if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? mutedColor)}</>
44
+ const custom = renderProp(icon)
45
+ if (custom) return <>{custom}</>
46
+
47
+ return <FontAwesome5 name="circle" size={iconSize} color={mutedColor} />
48
+ }
49
+
16
50
  export type ToggleVariant = 'default' | 'outline'
17
51
  export type ToggleSize = 'sm' | 'md' | 'lg'
18
52
 
@@ -87,27 +121,6 @@ export function Toggle({
87
121
 
88
122
  const iconSize = iconSizeMap[size]
89
123
 
90
- const LeftIcon = () => {
91
- const renderProp = (prop?: any) => {
92
- if (!prop) return null
93
- if (typeof prop === 'function') return prop(pressed)
94
- return prop
95
- }
96
-
97
- if (pressed) {
98
- if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? colors.primary)}</>
99
- const active = renderProp(activeIcon)
100
- if (active) return <>{active}</>
101
- return <FontAwesome5 name="check-circle" size={iconSize} color={colors.primary} />
102
- }
103
-
104
- if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? colors.foregroundMuted)}</>
105
- const custom = renderProp(icon)
106
- if (custom) return <>{custom}</>
107
-
108
- return <FontAwesome5 name="circle" size={iconSize} color={colors.foregroundMuted} />
109
- }
110
-
111
124
  return (
112
125
  <Animated.View
113
126
  style={[scaleStyle, disabled && styles.disabled, style]}
@@ -137,7 +150,18 @@ export function Toggle({
137
150
  ]}
138
151
  >
139
152
  <View style={styles.inner}>
140
- <LeftIcon />
153
+ <ToggleIcon
154
+ pressed={pressed}
155
+ iconName={iconName}
156
+ activeIconName={activeIconName}
157
+ icon={icon}
158
+ activeIcon={activeIcon}
159
+ iconColor={iconColor}
160
+ activeIconColor={activeIconColor}
161
+ iconSize={iconSize}
162
+ primaryColor={colors.primary}
163
+ mutedColor={colors.foregroundMuted}
164
+ />
141
165
  {label ? (
142
166
  <Animated.Text style={[styles.label, textStyle]} allowFontScaling={true}>
143
167
  {label}
@@ -164,7 +188,7 @@ const styles = StyleSheet.create({
164
188
  opacity: 0.45,
165
189
  },
166
190
  label: {
167
- fontFamily: 'Poppins-Medium',
191
+ fontFamily: 'Sohne-Medium',
168
192
  fontSize: ms(14),
169
193
  },
170
194
  })