@studio-piot/native-ui 0.2.5 → 0.17.0

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 (338) hide show
  1. package/README.md +22 -18
  2. package/dist/components/Button/Button.d.ts +20 -4
  3. package/dist/components/Button/Button.d.ts.map +1 -1
  4. package/dist/components/Button/Button.js +34 -21
  5. package/dist/components/Button/Button.js.map +1 -1
  6. package/dist/components/Button/buttonAppearance.d.ts +20 -0
  7. package/dist/components/Button/buttonAppearance.d.ts.map +1 -0
  8. package/dist/components/Button/buttonAppearance.js +28 -0
  9. package/dist/components/Button/buttonAppearance.js.map +1 -0
  10. package/dist/components/Button/buttonThemeRoles.d.ts +6 -0
  11. package/dist/components/Button/buttonThemeRoles.d.ts.map +1 -0
  12. package/dist/components/Button/buttonThemeRoles.js +8 -0
  13. package/dist/components/Button/buttonThemeRoles.js.map +1 -0
  14. package/dist/components/Button/index.d.ts +3 -1
  15. package/dist/components/Button/index.d.ts.map +1 -1
  16. package/dist/components/Button/index.js +4 -1
  17. package/dist/components/Button/index.js.map +1 -1
  18. package/dist/components/Choice/Choice.d.ts +10 -0
  19. package/dist/components/Choice/Choice.d.ts.map +1 -0
  20. package/dist/components/Choice/Choice.js +66 -0
  21. package/dist/components/Choice/Choice.js.map +1 -0
  22. package/dist/components/Choice/choiceLayout.d.ts +23 -0
  23. package/dist/components/Choice/choiceLayout.d.ts.map +1 -0
  24. package/dist/components/Choice/choiceLayout.js +32 -0
  25. package/dist/components/Choice/choiceLayout.js.map +1 -0
  26. package/dist/components/Choice/choiceThemeRoles.d.ts +9 -0
  27. package/dist/components/Choice/choiceThemeRoles.d.ts.map +1 -0
  28. package/dist/components/Choice/choiceThemeRoles.js +11 -0
  29. package/dist/components/Choice/choiceThemeRoles.js.map +1 -0
  30. package/dist/components/Choice/index.d.ts +5 -0
  31. package/dist/components/Choice/index.d.ts.map +1 -0
  32. package/dist/components/Choice/index.js +10 -0
  33. package/dist/components/Choice/index.js.map +1 -0
  34. package/dist/components/Choice/types.d.ts +33 -0
  35. package/dist/components/Choice/types.d.ts.map +1 -0
  36. package/dist/components/Choice/types.js +3 -0
  37. package/dist/components/Choice/types.js.map +1 -0
  38. package/dist/components/ErrorBanner/ErrorBanner.d.ts +1 -1
  39. package/dist/components/ErrorBanner/ErrorBanner.d.ts.map +1 -1
  40. package/dist/components/ErrorBanner/ErrorBanner.js +19 -19
  41. package/dist/components/ErrorBanner/ErrorBanner.js.map +1 -1
  42. package/dist/components/ErrorBanner/errorBannerContext.d.ts +23 -3
  43. package/dist/components/ErrorBanner/errorBannerContext.d.ts.map +1 -1
  44. package/dist/components/ErrorBanner/errorBannerContext.js +9 -5
  45. package/dist/components/ErrorBanner/errorBannerContext.js.map +1 -1
  46. package/dist/components/ErrorBanner/errorBannerLayout.d.ts +18 -0
  47. package/dist/components/ErrorBanner/errorBannerLayout.d.ts.map +1 -0
  48. package/dist/components/ErrorBanner/errorBannerLayout.js +54 -0
  49. package/dist/components/ErrorBanner/errorBannerLayout.js.map +1 -0
  50. package/dist/components/ErrorBanner/icons.d.ts +7 -5
  51. package/dist/components/ErrorBanner/icons.d.ts.map +1 -1
  52. package/dist/components/ErrorBanner/icons.js +8 -4
  53. package/dist/components/ErrorBanner/icons.js.map +1 -1
  54. package/dist/components/ErrorBanner/index.d.ts +1 -1
  55. package/dist/components/ErrorBanner/index.d.ts.map +1 -1
  56. package/dist/components/ErrorBanner/index.js.map +1 -1
  57. package/dist/components/Form/Form.d.ts +9 -1
  58. package/dist/components/Form/Form.d.ts.map +1 -1
  59. package/dist/components/Form/Form.js +56 -78
  60. package/dist/components/Form/Form.js.map +1 -1
  61. package/dist/components/Form/FormCloseButton.d.ts +3 -1
  62. package/dist/components/Form/FormCloseButton.d.ts.map +1 -1
  63. package/dist/components/Form/FormCloseButton.js +5 -4
  64. package/dist/components/Form/FormCloseButton.js.map +1 -1
  65. package/dist/components/Form/FormErrorBanner.d.ts +15 -0
  66. package/dist/components/Form/FormErrorBanner.d.ts.map +1 -0
  67. package/dist/components/Form/FormErrorBanner.js +30 -0
  68. package/dist/components/Form/FormErrorBanner.js.map +1 -0
  69. package/dist/components/Form/FormSubmitFooter.d.ts +17 -0
  70. package/dist/components/Form/FormSubmitFooter.d.ts.map +1 -0
  71. package/dist/components/Form/FormSubmitFooter.js +32 -0
  72. package/dist/components/Form/FormSubmitFooter.js.map +1 -0
  73. package/dist/components/Form/formThemeRoles.d.ts +7 -0
  74. package/dist/components/Form/formThemeRoles.d.ts.map +1 -0
  75. package/dist/components/Form/formThemeRoles.js +9 -0
  76. package/dist/components/Form/formThemeRoles.js.map +1 -0
  77. package/dist/components/Form/index.d.ts +1 -0
  78. package/dist/components/Form/index.d.ts.map +1 -1
  79. package/dist/components/Form/index.js +3 -1
  80. package/dist/components/Form/index.js.map +1 -1
  81. package/dist/components/Form/types.d.ts +14 -10
  82. package/dist/components/Form/types.d.ts.map +1 -1
  83. package/dist/components/FormSheet/FormSheet.d.ts +10 -0
  84. package/dist/components/FormSheet/FormSheet.d.ts.map +1 -0
  85. package/dist/components/FormSheet/FormSheet.js +153 -0
  86. package/dist/components/FormSheet/FormSheet.js.map +1 -0
  87. package/dist/components/FormSheet/formSheetLayout.d.ts +32 -0
  88. package/dist/components/FormSheet/formSheetLayout.d.ts.map +1 -0
  89. package/dist/components/FormSheet/formSheetLayout.js +43 -0
  90. package/dist/components/FormSheet/formSheetLayout.js.map +1 -0
  91. package/dist/components/FormSheet/formSheetThemeRoles.d.ts +8 -0
  92. package/dist/components/FormSheet/formSheetThemeRoles.d.ts.map +1 -0
  93. package/dist/components/FormSheet/formSheetThemeRoles.js +10 -0
  94. package/dist/components/FormSheet/formSheetThemeRoles.js.map +1 -0
  95. package/dist/components/FormSheet/index.d.ts +5 -0
  96. package/dist/components/FormSheet/index.d.ts.map +1 -0
  97. package/dist/components/FormSheet/index.js +10 -0
  98. package/dist/components/FormSheet/index.js.map +1 -0
  99. package/dist/components/FormSheet/types.d.ts +64 -0
  100. package/dist/components/FormSheet/types.d.ts.map +1 -0
  101. package/dist/components/FormSheet/types.js +3 -0
  102. package/dist/components/FormSheet/types.js.map +1 -0
  103. package/dist/components/HeaderButton/HeaderCapsule.d.ts +4 -0
  104. package/dist/components/HeaderButton/HeaderCapsule.d.ts.map +1 -0
  105. package/dist/components/HeaderButton/HeaderCapsule.js +32 -0
  106. package/dist/components/HeaderButton/HeaderCapsule.js.map +1 -0
  107. package/dist/components/HeaderButton/HeaderGroup.d.ts +4 -0
  108. package/dist/components/HeaderButton/HeaderGroup.d.ts.map +1 -0
  109. package/dist/components/HeaderButton/HeaderGroup.js +23 -0
  110. package/dist/components/HeaderButton/HeaderGroup.js.map +1 -0
  111. package/dist/components/HeaderButton/HeaderIcon.d.ts +4 -0
  112. package/dist/components/HeaderButton/HeaderIcon.d.ts.map +1 -0
  113. package/dist/components/HeaderButton/HeaderIcon.js +28 -0
  114. package/dist/components/HeaderButton/HeaderIcon.js.map +1 -0
  115. package/dist/components/HeaderButton/headerButtonLayout.d.ts +21 -0
  116. package/dist/components/HeaderButton/headerButtonLayout.d.ts.map +1 -0
  117. package/dist/components/HeaderButton/headerButtonLayout.js +34 -0
  118. package/dist/components/HeaderButton/headerButtonLayout.js.map +1 -0
  119. package/dist/components/HeaderButton/headerButtonThemeRoles.d.ts +5 -0
  120. package/dist/components/HeaderButton/headerButtonThemeRoles.d.ts.map +1 -0
  121. package/dist/components/HeaderButton/headerButtonThemeRoles.js +7 -0
  122. package/dist/components/HeaderButton/headerButtonThemeRoles.js.map +1 -0
  123. package/dist/components/HeaderButton/index.d.ts +7 -0
  124. package/dist/components/HeaderButton/index.d.ts.map +1 -0
  125. package/dist/components/HeaderButton/index.js +14 -0
  126. package/dist/components/HeaderButton/index.js.map +1 -0
  127. package/dist/components/HeaderButton/types.d.ts +40 -0
  128. package/dist/components/HeaderButton/types.d.ts.map +1 -0
  129. package/dist/components/HeaderButton/types.js +3 -0
  130. package/dist/components/HeaderButton/types.js.map +1 -0
  131. package/dist/components/HeaderWash/HeaderWash.d.ts +10 -0
  132. package/dist/components/HeaderWash/HeaderWash.d.ts.map +1 -0
  133. package/dist/components/HeaderWash/HeaderWash.js +19 -0
  134. package/dist/components/HeaderWash/HeaderWash.js.map +1 -0
  135. package/dist/components/HeaderWash/headerWashGradient.d.ts +8 -0
  136. package/dist/components/HeaderWash/headerWashGradient.d.ts.map +1 -0
  137. package/dist/components/HeaderWash/headerWashGradient.js +21 -0
  138. package/dist/components/HeaderWash/headerWashGradient.js.map +1 -0
  139. package/dist/components/HeaderWash/index.d.ts +2 -0
  140. package/dist/components/HeaderWash/index.d.ts.map +1 -0
  141. package/dist/components/HeaderWash/index.js +6 -0
  142. package/dist/components/HeaderWash/index.js.map +1 -0
  143. package/dist/components/Page/Page.d.ts +13 -0
  144. package/dist/components/Page/Page.d.ts.map +1 -0
  145. package/dist/components/Page/Page.js +49 -0
  146. package/dist/components/Page/Page.js.map +1 -0
  147. package/dist/components/Page/PageBleed.d.ts +9 -0
  148. package/dist/components/Page/PageBleed.d.ts.map +1 -0
  149. package/dist/components/Page/PageBleed.js +12 -0
  150. package/dist/components/Page/PageBleed.js.map +1 -0
  151. package/dist/components/Page/index.d.ts +5 -0
  152. package/dist/components/Page/index.d.ts.map +1 -0
  153. package/dist/components/Page/index.js +10 -0
  154. package/dist/components/Page/index.js.map +1 -0
  155. package/dist/components/Page/pageContext.d.ts +4 -0
  156. package/dist/components/Page/pageContext.d.ts.map +1 -0
  157. package/dist/components/Page/pageContext.js +11 -0
  158. package/dist/components/Page/pageContext.js.map +1 -0
  159. package/dist/components/Page/pageInsets.d.ts +26 -0
  160. package/dist/components/Page/pageInsets.d.ts.map +1 -0
  161. package/dist/components/Page/pageInsets.js +37 -0
  162. package/dist/components/Page/pageInsets.js.map +1 -0
  163. package/dist/components/Page/pageLayout.d.ts +5 -0
  164. package/dist/components/Page/pageLayout.d.ts.map +1 -0
  165. package/dist/components/Page/pageLayout.js +8 -0
  166. package/dist/components/Page/pageLayout.js.map +1 -0
  167. package/dist/components/Page/pageThemeRoles.d.ts +5 -0
  168. package/dist/components/Page/pageThemeRoles.d.ts.map +1 -0
  169. package/dist/components/Page/pageThemeRoles.js +7 -0
  170. package/dist/components/Page/pageThemeRoles.js.map +1 -0
  171. package/dist/components/Page/types.d.ts +22 -0
  172. package/dist/components/Page/types.d.ts.map +1 -0
  173. package/dist/components/Page/types.js +3 -0
  174. package/dist/components/Page/types.js.map +1 -0
  175. package/dist/components/Select/Select.d.ts +1 -1
  176. package/dist/components/Select/Select.d.ts.map +1 -1
  177. package/dist/components/Select/Select.js +8 -9
  178. package/dist/components/Select/Select.js.map +1 -1
  179. package/dist/components/Select/selectThemeRoles.d.ts +2 -0
  180. package/dist/components/Select/selectThemeRoles.d.ts.map +1 -1
  181. package/dist/components/Select/selectThemeRoles.js +2 -0
  182. package/dist/components/Select/selectThemeRoles.js.map +1 -1
  183. package/dist/components/Select/types.d.ts +4 -0
  184. package/dist/components/Select/types.d.ts.map +1 -1
  185. package/dist/components/TabAccessoryButton/TabAccessoryButton.d.ts +8 -0
  186. package/dist/components/TabAccessoryButton/TabAccessoryButton.d.ts.map +1 -0
  187. package/dist/components/TabAccessoryButton/TabAccessoryButton.js +77 -0
  188. package/dist/components/TabAccessoryButton/TabAccessoryButton.js.map +1 -0
  189. package/dist/components/TabAccessoryButton/index.d.ts +5 -0
  190. package/dist/components/TabAccessoryButton/index.d.ts.map +1 -0
  191. package/dist/components/TabAccessoryButton/index.js +11 -0
  192. package/dist/components/TabAccessoryButton/index.js.map +1 -0
  193. package/dist/components/TabAccessoryButton/tabAccessoryLayout.d.ts +27 -0
  194. package/dist/components/TabAccessoryButton/tabAccessoryLayout.d.ts.map +1 -0
  195. package/dist/components/TabAccessoryButton/tabAccessoryLayout.js +38 -0
  196. package/dist/components/TabAccessoryButton/tabAccessoryLayout.js.map +1 -0
  197. package/dist/components/TabAccessoryButton/tabAccessoryThemeRoles.d.ts +14 -0
  198. package/dist/components/TabAccessoryButton/tabAccessoryThemeRoles.d.ts.map +1 -0
  199. package/dist/components/TabAccessoryButton/tabAccessoryThemeRoles.js +19 -0
  200. package/dist/components/TabAccessoryButton/tabAccessoryThemeRoles.js.map +1 -0
  201. package/dist/components/TabAccessoryButton/types.d.ts +27 -0
  202. package/dist/components/TabAccessoryButton/types.d.ts.map +1 -0
  203. package/dist/components/TabAccessoryButton/types.js +3 -0
  204. package/dist/components/TabAccessoryButton/types.js.map +1 -0
  205. package/dist/components/index.d.ts +9 -3
  206. package/dist/components/index.d.ts.map +1 -1
  207. package/dist/components/index.js +30 -1
  208. package/dist/components/index.js.map +1 -1
  209. package/dist/index.d.ts +11 -4
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +34 -2
  212. package/dist/index.js.map +1 -1
  213. package/dist/layout/HeaderInsetProbe.d.ts +15 -0
  214. package/dist/layout/HeaderInsetProbe.d.ts.map +1 -0
  215. package/dist/layout/HeaderInsetProbe.js +28 -0
  216. package/dist/layout/HeaderInsetProbe.js.map +1 -0
  217. package/dist/layout/headerInset.d.ts +26 -0
  218. package/dist/layout/headerInset.d.ts.map +1 -0
  219. package/dist/layout/headerInset.js +29 -0
  220. package/dist/layout/headerInset.js.map +1 -0
  221. package/dist/layout/index.d.ts +1 -0
  222. package/dist/layout/index.d.ts.map +1 -1
  223. package/dist/layout/index.js +3 -1
  224. package/dist/layout/index.js.map +1 -1
  225. package/dist/layout/sheet.d.ts +3 -0
  226. package/dist/layout/sheet.d.ts.map +1 -0
  227. package/dist/layout/sheet.js +6 -0
  228. package/dist/layout/sheet.js.map +1 -0
  229. package/dist/theme/ThemeProvider.d.ts +4 -1
  230. package/dist/theme/ThemeProvider.d.ts.map +1 -1
  231. package/dist/theme/ThemeProvider.js +5 -2
  232. package/dist/theme/ThemeProvider.js.map +1 -1
  233. package/dist/theme/index.d.ts +2 -0
  234. package/dist/theme/index.d.ts.map +1 -1
  235. package/dist/theme/index.js +6 -1
  236. package/dist/theme/index.js.map +1 -1
  237. package/dist/theme/resolveThemeSpacing.d.ts +12 -0
  238. package/dist/theme/resolveThemeSpacing.d.ts.map +1 -0
  239. package/dist/theme/resolveThemeSpacing.js +14 -0
  240. package/dist/theme/resolveThemeSpacing.js.map +1 -0
  241. package/dist/theme/types.d.ts +2 -0
  242. package/dist/theme/types.d.ts.map +1 -1
  243. package/dist/theme/useThemeSpacing.d.ts +4 -0
  244. package/dist/theme/useThemeSpacing.d.ts.map +1 -0
  245. package/dist/theme/useThemeSpacing.js +11 -0
  246. package/dist/theme/useThemeSpacing.js.map +1 -0
  247. package/docs/components/button.md +92 -38
  248. package/docs/components/choice.md +111 -0
  249. package/docs/components/error-banner.md +46 -3
  250. package/docs/components/form-sheet.md +140 -0
  251. package/docs/components/form.md +80 -45
  252. package/docs/components/header-button.md +310 -0
  253. package/docs/components/header-wash.md +40 -0
  254. package/docs/components/page.md +183 -0
  255. package/docs/components/select.md +2 -2
  256. package/docs/components/tab-accessory-button.md +146 -0
  257. package/docs/specifications/Liquid Glass header buttons in Expo Router.md +327 -0
  258. package/package.json +1 -1
  259. package/src/components/Button/Button.tsx +82 -38
  260. package/src/components/Button/buttonAppearance.test.ts +44 -0
  261. package/src/components/Button/buttonAppearance.ts +38 -0
  262. package/src/components/Button/buttonThemeRoles.test.ts +10 -0
  263. package/src/components/Button/buttonThemeRoles.ts +6 -0
  264. package/src/components/Button/index.ts +12 -1
  265. package/src/components/Choice/Choice.tsx +120 -0
  266. package/src/components/Choice/choiceLayout.test.ts +49 -0
  267. package/src/components/Choice/choiceLayout.ts +34 -0
  268. package/src/components/Choice/choiceThemeRoles.test.ts +15 -0
  269. package/src/components/Choice/choiceThemeRoles.ts +9 -0
  270. package/src/components/Choice/index.ts +4 -0
  271. package/src/components/Choice/types.ts +33 -0
  272. package/src/components/ErrorBanner/ErrorBanner.tsx +32 -22
  273. package/src/components/ErrorBanner/errorBannerContext.tsx +31 -6
  274. package/src/components/ErrorBanner/errorBannerLayout.test.ts +49 -0
  275. package/src/components/ErrorBanner/errorBannerLayout.ts +59 -0
  276. package/src/components/ErrorBanner/icons.tsx +25 -6
  277. package/src/components/ErrorBanner/index.ts +1 -0
  278. package/src/components/Form/Form.tsx +77 -173
  279. package/src/components/Form/FormCloseButton.tsx +12 -4
  280. package/src/components/Form/FormErrorBanner.tsx +57 -0
  281. package/src/components/Form/FormSubmitFooter.tsx +70 -0
  282. package/src/components/Form/formThemeRoles.test.ts +13 -0
  283. package/src/components/Form/formThemeRoles.ts +7 -0
  284. package/src/components/Form/index.ts +1 -0
  285. package/src/components/Form/types.ts +14 -10
  286. package/src/components/FormSheet/FormSheet.tsx +279 -0
  287. package/src/components/FormSheet/formSheetLayout.test.ts +66 -0
  288. package/src/components/FormSheet/formSheetLayout.ts +55 -0
  289. package/src/components/FormSheet/formSheetThemeRoles.test.ts +14 -0
  290. package/src/components/FormSheet/formSheetThemeRoles.ts +8 -0
  291. package/src/components/FormSheet/index.ts +7 -0
  292. package/src/components/FormSheet/types.ts +66 -0
  293. package/src/components/HeaderButton/HeaderCapsule.tsx +63 -0
  294. package/src/components/HeaderButton/HeaderGroup.tsx +36 -0
  295. package/src/components/HeaderButton/HeaderIcon.tsx +56 -0
  296. package/src/components/HeaderButton/headerButtonLayout.test.ts +47 -0
  297. package/src/components/HeaderButton/headerButtonLayout.ts +36 -0
  298. package/src/components/HeaderButton/headerButtonThemeRoles.test.ts +9 -0
  299. package/src/components/HeaderButton/headerButtonThemeRoles.ts +5 -0
  300. package/src/components/HeaderButton/index.ts +14 -0
  301. package/src/components/HeaderButton/types.ts +40 -0
  302. package/src/components/HeaderWash/HeaderWash.tsx +20 -0
  303. package/src/components/HeaderWash/headerWashGradient.test.ts +22 -0
  304. package/src/components/{Form/withAlpha.ts → HeaderWash/headerWashGradient.ts} +9 -0
  305. package/src/components/HeaderWash/index.ts +1 -0
  306. package/src/components/Page/Page.tsx +80 -0
  307. package/src/components/Page/PageBleed.tsx +14 -0
  308. package/src/components/Page/index.ts +4 -0
  309. package/src/components/Page/pageContext.ts +8 -0
  310. package/src/components/Page/pageInsets.test.ts +73 -0
  311. package/src/components/Page/pageInsets.ts +52 -0
  312. package/src/components/Page/pageThemeRoles.test.ts +9 -0
  313. package/src/components/Page/pageThemeRoles.ts +5 -0
  314. package/src/components/Page/types.ts +26 -0
  315. package/src/components/Select/Select.tsx +11 -4
  316. package/src/components/Select/selectThemeRoles.test.ts +5 -0
  317. package/src/components/Select/selectThemeRoles.ts +2 -0
  318. package/src/components/Select/types.ts +4 -0
  319. package/src/components/TabAccessoryButton/TabAccessoryButton.tsx +150 -0
  320. package/src/components/TabAccessoryButton/index.ts +12 -0
  321. package/src/components/TabAccessoryButton/tabAccessoryLayout.test.ts +42 -0
  322. package/src/components/TabAccessoryButton/tabAccessoryLayout.ts +43 -0
  323. package/src/components/TabAccessoryButton/tabAccessoryThemeRoles.test.ts +28 -0
  324. package/src/components/TabAccessoryButton/tabAccessoryThemeRoles.ts +22 -0
  325. package/src/components/TabAccessoryButton/types.ts +27 -0
  326. package/src/components/index.ts +64 -2
  327. package/src/index.ts +61 -2
  328. package/src/layout/headerInset.test.ts +60 -0
  329. package/src/layout/headerInset.ts +41 -0
  330. package/src/layout/index.ts +1 -0
  331. package/src/layout/sheet.ts +2 -0
  332. package/src/theme/ThemeProvider.tsx +9 -1
  333. package/src/theme/index.ts +6 -0
  334. package/src/theme/resolveThemeSpacing.test.ts +29 -0
  335. package/src/theme/resolveThemeSpacing.ts +21 -0
  336. package/src/theme/types.ts +3 -0
  337. package/src/theme/useThemeSpacing.ts +8 -0
  338. package/src/components/Form/withAlpha.test.ts +0 -13
