react-native-terra-ui 0.1.1 → 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.
- package/CHANGELOG.md +90 -0
- package/lib/module/components/avatar/Avatar.js +118 -35
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/box/Box.js +15 -2
- package/lib/module/components/box/Box.js.map +1 -1
- package/lib/module/components/box/utils.js +4 -1
- package/lib/module/components/box/utils.js.map +1 -1
- package/lib/module/components/button/Button.js +7 -14
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/chip/Chip.js +372 -0
- package/lib/module/components/chip/Chip.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -0
- package/lib/module/components/chip/index.js.map +1 -0
- package/lib/module/components/chip/types.js +2 -0
- package/lib/module/components/chip/types.js.map +1 -0
- package/lib/module/components/chip/utils.js +36 -0
- package/lib/module/components/chip/utils.js.map +1 -0
- package/lib/module/components/empty-state/EmptyState.js +200 -0
- package/lib/module/components/empty-state/EmptyState.js.map +1 -0
- package/lib/module/components/empty-state/index.js +4 -0
- package/lib/module/components/empty-state/index.js.map +1 -0
- package/lib/module/components/empty-state/types.js +2 -0
- package/lib/module/components/empty-state/types.js.map +1 -0
- package/lib/module/components/header/header-buttons.js +31 -36
- package/lib/module/components/header/header-buttons.js.map +1 -1
- package/lib/module/components/header/index.js +1 -1
- package/lib/module/components/header/index.js.map +1 -1
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +38 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -0
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +69 -99
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +32 -63
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/icon/Icon.js +20 -8
- package/lib/module/components/icon/Icon.js.map +1 -1
- package/lib/module/components/image/Image.js +54 -0
- package/lib/module/components/image/Image.js.map +1 -0
- package/lib/module/components/image/index.js +4 -0
- package/lib/module/components/image/index.js.map +1 -0
- package/lib/module/components/image/types.js +4 -0
- package/lib/module/components/image/types.js.map +1 -0
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/portal/Portal.js +42 -0
- package/lib/module/components/portal/Portal.js.map +1 -0
- package/lib/module/components/portal/PortalContext.js +1 -16
- package/lib/module/components/portal/PortalContext.js.map +1 -1
- package/lib/module/components/portal/PortalHost.js +16 -14
- package/lib/module/components/portal/PortalHost.js.map +1 -1
- package/lib/module/components/portal/PortalProvider.js +51 -19
- package/lib/module/components/portal/PortalProvider.js.map +1 -1
- package/lib/module/components/portal/index.js +1 -1
- package/lib/module/components/portal/index.js.map +1 -1
- package/lib/module/components/screen/Screen.js +25 -28
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +7 -5
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenFlashList.js +144 -0
- package/lib/module/components/screen/ScreenFlashList.js.map +1 -0
- package/lib/module/components/screen/ScreenFlatList.js +88 -27
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +101 -34
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/index.js +1 -0
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/screen/parts/BottomSafeArea.js +62 -0
- package/lib/module/components/screen/parts/BottomSafeArea.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js +50 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -0
- package/lib/module/components/screen/types.js +4 -0
- package/lib/module/components/screen/types.js.map +1 -0
- package/lib/module/components/screen/utils.js +12 -9
- package/lib/module/components/screen/utils.js.map +1 -1
- package/lib/module/components/surface/Surface.js +16 -9
- package/lib/module/components/surface/Surface.js.map +1 -1
- package/lib/module/components/text/Text.js +23 -5
- package/lib/module/components/text/Text.js.map +1 -1
- package/lib/module/components/toast/Toast.js +8 -8
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toolbar/Toolbar.js +268 -0
- package/lib/module/components/toolbar/Toolbar.js.map +1 -0
- package/lib/module/components/toolbar/index.js +4 -0
- package/lib/module/components/toolbar/index.js.map +1 -0
- package/lib/module/context/ThemeProvider.js +5 -2
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/{components/icon/utils.js → theme/defaults/icons.js} +1 -1
- package/lib/module/theme/defaults/icons.js.map +1 -0
- package/lib/module/theme/defaults/image.js +30 -0
- package/lib/module/theme/defaults/image.js.map +1 -0
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/{configure.js → registry.js} +21 -28
- package/lib/module/theme/registry.js.map +1 -0
- package/lib/module/theme/runtime.js +1 -1
- package/lib/module/theme/theme.js +91 -10
- package/lib/module/theme/theme.js.map +1 -1
- package/lib/module/theme/tokens/dark.js +59 -68
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +49 -67
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +241 -20
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/module/utils/accent-utils.js +4 -4
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/types.d.ts +4 -4
- package/lib/typescript/src/components/avatar/types.d.ts.map +1 -1
- package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
- package/lib/typescript/src/components/box/utils.d.ts +6 -1
- package/lib/typescript/src/components/box/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/button/Button.d.ts +1 -1
- package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/chip/Chip.d.ts +24 -0
- package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts +4 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/types.d.ts +8 -0
- package/lib/typescript/src/components/chip/types.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/utils.d.ts +8 -0
- package/lib/typescript/src/components/chip/utils.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts +45 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/index.d.ts +4 -0
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/types.d.ts +6 -0
- package/lib/typescript/src/components/empty-state/types.d.ts.map +1 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts +27 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts.map +1 -1
- package/lib/typescript/src/components/header/index.d.ts +1 -1
- package/lib/typescript/src/components/header/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +7 -0
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +18 -7
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/components/image/Image.d.ts +16 -0
- package/lib/typescript/src/components/image/Image.d.ts.map +1 -0
- package/lib/typescript/src/components/image/index.d.ts +3 -0
- package/lib/typescript/src/components/image/index.d.ts.map +1 -0
- package/lib/typescript/src/components/image/types.d.ts +7 -0
- package/lib/typescript/src/components/image/types.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/Portal.d.ts +26 -0
- package/lib/typescript/src/components/portal/Portal.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/PortalContext.d.ts +11 -14
- package/lib/typescript/src/components/portal/PortalContext.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalHost.d.ts +9 -4
- package/lib/typescript/src/components/portal/PortalHost.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalProvider.d.ts +3 -3
- package/lib/typescript/src/components/portal/PortalProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/index.d.ts +4 -2
- package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/Screen.d.ts +18 -13
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +12 -9
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +43 -0
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +17 -7
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +51 -16
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/index.d.ts +3 -0
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +30 -0
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +22 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/types.d.ts +42 -0
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/utils.d.ts +9 -9
- package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts +7 -0
- package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts +115 -0
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/toolbar/index.d.ts +3 -0
- package/lib/typescript/src/components/toolbar/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/{components/icon/utils.d.ts → theme/defaults/icons.d.ts} +2 -2
- package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -0
- package/lib/typescript/src/theme/defaults/image.d.ts +4 -0
- package/lib/typescript/src/theme/defaults/image.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +1 -1
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/{configure.d.ts → registry.d.ts} +1 -1
- package/lib/typescript/src/theme/registry.d.ts.map +1 -0
- package/lib/typescript/src/theme/theme.d.ts +21 -1
- package/lib/typescript/src/theme/theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/dark.d.ts +41 -64
- package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/light.d.ts +44 -65
- package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +228 -17
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +65 -35
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +23 -9
- package/skills/terra-ui/SKILL.md +193 -0
- package/skills/terra-ui/references/recipes.md +198 -0
- package/skills/terra-ui/references/troubleshooting.md +112 -0
- package/src/components/avatar/Avatar.tsx +128 -38
- package/src/components/avatar/types.ts +5 -5
- package/src/components/box/Box.tsx +13 -1
- package/src/components/box/utils.ts +10 -0
- package/src/components/button/Button.tsx +7 -15
- package/src/components/chip/Chip.tsx +444 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/chip/types.ts +15 -0
- package/src/components/chip/utils.ts +34 -0
- package/src/components/empty-state/EmptyState.tsx +209 -0
- package/src/components/empty-state/index.ts +3 -0
- package/src/components/empty-state/types.ts +5 -0
- package/src/components/header/header-buttons.tsx +33 -45
- package/src/components/header/index.ts +2 -0
- package/src/components/header/parts/HeaderGradientOverlay.tsx +29 -0
- package/src/components/header/variants/LargeTitleHeader/index.tsx +68 -102
- package/src/components/header/variants/TitleHeader/index.tsx +47 -56
- package/src/components/icon/Icon.tsx +20 -15
- package/src/components/image/Image.tsx +48 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/image/types.ts +7 -0
- package/src/components/index.ts +4 -0
- package/src/components/portal/Portal.tsx +55 -0
- package/src/components/portal/PortalContext.tsx +14 -22
- package/src/components/portal/PortalHost.tsx +17 -10
- package/src/components/portal/PortalProvider.tsx +58 -21
- package/src/components/portal/index.ts +4 -2
- package/src/components/screen/Screen.tsx +45 -59
- package/src/components/screen/ScreenContext.tsx +19 -12
- package/src/components/screen/ScreenFlashList.tsx +196 -0
- package/src/components/screen/ScreenFlatList.tsx +100 -39
- package/src/components/screen/ScreenScrollView.tsx +114 -53
- package/src/components/screen/index.ts +3 -0
- package/src/components/screen/parts/BottomSafeArea.tsx +60 -0
- package/src/components/screen/parts/ScreenPlaceholder.tsx +55 -0
- package/src/components/screen/types.ts +43 -0
- package/src/components/screen/utils.ts +29 -15
- package/src/components/surface/Surface.tsx +17 -20
- package/src/components/text/Text.tsx +23 -5
- package/src/components/toast/Toast.tsx +8 -8
- package/src/components/toolbar/Toolbar.tsx +353 -0
- package/src/components/toolbar/index.ts +6 -0
- package/src/context/ThemeProvider.tsx +5 -4
- package/src/{components/icon/utils.tsx → theme/defaults/icons.tsx} +1 -1
- package/src/theme/defaults/image.tsx +34 -0
- package/src/theme/index.ts +1 -1
- package/src/theme/{configure.ts → registry.ts} +19 -34
- package/src/theme/runtime.ts +1 -1
- package/src/theme/theme.ts +127 -13
- package/src/theme/tokens/dark.ts +60 -69
- package/src/theme/tokens/light.ts +50 -68
- package/src/theme/tokens/primitives.ts +248 -20
- package/src/theme/types.ts +67 -35
- package/src/utils/accent-utils.ts +4 -4
- package/lib/module/components/icon/utils.js.map +0 -1
- package/lib/module/theme/configure.js.map +0 -1
- package/lib/typescript/src/components/icon/utils.d.ts.map +0 -1
- package/lib/typescript/src/theme/configure.d.ts.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { FlashList, type FlashListProps } from '@shopify/flash-list';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
type NativeScrollEvent,
|
|
5
|
+
type NativeSyntheticEvent,
|
|
6
|
+
View,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import Animated, {
|
|
9
|
+
useAnimatedScrollHandler,
|
|
10
|
+
useComposedEventHandler,
|
|
11
|
+
} from 'react-native-reanimated';
|
|
12
|
+
|
|
13
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
14
|
+
|
|
15
|
+
import { PortalHost } from '../portal';
|
|
16
|
+
import { BottomSafeArea } from './parts/BottomSafeArea';
|
|
17
|
+
import { renderScreenPlaceholder } from './parts/ScreenPlaceholder';
|
|
18
|
+
import { SCREEN_HEADER_PORTAL_HOST, useScreen } from './ScreenContext';
|
|
19
|
+
import type { ScreenMargins, ScreenScrollableProps } from './types';
|
|
20
|
+
import { hasXMargin, hasYMargin, renderListHeader } from './utils';
|
|
21
|
+
|
|
22
|
+
const AnimatedFlashList = Animated.createAnimatedComponent(
|
|
23
|
+
FlashList as unknown as ComponentType<FlashListProps<unknown>>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export interface ScreenFlashListProps<T>
|
|
27
|
+
extends Omit<FlashListProps<T>, 'horizontal'>,
|
|
28
|
+
ScreenScrollableProps {
|
|
29
|
+
/**
|
|
30
|
+
* Extra bottom padding on the list content — clearance for a tab bar or home
|
|
31
|
+
* indicator. Defaults to the `layout.screen.margin.y` token when margins are
|
|
32
|
+
* enabled, otherwise `0`.
|
|
33
|
+
*/
|
|
34
|
+
bottomInset?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `FlashList` variant of `Screen.ScrollView`: mirrors scroll offset into the
|
|
39
|
+
* screen's shared `scrollY`, hosts the portal target above the list, and
|
|
40
|
+
* applies token-driven content padding.
|
|
41
|
+
*
|
|
42
|
+
* Requires `@shopify/flash-list` — it is a peer dependency of
|
|
43
|
+
* `react-native-terra-ui` and must be installed in the consuming app.
|
|
44
|
+
*
|
|
45
|
+
* Note: unlike `Screen.FlatList`, this does not bind the screen's shared
|
|
46
|
+
* imperative `scrollRef` — `FlashList`'s ref exposes a different (non
|
|
47
|
+
* Reanimated-compatible) imperative API.
|
|
48
|
+
*
|
|
49
|
+
* Shares `loading` / `LoadingComponent` / `EmptyComponent` / `margins` /
|
|
50
|
+
* `horizontal` with `Screen.ScrollView`, so a screen can swap one container for
|
|
51
|
+
* another without relearning its API.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* <Screen.FlashList data={items} renderItem={({ item }) => <Row item={item} />} />
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example Loading and empty placeholders
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <Screen.FlashList
|
|
61
|
+
* data={items}
|
|
62
|
+
* loading={isLoading}
|
|
63
|
+
* EmptyComponent={<EmptyState icon="status.info" title="Nothing yet" fill />}
|
|
64
|
+
* renderItem={({ item }) => <Row item={item} />}
|
|
65
|
+
* />
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function ScreenFlashList<T>({
|
|
69
|
+
data,
|
|
70
|
+
style,
|
|
71
|
+
contentContainerStyle,
|
|
72
|
+
ListHeaderComponent,
|
|
73
|
+
ListFooterComponent,
|
|
74
|
+
ListEmptyComponent,
|
|
75
|
+
horizontal = false,
|
|
76
|
+
loading = false,
|
|
77
|
+
LoadingComponent,
|
|
78
|
+
EmptyComponent,
|
|
79
|
+
margins,
|
|
80
|
+
bottomInset,
|
|
81
|
+
onScroll,
|
|
82
|
+
...rest
|
|
83
|
+
}: ScreenFlashListProps<T>) {
|
|
84
|
+
if (FlashList == null) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
'[react-native-terra-ui] Screen.FlashList requires "@shopify/flash-list" to be installed in your app. Run `yarn add @shopify/flash-list` (or the npm/pnpm equivalent) and rebuild.'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
background,
|
|
92
|
+
scrollHandler,
|
|
93
|
+
headerSnapOffsets,
|
|
94
|
+
margins: screenMargins,
|
|
95
|
+
hasHeader,
|
|
96
|
+
} = useScreen();
|
|
97
|
+
|
|
98
|
+
const resolvedMargins = margins ?? screenMargins;
|
|
99
|
+
// The placeholder rides in on `ListEmptyComponent` rather than replacing the
|
|
100
|
+
// list, so the header portal and snap offsets stay put. While loading the
|
|
101
|
+
// list is fed an empty `data` so that slot is the one that renders.
|
|
102
|
+
const placeholder = renderScreenPlaceholder({
|
|
103
|
+
loading,
|
|
104
|
+
LoadingComponent,
|
|
105
|
+
EmptyComponent,
|
|
106
|
+
isEmpty: !data || data.length === 0,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const jsScrollHandler = useAnimatedScrollHandler(
|
|
110
|
+
(event) => {
|
|
111
|
+
if (onScroll)
|
|
112
|
+
onScroll(event as unknown as NativeSyntheticEvent<NativeScrollEvent>);
|
|
113
|
+
},
|
|
114
|
+
[onScroll]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const composedHandler = useComposedEventHandler([
|
|
118
|
+
scrollHandler,
|
|
119
|
+
jsScrollHandler,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
// `AnimatedFlashList` is typed for `unknown` items, so the remaining props
|
|
123
|
+
// are cast back to its prop type — minus the ones set explicitly below,
|
|
124
|
+
// which the spread would otherwise re-supply.
|
|
125
|
+
const listProps = rest as Omit<FlashListProps<unknown>, 'data' | 'onScroll'>;
|
|
126
|
+
|
|
127
|
+
// A caller naming its own `bottomInset` has taken the bottom over entirely,
|
|
128
|
+
// and a horizontal list has no bottom edge to clear.
|
|
129
|
+
const needsBottomSafeArea = bottomInset === undefined && !horizontal;
|
|
130
|
+
|
|
131
|
+
const footerComponent = (
|
|
132
|
+
<>
|
|
133
|
+
{renderListHeader(ListFooterComponent)}
|
|
134
|
+
{needsBottomSafeArea ? <BottomSafeArea /> : null}
|
|
135
|
+
</>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const headerComponent = (
|
|
139
|
+
<View style={styles.headerWrapper(resolvedMargins)}>
|
|
140
|
+
<PortalHost name={SCREEN_HEADER_PORTAL_HOST} />
|
|
141
|
+
{renderListHeader(ListHeaderComponent)}
|
|
142
|
+
</View>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<AnimatedFlashList
|
|
147
|
+
data={loading ? [] : (data as readonly unknown[] | null | undefined)}
|
|
148
|
+
style={[{ backgroundColor: background }, style]}
|
|
149
|
+
horizontal={horizontal}
|
|
150
|
+
contentInsetAdjustmentBehavior={hasHeader ? 'never' : undefined}
|
|
151
|
+
contentContainerStyle={[
|
|
152
|
+
styles.content(resolvedMargins, hasHeader, bottomInset),
|
|
153
|
+
contentContainerStyle,
|
|
154
|
+
]}
|
|
155
|
+
ListHeaderComponent={headerComponent}
|
|
156
|
+
ListFooterComponent={footerComponent}
|
|
157
|
+
ListEmptyComponent={placeholder ?? ListEmptyComponent}
|
|
158
|
+
showsVerticalScrollIndicator={false}
|
|
159
|
+
scrollEventThrottle={16}
|
|
160
|
+
snapToOffsets={headerSnapOffsets}
|
|
161
|
+
{...listProps}
|
|
162
|
+
onScroll={composedHandler}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const styles = StyleSheet.create((theme, runtime) => ({
|
|
168
|
+
content: (
|
|
169
|
+
margins: ScreenMargins,
|
|
170
|
+
hasHeader: boolean,
|
|
171
|
+
bottomInset: number | undefined
|
|
172
|
+
) => {
|
|
173
|
+
return {
|
|
174
|
+
flexGrow: 1,
|
|
175
|
+
// The header is absolutely positioned, so the list reserves its full
|
|
176
|
+
// height — bar plus top safe area. This assumes the scroll view does not
|
|
177
|
+
// also get UIKit's automatic inset adjustment: on a native tab screen
|
|
178
|
+
// that means `disableAutomaticContentInsets` on the tab, or the two
|
|
179
|
+
// insets are counted twice.
|
|
180
|
+
paddingTop:
|
|
181
|
+
(hasHeader ? theme.layout.header.height + runtime.insets.top : 0) +
|
|
182
|
+
(hasYMargin(margins) ? theme.layout.screen.margin.y : 0),
|
|
183
|
+
paddingHorizontal: hasXMargin(margins) ? theme.layout.screen.margin.x : 0,
|
|
184
|
+
// The safe area is `BottomSafeArea`'s job; counting it here as well
|
|
185
|
+
// would leave a gap the height of the gesture bar.
|
|
186
|
+
paddingBottom:
|
|
187
|
+
bottomInset ?? (hasYMargin(margins) ? theme.layout.screen.margin.y : 0),
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
safeArea: {
|
|
191
|
+
height: runtime.insets.top,
|
|
192
|
+
},
|
|
193
|
+
headerWrapper: (margins: ScreenMargins) => ({
|
|
194
|
+
marginHorizontal: hasXMargin(margins) ? -theme.layout.screen.margin.x : 0,
|
|
195
|
+
}),
|
|
196
|
+
}));
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createElement,
|
|
3
|
-
type FunctionComponent,
|
|
4
|
-
isValidElement,
|
|
5
|
-
type ReactElement,
|
|
6
|
-
} from 'react';
|
|
7
1
|
import {
|
|
8
2
|
type FlatListProps,
|
|
9
3
|
type NativeScrollEvent,
|
|
@@ -16,20 +10,18 @@ import Animated, {
|
|
|
16
10
|
useAnimatedScrollHandler,
|
|
17
11
|
useComposedEventHandler,
|
|
18
12
|
} from 'react-native-reanimated';
|
|
19
|
-
import {
|
|
20
|
-
import { useUnistyles } from 'react-native-unistyles';
|
|
13
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
21
14
|
|
|
22
15
|
import { PortalHost } from '../portal';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
16
|
+
import { BottomSafeArea } from './parts/BottomSafeArea';
|
|
17
|
+
import { renderScreenPlaceholder } from './parts/ScreenPlaceholder';
|
|
18
|
+
import { SCREEN_HEADER_PORTAL_HOST, useScreen } from './ScreenContext';
|
|
19
|
+
import type { ScreenMargins, ScreenScrollableProps } from './types';
|
|
20
|
+
import { hasXMargin, hasYMargin, renderListHeader } from './utils';
|
|
25
21
|
|
|
26
22
|
export interface ScreenFlatListProps<T>
|
|
27
|
-
extends Omit<FlatListProps<T>, 'CellRendererComponent'
|
|
28
|
-
|
|
29
|
-
* Apply `layout.screen.margin` padding. Defaults to the enclosing `Screen`'s
|
|
30
|
-
* `margins` value.
|
|
31
|
-
*/
|
|
32
|
-
margins?: boolean;
|
|
23
|
+
extends Omit<FlatListProps<T>, 'CellRendererComponent' | 'horizontal'>,
|
|
24
|
+
ScreenScrollableProps {
|
|
33
25
|
/**
|
|
34
26
|
* Extra bottom padding on the list content — clearance for a tab bar or home
|
|
35
27
|
* indicator. Defaults to the `layout.screen.margin.y` token when margins are
|
|
@@ -38,36 +30,48 @@ export interface ScreenFlatListProps<T>
|
|
|
38
30
|
bottomInset?: number;
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
function renderListHeader(
|
|
42
|
-
ListHeaderComponent: FlatListProps<unknown>['ListHeaderComponent']
|
|
43
|
-
): ReactElement | null {
|
|
44
|
-
if (!ListHeaderComponent) return null;
|
|
45
|
-
if (isValidElement(ListHeaderComponent)) return ListHeaderComponent;
|
|
46
|
-
return createElement(ListHeaderComponent as FunctionComponent);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
33
|
/**
|
|
50
34
|
* `FlatList` variant of `Screen.ScrollView`: binds the screen's shared scroll
|
|
51
35
|
* ref, mirrors offset into `scrollY`, hosts the portal target above the list,
|
|
52
36
|
* and applies token-driven content padding.
|
|
53
37
|
*
|
|
38
|
+
* Shares `loading` / `LoadingComponent` / `EmptyComponent` / `margins` /
|
|
39
|
+
* `horizontal` with `Screen.ScrollView`, so a screen can swap one container for
|
|
40
|
+
* another without relearning its API.
|
|
41
|
+
*
|
|
54
42
|
* @example
|
|
55
43
|
* ```tsx
|
|
56
44
|
* <Screen.FlatList data={items} renderItem={({ item }) => <Row item={item} />} />
|
|
57
45
|
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example Loading and empty placeholders
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <Screen.FlatList
|
|
50
|
+
* data={items}
|
|
51
|
+
* loading={isLoading}
|
|
52
|
+
* EmptyComponent={<EmptyState icon="status.info" title="Nothing yet" fill />}
|
|
53
|
+
* renderItem={({ item }) => <Row item={item} />}
|
|
54
|
+
* />
|
|
55
|
+
* ```
|
|
58
56
|
*/
|
|
59
57
|
export function ScreenFlatList<T>({
|
|
58
|
+
data,
|
|
60
59
|
style,
|
|
61
60
|
contentContainerStyle,
|
|
62
61
|
ListHeaderComponent,
|
|
62
|
+
ListFooterComponent,
|
|
63
|
+
ListEmptyComponent,
|
|
64
|
+
horizontal = false,
|
|
65
|
+
loading = false,
|
|
66
|
+
LoadingComponent,
|
|
67
|
+
EmptyComponent,
|
|
63
68
|
margins,
|
|
64
69
|
bottomInset,
|
|
65
70
|
onScroll,
|
|
66
71
|
...rest
|
|
67
72
|
}: ScreenFlatListProps<T>) {
|
|
68
|
-
const { theme } = useUnistyles();
|
|
69
|
-
const { top } = useSafeAreaInsets();
|
|
70
73
|
const {
|
|
74
|
+
background,
|
|
71
75
|
scrollRef,
|
|
72
76
|
scrollHandler,
|
|
73
77
|
headerSnapOffsets,
|
|
@@ -75,15 +79,17 @@ export function ScreenFlatList<T>({
|
|
|
75
79
|
hasHeader,
|
|
76
80
|
} = useScreen();
|
|
77
81
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
const resolvedMargins = margins ?? screenMargins;
|
|
83
|
+
// The placeholder rides in on `ListEmptyComponent` rather than replacing the
|
|
84
|
+
// list, so the header portal, scroll binding and snap offsets all stay put.
|
|
85
|
+
// While loading the list is fed an empty `data` so that slot is the one that
|
|
86
|
+
// renders.
|
|
87
|
+
const placeholder = renderScreenPlaceholder({
|
|
88
|
+
loading,
|
|
89
|
+
LoadingComponent,
|
|
90
|
+
EmptyComponent,
|
|
91
|
+
isEmpty: !data || data.length === 0,
|
|
92
|
+
});
|
|
87
93
|
|
|
88
94
|
const jsScrollHandler = useAnimatedScrollHandler(
|
|
89
95
|
(event) => {
|
|
@@ -98,9 +104,21 @@ export function ScreenFlatList<T>({
|
|
|
98
104
|
jsScrollHandler,
|
|
99
105
|
]);
|
|
100
106
|
|
|
107
|
+
// A caller naming its own `bottomInset` has taken the bottom over entirely,
|
|
108
|
+
// and a horizontal list has no bottom edge to clear.
|
|
109
|
+
const needsBottomSafeArea = bottomInset === undefined && !horizontal;
|
|
110
|
+
|
|
111
|
+
const footerComponent = (
|
|
112
|
+
<>
|
|
113
|
+
{renderListHeader(ListFooterComponent)}
|
|
114
|
+
{needsBottomSafeArea ? <BottomSafeArea /> : null}
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
|
|
101
118
|
const headerComponent = (
|
|
102
|
-
<View>
|
|
103
|
-
<
|
|
119
|
+
<View style={styles.headerWrapper(resolvedMargins)}>
|
|
120
|
+
{hasHeader ? <View style={styles.header} /> : null}
|
|
121
|
+
<PortalHost name={SCREEN_HEADER_PORTAL_HOST} />
|
|
104
122
|
{renderListHeader(ListHeaderComponent)}
|
|
105
123
|
</View>
|
|
106
124
|
);
|
|
@@ -108,9 +126,17 @@ export function ScreenFlatList<T>({
|
|
|
108
126
|
return (
|
|
109
127
|
<Animated.FlatList
|
|
110
128
|
ref={scrollRef as unknown as AnimatedRef<Animated.FlatList<T>>}
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
data={loading ? [] : data}
|
|
130
|
+
style={[{ backgroundColor: background }, style]}
|
|
131
|
+
horizontal={horizontal}
|
|
132
|
+
contentInsetAdjustmentBehavior={hasHeader ? 'never' : undefined}
|
|
133
|
+
contentContainerStyle={[
|
|
134
|
+
styles.content(resolvedMargins, hasHeader, bottomInset),
|
|
135
|
+
contentContainerStyle,
|
|
136
|
+
]}
|
|
113
137
|
ListHeaderComponent={headerComponent}
|
|
138
|
+
ListFooterComponent={footerComponent}
|
|
139
|
+
ListEmptyComponent={placeholder ?? ListEmptyComponent}
|
|
114
140
|
showsVerticalScrollIndicator={false}
|
|
115
141
|
scrollEventThrottle={16}
|
|
116
142
|
snapToOffsets={headerSnapOffsets}
|
|
@@ -119,3 +145,38 @@ export function ScreenFlatList<T>({
|
|
|
119
145
|
/>
|
|
120
146
|
);
|
|
121
147
|
}
|
|
148
|
+
|
|
149
|
+
const styles = StyleSheet.create((theme, runtime) => ({
|
|
150
|
+
scrollContent: {
|
|
151
|
+
flexGrow: 1,
|
|
152
|
+
},
|
|
153
|
+
content: (
|
|
154
|
+
margins: ScreenMargins,
|
|
155
|
+
hasHeader: boolean,
|
|
156
|
+
bottomInset: number | undefined
|
|
157
|
+
) => ({
|
|
158
|
+
flexGrow: 1,
|
|
159
|
+
// The header is absolutely positioned, so the list reserves its full
|
|
160
|
+
// height — bar plus top safe area. This assumes the scroll view does not
|
|
161
|
+
// also get UIKit's automatic inset adjustment: on a native tab screen
|
|
162
|
+
// that means `disableAutomaticContentInsets` on the tab, or the two
|
|
163
|
+
// insets are counted twice.
|
|
164
|
+
paddingTop:
|
|
165
|
+
(hasHeader ? theme.layout.header.height + runtime.insets.top : 0) +
|
|
166
|
+
(hasYMargin(margins) ? theme.layout.screen.margin.y : 0),
|
|
167
|
+
paddingHorizontal: hasXMargin(margins) ? theme.layout.screen.margin.x : 0,
|
|
168
|
+
// The safe area is `BottomSafeArea`'s job; counting it here as well would
|
|
169
|
+
// leave a gap the height of the gesture bar.
|
|
170
|
+
paddingBottom:
|
|
171
|
+
bottomInset ?? (hasYMargin(margins) ? theme.layout.screen.margin.y : 0),
|
|
172
|
+
}),
|
|
173
|
+
safeArea: {
|
|
174
|
+
height: runtime.insets.top,
|
|
175
|
+
},
|
|
176
|
+
headerWrapper: (margins: ScreenMargins) => ({
|
|
177
|
+
marginHorizontal: hasXMargin(margins) ? -theme.layout.screen.margin.x : 0,
|
|
178
|
+
}),
|
|
179
|
+
header: {
|
|
180
|
+
height: theme.layout.header.height,
|
|
181
|
+
},
|
|
182
|
+
}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Children, type ReactNode } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type NativeScrollEvent,
|
|
4
4
|
type NativeSyntheticEvent,
|
|
@@ -11,29 +11,24 @@ import Animated, {
|
|
|
11
11
|
useAnimatedScrollHandler,
|
|
12
12
|
useComposedEventHandler,
|
|
13
13
|
} from 'react-native-reanimated';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
14
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
15
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
17
16
|
import { PortalHost } from '../portal';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
17
|
+
import { BottomSafeArea } from './parts/BottomSafeArea';
|
|
18
|
+
import { renderScreenPlaceholder } from './parts/ScreenPlaceholder';
|
|
19
|
+
import { SCREEN_HEADER_PORTAL_HOST, useScreen } from './ScreenContext';
|
|
20
|
+
import type { ScreenMargins, ScreenScrollableProps } from './types';
|
|
21
|
+
import { hasXMargin, hasYMargin } from './utils';
|
|
20
22
|
|
|
21
|
-
export interface ScreenScrollViewProps
|
|
23
|
+
export interface ScreenScrollViewProps
|
|
24
|
+
extends Omit<ScrollViewProps, 'horizontal'>,
|
|
25
|
+
ScreenScrollableProps {
|
|
22
26
|
children?: ReactNode;
|
|
23
27
|
/**
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Extra bottom padding on the scroll content — clearance for a tab bar or
|
|
30
|
-
* home indicator. Defaults to the `layout.screen.margin.y` token when margins
|
|
31
|
-
* are enabled, otherwise `0`.
|
|
32
|
-
*/
|
|
33
|
-
bottomInset?: number;
|
|
34
|
-
/**
|
|
35
|
-
* Optional Reanimated scroll handler composed with the screen's own scroll
|
|
36
|
-
* tracking and any JS `onScroll` callback.
|
|
28
|
+
* Reanimated scroll handler for custom scroll-driven animation, composed
|
|
29
|
+
* alongside the screen's own scroll tracking and any JS `onScroll`
|
|
30
|
+
* callback. Runs on the UI thread — for simple JS-side callbacks, use
|
|
31
|
+
* `onScroll` instead; it doesn't require writing a worklet.
|
|
37
32
|
*/
|
|
38
33
|
scrollHandler?: ScrollHandlerProcessed<Record<string, unknown>>;
|
|
39
34
|
}
|
|
@@ -43,45 +38,96 @@ export interface ScreenScrollViewProps extends ScrollViewProps {
|
|
|
43
38
|
* ref, mirrors offset into `scrollY`, hosts the portal target at the top (where
|
|
44
39
|
* header content is injected), and applies token-driven content padding.
|
|
45
40
|
*
|
|
41
|
+
* For simple side effects (analytics, infinite-scroll triggers), pass a plain
|
|
42
|
+
* `onScroll` callback. For scroll-driven animation, pass your own
|
|
43
|
+
* `useAnimatedScrollHandler` via `scrollHandler` instead — it runs on the UI
|
|
44
|
+
* thread alongside the screen's own handler, with no JS-thread round trip.
|
|
45
|
+
*
|
|
46
46
|
* @example
|
|
47
47
|
* ```tsx
|
|
48
48
|
* <Screen.ScrollView>
|
|
49
49
|
* <Text>Scrollable content</Text>
|
|
50
50
|
* </Screen.ScrollView>
|
|
51
51
|
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Loading placeholder instead of a per-screen spinner branch
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const { data, loading } = useQuery();
|
|
56
|
+
*
|
|
57
|
+
* <Screen.ScrollView loading={loading}>
|
|
58
|
+
* <Text>{data?.title}</Text>
|
|
59
|
+
* </Screen.ScrollView>
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Empty placeholder when there is nothing to render
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <Screen.ScrollView EmptyComponent={<EmptyState icon="status.info" title="Nothing yet" fill />}>
|
|
65
|
+
* {items.map((item) => <Row key={item.id} item={item} />)}
|
|
66
|
+
* </Screen.ScrollView>
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @example Simple JS-side scroll callback
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <Screen.ScrollView onScroll={(e) => console.log(e.nativeEvent.contentOffset.y)}>
|
|
72
|
+
* <Text>Scrollable content</Text>
|
|
73
|
+
* </Screen.ScrollView>
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Custom scroll-driven animation
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const headerOpacity = useSharedValue(1);
|
|
79
|
+
* const scrollHandler = useAnimatedScrollHandler({
|
|
80
|
+
* onScroll: (event) => {
|
|
81
|
+
* headerOpacity.value = interpolate(event.contentOffset.y, [0, 100], [1, 0]);
|
|
82
|
+
* // Need a JS-side side effect too? Call scheduleOnRN(fn, ...args) here.
|
|
83
|
+
* },
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* <Screen.ScrollView scrollHandler={scrollHandler}>
|
|
87
|
+
* <Text>Scrollable content</Text>
|
|
88
|
+
* </Screen.ScrollView>
|
|
89
|
+
* ```
|
|
52
90
|
*/
|
|
53
91
|
export function ScreenScrollView({
|
|
54
92
|
children,
|
|
55
93
|
style,
|
|
56
94
|
contentContainerStyle,
|
|
57
|
-
hasMargins,
|
|
58
|
-
bottomInset,
|
|
59
95
|
scrollHandler: externalScrollHandler,
|
|
60
96
|
onScroll: onScrollProp,
|
|
61
97
|
horizontal = false,
|
|
98
|
+
loading = false,
|
|
99
|
+
LoadingComponent,
|
|
100
|
+
EmptyComponent,
|
|
101
|
+
margins,
|
|
62
102
|
...rest
|
|
63
103
|
}: ScreenScrollViewProps) {
|
|
64
|
-
const { theme } = useUnistyles();
|
|
65
|
-
const { top } = useSafeAreaInsets();
|
|
66
104
|
const {
|
|
67
105
|
scrollRef,
|
|
68
106
|
scrollHandler: screenScrollHandler,
|
|
69
|
-
headerSnapOffsets,
|
|
70
107
|
margins: screenMargins,
|
|
71
108
|
hasHeader,
|
|
109
|
+
background,
|
|
72
110
|
} = useScreen();
|
|
73
111
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
const resolvedMargins = margins ?? screenMargins;
|
|
113
|
+
// `toArray` drops the nullish/boolean children a conditional render leaves
|
|
114
|
+
// behind, so `{items.map(...)}` over an empty array counts as empty.
|
|
115
|
+
const placeholder = renderScreenPlaceholder({
|
|
116
|
+
loading,
|
|
117
|
+
LoadingComponent,
|
|
118
|
+
EmptyComponent,
|
|
119
|
+
isEmpty: Children.toArray(children).length === 0,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const jsScrollHandler = useAnimatedScrollHandler({
|
|
123
|
+
onScroll: (event) => {
|
|
124
|
+
if (onScrollProp) {
|
|
125
|
+
scheduleOnRN(onScrollProp, {
|
|
126
|
+
nativeEvent: event,
|
|
127
|
+
} as unknown as NativeSyntheticEvent<NativeScrollEvent>);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
85
131
|
|
|
86
132
|
const handlers: ScrollHandlerProcessed<Record<string, unknown>>[] = [
|
|
87
133
|
screenScrollHandler,
|
|
@@ -91,40 +137,55 @@ export function ScreenScrollView({
|
|
|
91
137
|
|
|
92
138
|
const composedHandler = useComposedEventHandler(handlers);
|
|
93
139
|
|
|
94
|
-
const margin = theme.layout.screen.margin;
|
|
95
|
-
const marginsEnabled = hasMargins ?? screenMargins;
|
|
96
|
-
const compactHeaderHeight = hasHeader ? top + theme.layout.header.height : 0;
|
|
97
|
-
const { contentPadding } = resolveScreenContentInsets(
|
|
98
|
-
margin,
|
|
99
|
-
marginsEnabled,
|
|
100
|
-
bottomInset,
|
|
101
|
-
compactHeaderHeight
|
|
102
|
-
);
|
|
103
|
-
|
|
104
140
|
return (
|
|
105
141
|
<Animated.ScrollView
|
|
106
142
|
ref={scrollRef}
|
|
107
|
-
style={style}
|
|
143
|
+
style={[{ backgroundColor: background }, style]}
|
|
108
144
|
horizontal={horizontal}
|
|
145
|
+
contentInsetAdjustmentBehavior={hasHeader ? 'never' : undefined}
|
|
109
146
|
showsVerticalScrollIndicator={false}
|
|
110
147
|
scrollEventThrottle={16}
|
|
111
|
-
contentContainerStyle={
|
|
112
|
-
snapToOffsets={headerSnapOffsets}
|
|
148
|
+
contentContainerStyle={styles.scrollContent(hasHeader, resolvedMargins)}
|
|
149
|
+
// snapToOffsets={headerSnapOffsets}
|
|
113
150
|
{...rest}
|
|
114
151
|
onScroll={composedHandler}
|
|
115
152
|
>
|
|
153
|
+
{/* <View style={styles.safeArea} /> */}
|
|
116
154
|
<View>
|
|
117
|
-
<PortalHost />
|
|
155
|
+
<PortalHost name={SCREEN_HEADER_PORTAL_HOST} />
|
|
118
156
|
</View>
|
|
119
157
|
<View
|
|
120
158
|
style={[
|
|
159
|
+
styles.content(horizontal, resolvedMargins),
|
|
121
160
|
contentContainerStyle,
|
|
122
|
-
{ flexDirection: horizontal ? 'row' : 'column' },
|
|
123
161
|
]}
|
|
124
162
|
>
|
|
125
|
-
{
|
|
126
|
-
{children}
|
|
163
|
+
{placeholder ?? children}
|
|
127
164
|
</View>
|
|
165
|
+
{/* Outside the padded content view so the horizontal margins do not
|
|
166
|
+
apply to it, and last so it sits below everything. A horizontal
|
|
167
|
+
scroller has no bottom edge to clear. */}
|
|
168
|
+
{horizontal ? null : <BottomSafeArea />}
|
|
128
169
|
</Animated.ScrollView>
|
|
129
170
|
);
|
|
130
171
|
}
|
|
172
|
+
|
|
173
|
+
const styles = StyleSheet.create((theme, rt) => ({
|
|
174
|
+
scrollContent: (hasHeader: boolean, margins: ScreenMargins) => ({
|
|
175
|
+
flexGrow: 1,
|
|
176
|
+
// The header inset and the safe area are structural — they apply whether
|
|
177
|
+
// or not margins are on; the y margin stacks on top of them.
|
|
178
|
+
paddingTop:
|
|
179
|
+
(hasHeader ? theme.layout.header.height + rt.insets.top : 0) +
|
|
180
|
+
(hasYMargin(margins) ? theme.layout.screen.margin.y : 0),
|
|
181
|
+
// The safe area is `BottomSafeArea`'s job; counting it here as well would
|
|
182
|
+
// leave a gap the height of the gesture bar.
|
|
183
|
+
paddingBottom: hasYMargin(margins) ? theme.layout.screen.margin.y : 0,
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
content: (horizontal: boolean, margins: ScreenMargins) => ({
|
|
187
|
+
flexGrow: 1,
|
|
188
|
+
paddingHorizontal: hasXMargin(margins) ? theme.layout.screen.margin.x : 0,
|
|
189
|
+
flexDirection: horizontal ? 'row' : 'column',
|
|
190
|
+
}),
|
|
191
|
+
}));
|
|
@@ -2,7 +2,10 @@ export type { ScreenHeaderProps, ScreenProps } from './Screen';
|
|
|
2
2
|
export { Screen } from './Screen';
|
|
3
3
|
export type { ScreenContextValue } from './ScreenContext';
|
|
4
4
|
export { useScreen } from './ScreenContext';
|
|
5
|
+
export type { ScreenFlashListProps } from './ScreenFlashList';
|
|
6
|
+
export { ScreenFlashList } from './ScreenFlashList';
|
|
5
7
|
export type { ScreenFlatListProps } from './ScreenFlatList';
|
|
6
8
|
export { ScreenFlatList } from './ScreenFlatList';
|
|
7
9
|
export type { ScreenScrollViewProps } from './ScreenScrollView';
|
|
8
10
|
export { ScreenScrollView } from './ScreenScrollView';
|
|
11
|
+
export type { ScreenMargins, ScreenScrollableProps } from './types';
|