react-native-terra-ui 0.1.0 → 0.2.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 (304) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/lib/module/components/avatar/Avatar.js +118 -35
  3. package/lib/module/components/avatar/Avatar.js.map +1 -1
  4. package/lib/module/components/avatar/index.js.map +1 -1
  5. package/lib/module/components/box/Box.js +15 -2
  6. package/lib/module/components/box/Box.js.map +1 -1
  7. package/lib/module/components/box/index.js.map +1 -1
  8. package/lib/module/components/box/utils.js +5 -2
  9. package/lib/module/components/box/utils.js.map +1 -1
  10. package/lib/module/components/button/Button.js +7 -14
  11. package/lib/module/components/button/Button.js.map +1 -1
  12. package/lib/module/components/button/index.js.map +1 -1
  13. package/lib/module/components/chip/Chip.js +372 -0
  14. package/lib/module/components/chip/Chip.js.map +1 -0
  15. package/lib/module/components/chip/index.js +4 -0
  16. package/lib/module/components/chip/index.js.map +1 -0
  17. package/lib/module/components/chip/types.js +2 -0
  18. package/lib/module/components/chip/types.js.map +1 -0
  19. package/lib/module/components/chip/utils.js +36 -0
  20. package/lib/module/components/chip/utils.js.map +1 -0
  21. package/lib/module/components/empty-state/EmptyState.js +200 -0
  22. package/lib/module/components/empty-state/EmptyState.js.map +1 -0
  23. package/lib/module/components/empty-state/index.js +4 -0
  24. package/lib/module/components/empty-state/index.js.map +1 -0
  25. package/lib/module/components/empty-state/types.js +2 -0
  26. package/lib/module/components/empty-state/types.js.map +1 -0
  27. package/lib/module/components/header/Header.js.map +1 -1
  28. package/lib/module/components/header/header-buttons.js +31 -36
  29. package/lib/module/components/header/header-buttons.js.map +1 -1
  30. package/lib/module/components/header/index.js +1 -1
  31. package/lib/module/components/header/index.js.map +1 -1
  32. package/lib/module/components/header/parts/HeaderGradientOverlay.js +38 -0
  33. package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -0
  34. package/lib/module/components/header/variants/LargeTitleHeader/index.js +71 -101
  35. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  36. package/lib/module/components/header/variants/TitleHeader/index.js +34 -65
  37. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  38. package/lib/module/components/icon/Icon.js +21 -9
  39. package/lib/module/components/icon/Icon.js.map +1 -1
  40. package/lib/module/components/icon/index.js.map +1 -1
  41. package/lib/module/components/image/Image.js +54 -0
  42. package/lib/module/components/image/Image.js.map +1 -0
  43. package/lib/module/components/image/index.js +4 -0
  44. package/lib/module/components/image/index.js.map +1 -0
  45. package/lib/module/components/image/types.js +4 -0
  46. package/lib/module/components/image/types.js.map +1 -0
  47. package/lib/module/components/index.js +4 -0
  48. package/lib/module/components/index.js.map +1 -1
  49. package/lib/module/components/page-indicator/PageIndicator.js.map +1 -1
  50. package/lib/module/components/page-indicator/PageIndicatorRoot.js.map +1 -1
  51. package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js +1 -1
  52. package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js.map +1 -1
  53. package/lib/module/components/page-indicator/hooks/use-page-indicator-core.js.map +1 -1
  54. package/lib/module/components/page-indicator/hooks/use-page-indicator-progress.js.map +1 -1
  55. package/lib/module/components/page-indicator/index.js.map +1 -1
  56. package/lib/module/components/page-indicator/variants/DotIndicator/InactiveDot.js.map +1 -1
  57. package/lib/module/components/page-indicator/variants/DotIndicator/LoadingOrbit.js.map +1 -1
  58. package/lib/module/components/page-indicator/variants/DotIndicator/TravelingPill.js.map +1 -1
  59. package/lib/module/components/page-indicator/variants/DotIndicator/index.js.map +1 -1
  60. package/lib/module/components/page-indicator/variants/PillIndicator/PillIndicatorDot.js.map +1 -1
  61. package/lib/module/components/page-indicator/variants/PillIndicator/index.js.map +1 -1
  62. package/lib/module/components/portal/Portal.js +42 -0
  63. package/lib/module/components/portal/Portal.js.map +1 -0
  64. package/lib/module/components/portal/PortalContext.js +1 -16
  65. package/lib/module/components/portal/PortalContext.js.map +1 -1
  66. package/lib/module/components/portal/PortalHost.js +16 -14
  67. package/lib/module/components/portal/PortalHost.js.map +1 -1
  68. package/lib/module/components/portal/PortalProvider.js +51 -19
  69. package/lib/module/components/portal/PortalProvider.js.map +1 -1
  70. package/lib/module/components/portal/index.js +1 -1
  71. package/lib/module/components/portal/index.js.map +1 -1
  72. package/lib/module/components/screen/Screen.js +26 -29
  73. package/lib/module/components/screen/Screen.js.map +1 -1
  74. package/lib/module/components/screen/ScreenContext.js +7 -5
  75. package/lib/module/components/screen/ScreenContext.js.map +1 -1
  76. package/lib/module/components/screen/ScreenFlashList.js +144 -0
  77. package/lib/module/components/screen/ScreenFlashList.js.map +1 -0
  78. package/lib/module/components/screen/ScreenFlatList.js +88 -27
  79. package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
  80. package/lib/module/components/screen/ScreenScrollView.js +101 -34
  81. package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
  82. package/lib/module/components/screen/index.js +1 -0
  83. package/lib/module/components/screen/index.js.map +1 -1
  84. package/lib/module/components/screen/parts/BottomSafeArea.js +62 -0
  85. package/lib/module/components/screen/parts/BottomSafeArea.js.map +1 -0
  86. package/lib/module/components/screen/parts/ScreenPlaceholder.js +50 -0
  87. package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -0
  88. package/lib/module/components/screen/types.js +4 -0
  89. package/lib/module/components/screen/types.js.map +1 -0
  90. package/lib/module/components/screen/utils.js +12 -9
  91. package/lib/module/components/screen/utils.js.map +1 -1
  92. package/lib/module/components/slot/Slot.js.map +1 -1
  93. package/lib/module/components/slot/index.js.map +1 -1
  94. package/lib/module/components/spinner/Spinner.js +1 -1
  95. package/lib/module/components/spinner/Spinner.js.map +1 -1
  96. package/lib/module/components/spinner/index.js.map +1 -1
  97. package/lib/module/components/surface/Surface.js +16 -9
  98. package/lib/module/components/surface/Surface.js.map +1 -1
  99. package/lib/module/components/surface/index.js.map +1 -1
  100. package/lib/module/components/text/Text.js +25 -7
  101. package/lib/module/components/text/Text.js.map +1 -1
  102. package/lib/module/components/text/index.js.map +1 -1
  103. package/lib/module/components/toast/Toast.js +8 -8
  104. package/lib/module/components/toast/Toast.js.map +1 -1
  105. package/lib/module/components/toast/ToastProvider.js.map +1 -1
  106. package/lib/module/components/toast/index.js.map +1 -1
  107. package/lib/module/components/toolbar/Toolbar.js +268 -0
  108. package/lib/module/components/toolbar/Toolbar.js.map +1 -0
  109. package/lib/module/components/toolbar/index.js +4 -0
  110. package/lib/module/components/toolbar/index.js.map +1 -0
  111. package/lib/module/context/ThemeProvider.js +5 -2
  112. package/lib/module/context/ThemeProvider.js.map +1 -1
  113. package/lib/module/context/index.js.map +1 -1
  114. package/lib/module/index.js.map +1 -1
  115. package/lib/module/{components/icon/utils.js → theme/defaults/icons.js} +1 -1
  116. package/lib/module/theme/defaults/icons.js.map +1 -0
  117. package/lib/module/theme/defaults/image.js +30 -0
  118. package/lib/module/theme/defaults/image.js.map +1 -0
  119. package/lib/module/theme/index.js +6 -6
  120. package/lib/module/theme/index.js.map +1 -1
  121. package/lib/module/theme/{configure.js → registry.js} +23 -30
  122. package/lib/module/theme/registry.js.map +1 -0
  123. package/lib/module/theme/runtime.js +1 -1
  124. package/lib/module/theme/runtime.js.map +1 -1
  125. package/lib/module/theme/theme.js +92 -11
  126. package/lib/module/theme/theme.js.map +1 -1
  127. package/lib/module/theme/tokens/dark.js +59 -68
  128. package/lib/module/theme/tokens/dark.js.map +1 -1
  129. package/lib/module/theme/tokens/light.js +49 -67
  130. package/lib/module/theme/tokens/light.js.map +1 -1
  131. package/lib/module/theme/tokens/primitives.js +241 -20
  132. package/lib/module/theme/tokens/primitives.js.map +1 -1
  133. package/lib/module/utils/accent-utils.js +4 -4
  134. package/lib/module/utils/accent-utils.js.map +1 -1
  135. package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
  136. package/lib/typescript/src/components/avatar/types.d.ts +5 -5
  137. package/lib/typescript/src/components/avatar/types.d.ts.map +1 -1
  138. package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
  139. package/lib/typescript/src/components/box/utils.d.ts +6 -1
  140. package/lib/typescript/src/components/box/utils.d.ts.map +1 -1
  141. package/lib/typescript/src/components/button/Button.d.ts +2 -2
  142. package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
  143. package/lib/typescript/src/components/chip/Chip.d.ts +24 -0
  144. package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -0
  145. package/lib/typescript/src/components/chip/index.d.ts +4 -0
  146. package/lib/typescript/src/components/chip/index.d.ts.map +1 -0
  147. package/lib/typescript/src/components/chip/types.d.ts +8 -0
  148. package/lib/typescript/src/components/chip/types.d.ts.map +1 -0
  149. package/lib/typescript/src/components/chip/utils.d.ts +8 -0
  150. package/lib/typescript/src/components/chip/utils.d.ts.map +1 -0
  151. package/lib/typescript/src/components/empty-state/EmptyState.d.ts +45 -0
  152. package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -0
  153. package/lib/typescript/src/components/empty-state/index.d.ts +4 -0
  154. package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -0
  155. package/lib/typescript/src/components/empty-state/types.d.ts +6 -0
  156. package/lib/typescript/src/components/empty-state/types.d.ts.map +1 -0
  157. package/lib/typescript/src/components/header/header-buttons.d.ts +27 -0
  158. package/lib/typescript/src/components/header/header-buttons.d.ts.map +1 -1
  159. package/lib/typescript/src/components/header/index.d.ts +1 -1
  160. package/lib/typescript/src/components/header/index.d.ts.map +1 -1
  161. package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +7 -0
  162. package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -0
  163. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +1 -1
  164. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  165. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +19 -8
  166. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  167. package/lib/typescript/src/components/icon/Icon.d.ts +1 -1
  168. package/lib/typescript/src/components/icon/Icon.d.ts.map +1 -1
  169. package/lib/typescript/src/components/image/Image.d.ts +16 -0
  170. package/lib/typescript/src/components/image/Image.d.ts.map +1 -0
  171. package/lib/typescript/src/components/image/index.d.ts +3 -0
  172. package/lib/typescript/src/components/image/index.d.ts.map +1 -0
  173. package/lib/typescript/src/components/image/types.d.ts +7 -0
  174. package/lib/typescript/src/components/image/types.d.ts.map +1 -0
  175. package/lib/typescript/src/components/index.d.ts +4 -0
  176. package/lib/typescript/src/components/index.d.ts.map +1 -1
  177. package/lib/typescript/src/components/page-indicator/PageIndicator.d.ts +1 -1
  178. package/lib/typescript/src/components/page-indicator/hooks/use-page-indicator-colors.d.ts +1 -1
  179. package/lib/typescript/src/components/page-indicator/variants/DotIndicator/index.d.ts +1 -1
  180. package/lib/typescript/src/components/page-indicator/variants/PillIndicator/index.d.ts +1 -1
  181. package/lib/typescript/src/components/portal/Portal.d.ts +26 -0
  182. package/lib/typescript/src/components/portal/Portal.d.ts.map +1 -0
  183. package/lib/typescript/src/components/portal/PortalContext.d.ts +11 -14
  184. package/lib/typescript/src/components/portal/PortalContext.d.ts.map +1 -1
  185. package/lib/typescript/src/components/portal/PortalHost.d.ts +9 -4
  186. package/lib/typescript/src/components/portal/PortalHost.d.ts.map +1 -1
  187. package/lib/typescript/src/components/portal/PortalProvider.d.ts +3 -3
  188. package/lib/typescript/src/components/portal/PortalProvider.d.ts.map +1 -1
  189. package/lib/typescript/src/components/portal/index.d.ts +4 -2
  190. package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
  191. package/lib/typescript/src/components/screen/Screen.d.ts +19 -14
  192. package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
  193. package/lib/typescript/src/components/screen/ScreenContext.d.ts +12 -9
  194. package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
  195. package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +43 -0
  196. package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -0
  197. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +17 -7
  198. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
  199. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +51 -16
  200. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
  201. package/lib/typescript/src/components/screen/index.d.ts +3 -0
  202. package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
  203. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +30 -0
  204. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +1 -0
  205. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +22 -0
  206. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -0
  207. package/lib/typescript/src/components/screen/types.d.ts +42 -0
  208. package/lib/typescript/src/components/screen/types.d.ts.map +1 -0
  209. package/lib/typescript/src/components/screen/utils.d.ts +9 -9
  210. package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
  211. package/lib/typescript/src/components/spinner/Spinner.d.ts +1 -1
  212. package/lib/typescript/src/components/surface/Surface.d.ts +8 -1
  213. package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
  214. package/lib/typescript/src/components/text/Text.d.ts +1 -1
  215. package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
  216. package/lib/typescript/src/components/toast/Toast.d.ts +1 -1
  217. package/lib/typescript/src/components/toolbar/Toolbar.d.ts +115 -0
  218. package/lib/typescript/src/components/toolbar/Toolbar.d.ts.map +1 -0
  219. package/lib/typescript/src/components/toolbar/index.d.ts +3 -0
  220. package/lib/typescript/src/components/toolbar/index.d.ts.map +1 -0
  221. package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
  222. package/lib/typescript/src/{components/icon/utils.d.ts → theme/defaults/icons.d.ts} +2 -2
  223. package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -0
  224. package/lib/typescript/src/theme/defaults/image.d.ts +4 -0
  225. package/lib/typescript/src/theme/defaults/image.d.ts.map +1 -0
  226. package/lib/typescript/src/theme/index.d.ts +7 -7
  227. package/lib/typescript/src/theme/index.d.ts.map +1 -1
  228. package/lib/typescript/src/theme/{configure.d.ts → registry.d.ts} +1 -1
  229. package/lib/typescript/src/theme/registry.d.ts.map +1 -0
  230. package/lib/typescript/src/theme/theme.d.ts +21 -1
  231. package/lib/typescript/src/theme/theme.d.ts.map +1 -1
  232. package/lib/typescript/src/theme/tokens/dark.d.ts +41 -64
  233. package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
  234. package/lib/typescript/src/theme/tokens/light.d.ts +44 -65
  235. package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
  236. package/lib/typescript/src/theme/tokens/primitives.d.ts +228 -17
  237. package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
  238. package/lib/typescript/src/theme/types.d.ts +66 -36
  239. package/lib/typescript/src/theme/types.d.ts.map +1 -1
  240. package/lib/typescript/src/utils/accent-utils.d.ts +1 -1
  241. package/lib/typescript/src/utils/resolve-theme-color.d.ts +1 -1
  242. package/lib/typescript/src/utils/typography.d.ts +1 -1
  243. package/package.json +28 -12
  244. package/skills/terra-ui/SKILL.md +193 -0
  245. package/skills/terra-ui/references/recipes.md +198 -0
  246. package/skills/terra-ui/references/troubleshooting.md +112 -0
  247. package/src/components/avatar/Avatar.tsx +128 -38
  248. package/src/components/avatar/types.ts +5 -5
  249. package/src/components/box/Box.tsx +13 -1
  250. package/src/components/box/utils.ts +10 -0
  251. package/src/components/button/Button.tsx +7 -15
  252. package/src/components/chip/Chip.tsx +444 -0
  253. package/src/components/chip/index.ts +3 -0
  254. package/src/components/chip/types.ts +15 -0
  255. package/src/components/chip/utils.ts +34 -0
  256. package/src/components/empty-state/EmptyState.tsx +209 -0
  257. package/src/components/empty-state/index.ts +3 -0
  258. package/src/components/empty-state/types.ts +5 -0
  259. package/src/components/header/header-buttons.tsx +33 -45
  260. package/src/components/header/index.ts +2 -0
  261. package/src/components/header/parts/HeaderGradientOverlay.tsx +29 -0
  262. package/src/components/header/variants/LargeTitleHeader/index.tsx +68 -102
  263. package/src/components/header/variants/TitleHeader/index.tsx +47 -56
  264. package/src/components/icon/Icon.tsx +20 -15
  265. package/src/components/image/Image.tsx +48 -0
  266. package/src/components/image/index.ts +2 -0
  267. package/src/components/image/types.ts +7 -0
  268. package/src/components/index.ts +4 -0
  269. package/src/components/portal/Portal.tsx +55 -0
  270. package/src/components/portal/PortalContext.tsx +14 -22
  271. package/src/components/portal/PortalHost.tsx +17 -10
  272. package/src/components/portal/PortalProvider.tsx +58 -21
  273. package/src/components/portal/index.ts +4 -2
  274. package/src/components/screen/Screen.tsx +45 -59
  275. package/src/components/screen/ScreenContext.tsx +19 -12
  276. package/src/components/screen/ScreenFlashList.tsx +196 -0
  277. package/src/components/screen/ScreenFlatList.tsx +100 -39
  278. package/src/components/screen/ScreenScrollView.tsx +114 -53
  279. package/src/components/screen/index.ts +3 -0
  280. package/src/components/screen/parts/BottomSafeArea.tsx +60 -0
  281. package/src/components/screen/parts/ScreenPlaceholder.tsx +55 -0
  282. package/src/components/screen/types.ts +43 -0
  283. package/src/components/screen/utils.ts +29 -15
  284. package/src/components/surface/Surface.tsx +17 -20
  285. package/src/components/text/Text.tsx +23 -5
  286. package/src/components/toast/Toast.tsx +8 -8
  287. package/src/components/toolbar/Toolbar.tsx +353 -0
  288. package/src/components/toolbar/index.ts +6 -0
  289. package/src/context/ThemeProvider.tsx +5 -4
  290. package/src/{components/icon/utils.tsx → theme/defaults/icons.tsx} +1 -1
  291. package/src/theme/defaults/image.tsx +34 -0
  292. package/src/theme/index.ts +1 -1
  293. package/src/theme/{configure.ts → registry.ts} +19 -34
  294. package/src/theme/runtime.ts +1 -1
  295. package/src/theme/theme.ts +127 -13
  296. package/src/theme/tokens/dark.ts +60 -69
  297. package/src/theme/tokens/light.ts +50 -68
  298. package/src/theme/tokens/primitives.ts +248 -20
  299. package/src/theme/types.ts +67 -35
  300. package/src/utils/accent-utils.ts +4 -4
  301. package/lib/module/components/icon/utils.js.map +0 -1
  302. package/lib/module/theme/configure.js.map +0 -1
  303. package/lib/typescript/src/components/icon/utils.d.ts.map +0 -1
  304. package/lib/typescript/src/theme/configure.d.ts.map +0 -1
