react-native-terra-ui 0.7.2 → 0.8.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 (252) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/lib/module/components/avatar/Avatar.js +20 -20
  4. package/lib/module/components/avatar/Avatar.js.map +1 -1
  5. package/lib/module/components/button/Button.js +11 -11
  6. package/lib/module/components/button/Button.js.map +1 -1
  7. package/lib/module/components/chip/Chip.js +32 -32
  8. package/lib/module/components/chip/Chip.js.map +1 -1
  9. package/lib/module/components/empty-state/EmptyState.js +3 -3
  10. package/lib/module/components/empty-state/EmptyState.js.map +1 -1
  11. package/lib/module/components/header/variants/LargeTitleHeader/index.js +30 -32
  12. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  13. package/lib/module/components/header/variants/TitleHeader/index.js +26 -18
  14. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  15. package/lib/module/components/icon/Icon.js +3 -3
  16. package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js +2 -2
  17. package/lib/module/components/screen/Screen.js +63 -34
  18. package/lib/module/components/screen/Screen.js.map +1 -1
  19. package/lib/module/components/screen/ScreenContext.js +28 -9
  20. package/lib/module/components/screen/ScreenContext.js.map +1 -1
  21. package/lib/module/components/screen/ScreenFlatList.js +4 -162
  22. package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
  23. package/lib/module/components/screen/ScreenList.js +81 -97
  24. package/lib/module/components/screen/ScreenList.js.map +1 -1
  25. package/lib/module/components/screen/ScreenScrollView.js +64 -115
  26. package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
  27. package/lib/module/components/screen/hooks/use-screen-ref.js +18 -0
  28. package/lib/module/components/screen/hooks/use-screen-ref.js.map +1 -0
  29. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js +19 -0
  30. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js.map +1 -0
  31. package/lib/module/components/screen/index.js.map +1 -1
  32. package/lib/module/components/screen/parts/ScreenFrame.js +47 -0
  33. package/lib/module/components/screen/parts/ScreenFrame.js.map +1 -0
  34. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js +43 -0
  35. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js.map +1 -0
  36. package/lib/module/components/screen/parts/ScreenPlaceholder.js +11 -13
  37. package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -1
  38. package/lib/module/components/screen/parts/ScreenSafeArea.android.js +27 -0
  39. package/lib/module/components/screen/parts/ScreenSafeArea.android.js.map +1 -0
  40. package/lib/module/components/screen/parts/ScreenSafeArea.js +26 -0
  41. package/lib/module/components/screen/parts/ScreenSafeArea.js.map +1 -0
  42. package/lib/module/components/screen/utils.js +18 -9
  43. package/lib/module/components/screen/utils.js.map +1 -1
  44. package/lib/module/components/spinner/Spinner.js +2 -2
  45. package/lib/module/components/text/Text.js +1 -1
  46. package/lib/module/components/toast/Toast.js +13 -13
  47. package/lib/module/components/toast/Toast.js.map +1 -1
  48. package/lib/module/components/toolbar/Toolbar.js +2 -2
  49. package/lib/module/context/ThemeProvider.js +22 -39
  50. package/lib/module/context/ThemeProvider.js.map +1 -1
  51. package/lib/module/context/index.js +1 -1
  52. package/lib/module/context/index.js.map +1 -1
  53. package/lib/module/theme/breakpoints.js +1 -1
  54. package/lib/module/theme/define-config.js +21 -0
  55. package/lib/module/theme/define-config.js.map +1 -0
  56. package/lib/module/theme/diagnostics.js +13 -0
  57. package/lib/module/theme/diagnostics.js.map +1 -0
  58. package/lib/module/theme/errors.js +15 -0
  59. package/lib/module/theme/errors.js.map +1 -0
  60. package/lib/module/theme/index.js +7 -1
  61. package/lib/module/theme/index.js.map +1 -1
  62. package/lib/module/theme/legacy-tokens.js +119 -0
  63. package/lib/module/theme/legacy-tokens.js.map +1 -0
  64. package/lib/module/theme/registry.js +122 -130
  65. package/lib/module/theme/registry.js.map +1 -1
  66. package/lib/module/theme/resolve-config.js +346 -0
  67. package/lib/module/theme/resolve-config.js.map +1 -0
  68. package/lib/module/theme/runtime.js +57 -12
  69. package/lib/module/theme/runtime.js.map +1 -1
  70. package/lib/module/theme/screen-margin.js +6 -14
  71. package/lib/module/theme/screen-margin.js.map +1 -1
  72. package/lib/module/theme/screen-padding.js +17 -0
  73. package/lib/module/theme/screen-padding.js.map +1 -0
  74. package/lib/module/theme/selection-store.js +75 -0
  75. package/lib/module/theme/selection-store.js.map +1 -0
  76. package/lib/module/theme/theme.js +81 -36
  77. package/lib/module/theme/theme.js.map +1 -1
  78. package/lib/module/theme/tokens/canonical-color-keys.js +102 -0
  79. package/lib/module/theme/tokens/canonical-color-keys.js.map +1 -0
  80. package/lib/module/theme/tokens/dark.js +72 -88
  81. package/lib/module/theme/tokens/dark.js.map +1 -1
  82. package/lib/module/theme/tokens/light.js +69 -83
  83. package/lib/module/theme/tokens/light.js.map +1 -1
  84. package/lib/module/theme/tokens/primitives.js +14 -14
  85. package/lib/module/theme/tokens/primitives.js.map +1 -1
  86. package/lib/module/theme/tokens/validate.js +238 -0
  87. package/lib/module/theme/tokens/validate.js.map +1 -0
  88. package/lib/module/theme/unistyles-adapter.js +77 -0
  89. package/lib/module/theme/unistyles-adapter.js.map +1 -0
  90. package/lib/module/utils/accent-generator.js +476 -0
  91. package/lib/module/utils/accent-generator.js.map +1 -0
  92. package/lib/module/utils/accent-utils.js +52 -35
  93. package/lib/module/utils/accent-utils.js.map +1 -1
  94. package/lib/module/utils/color-utils.js +224 -7
  95. package/lib/module/utils/color-utils.js.map +1 -1
  96. package/lib/module/utils/deep-merge.js +1 -1
  97. package/lib/module/utils/deep-merge.js.map +1 -1
  98. package/lib/module/utils/resolve-theme-color.js +2 -2
  99. package/lib/typescript/src/components/chip/types.d.ts +1 -1
  100. package/lib/typescript/src/components/empty-state/EmptyState.d.ts +1 -1
  101. package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -1
  102. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +6 -5
  103. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  104. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +11 -5
  105. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  106. package/lib/typescript/src/components/icon/Icon.d.ts +2 -2
  107. package/lib/typescript/src/components/screen/Screen.d.ts +45 -16
  108. package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
  109. package/lib/typescript/src/components/screen/ScreenContext.d.ts +42 -26
  110. package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
  111. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +5 -46
  112. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
  113. package/lib/typescript/src/components/screen/ScreenList.d.ts +62 -50
  114. package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
  115. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +18 -58
  116. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
  117. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts +5 -0
  118. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts.map +1 -0
  119. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts +53 -0
  120. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts.map +1 -0
  121. package/lib/typescript/src/components/screen/index.d.ts +2 -2
  122. package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
  123. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts +6 -0
  124. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts.map +1 -0
  125. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts +21 -0
  126. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts.map +1 -0
  127. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +6 -8
  128. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -1
  129. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts +7 -0
  130. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts.map +1 -0
  131. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts +6 -0
  132. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts.map +1 -0
  133. package/lib/typescript/src/components/screen/types.d.ts +27 -32
  134. package/lib/typescript/src/components/screen/types.d.ts.map +1 -1
  135. package/lib/typescript/src/components/screen/utils.d.ts +9 -8
  136. package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
  137. package/lib/typescript/src/components/text/Text.d.ts +1 -1
  138. package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
  139. package/lib/typescript/src/components/toolbar/Toolbar.d.ts +2 -2
  140. package/lib/typescript/src/context/ThemeProvider.d.ts +29 -12
  141. package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
  142. package/lib/typescript/src/context/index.d.ts +1 -1
  143. package/lib/typescript/src/context/index.d.ts.map +1 -1
  144. package/lib/typescript/src/theme/breakpoints.d.ts +1 -1
  145. package/lib/typescript/src/theme/define-config.d.ts +23 -0
  146. package/lib/typescript/src/theme/define-config.d.ts.map +1 -0
  147. package/lib/typescript/src/theme/diagnostics.d.ts +4 -0
  148. package/lib/typescript/src/theme/diagnostics.d.ts.map +1 -0
  149. package/lib/typescript/src/theme/errors.d.ts +10 -0
  150. package/lib/typescript/src/theme/errors.d.ts.map +1 -0
  151. package/lib/typescript/src/theme/index.d.ts +10 -2
  152. package/lib/typescript/src/theme/index.d.ts.map +1 -1
  153. package/lib/typescript/src/theme/legacy-tokens.d.ts +187 -0
  154. package/lib/typescript/src/theme/legacy-tokens.d.ts.map +1 -0
  155. package/lib/typescript/src/theme/registry.d.ts +12 -17
  156. package/lib/typescript/src/theme/registry.d.ts.map +1 -1
  157. package/lib/typescript/src/theme/resolve-config.d.ts +99 -0
  158. package/lib/typescript/src/theme/resolve-config.d.ts.map +1 -0
  159. package/lib/typescript/src/theme/runtime.d.ts +25 -4
  160. package/lib/typescript/src/theme/runtime.d.ts.map +1 -1
  161. package/lib/typescript/src/theme/screen-margin.d.ts +6 -14
  162. package/lib/typescript/src/theme/screen-margin.d.ts.map +1 -1
  163. package/lib/typescript/src/theme/screen-padding.d.ts +15 -0
  164. package/lib/typescript/src/theme/screen-padding.d.ts.map +1 -0
  165. package/lib/typescript/src/theme/selection-store.d.ts +41 -0
  166. package/lib/typescript/src/theme/selection-store.d.ts.map +1 -0
  167. package/lib/typescript/src/theme/theme.d.ts +54 -1
  168. package/lib/typescript/src/theme/theme.d.ts.map +1 -1
  169. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts +95 -0
  170. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts.map +1 -0
  171. package/lib/typescript/src/theme/tokens/dark.d.ts +56 -63
  172. package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
  173. package/lib/typescript/src/theme/tokens/light.d.ts +56 -71
  174. package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
  175. package/lib/typescript/src/theme/tokens/primitives.d.ts +11 -10
  176. package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
  177. package/lib/typescript/src/theme/tokens/validate.d.ts +34 -0
  178. package/lib/typescript/src/theme/tokens/validate.d.ts.map +1 -0
  179. package/lib/typescript/src/theme/types.d.ts +166 -86
  180. package/lib/typescript/src/theme/types.d.ts.map +1 -1
  181. package/lib/typescript/src/theme/unistyles-adapter.d.ts +33 -0
  182. package/lib/typescript/src/theme/unistyles-adapter.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/accent-generator.d.ts +103 -0
  184. package/lib/typescript/src/utils/accent-generator.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/accent-utils.d.ts +15 -2
  186. package/lib/typescript/src/utils/accent-utils.d.ts.map +1 -1
  187. package/lib/typescript/src/utils/color-utils.d.ts +59 -2
  188. package/lib/typescript/src/utils/color-utils.d.ts.map +1 -1
  189. package/lib/typescript/src/utils/deep-merge.d.ts +1 -0
  190. package/lib/typescript/src/utils/deep-merge.d.ts.map +1 -1
  191. package/lib/typescript/src/utils/resolve-theme-color.d.ts +2 -2
  192. package/package.json +1 -1
  193. package/src/components/avatar/Avatar.tsx +20 -20
  194. package/src/components/button/Button.tsx +11 -11
  195. package/src/components/chip/Chip.tsx +32 -32
  196. package/src/components/chip/types.ts +1 -1
  197. package/src/components/empty-state/EmptyState.tsx +4 -4
  198. package/src/components/header/variants/LargeTitleHeader/index.tsx +31 -30
  199. package/src/components/header/variants/TitleHeader/index.tsx +33 -22
  200. package/src/components/icon/Icon.tsx +4 -4
  201. package/src/components/page-indicator/hooks/use-page-indicator-colors.ts +2 -2
  202. package/src/components/screen/Screen.tsx +89 -43
  203. package/src/components/screen/ScreenContext.tsx +48 -28
  204. package/src/components/screen/ScreenFlatList.tsx +5 -191
  205. package/src/components/screen/ScreenList.tsx +197 -175
  206. package/src/components/screen/ScreenScrollView.tsx +92 -167
  207. package/src/components/screen/hooks/use-screen-ref.ts +24 -0
  208. package/src/components/screen/hooks/use-screen-scroll-handler.ts +31 -0
  209. package/src/components/screen/index.ts +8 -2
  210. package/src/components/screen/parts/ScreenFrame.tsx +49 -0
  211. package/src/components/screen/parts/ScreenHeaderAppearance.tsx +52 -0
  212. package/src/components/screen/parts/ScreenPlaceholder.tsx +16 -16
  213. package/src/components/screen/parts/ScreenSafeArea.android.tsx +22 -0
  214. package/src/components/screen/parts/ScreenSafeArea.tsx +20 -0
  215. package/src/components/screen/types.ts +29 -32
  216. package/src/components/screen/utils.ts +24 -11
  217. package/src/components/spinner/Spinner.tsx +2 -2
  218. package/src/components/text/Text.tsx +2 -2
  219. package/src/components/toast/Toast.tsx +13 -13
  220. package/src/components/toolbar/Toolbar.tsx +4 -4
  221. package/src/context/ThemeProvider.tsx +47 -51
  222. package/src/context/index.ts +1 -1
  223. package/src/theme/breakpoints.ts +1 -1
  224. package/src/theme/define-config.ts +27 -0
  225. package/src/theme/diagnostics.ts +11 -0
  226. package/src/theme/errors.ts +14 -0
  227. package/src/theme/index.ts +40 -1
  228. package/src/theme/legacy-tokens.ts +227 -0
  229. package/src/theme/registry.ts +119 -137
  230. package/src/theme/resolve-config.ts +406 -0
  231. package/src/theme/runtime.ts +52 -14
  232. package/src/theme/screen-margin.ts +6 -25
  233. package/src/theme/screen-padding.ts +26 -0
  234. package/src/theme/selection-store.ts +76 -0
  235. package/src/theme/theme.ts +104 -39
  236. package/src/theme/tokens/canonical-color-keys.ts +107 -0
  237. package/src/theme/tokens/dark.ts +91 -98
  238. package/src/theme/tokens/light.ts +88 -93
  239. package/src/theme/tokens/primitives.ts +14 -14
  240. package/src/theme/tokens/validate.ts +271 -0
  241. package/src/theme/types.ts +184 -95
  242. package/src/theme/unistyles-adapter.ts +75 -0
  243. package/src/utils/accent-generator.ts +530 -0
  244. package/src/utils/accent-utils.ts +66 -39
  245. package/src/utils/color-utils.ts +225 -8
  246. package/src/utils/deep-merge.ts +1 -1
  247. package/src/utils/resolve-theme-color.ts +2 -2
  248. package/lib/module/components/screen/parts/BottomSafeArea.js +0 -62
  249. package/lib/module/components/screen/parts/BottomSafeArea.js.map +0 -1
  250. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +0 -30
  251. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +0 -1
  252. package/src/components/screen/parts/BottomSafeArea.tsx +0 -59
