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
@@ -1,238 +1,260 @@
1
1
  import {
2
2
  type ComponentPropsWithoutRef,
3
+ type ComponentPropsWithRef,
3
4
  type ComponentType,
4
5
  type ElementType,
6
+ forwardRef,
5
7
  type ReactElement,
8
+ type Ref,
9
+ type RefAttributes,
6
10
  useMemo,
7
11
  } from "react";
8
- import {
9
- FlatList,
10
- type NativeScrollEvent,
11
- type NativeSyntheticEvent,
12
- type StyleProp,
13
- View,
14
- type ViewStyle,
15
- } from "react-native";
16
-
17
- import Animated, {
18
- type AnimatedRef,
19
- useAnimatedScrollHandler,
20
- useComposedEventHandler,
21
- } from "react-native-reanimated";
12
+ import { FlatList, type FlatListProps, type ListRenderItem, View } from "react-native";
13
+
14
+ import Animated from "react-native-reanimated";
22
15
  import { StyleSheet } from "react-native-unistyles";
23
16
 
24
- import { resolveScreenMargin } from "#theme/screen-margin";
17
+ import { resolveScreenPadding } from "#theme/screen-padding";
25
18
  import { PortalHost } from "../portal";
26
- import { BottomSafeArea } from "./parts/BottomSafeArea";
19
+ import { useScreenRef } from "./hooks/use-screen-ref";
20
+ import { useScreenScrollHandler } from "./hooks/use-screen-scroll-handler";
27
21
  import { renderScreenPlaceholder } from "./parts/ScreenPlaceholder";
28
22
  import { SCREEN_HEADER_PORTAL_HOST, useScreen } from "./ScreenContext";
29
- import type { ScreenMargins, ScreenScrollableProps } from "./types";
30
- import { hasXMargin, hasYMargin, type ListHeaderComponent, renderListHeader } from "./utils";
23
+ import type { ScreenContentPadding, ScreenScrollableProps } from "./types";
24
+ import { hasHorizontalPadding, hasVerticalPadding, renderListComponent } from "./utils";
31
25
 
26
+ /** Minimal row information supported by custom list adapters. FlatList also supplies separators. */
32
27
  export interface ScreenListRenderItemInfo<T> {
33
28
  item: T;
34
29
  index: number;
35
30
  }
31
+ /** Native viewability token with the item inferred from data. */
32
+ type NativeViewToken = Parameters<
33
+ NonNullable<FlatListProps<unknown>["onViewableItemsChanged"]>
34
+ >[0]["viewableItems"][number];
35
+ export type ScreenListViewToken<T> = Omit<NativeViewToken, "item"> & { item: T };
36
+
37
+ type ViewabilityCallback<T> = (info: {
38
+ viewableItems: ScreenListViewToken<T>[];
39
+ changed: ScreenListViewToken<T>[];
40
+ }) => void;
41
+
42
+ type ListLayoutProps = ScreenScrollableProps & {
43
+ /** @deprecated Use extraBottomPadding. This replaces themed bottom padding; safeArea belongs to Screen. */
44
+ bottomInset?: number;
45
+ };
36
46
 
37
- export type ScreenListProps<T = unknown, As extends ElementType = typeof FlatList> = ScreenScrollableProps &
47
+ /** Native FlatList props with item inference retained across callbacks and refs. */
48
+ type NativeScreenListProps<T> = ListLayoutProps &
38
49
  Omit<
39
- ComponentPropsWithoutRef<As>,
50
+ FlatListProps<T>,
51
+ | keyof ListLayoutProps
40
52
  | "data"
41
53
  | "renderItem"