@@ -2,9 +2,9 @@ import type { ComponentRef } from 'react';
2
2
  import { forwardRef } from 'react';
3
3
  import { View, type ViewProps } from 'react-native';
4
4
 
5
- import { useUnistyles } from 'react-native-unistyles';
5
+ import { StyleSheet, useUnistyles } from 'react-native-unistyles';
6
6
 
7
- import { getIcon } from '#theme/configure';
7
+ import { getIcon } from '#theme/registry';
8
8
  import type { ColorToken, TerraIconName } from '#theme/types';
9
9
  import { resolveThemeColor } from '#utils/resolve-theme-color';
10
10
 
@@ -50,19 +50,7 @@ export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(
50
50
  '';
51
51
 
52
52
  return (
53
- <View
54
- ref={ref}
55
- style={[
56
- {
57
- width: size,
58
- height: size,
59
- alignItems: 'center',
60
- justifyContent: 'center',
61
- },
62
- style,
63
- ]}
64
- {...rest}
65
- >
53
+ <View ref={ref} style={[styles.container(size), style]} {...rest}>
66
54
  <IconComponent
67
55
  color={resolvedColor}
68
56
  size={size}
@@ -72,3 +60,20 @@ export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(
72
60
  );
73
61
  }
74
62
  );
63
+
64
+ // ─── Styles ───────────────────────────────────────────────────────────────────
65
+ //
66
+ // This stylesheet is also what makes the Unistyles Babel plugin swap the `View`
67
+ // imported above for its Unistyles-aware counterpart, which registers the
68
+ // ShadowNode natively. Without it, a Unistyles style passed in through `style`
69
+ // would freeze at whichever theme was active when it was created — `Icon` does
70
+ // not re-render on theme change unless something it reads from the theme
71
+ // happens to change.
72
+ const styles = StyleSheet.create(() => ({
73
+ container: (size: number) => ({
74
+ width: size,
75
+ height: size,
76
+ alignItems: 'center',
77
+ justifyContent: 'center',
78
+ }),
79
+ }));
@@ -0,0 +1,48 @@
1
+ import { View } from 'react-native';
2
+ import { StyleSheet } from 'react-native-unistyles';
3
+ import { getImageComponent } from '#theme/registry';
4
+ import type { RadiusKey } from '#theme/types.js';
5
+ import type { ImageProps } from './types';
6
+
7
+ /**
8
+ * Renders the image implementation configured via
9
+ * `configureTerraUI({ image })` (defaults to React Native's `Image`).
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Image
14
+ * source={{ uri: 'https://example.com/photo.jpg' }}
15
+ * contentFit="cover"
16
+ * style={{ width: 40, height: 40 }}
17
+ * />
18
+ * ```
19
+ */
20
+ export function Image({
21
+ contentFit = 'cover',
22
+ width,
23
+ height,
24
+ radius = 'none',
25
+ style,
26
+ ...rest
27
+ }: ImageProps) {
28
+ const ImageComponent = getImageComponent();
29
+
30
+ return (
31
+ <View style={[styles.container(width, height, radius), style]}>
32
+ <ImageComponent contentFit={contentFit} style={styles.image} {...rest} />
33
+ </View>
34
+ );
35
+ }
36
+
37
+ const styles = StyleSheet.create((_theme) => ({
38
+ container: (w: number | undefined, h: number | undefined, r: RadiusKey) => ({
39
+ ...(w ? { width: w } : {}),
40
+ ...(h ? { height: h } : {}),
41
+ borderRadius: _theme.radius[r],
42
+ overflow: 'hidden',
43
+ }),
44
+ image: {
45
+ width: '100%',
46
+ height: '100%',
47
+ },
48
+ }));
@@ -0,0 +1,2 @@
1
+ export { Image } from './Image';
2
+ export type { ImageProps } from './types';
@@ -0,0 +1,7 @@
1
+ import type { RadiusKey, TerraImageProps } from '#theme/types';
2
+
3
+ export interface ImageProps extends TerraImageProps {
4
+ width?: number;
5
+ height?: number;
6
+ radius?: RadiusKey;
7
+ }
@@ -1,8 +1,11 @@
1
1
  export * from './avatar';