@@ -7,6 +7,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
7
7
  import { resolveThemeColor } from "#utils/resolve-theme-color";
8
8
  import { FONT_WEIGHT_VALUE } from "#utils/typography";
9
9
  import { Portal } from "../../../portal";
10
+ import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearance";
10
11
  import { SCREEN_HEADER_PORTAL_HOST, useScreen } from "../../../screen/ScreenContext";
11
12
  import { Toolbar } from "../../../toolbar";
12
13
  import { HeaderDismissButton } from "../../header-buttons";
@@ -88,11 +89,12 @@ function LargeTitlePortalContent({
88
89
  /**
89
90
  * iOS-style collapsing large-title header. The large title is injected (via the
90
91
  * `Screen`'s portal) into the top of the scroll content, so it scrolls away
91
- * naturally; the compact bar title cross-fades and slides in over the measured
92
- * portal height. Native scroll snap (`snapToOffsets`) settles between expanded
93
- * and collapsed when released mid-collapse.
92
+ * naturally; the compact title cross-fades over the theme header height.
93
+ * The top safe area and bar overlay fade from transparent when expanded to
94
+ * the header background when collapsed. The large-title content stays transparent.
95
+ * Snapping is opt-in through the scroll container's snapToOffsets.
94
96
  *
95
- * Must be used inside a `Screen` with a `Screen.ScrollView` / `Screen.FlatList`.
97
+ * Must be used inside a `Screen` with a `Screen.ScrollView` or `Screen.List`.
96
98
  *
97
99
  * @example
98
100
  * ```tsx
@@ -109,6 +111,8 @@ export function LargeTitleHeader({
109
111
  title,
110
112
  overline,
111
113
  subtitle,
114
+ leadingActions,
115
+ trailingActions,
112
116
  LeftToolbar,
113
117
  RightToolbar,
114
118
  dismissAction = "none",
@@ -116,15 +120,19 @@ export function LargeTitleHeader({
116
120
  onDismiss,
117
121
  titleAlignment = "center",
118
122
  isLargeTitleHidden = false,
119
- bg = "background",
123
+ background,
124
+ bg,
120
125
  }: LargeTitleHeaderProps) {
121
126
  const { theme } = useUnistyles();
122
127
 
123
- const bgColor = resolveThemeColor(bg, theme) ?? "";
128
+ const { scrollY, background: screenBackground } = useScreen();
129
+ const colorToken = background ?? bg;
130
+ const bgColor =
131
+ (colorToken ? resolveThemeColor(colorToken, theme) : screenBackground) ?? theme.color["surface.canvas"];
132
+ const leadingContent = leadingActions !== undefined ? leadingActions : LeftToolbar;
133
+ const trailingContent = trailingActions !== undefined ? trailingActions : RightToolbar;
124
134
 
125
- const isTransparent = bgColor === "transparent";
126
- const gradientColor = isTransparent ? theme.color.background : bgColor;
127
- const { scrollY } = useScreen();
135
+ const gradientColor = bgColor;
128
136
  const isLargeTitleHiddenValue = useSharedValue(isLargeTitleHidden);
129
137
  const headerCollapseHeight = theme.layout.header.height;
130
138
  styles.useVariants({ titleAlignment });
@@ -143,6 +151,8 @@ export function LargeTitleHeader({
143
151
  };
144
152
  });
145
153
 
154
+ useHeaderBackground(bgColor, gradientOpacityStyle);
155
+
146
156
  const compactTitleAnimatedStyle = useAnimatedStyle(() => {
147
157
  if (isLargeTitleHiddenValue.value) {
148
158
  return { opacity: 1, transform: [{ translateY: 0 }] };
@@ -174,20 +184,20 @@ export function LargeTitleHeader({
174
184
  dismissAction === "back" ? (
175
185
  <>
176
186
  <HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
177
- {LeftToolbar}
187
+ {leadingContent}
178
188
  </>
179
189
  ) : (
180
- LeftToolbar
190
+ leadingContent
181
191
  );
182
192
 
183
193
  const trailing =
184
194
  dismissAction === "close" ? (
185
195
  <>
186
- {RightToolbar}
196
+ {trailingContent}
187
197
  <HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
188
198
  </>
189
199
  ) : (
190
- RightToolbar
200
+ trailingContent
191
201
  );
192
202
 
193
203
  return (
@@ -202,7 +212,6 @@ export function LargeTitleHeader({
202
212
  />
203
213
  </Portal>
204
214
  )}
205
- <Animated.View style={[styles.safeArea(gradientColor), gradientOpacityStyle]} />
206
215
  <View style={styles.bar}>
207
216
  <Animated.View style={[styles.gradient, gradientOpacityStyle]} pointerEvents="none">
208
217
  <HeaderGradientOverlay color={gradientColor} />
@@ -222,7 +231,7 @@ export function LargeTitleHeader({
222
231
  );
223
232
  }
224
233
 
225
- const styles = StyleSheet.create((theme, runtime) => ({
234
+ const styles = StyleSheet.create((theme) => ({
226
235
  container: {
227
236
  position: "absolute",
228
237
  top: 0,
@@ -230,14 +239,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
230
239
  right: 0,
231
240
  zIndex: 1000,
232
241
  },
233
- safeArea: (bgColor) => ({
234
- height: runtime.insets.top,
235
- backgroundColor: bgColor,
236
- }),
237
- // Overhangs the bar by `HEADER_FADE_EXTENT` so the fade has room to finish
238
- // below it — confined to the bar, the curve would be steep enough to read as a
239
- // band. Nothing on the path clips and the overlay is `pointerEvents="none"`,
240
- // so the spill costs neither layout nor touches.
242
+ // Let the fade extend below the bar without intercepting touches.
241
243
  gradient: {
242
244
  position: "absolute",
243
245
  top: 0,
@@ -249,7 +251,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
249
251
  height: theme.layout.header.height,
250
252
  flexDirection: "row",
251
253
  alignItems: "center",
252
- paddingHorizontal: theme.layout.screen.margin.x,
254
+ paddingHorizontal: theme.layout.screen.padding.x,
253
255
  gap: theme.spacing["3"],
254
256
  justifyContent: "space-between",
255
257
  },
@@ -259,16 +261,15 @@ const styles = StyleSheet.create((theme, runtime) => ({
259
261
  portalContent: {
260
262
  gap: 2,
261
263
  minHeight: theme.layout.header.height,
262
- // paddingTop: theme.spacing['1'],
263
264
  paddingBottom: theme.spacing["3"],
264
- paddingHorizontal: theme.layout.screen.margin.x,
265
+ paddingHorizontal: theme.layout.screen.padding.x,
265
266
  },
266
267
  overline: {
267
268
  fontSize: theme.typography.variants["label-sm"].fontSize,
268
269
  lineHeight: theme.typography.variants["label-sm"].lineHeight,
269
270
  letterSpacing: theme.typography.variants["label-sm"].letterSpacing,
270
271
  fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["label-sm"].fontWeight],
271
- color: (theme.color as unknown as Record<string, string | undefined>)["text.subtle"] ?? "",
272
+ color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
272
273
  variants: {
273
274
  titleAlignment: {
274
275
  center: { textAlign: "center" },
@@ -281,7 +282,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
281
282
  lineHeight: theme.typography.variants["headline-lg"].lineHeight,
282
283
  letterSpacing: theme.typography.variants["headline-lg"].letterSpacing,
283
284
  fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["headline-lg"].fontWeight],
284
- color: (theme.color as unknown as Record<string, string | undefined>)["text.default"] ?? "",
285
+ color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
285
286
  variants: {
286
287
  titleAlignment: {
287
288
  center: { textAlign: "center" },
@@ -293,7 +294,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
293
294
  fontSize: theme.typography.variants["body-lg"].fontSize,
294
295
  lineHeight: theme.typography.variants["body-lg"].lineHeight,
295
296
  letterSpacing: theme.typography.variants["body-lg"].letterSpacing,
296
- color: (theme.color as unknown as Record<string, string | undefined>)["text.subtle"] ?? "",
297
+ color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
297
298
  variants: {
298
299
  titleAlignment: {
299
300
  center: { textAlign: "center" },
@@ -306,7 +307,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
306
307
  lineHeight: theme.typography.variants["title-md"].lineHeight,
307
308
  letterSpacing: theme.typography.variants["title-md"].letterSpacing,
308
309
  fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["title-md"].fontWeight],
309
- color: (theme.color as unknown as Record<string, string | undefined>)["text.default"] ?? "",
310
+ color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
310
311
  variants: {
311
312
  titleAlignment: {
312
313
  center: { textAlign: "center" },
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode, useContext } from "react";
2
2
  import { Text, View } from "react-native";
3
3
 
4
4
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -6,6 +6,8 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
6
6
  import type { ColorToken } from "#theme/types";
7
7
  import { resolveThemeColor } from "#utils/resolve-theme-color";
8
8
  import { FONT_WEIGHT_VALUE } from "#utils/typography";
9
+ import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearance";
10
+ import { ScreenContext } from "../../../screen/ScreenContext";
9
11
  import { Toolbar } from "../../../toolbar";
10
12
  import { HeaderDismissButton, type HeaderDismissProps } from "../../header-buttons";
11
13
  import { HEADER_FADE_EXTENT, HeaderGradientOverlay } from "../../parts/HeaderGradientOverlay";
@@ -16,21 +18,27 @@ export interface TitleHeaderProps extends HeaderDismissProps {
16
18
  * Leading-side actions, wrapped in a `Toolbar` row. Pass `Toolbar.Group` /
17
19
  * `Toolbar.Button` children directly — no `Toolbar` of your own.
18
20
  */
21
+ leadingActions?: ReactNode;
22
+ /** @deprecated Use leadingActions. */
19
23
  LeftToolbar?: ReactNode;
20
24
  /**
21
25
  * Trailing-side actions, wrapped in a `Toolbar` row. Pass `Toolbar.Group` /
22
26
  * `Toolbar.Button` children directly — no `Toolbar` of your own.
23
27
  */
28
+ trailingActions?: ReactNode;
29
+ /** @deprecated Use trailingActions. */
24
30
  RightToolbar?: ReactNode;
25
31
  /** Title placement within the bar. Defaults to `'center'`. */
26
32
  titleAlignment?: "center" | "left";
27
- /** Background color token. Defaults to `surface.default`. */
33
+ /** Color token for the top safe area and fading bar overlay. Defaults to the Screen background. */
34
+ background?: ColorToken;
35
+ /** @deprecated Use background. */
28
36
  bg?: ColorToken;
29
37
  }
30
38
 
31
39
  /**
32
40
  * Compact title bar (leading slot · title · trailing slot) with a top safe-area
33
- * inset. The inline counterpart to `Header.LargeTitle`; use it for screens that
41
+ * inset outside Screen. Within Screen, the root owns safe-area clearance. The inline counterpart to `Header.LargeTitle`; use it for screens that
34
42
  * don't need a collapsing large title.
35
43
  *
36
44
  * @example
@@ -38,7 +46,7 @@ export interface TitleHeaderProps extends HeaderDismissProps {
38
46
  * <Screen.Header>
39
47
  * <Header.Title
40
48
  * title="Stats"
41
- * RightToolbar={
49
+ * trailingActions={
42
50
  * <Toolbar.Button icon="add" accessibilityLabel="Add" onPress={add} />
43
51
  * }
44
52
  * />
@@ -47,46 +55,52 @@ export interface TitleHeaderProps extends HeaderDismissProps {
47
55
  */
48
56
  export function TitleHeader({
49
57
  title,
58
+ leadingActions,
59
+ trailingActions,
50
60
  LeftToolbar,
51
61
  RightToolbar,
52
62
  dismissAction = "none",
53
63
  navigation,
54
64
  onDismiss,
55
65
  titleAlignment = "center",
56
- bg = "background",
66
+ background,
67
+ bg,
57
68
  }: TitleHeaderProps) {
58
69
  const { theme } = useUnistyles();
59
70
 
71
+ const screen = useContext(ScreenContext);
72
+ const colorToken = background ?? bg;
60
73
  const bgColor =
61
- resolveThemeColor(bg, theme) ??
62
- (theme.color as unknown as Record<string, string | undefined>)["surface.default"] ??
63
- "";
74
+ (colorToken ? resolveThemeColor(colorToken, theme) : screen?.background) ?? theme.color["surface.canvas"];
75
+ useHeaderBackground(bgColor);
76
+ const leadingContent = leadingActions !== undefined ? leadingActions : LeftToolbar;
77
+ const trailingContent = trailingActions !== undefined ? trailingActions : RightToolbar;
64
78
  const isTransparent = bgColor === "transparent";
65
- const gradientColor = isTransparent ? theme.color["surface.default"] : bgColor;
79
+ const gradientColor = bgColor;
66
80
  styles.useVariants({ titleAlignment });
67
81
 
68
82
  const leading =
69
83
  dismissAction === "back" ? (
70
84
  <>
71
85
  <HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
72
- {LeftToolbar}
86
+ {leadingContent}
73
87
  </>
74
88
  ) : (
75
- LeftToolbar
89
+ leadingContent
76
90
  );
77
91
 
78
92
  const trailing =
79
93
  dismissAction === "close" ? (
80
94
  <>
81
- {RightToolbar}
95
+ {trailingContent}
82
96
  <HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
83
97
  </>
84
98
  ) : (
85
- RightToolbar
99
+ trailingContent
86
100
  );
87
101
  return (
88
102
  <View style={styles.container}>
89
- <View style={styles.safeArea(isTransparent, bgColor)} />
103
+ <View style={styles.safeArea(isTransparent, bgColor, screen !== null)} />
90
104
  <View style={styles.bar}>
91
105
  <View style={styles.gradient} pointerEvents="none">
92
106
  <HeaderGradientOverlay color={gradientColor} />
@@ -109,14 +123,11 @@ const styles = StyleSheet.create((theme, runtime) => ({
109
123
  right: 0,
110
124
  zIndex: 1000,
111
125
  },
112
- safeArea: (isTransparent, bgColor) => ({
113
- height: runtime.insets.top,
126
+ safeArea: (isTransparent: boolean, bgColor: string, withinScreen: boolean) => ({
127
+ height: withinScreen ? 0 : runtime.insets.top,
114
128
  backgroundColor: isTransparent ? "transparent" : bgColor,
115
129
  }),
116
- // Overhangs the bar by `HEADER_FADE_EXTENT` so the fade has room to finish
117
- // below it — confined to the bar, the curve would be steep enough to read as a
118
- // band. Nothing on the path clips and the overlay is `pointerEvents="none"`,
119
- // so the spill costs neither layout nor touches.
130
+ // Extend the fade below the bar without clipping content or intercepting touches.
120
131
  gradient: {
121
132
  position: "absolute",
122
133
  top: 0,
@@ -128,7 +139,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
128
139
  height: theme.layout.header.height,
129
140
  flexDirection: "row",
130
141
  alignItems: "center",
131
- paddingHorizontal: theme.layout.screen.margin.x,
142
+ paddingHorizontal: theme.layout.screen.padding.x,
132
143
  gap: theme.spacing["3"],
133
144
  justifyContent: "space-between",
134
145
  },
@@ -156,7 +167,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
156
167
  lineHeight: theme.typography.variants["title-md"].lineHeight,
157
168
  letterSpacing: theme.typography.variants["title-md"].letterSpacing,
158
169
  fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["title-md"].fontWeight],
159
- color: (theme.color as unknown as Record<string, string | undefined>)["text.default"] ?? "",
170
+ color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
160
171
  variants: {
161
172
  titleAlignment: {
162
173
  center: { textAlign: "center" },
@@ -13,7 +13,7 @@ export interface IconProps extends Omit<ViewProps, "children"> {
13
13
  name: TerraIconName;
14
14
  /** Icon size in density-independent pixels. Defaults to `20`. */
15
15
  size?: number;
16
- /** Theme color token or raw CSS color. Defaults to `text.default`. */
16
+ /** Theme color token or raw CSS color. Defaults to `text.primary`. */
17
17
  color?: ColorToken;
18
18
  strokeWidth?: number;
19
19
  }
@@ -24,11 +24,11 @@ export interface IconProps extends Omit<ViewProps, "children"> {
24
24
  *
25
25
  * @example
26
26
  * ```tsx
27
- * <Icon name="navigation.close" size={24} color="text.default" />
27
+ * <Icon name="navigation.close" size={24} color="text.primary" />
28
28
  * ```
29
29
  */
30
30
  export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(function Icon(
31
- { name, size = 20, color = "text.default", strokeWidth, style, ...rest },
31
+ { name, size = 20, color = "text.primary", strokeWidth, style, ...rest },
32
32
  ref
33
33
  ) {
34
34
  const { theme } = useUnistyles();
@@ -43,7 +43,7 @@ export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(function Ic
43
43
 
44
44
  const resolvedColor =
45
45
  resolveThemeColor(color, theme) ??
46
- (theme.color as unknown as Record<string, string | undefined>)["text.default"] ??
46
+ (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ??
47
47
  "";
48
48
 
49
49
  return (
@@ -19,7 +19,7 @@ export type PageIndicatorColorInput = {
19
19
  };
20
20
 
21
21
  const DEFAULT_COLOR_TOKENS: Required<PageIndicatorColorInput> = {
22
- activeColor: "text.default",
22
+ activeColor: "text.primary",
23
23
  inactiveColor: "text.disabled",
24
24
  loadingColor: "text.disabled",
25
25
  };
@@ -31,7 +31,7 @@ export function usePageIndicatorColors(colors: PageIndicatorColorInput = {}): Re
31
31
  () => ({
32
32
  activeColor:
33
33
  resolveThemeColor(colors.activeColor ?? DEFAULT_COLOR_TOKENS.activeColor, theme) ??
34
- (theme.color as unknown as Record<string, string | undefined>)["text.default"] ??
34
+ (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ??
35
35
  "",
36
36
  inactiveColor:
37
37
  resolveThemeColor(colors.inactiveColor ?? DEFAULT_COLOR_TOKENS.inactiveColor, theme) ??
@@ -10,37 +10,40 @@ import {
10
10
  type ReactNode,
11
11
  useMemo,
12
12
  } from "react";
13
- import { View } from "react-native";
13
+ import { View, type ViewProps } from "react-native";
14
14
 
15
- import type { ViewProps } from "react-native-svg/lib/typescript/fabric/utils";
16
15
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
17
16
 
18
17
  import type { ColorToken } from "#theme/types";
19
18
  import { resolveThemeColor } from "#utils/resolve-theme-color";
20
19
  import { PortalProvider } from "../portal";
20
+ import { ScreenFrame } from "./parts/ScreenFrame";
21
+ import { ScreenHeaderAppearanceProvider, ScreenHeaderAppearanceSlot } from "./parts/ScreenHeaderAppearance";
21
22
  import { ScreenScrollProvider } from "./ScreenContext";
22
23
  import { ScreenFlatList } from "./ScreenFlatList";
23
24
  import { ScreenList } from "./ScreenList";
24
25
  import { ScreenScrollView } from "./ScreenScrollView";
25
- import type { ScreenMargins } from "./types";
26
+ import type { ScreenContentPadding, ScreenMargins, ScreenSafeArea as ScreenSafeAreaPolicy } from "./types";
27
+ import { resolveSafeAreaEdges } from "./utils";
26
28
 
29
+ /** Props for `Screen.Header`, including the props accepted by its `as` component. */
27
30
  export type ScreenHeaderProps<T extends ElementType = typeof Fragment> = PropsWithChildren<
28
31
  {
29
32
  /**
30
- * Component to render the header as. Defaults to `Fragment` (no
31
- * wrapper). Use this to cast the header to a different container,
32
- * e.g. `as={View}` — any extra props are forwarded to it.
33
+ * Header component or container.
34
+ * Additional props are forwarded to this component.
35
+ * @defaultValue Fragment
33
36
  */
34
37
  as?: T;
35
38
  } & Omit<ComponentPropsWithoutRef<T>, "children">
36
39
  >;
37
40
 
38
41
  /**
39
- * Marker slot identifying the screen's header its purpose is to let
40
- * `Screen` detect a header synchronously and drop the `top` safe-area edge
41
- * (the header manages its own top inset).
42
+ * Marks a child as the screen header so scroll containers reserve the header bar height.
42
43
  *
43
- * Place a `Header.Title` / `Header.LargeTitle` (or any custom header) inside it.
44
+ * Place this slot under `Screen`, optionally inside fragments. Custom wrapper
45
+ * components are not inspected. Use `as` to render a reusable custom header. Reserved
46
+ * space uses the theme's header height, not the custom header's measured height.
44
47
  *
45
48
  * @example
46
49
  * ```tsx
@@ -53,77 +56,103 @@ function ScreenHeader<T extends ElementType = typeof Fragment>({
53
56
  ...rest
54
57
  }: ScreenHeaderProps<T>) {
55
58
  const Component = as ?? Fragment;
56
- return <Component {...rest}>{children}</Component>;
59
+ return (
60
+ <ScreenHeaderAppearanceSlot>
61
+ <Component {...rest}>{children}</Component>
62
+ </ScreenHeaderAppearanceSlot>
63
+ );
57
64
  }
58
65
  ScreenHeader.displayName = "Screen.Header";
59
66
 
60
- /** True if `children` directly contains a `Screen.Header`. */
67
+ /** Inspect fragments, but do not execute arbitrary child components. */
61
68
  function hasScreenHeader(children: ReactNode): boolean {
62
69
  let found = false;
63
70
  Children.forEach(children, (child) => {
64
71
  if (found) return;
72
+ if (!isValidElement(child)) return;
73
+ // Fast Refresh can retain an older function identity in existing child elements.
65
74
  if (
66
- isValidElement(child) &&
67
- typeof child.type !== "string" &&
75
+ child.type === ScreenHeader ||
68
76
  (child.type as { displayName?: string }).displayName === "Screen.Header"
69
77
  )
70
78
  found = true;
79
+ if (child.type === Fragment) found = hasScreenHeader((child.props as PropsWithChildren).children);
71
80
  });
72
81
  return found;
73
82
  }
74
83
 
84
+ /** Props for the screen root. Additional View props are forwarded to the root View. */
75
85
  export interface ScreenProps extends ViewProps {
86
+ /** Header, content, and overlays within the screen's scroll and portal providers. */
76
87
  children: ReactNode;
77
- /** Background color token. Defaults to the `background` token. */
88
+ /**
89
+ * Background color token shared by the root and scroll containers.
90
+ * A root `style.backgroundColor` override does not update the shared color.
91
+ * @defaultValue "surface.canvas"
92
+ */
78
93
  background?: ColorToken;
79
- /** Hosted inside a tab navigator? Exposed via `useScreen()`. Default false. */
94
+ /** @deprecated Has no effect. Bottom safe-area clearance is detected automatically. */
80
95
  inTabView?: boolean;
81
96
  /**
82
- * Which axes of `layout.screen.margin` the scroll containers pad their
83
- * content with. Default `'all'`. Overridable per scroll container.
97
+ * Axes that receive `layout.screen.padding` content padding in scroll containers.
98
+ * Does not pad the root View. Each scroll container can override this value.
99
+ * @defaultValue "all"
84
100
  */
101
+ contentPadding?: ScreenContentPadding;
102
+ /** Native safe-area edges applied once to the root. @defaultValue "auto" */
103
+ safeArea?: ScreenSafeAreaPolicy;
104
+ /** @deprecated Use contentPadding. */
85
105
  margins?: ScreenMargins;
86
106
  /**
87
- * @deprecated Use `margins` `false` maps to `margins="none"`. Ignored when
88
- * `margins` is set.
107
+ * @deprecated Use `contentPadding`. False maps to "none".
89
108
  */
90
109
  hasMargins?: boolean;
91
110
  }
92
111
 
93
112
  type ScreenComponent = ReturnType<typeof forwardRef<ComponentRef<typeof View>, ScreenProps>> & {
113
+ /** Header slot; supports direct children and fragments. */
94
114
  Header: typeof ScreenHeader;
115
+ /** Scrollable content with themed padding and loading/empty placeholders. */
95
116
  ScrollView: typeof ScreenScrollView;
96
117
  /** @deprecated Use {@link ScreenList} (`Screen.List`) instead. */
97
118
  FlatList: typeof ScreenFlatList;
119
+ /** Virtualized content using FlatList or a compatible custom list. */
98
120
  List: typeof ScreenList;
99
121
  };
100
122
 
101
- /**
102
- * Themed page container: solid themed background, safe-area handling, and a
103
- * shared scroll context for headers. Pair with `Screen.ScrollView` /
104
- * `Screen.List` and a `Header.LargeTitle` / `Header.Title`.
105
- *
106
- * @example
107
- * ```tsx
108
- * <Screen>
109
- * <Screen.Header as={Header.LargeTitle} title="Meditate" />
110
- * <Screen.ScrollView>{content}</Screen.ScrollView>
111
- * </Screen>
112
- * ```
113
- */
114
123
  const ScreenBase = forwardRef<ComponentRef<typeof View>, ScreenProps>(function Screen(
115
- { children, background = "background", margins, hasMargins, style, ...rest },
116
- _ref
124
+ {
125
+ children,
126
+ background = "surface.canvas",
127
+ contentPadding,
128
+ safeArea = "auto",
129
+ margins,
130
+ hasMargins,
131
+ inTabView: _inTabView,
132
+ style,
133
+ ...rest
134
+ },
135
+ ref
117
136
  ) {
118
137
  const hasHeader = useMemo(() => hasScreenHeader(children), [children]);
119
- const resolvedMargins = margins ?? (hasMargins === false ? "none" : "all");
138
+ const resolvedMargins = contentPadding ?? margins ?? (hasMargins === false ? "none" : "all");
139
+ const edges = useMemo(() => resolveSafeAreaEdges(safeArea), [safeArea]);
120
140
  const { theme } = useUnistyles();
121
- const resolvedBackground = resolveThemeColor(background, theme) ?? theme.color.background;
141
+ const resolvedBackground = resolveThemeColor(background, theme) ?? theme.color["surface.canvas"];
122
142
 
123
143
  return (
124
- <View style={[styles.container, style]} {...rest}>
125
- <ScreenScrollProvider background={resolvedBackground} margins={resolvedMargins} hasHeader={hasHeader}>
126
- <PortalProvider>{children}</PortalProvider>
144
+ <View ref={ref} style={[styles.container(resolvedBackground), style]} {...rest}>
145
+ <ScreenScrollProvider
146
+ background={resolvedBackground}
147
+ contentPadding={resolvedMargins}
148
+ safeArea={safeArea}
149
+ hasHeader={hasHeader}
150
+ >
151
+ <ScreenHeaderAppearanceProvider>
152
+ <ScreenFrame edges={edges}>
153
+ <PortalProvider>{children}</PortalProvider>
154
+ </ScreenFrame>
155
+ </ScreenHeaderAppearanceProvider>
127
156
  </ScreenScrollProvider>
128
157
  </View>
129
158
  );
@@ -134,10 +163,27 @@ ScreenBase.ScrollView = ScreenScrollView;
134
163
  ScreenBase.FlatList = ScreenFlatList;
135
164
  ScreenBase.List = ScreenList;
136
165
 
166
+ /**
167
+ * Themed screen root with shared scroll state and a local header portal.
168
+ *
169
+ * Pair a `Screen.Header` child with one primary `Screen.ScrollView`
170
+ * or `Screen.List`. The root applies safe-area clearance to all children;
171
+ * scroll containers add themed content padding and header clearance. The ref targets
172
+ * the root View, not the scroll container.
173
+ *
174
+ * @example
175
+ * ```tsx
176
+ * <Screen contentPadding="x" safeArea="auto">
177
+ * <Screen.Header as={Header.LargeTitle} title="Meditate" />
178
+ * <Screen.ScrollView>{content}</Screen.ScrollView>
179
+ * </Screen>
180
+ * ```
181
+ */
137
182
  export const Screen = ScreenBase;
138
183
 
139
- const styles = StyleSheet.create((_theme, _runtime) => ({
140
- container: {
184
+ const styles = StyleSheet.create(() => ({
185
+ container: (backgroundColor: string) => ({
141
186
  flex: 1,
142
- },
187
+ backgroundColor,
188
+ }),
143
189
  }));