42
- | "keyExtractor"
43
- | "style"
44
- | "contentContainerStyle"
45
- | "onScroll"
46
- | "ListHeaderComponent"
47
- | "ListFooterComponent"
48
- | "ListEmptyComponent"
49
- | keyof ScreenScrollableProps
54
+ | "onViewableItemsChanged"
55
+ | "viewabilityConfigCallbackPairs"
50
56
  > & {
51
- /**
52
- * Which list component to render as. Defaults to React Native's
53
- * `FlatList` pass any list with a FlatList-shaped prop surface
54
- * (`data`, `renderItem`, `ListHeaderComponent`, …), e.g. `FlashList`
55
- * from `@shopify/flash-list`, or a custom virtualized list.
56
- *
57
- * Only the default `FlatList` binds the screen's shared imperative
58
- * scroll ref — a custom `as` is responsible for its own ref if it needs
59
- * one, since not every list exposes a Reanimated-compatible ref (e.g.
60
- * `FlashList`'s ref exposes a different, non Reanimated-compatible
61
- * imperative API).
62
- */
63
- as?: As;
57
+ /** Native FlatList is used by default. */
58
+ as?: typeof FlatList;
59
+ /** Null, undefined, and empty data use the empty slot. */
64
60
  data?: readonly T[] | null;
65
- renderItem?: (info: ScreenListRenderItemInfo<T>) => ReactElement | null;
66
- keyExtractor?: (item: T, index: number) => string;
67
- ListHeaderComponent?: ListHeaderComponent;
68
- ListFooterComponent?: ListHeaderComponent;
69
- ListEmptyComponent?: ListHeaderComponent;
70
- style?: StyleProp<ViewStyle>;
71
- contentContainerStyle?: StyleProp<ViewStyle>;
72
- onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
73
- /**
74
- * Extra bottom padding on the list content — clearance for a tab bar or
75
- * home indicator. Defaults to the `layout.screen.margin.y` token when
76
- * margins are enabled, otherwise `0`.
77
- */
78
- bottomInset?: number;
61
+ /** Render a row, including native separator helpers. */
62
+ renderItem: ListRenderItem<T>;
63
+ /** Item-typed native viewability callback. */
64
+ onViewableItemsChanged?: ViewabilityCallback<T> | null;
65
+ /** Item-typed native viewability callback/configuration pairs. */
66
+ viewabilityConfigCallbackPairs?: {
67
+ viewabilityConfig: NonNullable<FlatListProps<T>["viewabilityConfig"]>;
68
+ onViewableItemsChanged: ViewabilityCallback<T>;
69
+ }[];
79
70
  };
80
71
 