@@ -1,23 +1,46 @@
1
+ import type { ReactNode } from "react";
1
2
  import {
2
3
  ActivityIndicator,
4
+ Pressable,
3
5
  StyleSheet,
4
6
  Text,
5
- TouchableOpacity,
7
+ View,
6
8
  type StyleProp,
7
9
  type TextStyle,
8
10
  type ViewStyle,
9
11
  } from "react-native";
12
+ import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
13
+ import {
14
+ buttonColors,
15
+ buttonOpacity,
16
+ type ButtonVariant,
17
+ } from "./buttonAppearance";
10
18
  import { buttonRadiusValue, type ButtonRadius } from "./buttonRadius";
19
+ import { BUTTON_THEME_ROLES } from "./buttonThemeRoles";
20
+
21
+ export const BUTTON_ICON_SIZE = 20;
22
+
23
+ /** Renders the icon in the button's content colour, e.g. an expo-symbols `SymbolView`. */
24
+ export type ButtonIcon = (props: { color: string; size: number }) => ReactNode;
25
+
26
+ export type ButtonIconPosition = "start" | "end" | "only";
11
27
 
12
28
  export type ButtonProps = {
29
+ /** Shown text, and the accessibility label (also when `iconPosition="only"`). */
13
30
  label: string;
14
31
  onPress: () => void;
15
- backgroundColor: string;
16
- labelColor: string;
32
+ /** Filled: fill. Outline: border, label and icon. Defaults to theme role `buttonBackground`. */
33
+ backgroundColor?: string;
34
+ /** Filled: label, icon and spinner. Defaults to theme role `buttonLabel`. */
35
+ labelColor?: string;
17
36
  disabled?: boolean;
18
37
  loading?: boolean;
19
- variant?: "filled" | "outline";
38
+ variant?: ButtonVariant;
20
39
  radius?: ButtonRadius;
40
+ icon?: ButtonIcon;
41
+ /** Default `"start"`. `"only"` hides the label text; `label` stays the accessibility name. */
42
+ iconPosition?: ButtonIconPosition;
43
+ iconSize?: number;
21
44
  style?: StyleProp<ViewStyle>;
22
45
  labelStyle?: StyleProp<TextStyle>;
23
46
  };