2
2
  export * from './box';
3
3
  export * from './button';
4
+ export * from './chip';
5
+ export * from './empty-state';
4
6
  export * from './header';
5
7
  export * from './icon';
8
+ export * from './image';
6
9
  export * from './page-indicator';
7
10
  export * from './portal';
8
11
  export * from './screen';
@@ -11,3 +14,4 @@ export * from './spinner';
11
14
  export * from './surface';
12
15
  export * from './text';
13
16
  export * from './toast';
17
+ export * from './toolbar';
@@ -0,0 +1,55 @@
1
+ import { type FC, type ReactNode, useContext, useEffect, useRef } from 'react';
2
+
3
+ import { PortalContext } from './PortalContext';
4
+ import { PortalHost, type PortalHostProps } from './PortalHost';
5
+ import { PortalProvider, type PortalProviderProps } from './PortalProvider';
6
+
7
+ let nextPortalKey = 1;
8
+
9
+ export interface PortalProps {
10
+ /** Name of the `PortalHost` to teleport `children` into. */
11
+ hostName: string;
12
+ children?: ReactNode;
13
+ }
14
+
15
+ const PortalComponent: FC<PortalProps> = ({ hostName, children }) => {
16
+ const context = useContext(PortalContext);
17
+ const upsertPortal = context?.upsertPortal;
18
+ const removePortal = context?.removePortal;
19
+ const portalKeyRef = useRef<number>(nextPortalKey++);
20
+
21
+ useEffect(() => {
22
+ if (!upsertPortal || !removePortal) {
23
+ return;
24
+ }
25
+
26
+ upsertPortal(hostName, portalKeyRef.current, children ?? null);
27
+
28
+ return () => {
29
+ removePortal(hostName, portalKeyRef.current);
30
+ };
31
+ }, [children, hostName, removePortal, upsertPortal]);
32
+
33
+ return null;
34
+ };
35
+
36
+ type PortalWithCompatApi = FC<PortalProps> & {
37
+ Provider: FC<PortalProviderProps>;
38
+ Host: FC<PortalHostProps>;
39
+ };
40
+
41
+ /**
42
+ * Declaratively teleports `children` into the `PortalHost` named `hostName`,
43
+ * for as long as this component stays mounted.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <Portal hostName="screen-header">
48
+ * <LargeTitle />
49
+ * </Portal>
50
+ * ```
51
+ */
52
+ export const Portal = Object.assign(PortalComponent, {
53
+ Provider: PortalProvider,
54
+ Host: PortalHost,
55
+ }) as PortalWithCompatApi;
@@ -1,27 +1,19 @@
1
- import { createContext, type ReactNode, useContext } from 'react';
1
+ import { createContext, type ReactNode } from 'react';
2
2
 