81
- /**
82
- * Polymorphic list container — renders whichever list `as` names (defaults
83
- * to `FlatList`), applying the same margin/header/safe-area/scroll-handler
84
- * chrome regardless of which one it is. A screen can swap list
85
- * implementations by changing `as`, without relearning the surrounding API.
86
- *
87
- * @example
88
- * ```tsx
89
- * <Screen.List data={items} renderItem={({ item }) => <Row item={item} />} />
90
- * ```
91
- *
92
- * @example Any other list component
93
- * ```tsx
94
- * import { FlashList } from '@shopify/flash-list';
95
- *
96
- * <Screen.List
97
- * as={FlashList}
98
- * data={items}
99
- * renderItem={({ item }) => <Row item={item} />}
100
- * />
101
- * ```
102
- */
103
- export function ScreenList<T = unknown, As extends ElementType = typeof FlatList>({
104
- as,
105
- data,
106
- renderItem,
107
- style,
108
- contentContainerStyle,
109
- ListHeaderComponent,
110
- ListFooterComponent,
111
- ListEmptyComponent,
112
- horizontal = false,
113
- loading = false,
114
- LoadingComponent,
115
- EmptyComponent,
116
- margins,
117
- bottomInset,
118
- onScroll,
119
- ...rest
120
- }: ScreenListProps<T, As>) {
121
- const isDefaultFlatList = as === undefined;
122
- const Component = (as ?? FlatList) as ComponentType<Record<string, unknown>>;
72
+ /** Props an adapter must accept and forward to its scrollable list implementation. */
73
+ export type ScreenListAdapterProps<T> = Pick<
74
+ FlatListProps<T>,
75
+ | "style"
76
+ | "contentContainerStyle"
77
+ | "onScroll"
78
+ | "ListHeaderComponent"
79
+ | "ListFooterComponent"
80
+ | "ListEmptyComponent"
81
+ | "horizontal"
82
+ | "scrollEventThrottle"
83
+ | "contentInsetAdjustmentBehavior"
84
+ | "automaticallyAdjustContentInsets"
85
+ | "automaticallyAdjustsScrollIndicatorInsets"
86
+ | "showsVerticalScrollIndicator"
87
+ | "snapToOffsets"
88
+ > & {
89
+ data?: readonly T[] | null;
90
+ renderItem: (info: ScreenListRenderItemInfo<T>) => ReactElement | null;
91
+ keyExtractor?: (item: T, index: number) => string;
92
+ };
123
93
 
124
- // `Animated.createAnimatedComponent` should only run once per component,
125
- // not on every render — `as` is arbitrary here (not a fixed module-scope
126
- // reference like `Screen.FlatList` uses), so it's memoized on the resolved
127
- // component instead of hoisted to module scope.
128
- const AnimatedComponent = useMemo(() => Animated.createAnimatedComponent(Component), [Component]);
94
+ type AdapterKeys =
95
+ | "data"
96
+ | "renderItem"
97
+ | "onScroll"
98
+ | "contentContainerStyle"
99
+ | "ListHeaderComponent"
100
+ | "ListFooterComponent"
101
+ | "ListEmptyComponent";
102
+
103
+ /** Custom lists must accept the adapter contract and forward a ref for Reanimated. */
104
+ export type ScreenCustomListProps<T, As extends ElementType> = ListLayoutProps &
105
+ ScreenListAdapterProps<T> &
106
+ Omit<ComponentPropsWithoutRef<As>, keyof ListLayoutProps | keyof ScreenListAdapterProps<T>> & {
107
+ as: As &
108
+ (As extends typeof FlatList
109
+ ? never
110
+ : AdapterKeys extends keyof ComponentPropsWithoutRef<As>
111
+ ? readonly T[] extends NonNullable<ComponentPropsWithoutRef<As>["data"]>
112
+ ? "ref" extends keyof ComponentPropsWithRef<As>
113
+ ? unknown
114
+ : never
115
+ : never
116
+ : never);
117
+ ref?: ComponentPropsWithRef<As>["ref"];
118
+ };
119
+
120
+ /** Screen list props, optionally parameterized by a custom adapter component. */
121
+ export type ScreenListProps<
122
+ T = unknown,
123
+ As extends ElementType = typeof FlatList,
124
+ > = As extends typeof FlatList ? NativeScreenListProps<T> : ScreenCustomListProps<T, As>;
129
125
 
126
+ interface ScreenListComponent {
127
+ <T>(props: ScreenListProps<T> & RefAttributes<FlatList<T>>): ReactElement | null;
128
+ <T, As extends ElementType>(props: ScreenCustomListProps<T, As>): ReactElement | null;
129
+ displayName?: string;
130
+ }
131
+
132
+ type ImplementationProps = Omit<ScreenListProps<unknown>, "as" | "renderItem"> & {
133
+ as?: ElementType;
134
+ renderItem: (info: never) => ReactElement | null;
135
+ };
136
+
137
+ const ScreenListBase = forwardRef(function ScreenList(
138
+ {
139
+ as,
140
+ data,
141
+ renderItem,
142
+ style,
143
+ contentContainerStyle,
144
+ ListHeaderComponent,
145
+ ListFooterComponent,
146
+ ListEmptyComponent,
147
+ horizontal = false,
148
+ isLoading,
149
+ loading = false,
150
+ loadingContent,
151
+ LoadingComponent,
152
+ emptyContent,
153
+ EmptyComponent,
154
+ contentPadding,
155
+ margins,
156
+ bottomInset,
157
+ extraBottomPadding = 0,
158
+ scrollHandler: externalScrollHandler,
159
+ onScroll,
160
+ ...rest
161
+ }: ImplementationProps,
162
+ ref: Ref<unknown>
163
+ ) {
164
+ const isDefaultFlatList = as === undefined || as === FlatList;
165
+ const Component = (as ?? FlatList) as ComponentType<Record<string, unknown>>;
166
+ // Stable wrappers preserve the native list and its scroll position across renders.
167
+ const AnimatedComponent = useMemo(() => Animated.createAnimatedComponent(Component), [Component]);
130
168
  const {
131
169
  background,
132
170
  scrollRef,
133
171
  scrollHandler,
134
172
  headerSnapOffsets,
135
- margins: screenMargins,
173
+ contentPadding: screenPadding,
136
174
  hasHeader,
137
175
  } = useScreen();
138
-
139
- const resolvedMargins = margins ?? screenMargins;
140
- // The placeholder rides in on `ListEmptyComponent` rather than replacing the
141
- // list, so the header portal, scroll binding and snap offsets all stay put.
142
- // While loading the list is fed an empty `data` so that slot is the one that
143
- // renders.
176
+ const mergedRef = useScreenRef(isDefaultFlatList ? scrollRef : undefined, ref);
177
+ const resolvedPadding = contentPadding ?? margins ?? screenPadding;
178
+ const resolvedLoading = isLoading ?? loading;
144
179
  const placeholder = renderScreenPlaceholder({
145
- loading,
146
- LoadingComponent,
147
- EmptyComponent,
180
+ isLoading: resolvedLoading,
181
+ loadingContent: loadingContent !== undefined ? loadingContent : (LoadingComponent ?? undefined),
182
+ emptyContent: emptyContent !== undefined ? emptyContent : EmptyComponent,
148
183
  isEmpty: !data || data.length === 0,
149
184
  });
150
-
151
- const jsScrollHandler = useAnimatedScrollHandler(
152
- (event) => {
153
- if (onScroll) onScroll(event as unknown as NativeSyntheticEvent<NativeScrollEvent>);
154
- },
155
- [onScroll]
156
- );
157
-
158
- const composedHandler = useComposedEventHandler([scrollHandler, jsScrollHandler]);
159
-
160
- // A caller naming its own `bottomInset` has taken the bottom over entirely,
161
- // and a horizontal list has no bottom edge to clear.
162
- const needsBottomSafeArea = bottomInset === undefined && !horizontal;
163
-
164
- const footerComponent = (
165
- <>
166
- {renderListHeader(ListFooterComponent)}
167
- {needsBottomSafeArea ? <BottomSafeArea /> : null}
168
- </>
169
- );
170
-
171
- const headerComponent = (
185
+ const composedScrollHandler = useScreenScrollHandler(scrollHandler, onScroll, externalScrollHandler);
186
+ const header = (
172
187
  <>
173
- {/* `headerWrapper`'s negative margin cancels the list's inherited
174
- content padding for the portaled header (`LargeTitlePortalContent`
175
- etc. manage their own horizontal padding) — a caller's own
176
- `ListHeaderComponent` has no such self-padding, so it stays outside
177
- this wrapper and gets the list's normal item padding instead. */}
178
- <View style={styles.headerWrapper(resolvedMargins)}>
188
+ <View style={styles.headerWrapper(resolvedPadding)}>
179
189
  <PortalHost name={SCREEN_HEADER_PORTAL_HOST} />
180
190
  </View>
181
- {renderListHeader(ListHeaderComponent)}
191
+ {renderListComponent(ListHeaderComponent)}
182
192
  </>
183
193
  );
184
194
 
185
195
  return (
186
196
  <AnimatedComponent
187
- ref={isDefaultFlatList ? (scrollRef as unknown as AnimatedRef<never>) : undefined}
188
- data={loading ? [] : data}
197
+ ref={mergedRef}
198
+ data={resolvedLoading ? [] : data}
189
199
  renderItem={renderItem}
190
200
  style={[{ backgroundColor: background }, style]}
191
201
  horizontal={horizontal}
192
- contentInsetAdjustmentBehavior={hasHeader ? "never" : undefined}
193
- contentContainerStyle={[styles.content(resolvedMargins, hasHeader, bottomInset), contentContainerStyle]}
194
- ListHeaderComponent={headerComponent}
195
- ListFooterComponent={footerComponent}
202
+ contentInsetAdjustmentBehavior="never"
203
+ automaticallyAdjustContentInsets={false}
204
+ automaticallyAdjustsScrollIndicatorInsets={false}
205
+ contentContainerStyle={[
206
+ styles.content(resolvedPadding, hasHeader, bottomInset, extraBottomPadding),
207
+ contentContainerStyle,
208
+ ]}
209
+ ListHeaderComponent={header}
210
+ ListFooterComponent={ListFooterComponent}
196
211
  ListEmptyComponent={placeholder ?? ListEmptyComponent}
197
212
  showsVerticalScrollIndicator={false}
198
213
  scrollEventThrottle={16}
199
214
  snapToOffsets={headerSnapOffsets}
200
215
  {...rest}
201
- onScroll={composedHandler}
216
+ onScroll={composedScrollHandler}
202
217
  />
203
218
  );
204
- }
219
+ });
220
+
221
+ /**
222
+ * Virtualized Screen content. FlatList is the default and its native ref and
223
+ * item callbacks retain their types. Native refreshing keeps rows visible;
224
+ * isLoading replaces them with loadingContent (a Spinner by default).
225
+ *
226
+ * Custom `as` components must implement ScreenListAdapterProps, forward their
227
+ * ref, and forward the supplied scroll events, styles, and header/footer slots.
228
+ * Their native ref is forwarded to callers; useScreen().scrollRef is FlatList-only here.
229
+ * JS onScroll receives nativeEvent data without SyntheticEvent methods.
230
+ *
231
+ * @example
232
+ * ```tsx
233
+ * <Screen safeArea="auto">
234
+ * <Screen.List data={items} renderItem={({ item }) => <Row item={item} />}
235
+ * isLoading={isLoading} refreshing={isRefreshing} onRefresh={refresh} />
236
+ * </Screen>
237
+ * ```
238
+ */
239
+ export const ScreenList = ScreenListBase as ScreenListComponent;
205
240
  ScreenList.displayName = "Screen.List";
206
241
 
207
- // `resolveScreenMargin` is called *inside* each dynamic function below, not
208
- // hoisted to the factory's top level — the Babel plugin only attributes a
209
- // runtime dependency (here, breakpoint) to a style key when the `rt`/
210
- // `runtime` read it derives from sits inside that key's own value. Hoisting
211
- // it would silently drop the dependency, so the margin would freeze at
212
- // whatever breakpoint was active on mount and never update on rotation.
213
242
  const styles = StyleSheet.create((theme, runtime) => ({
214
- content: (margins: ScreenMargins, hasHeader: boolean, bottomInset: number | undefined) => {
215
- const { x: marginX, y: marginY } = resolveScreenMargin(theme, runtime.breakpoint);
243
+ content: (
244
+ padding: ScreenContentPadding,
245
+ hasHeader: boolean,
246
+ bottomInset: number | undefined,
247
+ extraBottomPadding: number
248
+ ) => {
249
+ const { x, y } = resolveScreenPadding(theme, runtime.breakpoint);
216
250
  return {
217
251
  flexGrow: 1,
218
- // The header is absolutely positioned, so the list reserves its full
219
- // height bar plus top safe area. This assumes the scroll view does not
220
- // also get UIKit's automatic inset adjustment: on a native tab screen
221
- // that means `disableAutomaticContentInsets` on the tab, or the two
222
- // insets are counted twice.
223
- paddingTop:
224
- (hasHeader ? theme.layout.header.height + runtime.insets.top : 0) +
225
- (hasYMargin(margins) ? marginY : 0),
226
- paddingHorizontal: hasXMargin(margins) ? marginX : 0,
227
- // The safe area is `BottomSafeArea`'s job; counting it here as well
228
- // would leave a gap the height of the gesture bar.
229
- paddingBottom: bottomInset ?? (hasYMargin(margins) ? marginY : 0),
230
- };
231
- },
232
- headerWrapper: (margins: ScreenMargins) => {
233
- const { x: marginX } = resolveScreenMargin(theme, runtime.breakpoint);
234
- return {
235
- marginHorizontal: hasXMargin(margins) ? -marginX : 0,
252
+ paddingTop: (hasHeader ? theme.layout.header.height : 0) + (hasVerticalPadding(padding) ? y : 0),
253
+ paddingHorizontal: hasHorizontalPadding(padding) ? x : 0,
254
+ paddingBottom: (bottomInset ?? (hasVerticalPadding(padding) ? y : 0)) + extraBottomPadding,
236
255
  };
237
256
  },
257
+ headerWrapper: (padding: ScreenContentPadding) => ({
258
+ marginHorizontal: hasHorizontalPadding(padding) ? -resolveScreenPadding(theme, runtime.breakpoint).x : 0,
259
+ }),
238
260
  }));