@@ -25,58 +48,69 @@ export type ButtonProps = {
25
48
  export function Button({
26
49
  label,
27
50
  onPress,
28
- backgroundColor,
29
- labelColor,
51
+ backgroundColor: backgroundColorProp,
52
+ labelColor: labelColorProp,
30
53
  disabled = false,
31
54
  loading = false,
32
55
  variant = "filled",
33
56
  radius = "xl",
57
+ icon,
58
+ iconPosition = "start",
59
+ iconSize = BUTTON_ICON_SIZE,
34
60
  style,
35
61
  labelStyle,
36
62
  }: ButtonProps) {
37
- const blocked = disabled || loading;
38
- const isOutline = variant === "outline";
39
- const borderRadius = buttonRadiusValue(radius);
63
+ const backgroundColor = useResolvedFieldColor(
64
+ backgroundColorProp,
65
+ BUTTON_THEME_ROLES.backgroundColor,
66
+ );
67
+ const labelColor = useResolvedFieldColor(
68
+ labelColorProp,
69
+ BUTTON_THEME_ROLES.labelColor,
70
+ );
71
+ const colors = buttonColors(variant, backgroundColor, labelColor);
72
+ const iconNode = icon?.({ color: colors.content, size: iconSize });
73
+ const showLabel = !(icon && iconPosition === "only");
40
74
 
41
75
  return (
42
- <TouchableOpacity
76
+ <Pressable
43
77
  accessibilityLabel={label}
44
78
  accessibilityRole="button"
45
- accessibilityState={{ disabled: blocked, busy: loading }}
46
- activeOpacity={0.75}
47
- disabled={blocked}
79
+ accessibilityState={{ disabled: disabled || loading, busy: loading }}
80
+ disabled={disabled || loading}
48
81
  onPress={onPress}
49
- style={[
82
+ style={({ pressed }) => [
50
83
  styles.shell,
51
- { borderRadius },
52
- isOutline
53
- ? {
54
- backgroundColor: "transparent",
55
- borderWidth: 1,
56
- borderColor: backgroundColor,
57
- }
58
- : { backgroundColor },
59
- blocked && styles.blocked,
84
+ {
85
+ borderRadius: buttonRadiusValue(radius),
86
+ backgroundColor: colors.fill,
87
+ opacity: buttonOpacity({ disabled, loading, pressed }),
88
+ },
89
+ colors.border != null && {
90
+ borderWidth: 1,
91
+ borderColor: colors.border,
92
+ },
60
93
  style,
61
94
  ]}
62
95
  >
96
+ {/* Kept in layout while loading so the button keeps its size. */}
97
+ <View style={[styles.content, loading && styles.hidden]}>
98
+ {iconPosition !== "end" ? iconNode : null}
99
+ {showLabel ? (
100
+ <Text style={[styles.label, { color: colors.content }, labelStyle]}>
101
+ {label}
102
+ </Text>
103
+ ) : null}
104
+ {iconPosition === "end" ? iconNode : null}
105
+ </View>
63
106
  {loading ? (
64
107
  <ActivityIndicator
65
- color={isOutline ? backgroundColor : labelColor}
108
+ color={colors.content}
66
109
  size="small"
110
+ style={StyleSheet.absoluteFill}
67
111
  />
68
- ) : (
69
- <Text
70
- style={[
71
- styles.label,
72
- { color: isOutline ? backgroundColor : labelColor },
73
- labelStyle,
74
- ]}
75
- >
76
- {label}
77
- </Text>
78
- )}
79
- </TouchableOpacity>
112
+ ) : null}
113
+ </Pressable>
80
114
  );
81
115
  }
82
116
 
@@ -84,14 +118,24 @@ const styles = StyleSheet.create({
84
118
  shell: {
85
119
  width: "100%",
86
120
  minHeight: 52,
121
+ paddingHorizontal: 20,
122
+ paddingVertical: 12,
123
+ alignItems: "center",
124
+ justifyContent: "center",
125
+ },
126
+ content: {
127
+ flexDirection: "row",
87
128
  alignItems: "center",
88
129
  justifyContent: "center",
130
+ gap: 8,
89
131
  },
90
- blocked: {
91
- opacity: 0.4,
132
+ hidden: {
133
+ opacity: 0,
92
134
  },
93
135
  label: {
136
+ flexShrink: 1,
94
137
  fontSize: 17,
95
138
  fontWeight: "600",
139
+ textAlign: "center",
96
140
  },
97
141
  });
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { buttonColors, buttonOpacity } from "./buttonAppearance.ts";
4
+
5
+ describe("buttonColors", () => {
6
+ it("fills with the background and draws content in the label colour", () => {
7
+ assert.deepEqual(buttonColors("filled", "#2563EB", "#FFFFFF"), {
8
+ fill: "#2563EB",
9
+ border: undefined,
10
+ content: "#FFFFFF",
11
+ });
12
+ });
13
+
14
+ it("outlines with the background colour and draws content in it too", () => {
15
+ assert.deepEqual(buttonColors("outline", "#2563EB", "#FFFFFF"), {
16
+ fill: "transparent",
17
+ border: "#2563EB",
18
+ content: "#2563EB",
19
+ });
20
+ });
21
+ });
22
+
23
+ describe("buttonOpacity", () => {
24
+ it("is fully opaque at rest", () => {
25
+ assert.equal(buttonOpacity({ disabled: false, loading: false, pressed: false }), 1);
26
+ });
27
+
28
+ it("dims while pressed", () => {
29
+ assert.equal(buttonOpacity({ disabled: false, loading: false, pressed: true }), 0.75);
30
+ });
31
+
32
+ it("fades when disabled", () => {
33
+ assert.equal(buttonOpacity({ disabled: true, loading: false, pressed: false }), 0.4);
34
+ });
35
+
36
+ it("stays fully opaque while loading so it does not look disabled", () => {
37
+ assert.equal(buttonOpacity({ disabled: false, loading: true, pressed: false }), 1);
38
+ assert.equal(buttonOpacity({ disabled: false, loading: true, pressed: true }), 1);
39
+ });
40
+
41
+ it("fades when disabled and loading", () => {
42
+ assert.equal(buttonOpacity({ disabled: true, loading: true, pressed: false }), 0.4);
43
+ });
44
+ });
@@ -0,0 +1,38 @@
1
+ export type ButtonVariant = "filled" | "outline";
2
+
3
+ /**
4
+ * Fill, border and content (label, icon, spinner) colours for a variant.
5
+ * Outline draws everything in `background`; `label` is unused there.
6
+ */
7
+ export function buttonColors(
8
+ variant: ButtonVariant,
9
+ background: string,
10
+ label: string,
11
+ ): { fill: string; border: string | undefined; content: string } {
12
+ if (variant === "outline") {
13
+ return { fill: "transparent", border: background, content: background };
14
+ }
15
+ return { fill: background, border: undefined, content: label };
16
+ }
17
+
18
+ /**
19
+ * Disabled fades the button. Loading only blocks presses: a spinner on a
20
+ * faded button reads as broken, not busy.
21
+ */
22
+ export function buttonOpacity({
23
+ disabled,
24
+ loading,
25
+ pressed,
26
+ }: {
27
+ disabled: boolean;
28
+ loading: boolean;
29
+ pressed: boolean;
30
+ }): number {
31
+ if (disabled) {
32
+ return 0.4;
33
+ }
34
+ if (pressed && !loading) {
35
+ return 0.75;
36
+ }
37
+ return 1;
38
+ }
@@ -0,0 +1,10 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { BUTTON_THEME_ROLES } from "./buttonThemeRoles.ts";
4
+
5
+ describe("BUTTON_THEME_ROLES", () => {
6
+ it("maps colour props to the roles Form already uses", () => {
7
+ assert.equal(BUTTON_THEME_ROLES.backgroundColor, "buttonBackground");
8
+ assert.equal(BUTTON_THEME_ROLES.labelColor, "buttonLabel");
9
+ });
10
+ });
@@ -0,0 +1,6 @@
1
+ export const BUTTON_THEME_ROLES = {
2
+ backgroundColor: "buttonBackground",
3
+ labelColor: "buttonLabel",
4
+ } as const;
5
+
6
+ export type ButtonThemeColorProp = keyof typeof BUTTON_THEME_ROLES;
@@ -1,2 +1,13 @@
1
- export { Button, type ButtonProps } from "./Button";
1
+ export {
2
+ Button,
3
+ BUTTON_ICON_SIZE,
4
+ type ButtonIcon,
5
+ type ButtonIconPosition,
6
+ type ButtonProps,
7
+ } from "./Button";
8
+ export type { ButtonVariant } from "./buttonAppearance";
2
9
  export { buttonRadiusValue, type ButtonRadius } from "./buttonRadius";
10
+ export {
11
+ BUTTON_THEME_ROLES,
12
+ type ButtonThemeColorProp,
13
+ } from "./buttonThemeRoles";
@@ -0,0 +1,120 @@
1
+ import { Pressable, StyleSheet, Text, View } from "react-native";
2
+ import { buttonOpacity } from "../Button/buttonAppearance";
3
+ import { buttonRadiusValue } from "../Button/buttonRadius";
4
+ import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
5
+ import { CHOICE_ICON_SIZE, choiceIconSlot, choiceSelection } from "./choiceLayout";
6
+ import { CHOICE_THEME_ROLES } from "./choiceThemeRoles";
7
+ import type { ChoiceProps } from "./types";
8
+
9
+ /**
10
+ * One option in a list: an icon, a title and an optional description, in a
11
+ * filled row. Tapping it acts straight away (open the next screen), or,
12
+ * with `selected`, selects it among other choices (e.g. candidate routes,
13
+ * each with a drawing in the icon slot). For a form value, use RadioField
14
+ * or Select.
15
+ */
16
+ export function Choice({
17
+ title,
18
+ description,
19
+ icon,
20
+ onPress,
21
+ selected,
22
+ disabled = false,
23
+ iconSize = CHOICE_ICON_SIZE,
24
+ radius = "xl",
25
+ backgroundColor: backgroundColorProp,
26
+ titleColor: titleColorProp,
27
+ descriptionColor: descriptionColorProp,
28
+ iconColor: iconColorProp,
29
+ selectedColor: selectedColorProp,
30
+ style,
31
+ }: ChoiceProps) {
32
+ const backgroundColor = useResolvedFieldColor(
33
+ backgroundColorProp,
34
+ CHOICE_THEME_ROLES.backgroundColor,
35
+ );
36
+ const titleColor = useResolvedFieldColor(
37
+ titleColorProp,
38
+ CHOICE_THEME_ROLES.titleColor,
39
+ );
40
+ const descriptionColor = useResolvedFieldColor(
41
+ descriptionColorProp,
42
+ CHOICE_THEME_ROLES.descriptionColor,
43
+ );
44
+ const iconColor = useResolvedFieldColor(
45
+ iconColorProp,
46
+ CHOICE_THEME_ROLES.iconColor,
47
+ );
48
+ const selectedColor = useResolvedFieldColor(
49
+ selectedColorProp,
50
+ CHOICE_THEME_ROLES.selectedColor,
51
+ );
52
+ const slot = choiceIconSlot(iconSize);
53
+ const selection = choiceSelection(selected);
54
+
55
+ return (
56
+ // No explicit label: the row groups its text, so VoiceOver reads the
57
+ // title and description together ("Running, Outdoors, with GPS, button").
58
+ <Pressable
59
+ accessibilityRole={selection.role}
60
+ accessibilityState={{ ...selection.state, disabled }}
61
+ disabled={disabled}
62
+ onPress={onPress}
63
+ style={({ pressed }) => [
64
+ styles.row,
65
+ {
66
+ backgroundColor,
67
+ borderColor: selection.highlighted ? selectedColor : "transparent",
68
+ borderWidth: selection.borderWidth,
69
+ borderRadius: buttonRadiusValue(radius),
70
+ opacity: buttonOpacity({ disabled, loading: false, pressed }),
71
+ },
72
+ style,
73
+ ]}
74
+ >
75
+ {icon ? (
76
+ <View
77
+ importantForAccessibility="no-hide-descendants"
78
+ accessibilityElementsHidden
79
+ style={[styles.icon, { width: slot, height: slot }]}
80
+ >
81
+ {icon({ color: iconColor, size: iconSize })}
82
+ </View>
83
+ ) : null}
84
+ <View style={styles.copy}>
85
+ <Text style={[styles.title, { color: titleColor }]}>{title}</Text>
86
+ {description ? (
87
+ <Text style={[styles.description, { color: descriptionColor }]}>
88
+ {description}
89
+ </Text>
90
+ ) : null}
91
+ </View>
92
+ </Pressable>
93
+ );
94
+ }
95
+
96
+ const styles = StyleSheet.create({
97
+ row: {
98
+ alignItems: "center",
99
+ flexDirection: "row",
100
+ gap: 12,
101
+ minHeight: 72,
102
+ paddingHorizontal: 16,
103
+ paddingVertical: 14,
104
+ },
105
+ icon: {
106
+ alignItems: "center",
107
+ justifyContent: "center",
108
+ },
109
+ copy: {
110
+ flex: 1,
111
+ gap: 2,
112
+ },
113
+ title: {
114
+ fontSize: 17,
115
+ fontWeight: "600",
116
+ },
117
+ description: {
118
+ fontSize: 13,
119
+ },
120
+ });
@@ -0,0 +1,49 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import {
4
+ CHOICE_ICON_SIZE,
5
+ CHOICE_ICON_SLOT,
6
+ CHOICE_SELECTED_BORDER,
7
+ choiceIconSlot,
8
+ choiceSelection,
9
+ } from "./choiceLayout.ts";
10
+
11
+ describe("choiceIconSlot", () => {
12
+ it("keeps a steady slot so titles line up across rows", () => {
13
+ assert.equal(choiceIconSlot(CHOICE_ICON_SIZE), CHOICE_ICON_SLOT);
14
+ assert.equal(choiceIconSlot(20), CHOICE_ICON_SLOT);
15
+ });
16
+
17
+ it("grows for an icon larger than the slot instead of overflowing", () => {
18
+ assert.equal(choiceIconSlot(40), 40);
19
+ });
20
+ });
21
+
22
+ describe("choiceSelection", () => {
23
+ it("is a plain button with no border when selection is not used", () => {
24
+ assert.deepEqual(choiceSelection(undefined), {
25
+ role: "button",
26
+ state: {},
27
+ borderWidth: 0,
28
+ highlighted: false,
29
+ });
30
+ });
31
+
32
+ it("is a selected radio with the highlight border", () => {
33
+ assert.deepEqual(choiceSelection(true), {
34
+ role: "radio",
35
+ state: { selected: true, checked: true },
36
+ borderWidth: CHOICE_SELECTED_BORDER,
37
+ highlighted: true,
38
+ });
39
+ });
40
+
41
+ it("keeps the border width when not selected, so rows do not jump", () => {
42
+ assert.deepEqual(choiceSelection(false), {
43
+ role: "radio",
44
+ state: { selected: false, checked: false },
45
+ borderWidth: CHOICE_SELECTED_BORDER,
46
+ highlighted: false,
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,34 @@
1
+ /** Default icon box, sized for an SF Symbol next to a two-line label. */
2
+ export const CHOICE_ICON_SIZE = 28;
3
+ /** Icon column width, so titles line up across a list of choices. */
4
+ export const CHOICE_ICON_SLOT = 32;
5
+
6
+ /** The icon column: the steady slot, or the icon itself when it is larger. Pure so it can be unit-tested. */
7
+ export function choiceIconSlot(iconSize: number): number {
8
+ return Math.max(CHOICE_ICON_SLOT, iconSize);
9
+ }
10
+
11
+ /** Border around a selectable choice; drawn transparent when not selected. */
12
+ export const CHOICE_SELECTED_BORDER = 2;
13
+
14
+ /**
15
+ * Role, state and border for a choice. Without `selected` it is a plain
16
+ * button. With it, a radio option that keeps its border width either way,
17
+ * so rows do not shift when the selection moves. Pure so it can be unit-tested.
18
+ */
19
+ export function choiceSelection(selected: boolean | undefined): {
20
+ role: "button" | "radio";
21
+ state: { selected?: boolean; checked?: boolean };
22
+ borderWidth: number;
23
+ highlighted: boolean;
24
+ } {
25
+ if (selected === undefined) {
26
+ return { role: "button", state: {}, borderWidth: 0, highlighted: false };
27
+ }
28
+ return {
29
+ role: "radio",
30
+ state: { selected, checked: selected },
31
+ borderWidth: CHOICE_SELECTED_BORDER,
32
+ highlighted: selected,
33
+ };
34
+ }
@@ -0,0 +1,15 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { CHOICE_THEME_ROLES } from "./choiceThemeRoles.ts";
4
+
5
+ describe("CHOICE_THEME_ROLES", () => {
6
+ it("maps colour props to choice-prefixed roles, not the app's own names", () => {
7
+ assert.deepEqual(CHOICE_THEME_ROLES, {
8
+ backgroundColor: "choiceBackground",
9
+ titleColor: "choiceTitle",
10
+ descriptionColor: "choiceDescription",
11
+ iconColor: "choiceIcon",
12
+ selectedColor: "choiceSelected",
13
+ });
14
+ });
15
+ });
@@ -0,0 +1,9 @@
1
+ export const CHOICE_THEME_ROLES = {
2
+ backgroundColor: "choiceBackground",
3
+ titleColor: "choiceTitle",
4
+ descriptionColor: "choiceDescription",
5
+ iconColor: "choiceIcon",
6
+ selectedColor: "choiceSelected",
7
+ } as const;
8
+
9
+ export type ChoiceThemeColorProp = keyof typeof CHOICE_THEME_ROLES;
@@ -0,0 +1,4 @@
1
+ export { Choice } from "./Choice";
2
+ export { CHOICE_ICON_SIZE } from "./choiceLayout";
3
+ export { CHOICE_THEME_ROLES, type ChoiceThemeColorProp } from "./choiceThemeRoles";
4
+ export type { ChoiceProps } from "./types";
@@ -0,0 +1,33 @@
1
+ import type { StyleProp, ViewStyle } from "react-native";
2
+ import type { ButtonIcon } from "../Button";
3
+ import type { ButtonRadius } from "../Button/buttonRadius";
4
+
5
+ export type ChoiceProps = {
6
+ title: string;
7
+ /** Second line under the title. */
8
+ description?: string;
9
+ /** `({ color, size }) => ReactNode`, e.g. an expo-symbols `SymbolView`. */
10
+ icon?: ButtonIcon;
11
+ onPress: () => void;
12
+ /**
13
+ * Makes the choice a selectable option: a highlight border when true,
14
+ * read as a selected radio. Leave it out for a choice that just acts.
15
+ */
16
+ selected?: boolean;
17
+ disabled?: boolean;
18
+ /** Icon box. Default 28. */
19
+ iconSize?: number;
20
+ /** Default `"xl"` (12), the same scale as `Button`. */
21
+ radius?: ButtonRadius;
22
+ /** Row fill. Defaults to theme role `choiceBackground`. */
23
+ backgroundColor?: string;
24
+ /** Defaults to theme role `choiceTitle`. */
25
+ titleColor?: string;
26
+ /** Defaults to theme role `choiceDescription`. */
27
+ descriptionColor?: string;
28
+ /** Defaults to theme role `choiceIcon`. */
29
+ iconColor?: string;
30
+ /** Border of a selected choice. Defaults to theme role `choiceSelected`. */
31
+ selectedColor?: string;
32
+ style?: StyleProp<ViewStyle>;
33
+ };