3
- export interface PortalContextValue {
4
- /** Register (or replace) portal content under a stable id. */
5
- registerContent: (id: string, content: ReactNode) => void;
6
- /** Remove the content previously registered under `id`. */
7
- unregisterContent: (id: string) => void;
8
- /** Currently registered content, keyed by id, rendered by `PortalHost`. */
9
- contents: Map<string, ReactNode>;
3
+ export interface PortalEntry {
4
+ key: number;
5
+ node: ReactNode;
10
6
  }
11
7
 
12
- export const PortalContext = createContext<PortalContextValue | null>(null);
8
+ export type PortalStore = Record<string, PortalEntry[]>;
13
9
 
14
- /**
15
- * Access the nearest {@link PortalProvider}. Used by components that need to
16
- * teleport content into a `PortalHost` rendered elsewhere in the tree (e.g. the
17
- * large-title header injecting its title into the scroll content).
18
- *
19
- * @throws if called outside a `PortalProvider`.
20
- */
21
- export function usePortal(): PortalContextValue {
22
- const context = useContext(PortalContext);
23
- if (!context) {
24
- throw new Error('usePortal must be used within a PortalProvider');
25
- }
26
- return context;
10
+ export interface PortalContextValue {
11
+ /** Portal entries currently registered, keyed by host name. */
12
+ portals: PortalStore;
13
+ /** Register (or replace) the entry `key` under `hostName`. */
14
+ upsertPortal: (hostName: string, key: number, node: ReactNode) => void;
15
+ /** Remove the entry `key` previously registered under `hostName`. */
16
+ removePortal: (hostName: string, key: number) => void;
27
17
  }
18
+
19
+ export const PortalContext = createContext<PortalContextValue | null>(null);
@@ -1,19 +1,26 @@
1
- import { View } from 'react-native';
1
+ import { Fragment, useContext } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
2
3
 
3
- import { usePortal } from './PortalContext';
4
+ import { PortalContext } from './PortalContext';
5
+
6
+ export type PortalHostProps = Omit<ViewProps, 'children'> & {
7
+ /** Renders every `Portal` registered under this host name, in insertion order. */
8
+ name: string;
9
+ };
4
10
 
5
11
  /**
6
- * Renders every node currently registered with the nearest
7
- * {@link PortalProvider}, in insertion order. Place it where teleported content
8
- * should appear (e.g. at the top of a `Screen.ScrollView`).
12
+ * Renders every entry currently registered under `name` on the nearest
13
+ * {@link PortalProvider}. Place it where teleported content should appear
14
+ * (e.g. at the top of a `Screen.ScrollView`).
9
15
  */
10
- export function PortalHost() {
11
- const { contents } = usePortal();
16
+ export function PortalHost({ name, ...viewProps }: PortalHostProps) {
17
+ const context = useContext(PortalContext);
18
+ const hostPortals = context?.portals[name] ?? [];
12
19
 
13
20
  return (
14
- <View collapsable={false}>
15
- {Array.from(contents.entries()).map(([id, content]) => (
16
- <View key={id}>{content}</View>
21
+ <View {...viewProps}>
22
+ {hostPortals.map((entry) => (
23
+ <Fragment key={entry.key}>{entry.node}</Fragment>
17
24
  ))}
18
25
  </View>
19
26
  );
@@ -1,41 +1,78 @@
1
1
  import { type ReactNode, useCallback, useMemo, useState } from 'react';
2
2
 
3
- import { PortalContext, type PortalContextValue } from './PortalContext';
3
+ import {
4
+ PortalContext,
5
+ type PortalContextValue,
6
+ type PortalStore,
7
+ } from './PortalContext';
4
8
 
5
9
  export interface PortalProviderProps {
6
10
  children: ReactNode;
7
11
  }
8
12
 
9
13
  /**
10
- * Holds a registry of teleported content. Children register/unregister nodes by
11
- * id (see {@link usePortal}); a `PortalHost` placed anywhere below renders them.
12
- * Context-only no animation dependency.
14
+ * Holds a registry of teleported content, keyed by host name. Descendant
15
+ * `Portal` components register/unregister entries; a `PortalHost` placed
16
+ * anywhere below renders the entries for its `name`.
13
17
  */
14
18
  export function PortalProvider({ children }: PortalProviderProps) {
15
- const [contents, setContents] = useState<Map<string, ReactNode>>(
16
- () => new Map()
19
+ const [portals, setPortals] = useState<PortalStore>({});
20
+
21
+ const upsertPortal = useCallback(
22
+ (hostName: string, key: number, node: ReactNode) => {
23
+ setPortals((prev) => {
24
+ const hostPortals = prev[hostName] ?? [];
25
+ const existingIndex = hostPortals.findIndex(
26
+ (entry) => entry.key === key
27
+ );
28
+
29
+ if (existingIndex === -1) {
30
+ return {
31
+ ...prev,
32
+ [hostName]: [...hostPortals, { key, node }],
33
+ };
34
+ }
35
+
36
+ if (hostPortals[existingIndex]?.node === node) {
37
+ return prev;
38
+ }
39
+
40
+ const nextHostPortals = [...hostPortals];
41
+ nextHostPortals[existingIndex] = { key, node };
42
+
43
+ return {
44
+ ...prev,
45
+ [hostName]: nextHostPortals,
46
+ };
47
+ });
48
+ },
49
+ []
17
50
  );
18
51
 
19
- const registerContent = useCallback((id: string, content: ReactNode) => {
20
- setContents((prev) => {
21
- const next = new Map(prev);
22
- next.set(id, content);
23
- return next;
24
- });
25
- }, []);
52
+ const removePortal = useCallback((hostName: string, key: number) => {
53
+ setPortals((prev) => {
54
+ const hostPortals = prev[hostName] ?? [];
55
+ const nextHostPortals = hostPortals.filter((entry) => entry.key !== key);
56
+
57
+ if (nextHostPortals.length === hostPortals.length) {
58
+ return prev;
59
+ }
60
+
61
+ if (nextHostPortals.length === 0) {
62
+ const { [hostName]: _removed, ...rest } = prev;
63
+ return rest;
64
+ }
26
65
 
27
- const unregisterContent = useCallback((id: string) => {
28
- setContents((prev) => {
29
- if (!prev.has(id)) return prev;
30
- const next = new Map(prev);
31
- next.delete(id);
32
- return next;
66
+ return {
67
+ ...prev,
68
+ [hostName]: nextHostPortals,
69
+ };
33
70
  });
34
71
  }, []);
35
72
 
36
73
  const value = useMemo<PortalContextValue>(
37
- () => ({ registerContent, unregisterContent, contents }),
38
- [registerContent, unregisterContent, contents]
74
+ () => ({ portals, upsertPortal, removePortal }),
75
+ [portals, removePortal, upsertPortal]
39
76
  );
40
77
 
41
78
  return (
@@ -1,5 +1,7 @@
1
- export type { PortalContextValue } from './PortalContext';
2
- export { usePortal } from './PortalContext';
1
+ export type { PortalProps } from './Portal';
2
+ export { Portal } from './Portal';
3
+ export type { PortalContextValue, PortalEntry } from './PortalContext';
4
+ export type { PortalHostProps } from './PortalHost';
3
5
  export { PortalHost } from './PortalHost';
4
6
  export type { PortalProviderProps } from './PortalProvider';
5
7
  export { PortalProvider } from './PortalProvider';
@@ -10,20 +10,17 @@ import {
10
10
  type ReactNode,
11
11
  useMemo,
12
12
  } from 'react';
13
- import {
14
- type Edge,
15
- SafeAreaView,
16
- type SafeAreaViewProps,
17
- } from 'react-native-safe-area-context';
18
- import { useUnistyles } from 'react-native-unistyles';
19
-
13
+ import { View } from 'react-native';
14
+ import type { ViewProps } from 'react-native-svg/lib/typescript/fabric/utils';
15
+ import { StyleSheet, useUnistyles } from 'react-native-unistyles';
20
16
  import type { ColorToken } from '#theme/types';
21
17
  import { resolveThemeColor } from '#utils/resolve-theme-color';
22
-
23
18
  import { PortalProvider } from '../portal';
24
19
  import { ScreenScrollProvider } from './ScreenContext';
20
+ import { ScreenFlashList } from './ScreenFlashList';
25
21
  import { ScreenFlatList } from './ScreenFlatList';
26
22
  import { ScreenScrollView } from './ScreenScrollView';
23
+ import type { ScreenMargins } from './types';
27
24
 
28
25
  export type ScreenHeaderProps<T extends ElementType = typeof Fragment> =
29
26
  PropsWithChildren<
@@ -57,43 +54,48 @@ function ScreenHeader<T extends ElementType = typeof Fragment>({
57
54
  const Component = as ?? Fragment;
58
55
  return <Component {...rest}>{children}</Component>;
59
56
  }
57
+ ScreenHeader.displayName = 'Screen.Header';
60
58
 
61
59
  /** True if `children` directly contains a `Screen.Header`. */
62
60
  function hasScreenHeader(children: ReactNode): boolean {
63
61
  let found = false;
64
62
  Children.forEach(children, (child) => {
65
63
  if (found) return;
66
- if (isValidElement(child) && child.type === ScreenHeader) found = true;
64
+ if (
65
+ isValidElement(child) &&
66
+ typeof child.type !== 'string' &&
67
+ (child.type as { displayName?: string }).displayName === 'Screen.Header'
68
+ )
69
+ found = true;
67
70
  });
68
71
  return found;
69
72
  }
70
73
 
71
- export interface ScreenProps extends Omit<SafeAreaViewProps, 'edges'> {
74
+ export interface ScreenProps extends ViewProps {
72
75
  children: ReactNode;
73
76
  /** Background color token. Defaults to the `background` token. */
74
77
  bg?: ColorToken;
75
- /**
76
- * Safe-area edges to inset. Defaults are header-aware: `[]` when a
77
- * `Screen.Header` is present (the header owns the top inset), otherwise
78
- * `['top', 'bottom']`. An explicit value is respected, except `top` is
79
- * dropped when a header is present.
80
- */
81
- edges?: Edge[];
82
78
  /** Hosted inside a tab navigator? Exposed via `useScreen()`. Default false. */
83
79
  inTabView?: boolean;
84
80
  /**
85
- * Apply `layout.screen.margin` padding on `Screen.ScrollView` /
86
- * `Screen.FlatList`. Default true. Overridable per scroll container.
81
+ * Which axes of `layout.screen.margin` the scroll containers pad their
82
+ * content with. Default `'all'`. Overridable per scroll container.
83
+ */
84
+ margins?: ScreenMargins;
85
+ /**
86
+ * @deprecated Use `margins` — `false` maps to `margins="none"`. Ignored when
87
+ * `margins` is set.
87
88
  */
88
- margins?: boolean;
89
+ hasMargins?: boolean;
89
90
  }
90
91
 
91
92
  type ScreenComponent = ReturnType<
92
- typeof forwardRef<ComponentRef<typeof SafeAreaView>, ScreenProps>
93
+ typeof forwardRef<ComponentRef<typeof View>, ScreenProps>
93
94
  > & {
94
95
  Header: typeof ScreenHeader;
95
96
  ScrollView: typeof ScreenScrollView;
96
97
  FlatList: typeof ScreenFlatList;
98
+ FlashList: typeof ScreenFlashList;
97
99
  };
98
100
 
99
101
  /**
@@ -109,49 +111,26 @@ type ScreenComponent = ReturnType<
109
111
  * </Screen>
110
112
  * ```
111
113
  */
112
- const ScreenBase = forwardRef<ComponentRef<typeof SafeAreaView>, ScreenProps>(
114
+ const ScreenBase = forwardRef<ComponentRef<typeof View>, ScreenProps>(
113
115
  function Screen(
114
- {
115
- children,
116
- bg = 'background',
117
- edges,
118
- inTabView = false,
119
- margins = true,
120
- style,
121
- ...rest
122
- },
123
- ref
116
+ { children, bg = 'background', margins, hasMargins, style, ...rest },
117
+ _ref
124
118
  ) {
125
- const { theme } = useUnistyles();
126
- const backgroundColor =
127
- resolveThemeColor(bg, theme) ?? theme.color.background;
128
-
129
119
  const hasHeader = useMemo(() => hasScreenHeader(children), [children]);
130
-
131
- const safeEdges = useMemo<Edge[]>(() => {
132
- if (edges) {
133
- return hasHeader ? edges.filter((edge) => edge !== 'top') : edges;
134
- }
135
- return hasHeader ? [] : ['top', 'bottom'];
136
- }, [edges, hasHeader]);
120
+ const resolvedMargins = margins ?? (hasMargins === false ? 'none' : 'all');
121
+ const { theme } = useUnistyles();
122
+ const resolvedBg = resolveThemeColor(bg, theme) ?? theme.color.background;
137
123
 
138
124
  return (
139
- <ScreenScrollProvider
140
- isInTabView={inTabView}
141
- margins={margins}
142
- hasHeader={hasHeader}
143
- >
144
- <PortalProvider>
145
- <SafeAreaView
146
- ref={ref}
147
- edges={safeEdges}
148
- style={[{ flex: 1, backgroundColor }, style]}
149
- {...rest}
150
- >
151
- {children}
152
- </SafeAreaView>
153
- </PortalProvider>
154
- </ScreenScrollProvider>
125
+ <View style={[styles.container, style]} {...rest}>
126
+ <ScreenScrollProvider
127
+ background={resolvedBg}
128
+ margins={resolvedMargins}
129
+ hasHeader={hasHeader}
130
+ >
131
+ <PortalProvider>{children}</PortalProvider>
132
+ </ScreenScrollProvider>
133
+ </View>
155
134
  );
156
135
  }
157
136
  ) as ScreenComponent;
@@ -159,5 +138,12 @@ const ScreenBase = forwardRef<ComponentRef<typeof SafeAreaView>, ScreenProps>(
159
138
  ScreenBase.Header = ScreenHeader;
160
139
  ScreenBase.ScrollView = ScreenScrollView;
161
140
  ScreenBase.FlatList = ScreenFlatList;
141
+ ScreenBase.FlashList = ScreenFlashList;
162
142
 
163
143
  export const Screen = ScreenBase;
144
+
145
+ const styles = StyleSheet.create((_theme, _runtime) => ({
146
+ container: {
147
+ flex: 1,
148
+ },
149
+ }));
@@ -16,14 +16,20 @@ import Animated, {
16
16
  useSharedValue,
17
17
  } from 'react-native-reanimated';
18
18
 
19
+ import type { ScreenMargins } from './types';
20
+
19
21
  export type ScreenScrollRef = AnimatedRef<Animated.ScrollView>;
20
22
 
23
+ /** `PortalHost` name shared by `Screen.ScrollView` / `Screen.FlatList` and header variants that portal content into them. */
24
+ export const SCREEN_HEADER_PORTAL_HOST = 'screen-header';
25
+
21
26
  export interface ScreenContextValue {
22
27
  /**
23
- * Whether scroll containers apply `layout.screen.margin` padding.
24
- * Overridable per `Screen.ScrollView` / `Screen.FlatList`.
28
+ * Which axes scroll containers pad with `layout.screen.margin`.
29
+ * Overridable per `Screen.ScrollView` / `Screen.FlatList` /
30
+ * `Screen.FlashList` via their own `margins` prop.
25
31
  */
26
- margins: boolean;
32
+ margins: ScreenMargins;
27
33
  /** Whether a `Screen.Header` is present. Used by scroll containers to add top inset. */
28
34
  hasHeader: boolean;
29
35
  /**
@@ -49,19 +55,20 @@ export interface ScreenContextValue {
49
55
  scrollRef: ScreenScrollRef;
50
56
  /** Scroll handler that mirrors offset into `scrollY`. */
51
57
  scrollHandler: ScrollHandlerProcessed<Record<string, unknown>>;
52
- /** Whether the screen is hosted inside a tab navigator (affects edges). */
53
- isInTabView: boolean;
58
+
59
+ background?: string;
54
60
  }
55
61
 
56
62
  export const ScreenContext = createContext<ScreenContextValue | null>(null);
57
63
 
58
64
  export interface ScreenScrollProviderProps {
59
65
  children: ReactNode;
60
- isInTabView: boolean;
61
- /** Apply `layout.screen.margin` to scroll containers. Default true. */
62
- margins?: boolean;
66
+ /** Which axes scroll containers pad with `layout.screen.margin`. Default `'all'`. */
67
+ margins?: ScreenMargins;
63
68
  /** Whether a `Screen.Header` child is present. Default false. */
64
69
  hasHeader?: boolean;
70
+
71
+ background?: string;
65
72
  }
66
73
 
67
74
  /**
@@ -70,9 +77,9 @@ export interface ScreenScrollProviderProps {
70
77
  * scroll containers bind the ref via {@link useScreen}.
71
78
  */
72
79
  export function ScreenScrollProvider({
80
+ background = 'transparent',
73
81
  children,
74
- isInTabView,
75
- margins = true,
82
+ margins = 'all',
76
83
  hasHeader = false,
77
84
  }: ScreenScrollProviderProps) {
78
85
  const scrollY = useSharedValue(0);
@@ -97,6 +104,7 @@ export function ScreenScrollProvider({
97
104
 
98
105
  const value = useMemo<ScreenContextValue>(
99
106
  () => ({
107
+ background,
100
108
  margins,
101
109
  hasHeader,
102
110
  scrollY,
@@ -105,9 +113,9 @@ export function ScreenScrollProvider({
105
113
  setHeaderCollapseHeight,
106
114
  scrollRef,
107
115
  scrollHandler,
108
- isInTabView,
109
116
  }),
110
117
  [
118
+ background,
111
119
  margins,
112
120
  hasHeader,
113
121
  scrollY,
@@ -116,7 +124,6 @@ export function ScreenScrollProvider({
116
124
  setHeaderCollapseHeight,
117
125
  scrollRef,
118
126
  scrollHandler,
119
- isInTabView,
120
127
  ]
121
128
  );
122
129