react-native-terra-ui 0.11.5 → 0.11.6
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 +7 -0
- package/lib/module/components/chip/Chip.js +25 -8
- package/lib/module/components/chip/Chip.js.map +1 -1
- package/lib/module/components/chip/utils.js +1 -1
- package/lib/module/components/chip/utils.js.map +1 -1
- package/lib/module/components/header/index.js.map +1 -1
- package/lib/module/components/header/parts/HeaderAccessory.js +55 -0
- package/lib/module/components/header/parts/HeaderAccessory.js.map +1 -0
- package/lib/module/components/header/parts/HeaderSurface.js +65 -0
- package/lib/module/components/header/parts/HeaderSurface.js.map +1 -0
- package/lib/module/components/header/types.js +2 -0
- package/lib/module/components/header/types.js.map +1 -0
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +30 -28
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +28 -27
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +10 -1
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenList.js +5 -3
- package/lib/module/components/screen/ScreenList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +5 -3
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/typescript/src/components/chip/Chip.d.ts +8 -0
- package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -1
- package/lib/typescript/src/components/chip/utils.d.ts +6 -1
- package/lib/typescript/src/components/chip/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/header/index.d.ts +1 -0
- package/lib/typescript/src/components/header/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts +19 -0
- package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts.map +1 -0
- package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts +24 -0
- package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts.map +1 -0
- package/lib/typescript/src/components/header/types.d.ts +11 -0
- package/lib/typescript/src/components/header/types.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 +15 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +15 -0
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chip/Chip.tsx +22 -9
- package/src/components/chip/utils.ts +7 -2
- package/src/components/header/index.ts +1 -0
- package/src/components/header/parts/HeaderAccessory.tsx +57 -0
- package/src/components/header/parts/HeaderSurface.tsx +65 -0
- package/src/components/header/types.ts +10 -0
- package/src/components/header/variants/LargeTitleHeader/index.tsx +33 -32
- package/src/components/header/variants/TitleHeader/index.tsx +35 -21
- package/src/components/screen/ScreenContext.tsx +26 -0
- package/src/components/screen/ScreenList.tsx +8 -3
- package/src/components/screen/ScreenScrollView.tsx +12 -3
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ReactNode, useCallback, useContext, useEffect } from "react";
|
|
2
|
+
import { type LayoutChangeEvent, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
5
|
+
|
|
6
|
+
import { ScreenContext } from "../../screen/ScreenContext";
|
|
7
|
+
import type { HeaderAccessoryPadding } from "../types";
|
|
8
|
+
|
|
9
|
+
export interface HeaderAccessoryProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/** Horizontal padding around the accessory. */
|
|
12
|
+
padding: HeaderAccessoryPadding;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pinned content below a header bar — a search field, a tab strip.
|
|
17
|
+
*
|
|
18
|
+
* Reports its measured height to the surrounding `Screen` so scroll containers
|
|
19
|
+
* reserve it above their content. The theme header height covers the bar alone
|
|
20
|
+
* and the accessory is whatever the caller passed, so nothing but a measurement
|
|
21
|
+
* can know how far down the content has to start.
|
|
22
|
+
*
|
|
23
|
+
* Outside a `Screen` there is nothing to report to and it simply renders.
|
|
24
|
+
*/
|
|
25
|
+
export function HeaderAccessory({ children, padding }: HeaderAccessoryProps) {
|
|
26
|
+
const setHeaderAccessoryHeight = useContext(ScreenContext)?.setHeaderAccessoryHeight;
|
|
27
|
+
|
|
28
|
+
const handleLayout = useCallback(
|
|
29
|
+
(event: LayoutChangeEvent) => {
|
|
30
|
+
setHeaderAccessoryHeight?.(event.nativeEvent.layout.height);
|
|
31
|
+
},
|
|
32
|
+
[setHeaderAccessoryHeight]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// An unmounted accessory never lays out again, so without this the screen
|
|
36
|
+
// would keep reserving room for content that is no longer there.
|
|
37
|
+
useEffect(() => () => setHeaderAccessoryHeight?.(0), [setHeaderAccessoryHeight]);
|
|
38
|
+
|
|
39
|
+
styles.useVariants({ padding });
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={styles.accessory} onLayout={handleLayout}>
|
|
43
|
+
{children}
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create((theme) => ({
|
|
49
|
+
accessory: {
|
|
50
|
+
variants: {
|
|
51
|
+
padding: {
|
|
52
|
+
x: { paddingHorizontal: theme.layout.screen.padding.x },
|
|
53
|
+
none: {},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import Animated, { type useAnimatedStyle } from "react-native-reanimated";
|
|
4
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
5
|
+
|
|
6
|
+
import { HEADER_FADE_EXTENT, HeaderGradientOverlay } from "./HeaderGradientOverlay";
|
|
7
|
+
|
|
8
|
+
type HeaderOpacityStyle = ReturnType<typeof useAnimatedStyle<{ opacity: number }>>;
|
|
9
|
+
|
|
10
|
+
export interface HeaderSurfaceProps {
|
|
11
|
+
/** Fill color, fading to fully transparent along the bottom edge. */
|
|
12
|
+
color: string;
|
|
13
|
+
/** Opacity style for variants that fade the whole surface in as they collapse. */
|
|
14
|
+
animatedStyle?: HeaderOpacityStyle;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The painted background behind a header — the bar, any accessory below it, and
|
|
19
|
+
* the fade that releases scrolling content at the bottom edge.
|
|
20
|
+
*
|
|
21
|
+
* Split into a flat fill and a fixed-height ramp rather than one gradient over
|
|
22
|
+
* the whole box, because {@link HeaderGradientOverlay}'s curve is expressed in
|
|
23
|
+
* fractions of its own height. Stretched over a tall accessory the fade would
|
|
24
|
+
* begin partway up it, and content passing behind would show through the thing
|
|
25
|
+
* the caller put there to be read. Pinning the ramp to the bar's height keeps it
|
|
26
|
+
* identical with or without an accessory, and everything above it stays solid.
|
|
27
|
+
*/
|
|
28
|
+
export function HeaderSurface({ color, animatedStyle }: HeaderSurfaceProps) {
|
|
29
|
+
return (
|
|
30
|
+
<Animated.View style={[styles.surface, animatedStyle]} pointerEvents="none">
|
|
31
|
+
<View style={styles.fill(color)} />
|
|
32
|
+
<View style={styles.fade}>
|
|
33
|
+
<HeaderGradientOverlay color={color} />
|
|
34
|
+
</View>
|
|
35
|
+
</Animated.View>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const styles = StyleSheet.create((theme) => ({
|
|
40
|
+
// Covers the bar and any accessory, overhanging so the fade's tail finishes in
|
|
41
|
+
// open space rather than being clipped at the header's edge.
|
|
42
|
+
surface: {
|
|
43
|
+
position: "absolute",
|
|
44
|
+
top: 0,
|
|
45
|
+
left: 0,
|
|
46
|
+
right: 0,
|
|
47
|
+
bottom: -HEADER_FADE_EXTENT,
|
|
48
|
+
},
|
|
49
|
+
// Collapses to nothing when there is no accessory, leaving the ramp alone.
|
|
50
|
+
fill: (color: string) => ({
|
|
51
|
+
position: "absolute",
|
|
52
|
+
top: 0,
|
|
53
|
+
left: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
bottom: theme.layout.header.height + HEADER_FADE_EXTENT,
|
|
56
|
+
backgroundColor: color,
|
|
57
|
+
}),
|
|
58
|
+
fade: {
|
|
59
|
+
position: "absolute",
|
|
60
|
+
left: 0,
|
|
61
|
+
right: 0,
|
|
62
|
+
bottom: 0,
|
|
63
|
+
height: theme.layout.header.height + HEADER_FADE_EXTENT,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horizontal padding applied to a header accessory.
|
|
3
|
+
*
|
|
4
|
+
* - `'x'` — the screen's horizontal padding, matching the bar above it. What a
|
|
5
|
+
* field-shaped accessory (a search input, a segmented control) wants.
|
|
6
|
+
* - `'none'` — full bleed. What a horizontally scrolling accessory (a tab strip,
|
|
7
|
+
* a filter row) wants, so its content can run to the screen edge instead of
|
|
8
|
+
* being clipped at an inset the caller cannot scroll past.
|
|
9
|
+
*/
|
|
10
|
+
export type HeaderAccessoryPadding = "x" | "none";
|
|
@@ -11,7 +11,8 @@ import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearanc
|
|
|
11
11
|
import { SCREEN_HEADER_PORTAL_HOST, useScreen } from "../../../screen/ScreenContext";
|
|
12
12
|
import { Toolbar } from "../../../toolbar";
|
|
13
13
|
import { HeaderDismissButton } from "../../header-buttons";
|
|
14
|
-
import {
|
|
14
|
+
import { HeaderAccessory } from "../../parts/HeaderAccessory";
|
|
15
|
+
import { HeaderSurface } from "../../parts/HeaderSurface";
|
|
15
16
|
import { isCustomTitle, isTextTitle } from "../../utils";
|
|
16
17
|
import type { TitleHeaderProps } from "../TitleHeader";
|
|
17
18
|
|
|
@@ -123,6 +124,8 @@ export function LargeTitleHeader({
|
|
|
123
124
|
trailingActions,
|
|
124
125
|
LeftToolbar,
|
|
125
126
|
RightToolbar,
|
|
127
|
+
accessory,
|
|
128
|
+
accessoryPadding = "x",
|
|
126
129
|
dismissAction = "none",
|
|
127
130
|
navigation,
|
|
128
131
|
onDismiss,
|
|
@@ -215,29 +218,33 @@ export function LargeTitleHeader({
|
|
|
215
218
|
<LargeTitlePortalContent title={title} overline={overline} subtitle={subtitle} />
|
|
216
219
|
</Portal>
|
|
217
220
|
)}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
221
|
+
{/* The fade covers the bar and any accessory as one surface, so content
|
|
222
|
+
scrolling under the header meets a single boundary rather than two. */}
|
|
223
|
+
<View style={styles.barGroup}>
|
|
224
|
+
<HeaderSurface color={gradientColor} animatedStyle={gradientOpacityStyle} />
|
|
225
|
+
<View style={styles.bar}>
|
|
226
|
+
{leading != null && <Toolbar>{leading}</Toolbar>}
|
|
227
|
+
{/* Text can never be the touch target, but custom content may be, so its
|
|
228
|
+
container passes touches through to it instead of swallowing them. */}
|
|
229
|
+
<Animated.View
|
|
230
|
+
style={[styles.titleContainer, compactTitleAnimatedStyle]}
|
|
231
|
+
pointerEvents={isCustomTitle(title) ? "box-none" : "none"}
|
|
232
|
+
>
|
|
233
|
+
{isTextTitle(title) ? (
|
|
234
|
+
<AnimatedText
|
|
235
|
+
style={styles.compactTitle}
|
|
236
|
+
maxFontSizeMultiplier={theme.typography.variants["title-md"].maxFontSizeMultiplier}
|
|
237
|
+
>
|
|
238
|
+
{title}
|
|
239
|
+
</AnimatedText>
|
|
240
|
+
) : (
|
|
241
|
+
title
|
|
242
|
+
)}
|
|
243
|
+
</Animated.View>
|
|
244
|
+
{trailing != null && <Toolbar style={styles.trailing}>{trailing}</Toolbar>}
|
|
245
|
+
</View>
|
|
246
|
+
{/* Pinned, so it stays legible while the large title collapses past it. */}
|
|
247
|
+
{accessory != null && <HeaderAccessory padding={accessoryPadding}>{accessory}</HeaderAccessory>}
|
|
241
248
|
</View>
|
|
242
249
|
</View>
|
|
243
250
|
);
|
|
@@ -251,14 +258,8 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
251
258
|
right: 0,
|
|
252
259
|
zIndex: 1000,
|
|
253
260
|
},
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
position: "absolute",
|
|
257
|
-
top: 0,
|
|
258
|
-
left: 0,
|
|
259
|
-
right: 0,
|
|
260
|
-
bottom: -HEADER_FADE_EXTENT,
|
|
261
|
-
},
|
|
261
|
+
// Anchors the absolutely positioned surface over the bar and the accessory both.
|
|
262
|
+
barGroup: { position: "relative" },
|
|
262
263
|
bar: {
|
|
263
264
|
height: theme.layout.header.height,
|
|
264
265
|
flexDirection: "row",
|
|
@@ -10,7 +10,9 @@ import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearanc
|
|
|
10
10
|
import { ScreenContext } from "../../../screen/ScreenContext";
|
|
11
11
|
import { Toolbar } from "../../../toolbar";
|
|
12
12
|
import { HeaderDismissButton, type HeaderDismissProps } from "../../header-buttons";
|
|
13
|
-
import {
|
|
13
|
+
import { HeaderAccessory } from "../../parts/HeaderAccessory";
|
|
14
|
+
import { HeaderSurface } from "../../parts/HeaderSurface";
|
|
15
|
+
import type { HeaderAccessoryPadding } from "../../types";
|
|
14
16
|
import { isCustomTitle, isTextTitle } from "../../utils";
|
|
15
17
|
|
|
16
18
|
export interface TitleHeaderProps extends HeaderDismissProps {
|
|
@@ -34,6 +36,19 @@ export interface TitleHeaderProps extends HeaderDismissProps {
|
|
|
34
36
|
trailingActions?: ReactNode;
|
|
35
37
|
/** @deprecated Use trailingActions. */
|
|
36
38
|
RightToolbar?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Content pinned directly below the bar — a search field, a scrollable tab
|
|
41
|
+
* strip, a filter row. It stays put while the screen scrolls, and the
|
|
42
|
+
* surrounding `Screen`'s scroll containers reserve its measured height so
|
|
43
|
+
* content starts below it rather than under it.
|
|
44
|
+
*/
|
|
45
|
+
accessory?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Horizontal padding around `accessory`. Use `'none'` for content that should
|
|
48
|
+
* run to the screen edge, such as a horizontally scrolling tab strip.
|
|
49
|
+
* Defaults to `'x'`, matching the bar.
|
|
50
|
+
*/
|
|
51
|
+
accessoryPadding?: HeaderAccessoryPadding;
|
|
37
52
|
/** Title placement within the bar. Defaults to `'center'`. */
|
|
38
53
|
titleAlignment?: "center" | "left";
|
|
39
54
|
/** Color token for the top safe area and fading bar overlay. Defaults to the Screen background. */
|
|
@@ -65,6 +80,8 @@ export function TitleHeader({
|
|
|
65
80
|
trailingActions,
|
|
66
81
|
LeftToolbar,
|
|
67
82
|
RightToolbar,
|
|
83
|
+
accessory,
|
|
84
|
+
accessoryPadding = "x",
|
|
68
85
|
dismissAction = "none",
|
|
69
86
|
navigation,
|
|
70
87
|
onDismiss,
|
|
@@ -107,19 +124,22 @@ export function TitleHeader({
|
|
|
107
124
|
return (
|
|
108
125
|
<View style={styles.container}>
|
|
109
126
|
<View style={styles.safeArea(isTransparent, bgColor, screen !== null)} />
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
{/* The fade covers the bar and any accessory as one surface, so content
|
|
128
|
+
scrolling under the header meets a single boundary rather than two. */}
|
|
129
|
+
<View style={styles.barGroup}>
|
|
130
|
+
<HeaderSurface color={gradientColor} />
|
|
131
|
+
<View style={styles.bar}>
|
|
132
|
+
{leading != null && <Toolbar>{leading}</Toolbar>}
|
|
133
|
+
{/* Text can never be the touch target, but custom content may be, so its
|
|
134
|
+
container passes touches through to it instead of swallowing them. */}
|
|
135
|
+
<View style={styles.titleContainer} pointerEvents={isCustomTitle(title) ? "box-none" : "none"}>
|
|
136
|
+
{isTextTitle(title)
|
|
137
|
+
? String(title).trim().length > 0 && <Text style={styles.title}>{title}</Text>
|
|
138
|
+
: title}
|
|
139
|
+
</View>
|
|
140
|
+
{trailing != null && <Toolbar style={styles.trailing}>{trailing}</Toolbar>}
|
|
121
141
|
</View>
|
|
122
|
-
{
|
|
142
|
+
{accessory != null && <HeaderAccessory padding={accessoryPadding}>{accessory}</HeaderAccessory>}
|
|
123
143
|
</View>
|
|
124
144
|
</View>
|
|
125
145
|
);
|
|
@@ -137,14 +157,8 @@ const styles = StyleSheet.create((theme, runtime) => ({
|
|
|
137
157
|
height: withinScreen ? 0 : runtime.insets.top,
|
|
138
158
|
backgroundColor: isTransparent ? "transparent" : bgColor,
|
|
139
159
|
}),
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
position: "absolute",
|
|
143
|
-
top: 0,
|
|
144
|
-
left: 0,
|
|
145
|
-
right: 0,
|
|
146
|
-
bottom: -HEADER_FADE_EXTENT,
|
|
147
|
-
},
|
|
160
|
+
// Anchors the absolutely positioned surface over the bar and the accessory both.
|
|
161
|
+
barGroup: { position: "relative" },
|
|
148
162
|
bar: {
|
|
149
163
|
height: theme.layout.header.height,
|
|
150
164
|
flexDirection: "row",
|
|
@@ -63,6 +63,21 @@ export interface ScreenContextValue {
|
|
|
63
63
|
margins: ScreenContentPadding;
|
|
64
64
|
/** Whether a `Screen.Header` is present. Used by scroll containers to add top inset. */
|
|
65
65
|
hasHeader: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Measured height (dp) of the pinned accessory a header renders below its bar
|
|
68
|
+
* — a search field, a tab strip. Zero when there is none.
|
|
69
|
+
*
|
|
70
|
+
* The theme header height covers the bar alone, and an accessory is whatever
|
|
71
|
+
* the caller put there, so scroll containers reserve this on top of it. That
|
|
72
|
+
* is why it is measured rather than declared: only the accessory knows how
|
|
73
|
+
* tall it is, and it can change with Dynamic Type or its own content.
|
|
74
|
+
*/
|
|
75
|
+
headerAccessoryHeight: number;
|
|
76
|
+
/**
|
|
77
|
+
* Reports the measured accessory height. Called by the header that owns the
|
|
78
|
+
* accessory; zero releases the reserved space.
|
|
79
|
+
*/
|
|
80
|
+
setHeaderAccessoryHeight: (height: number) => void;
|
|
66
81
|
/** Vertical scroll offset, updated on the UI thread for header animations. */
|
|
67
82
|
scrollY: SharedValue<number>;
|
|
68
83
|
/**
|
|
@@ -126,6 +141,7 @@ export function ScreenScrollProvider({
|
|
|
126
141
|
hasHeader = false,
|
|
127
142
|
}: ScreenScrollProviderProps) {
|
|
128
143
|
const scrollY = useSharedValue(0);
|
|
144
|
+
const [headerAccessoryHeight, setAccessoryHeight] = useState(0);
|
|
129
145
|
const headerCollapseHeight = useSharedValue(0);
|
|
130
146
|
const [headerSnapOffsets, setHeaderSnapOffsets] = useState<number[]>();
|
|
131
147
|
const scrollRef = useAnimatedRef<Animated.ScrollView>();
|
|
@@ -139,6 +155,12 @@ export function ScreenScrollProvider({
|
|
|
139
155
|
[headerCollapseHeight]
|
|
140
156
|
);
|
|
141
157
|
|
|
158
|
+
// Rounded so a subpixel layout measurement cannot loop the content padding
|
|
159
|
+
// against the layout that produced it.
|
|
160
|
+
const setHeaderAccessoryHeight = useCallback((height: number) => {
|
|
161
|
+
setAccessoryHeight(Math.max(0, Math.round(height)));
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
142
164
|
const scrollHandler = useAnimatedScrollHandler({
|
|
143
165
|
onScroll: (event) => {
|
|
144
166
|
scrollY.value = event.contentOffset.y;
|
|
@@ -155,6 +177,8 @@ export function ScreenScrollProvider({
|
|
|
155
177
|
platformInsetsEligible,
|
|
156
178
|
margins: contentPadding,
|
|
157
179
|
hasHeader,
|
|
180
|
+
headerAccessoryHeight,
|
|
181
|
+
setHeaderAccessoryHeight,
|
|
158
182
|
scrollY,
|
|
159
183
|
headerCollapseHeight,
|
|
160
184
|
headerSnapOffsets,
|
|
@@ -170,6 +194,8 @@ export function ScreenScrollProvider({
|
|
|
170
194
|
platformAdjustsInsets,
|
|
171
195
|
platformInsetsEligible,
|
|
172
196
|
hasHeader,
|
|
197
|
+
headerAccessoryHeight,
|
|
198
|
+
setHeaderAccessoryHeight,
|
|
173
199
|
scrollY,
|
|
174
200
|
headerCollapseHeight,
|
|
175
201
|
headerSnapOffsets,
|
|
@@ -174,6 +174,7 @@ const ScreenListBase = forwardRef(function ScreenList(
|
|
|
174
174
|
headerSnapOffsets,
|
|
175
175
|
contentPadding: screenPadding,
|
|
176
176
|
hasHeader,
|
|
177
|
+
headerAccessoryHeight,
|
|
177
178
|
contentOwnsBottomInset,
|
|
178
179
|
platformAdjustsInsets,
|
|
179
180
|
} = useScreen();
|
|
@@ -224,7 +225,7 @@ const ScreenListBase = forwardRef(function ScreenList(
|
|
|
224
225
|
automaticallyAdjustContentInsets={false}
|
|
225
226
|
automaticallyAdjustsScrollIndicatorInsets={false}
|
|
226
227
|
contentContainerStyle={[
|
|
227
|
-
styles.content(resolvedPadding, hasHeader, bottomInset, extraBottomPadding),
|
|
228
|
+
styles.content(resolvedPadding, hasHeader, bottomInset, extraBottomPadding, headerAccessoryHeight),
|
|
228
229
|
contentContainerStyle,
|
|
229
230
|
]}
|
|
230
231
|
ListHeaderComponent={header}
|
|
@@ -265,12 +266,16 @@ const styles = StyleSheet.create((theme, runtime) => ({
|
|
|
265
266
|
padding: ScreenContentPadding,
|
|
266
267
|
hasHeader: boolean,
|
|
267
268
|
bottomInset: number | undefined,
|
|
268
|
-
extraBottomPadding: number
|
|
269
|
+
extraBottomPadding: number,
|
|
270
|
+
accessoryHeight: number
|
|
269
271
|
) => {
|
|
270
272
|
const { x, y } = resolveScreenPadding(theme, runtime.breakpoint);
|
|
271
273
|
return {
|
|
272
274
|
flexGrow: 1,
|
|
273
|
-
|
|
275
|
+
// The theme height covers the bar; an accessory pinned under it is extra.
|
|
276
|
+
paddingTop:
|
|
277
|
+
(hasHeader ? theme.layout.header.height + accessoryHeight : 0) +
|
|
278
|
+
(hasVerticalPadding(padding) ? y : 0),
|
|
274
279
|
paddingHorizontal: hasHorizontalPadding(padding) ? x : 0,
|
|
275
280
|
paddingBottom: (bottomInset ?? (hasVerticalPadding(padding) ? y : 0)) + extraBottomPadding,
|
|
276
281
|
};
|
|
@@ -71,6 +71,7 @@ export const ScreenScrollView = forwardRef<ComponentRef<typeof ScrollView>, Scre
|
|
|
71
71
|
scrollHandler,
|
|
72
72
|
contentPadding: screenPadding,
|
|
73
73
|
hasHeader,
|
|
74
|
+
headerAccessoryHeight,
|
|
74
75
|
background,
|
|
75
76
|
contentOwnsBottomInset,
|
|
76
77
|
platformAdjustsInsets,
|
|
@@ -105,7 +106,7 @@ export const ScreenScrollView = forwardRef<ComponentRef<typeof ScrollView>, Scre
|
|
|
105
106
|
showsVerticalScrollIndicator={false}
|
|
106
107
|
scrollEventThrottle={16}
|
|
107
108
|
contentContainerStyle={[
|
|
108
|
-
styles.scrollContent(hasHeader, resolvedPadding, extraBottomPadding),
|
|
109
|
+
styles.scrollContent(hasHeader, resolvedPadding, extraBottomPadding, headerAccessoryHeight),
|
|
109
110
|
contentContainerStyle,
|
|
110
111
|
]}
|
|
111
112
|
{...rest}
|
|
@@ -124,11 +125,19 @@ export const ScreenScrollView = forwardRef<ComponentRef<typeof ScrollView>, Scre
|
|
|
124
125
|
ScreenScrollView.displayName = "Screen.ScrollView";
|
|
125
126
|
|
|
126
127
|
const styles = StyleSheet.create((theme, runtime) => ({
|
|
127
|
-
scrollContent: (
|
|
128
|
+
scrollContent: (
|
|
129
|
+
hasHeader: boolean,
|
|
130
|
+
padding: ScreenContentPadding,
|
|
131
|
+
extraBottomPadding: number,
|
|
132
|
+
accessoryHeight: number
|
|
133
|
+
) => {
|
|
128
134
|
const { x, y } = resolveScreenPadding(theme, runtime.breakpoint);
|
|
129
135
|
return {
|
|
130
136
|
flexGrow: 1,
|
|
131
|
-
|
|
137
|
+
// The theme height covers the bar; an accessory pinned under it is extra.
|
|
138
|
+
paddingTop:
|
|
139
|
+
(hasHeader ? theme.layout.header.height + accessoryHeight : 0) +
|
|
140
|
+
(hasVerticalPadding(padding) ? y : 0),
|
|
132
141
|
paddingHorizontal: hasHorizontalPadding(padding) ? x : 0,
|
|
133
142
|
paddingBottom: (hasVerticalPadding(padding) ? y : 0) + extraBottomPadding,
|
|
134
143
|
};
|