1
1
import React , {
2
+ ComponentProps ,
3
+ ReactNode ,
2
4
useCallback ,
3
5
useEffect ,
4
6
useMemo ,
@@ -13,29 +15,51 @@ import {
13
15
ScrollViewProps ,
14
16
StyleSheet
15
17
} from "react-native" ;
16
- import { ScaleInOutAnimation } from "../common/ScaleInOutAnimation" ;
17
18
import { IOSpringValues , IOVisualCostants } from "../../core" ;
18
19
import { IconButtonSolid } from "../buttons" ;
20
+ import { ScaleInOutAnimation } from "../common/ScaleInOutAnimation" ;
21
+ import { FooterActions } from "./FooterActions" ;
22
+ import { useFooterActionsInlineMeasurements } from "./hooks" ;
19
23
20
- type ForceScrollDownViewProps = {
24
+ type ForceScrollDownViewActions = {
21
25
/**
22
- * The content to display inside the scroll view .
26
+ * The distance from the bottom is computed automatically based on the actions .
23
27
*/
24
- children : React . ReactNode ;
28
+ threshold ?: never ;
29
+ footerActions : Omit <
30
+ ComponentProps < typeof FooterActions > ,
31
+ "fixed" | "onMeasure"
32
+ > ;
33
+ } ;
34
+
35
+ type ForceScrollDownViewCustomSlot = {
25
36
/**
26
37
* The distance from the bottom of the scrollable content at which the "scroll to bottom" button
27
- * should become hidden. Defaults to 100.
38
+ * should become hidden.
28
39
*/
29
- threshold ?: number ;
40
+ threshold : number ;
41
+ footerActions ?: never ;
42
+ } ;
43
+
44
+ type ForceScrollDownViewSlot =
45
+ | ForceScrollDownViewActions
46
+ | ForceScrollDownViewCustomSlot ;
47
+
48
+ export type ForceScrollDownView = {
49
+ /**
50
+ * The content to display inside the scroll view.
51
+ */
52
+ children : ReactNode ;
30
53
/**
31
54
* A callback that will be called whenever the scroll view crosses the threshold. The callback
32
55
* is passed a boolean indicating whether the threshold has been crossed (`true`) or not (`false`).
33
56
*/
34
57
onThresholdCrossed ?: ( crossed : boolean ) => void ;
35
- } & Pick <
36
- ScrollViewProps ,
37
- "style" | "contentContainerStyle" | "scrollEnabled" | "testID"
38
- > ;
58
+ } & ForceScrollDownViewSlot &
59
+ Pick <
60
+ ScrollViewProps ,
61
+ "style" | "contentContainerStyle" | "scrollEnabled" | "testID"
62
+ > ;
39
63
40
64
/**
41
65
* A React Native component that displays a scroll view with a button that scrolls to the bottom of the content
@@ -44,26 +68,36 @@ type ForceScrollDownViewProps = {
44
68
* `scrollEnabled` prop to `false`.
45
69
*/
46
70
const ForceScrollDownView = ( {
71
+ footerActions,
47
72
children,
48
- threshold = 100 ,
73
+ threshold : customThreshold ,
49
74
style,
50
75
contentContainerStyle,
51
76
scrollEnabled = true ,
52
77
onThresholdCrossed
53
- } : ForceScrollDownViewProps ) => {
78
+ } : ForceScrollDownView ) => {
54
79
const scrollViewRef = useRef < ScrollView > ( null ) ;
55
80
81
+ const {
82
+ footerActionsInlineMeasurements,
83
+ handleFooterActionsInlineMeasurements
84
+ } = useFooterActionsInlineMeasurements ( ) ;
85
+
86
+ const threshold = footerActions
87
+ ? footerActionsInlineMeasurements . safeBottomAreaHeight
88
+ : customThreshold ;
89
+
56
90
/**
57
91
* The height of the scroll view, used to determine whether or not the scrollable content fits inside
58
92
* the scroll view and whether the "scroll to bottom" button should be displayed.
59
93
*/
60
- const [ scrollViewHeight , setScrollViewHeight ] = useState < number > ( ) ;
94
+ const [ scrollViewHeight , setScrollViewHeight ] = useState < number > ( 0 ) ;
61
95
62
96
/**
63
97
* The height of the scrollable content, used to determine whether or not the "scroll to bottom" button
64
98
* should be displayed.
65
99
*/
66
- const [ contentHeight , setContentHeight ] = useState < number > ( ) ;
100
+ const [ contentHeight , setContentHeight ] = useState < number > ( 0 ) ;
67
101
68
102
/**
69
103
* Whether or not the scroll view has crossed the threshold from the bottom.
@@ -79,7 +113,7 @@ const ForceScrollDownView = ({
79
113
/**
80
114
* A callback that is called whenever the scroll view is scrolled. It checks whether or not the
81
115
* scroll view has crossed the threshold from the bottom and updates the state accordingly.
82
- * The callback is designed to updatr button visibility only when crossing the threshold.
116
+ * The callback is designed to update button visibility only when crossing the threshold.
83
117
*/
84
118
const handleScroll = useCallback (
85
119
( event : NativeSyntheticEvent < NativeScrollEvent > ) => {
@@ -88,19 +122,14 @@ const ForceScrollDownView = ({
88
122
89
123
const thresholdCrossed =
90
124
layoutMeasurement . height + contentOffset . y >=
91
- contentSize . height - threshold ;
92
-
93
- setThresholdCrossed ( previousState => {
94
- if ( ! previousState && thresholdCrossed ) {
95
- setButtonVisible ( false ) ;
96
- }
97
- if ( previousState && ! thresholdCrossed ) {
98
- setButtonVisible ( true ) ;
99
- }
100
- return thresholdCrossed ;
101
- } ) ;
125
+ contentSize . height - ( threshold ?? 0 ) ;
126
+
127
+ if ( isThresholdCrossed !== thresholdCrossed ) {
128
+ setThresholdCrossed ( thresholdCrossed ) ;
129
+ setButtonVisible ( ! thresholdCrossed ) ;
130
+ }
102
131
} ,
103
- [ threshold ]
132
+ [ threshold , isThresholdCrossed ]
104
133
) ;
105
134
106
135
/**
@@ -145,8 +174,8 @@ const ForceScrollDownView = ({
145
174
*/
146
175
const needsScroll = useMemo (
147
176
( ) =>
148
- scrollViewHeight != null &&
149
- contentHeight != null &&
177
+ scrollViewHeight > 0 &&
178
+ contentHeight > 0 &&
150
179
scrollViewHeight < contentHeight ,
151
180
[ scrollViewHeight , contentHeight ]
152
181
) ;
@@ -182,16 +211,22 @@ const ForceScrollDownView = ({
182
211
< ScrollView
183
212
testID = { "ScrollView" }
184
213
ref = { scrollViewRef }
185
- scrollIndicatorInsets = { { right : 1 } }
186
214
scrollEnabled = { scrollEnabled }
187
- onScroll = { handleScroll }
188
- scrollEventThrottle = { 400 }
189
215
style = { style }
216
+ onScroll = { handleScroll }
217
+ scrollEventThrottle = { 8 }
190
218
onLayout = { handleLayout }
191
219
onContentSizeChange = { handleContentSizeChange }
192
220
contentContainerStyle = { contentContainerStyle }
193
221
>
194
222
{ children }
223
+ { footerActions && (
224
+ < FooterActions
225
+ { ...footerActions }
226
+ onMeasure = { handleFooterActionsInlineMeasurements }
227
+ fixed = { false }
228
+ />
229
+ ) }
195
230
</ ScrollView >
196
231
{ scrollDownButton }
197
232
</ >
0 commit comments