Skip to content

Commit 2cf7289

Browse files
committed
refactor: updated footer api
1 parent 18a32e5 commit 2cf7289

17 files changed

Lines changed: 329 additions & 181 deletions

File tree

example/src/components/contactList/ContactList.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useCallback } from 'react';
1+
import React, { useMemo, useCallback, ComponentProps } from 'react';
22
import { StyleSheet, Text, Platform, View, ViewStyle } from 'react-native';
33
import { useSafeAreaInsets } from 'react-native-safe-area-context';
44
import { useFocusEffect } from '@react-navigation/native';
@@ -15,7 +15,11 @@ import {
1515
} from '../../utilities';
1616
import ContactItem from '../contactItem';
1717

18-
export interface ContactListProps {
18+
export interface ContactListProps
19+
extends Pick<
20+
ComponentProps<typeof BottomSheetFlatList>,
21+
'enableFooterMarginAdjustment'
22+
> {
1923
type: 'FlatList' | 'SectionList' | 'ScrollView' | 'View' | 'VirtualizedList';
2024
count?: number;
2125
style?: ViewStyle;
@@ -99,6 +103,7 @@ const ContactList = ({
99103
if (type === 'FlatList') {
100104
return (
101105
<BottomSheetFlatList
106+
{...rest}
102107
data={data}
103108
refreshing={false}
104109
onRefresh={onRefresh}
@@ -117,6 +122,7 @@ const ContactList = ({
117122
} else if (type === 'VirtualizedList') {
118123
return (
119124
<BottomSheetVirtualizedList
125+
{...rest}
120126
data={data}
121127
keyExtractor={keyExtractor}
122128
initialNumToRender={5}
@@ -135,6 +141,7 @@ const ContactList = ({
135141
} else if (type === 'ScrollView') {
136142
return (
137143
<BottomSheetScrollView
144+
{...rest}
138145
style={styles.container}
139146
contentContainerStyle={contentContainerStyle}
140147
bounces={true}
@@ -146,6 +153,7 @@ const ContactList = ({
146153
} else if (type === 'SectionList') {
147154
return (
148155
<BottomSheetSectionList
156+
{...rest}
149157
style={styles.container}
150158
contentContainerStyle={contentContainerStyle}
151159
stickySectionHeadersEnabled
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React, { useCallback, useMemo } from 'react';
2+
import { StyleSheet } from 'react-native';
3+
import {
4+
BottomSheetFooter,
5+
BottomSheetFooterProps,
6+
useBottomSheet,
7+
} from '@gorhom/bottom-sheet';
8+
import { RectButton } from 'react-native-gesture-handler';
9+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
10+
import Animated, {
11+
Extrapolate,
12+
interpolate,
13+
useAnimatedStyle,
14+
} from 'react-native-reanimated';
15+
import { toRad } from 'react-native-redash';
16+
17+
const AnimatedRectButton = Animated.createAnimatedComponent(RectButton);
18+
19+
interface CustomFooterProps extends BottomSheetFooterProps {}
20+
21+
const CustomFooter = ({ animatedFooterPosition }: CustomFooterProps) => {
22+
//#region hooks
23+
const { bottom: bottomSafeArea } = useSafeAreaInsets();
24+
const { expand, collapse, animatedIndex } = useBottomSheet();
25+
//#endregion
26+
27+
//#region styles
28+
const arrowAnimatedStyle = useAnimatedStyle(() => {
29+
const arrowRotate = interpolate(
30+
animatedIndex.value,
31+
[0, 1],
32+
[toRad(0), toRad(-180)],
33+
Extrapolate.CLAMP
34+
);
35+
return {
36+
transform: [{ rotate: `${arrowRotate}rad` }],
37+
};
38+
}, []);
39+
const arrowStyle = useMemo(
40+
() => [arrowAnimatedStyle, styles.arrow],
41+
[arrowAnimatedStyle]
42+
);
43+
const containerAnimatedStyle = useAnimatedStyle(
44+
() => ({
45+
opacity: interpolate(
46+
animatedIndex.value,
47+
[-0.85, 0],
48+
[0, 1],
49+
Extrapolate.CLAMP
50+
),
51+
}),
52+
[animatedIndex]
53+
);
54+
const containerStyle = useMemo(
55+
() => [containerAnimatedStyle, styles.container],
56+
[containerAnimatedStyle]
57+
);
58+
//#endregion
59+
60+
const handleArrowPress = useCallback(() => {
61+
if (animatedIndex.value === 0) {
62+
expand();
63+
} else {
64+
collapse();
65+
}
66+
}, [expand, collapse, animatedIndex]);
67+
68+
return (
69+
<BottomSheetFooter
70+
bottomInset={bottomSafeArea}
71+
animatedFooterPosition={animatedFooterPosition}
72+
>
73+
<AnimatedRectButton style={containerStyle} onPress={handleArrowPress}>
74+
<Animated.Text style={arrowStyle}></Animated.Text>
75+
</AnimatedRectButton>
76+
</BottomSheetFooter>
77+
);
78+
};
79+
80+
const styles = StyleSheet.create({
81+
container: {
82+
alignSelf: 'flex-end',
83+
justifyContent: 'center',
84+
alignItems: 'center',
85+
marginHorizontal: 24,
86+
marginBottom: 12,
87+
width: 50,
88+
height: 50,
89+
borderRadius: 25,
90+
backgroundColor: '#80f',
91+
shadowOffset: {
92+
width: 0,
93+
height: 12,
94+
},
95+
shadowOpacity: 0.25,
96+
shadowRadius: 8.0,
97+
98+
elevation: 8,
99+
},
100+
arrow: {
101+
fontSize: 20,
102+
height: 20,
103+
textAlignVertical: 'center',
104+
fontWeight: '900',
105+
color: '#fff',
106+
},
107+
});
108+
109+
export default CustomFooter;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CustomFooter';

example/src/screens/advanced/FooterExample.tsx

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,19 @@
1-
import React, { useCallback, useMemo, useRef, useState } from 'react';
2-
import { View, StyleSheet, Text } from 'react-native';
3-
import BottomSheet, { BottomSheetFooter } from '@gorhom/bottom-sheet';
4-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
5-
import SearchHandle from '../../components/searchHandle';
1+
import React, { useCallback, useMemo, useRef } from 'react';
2+
import { View, StyleSheet } from 'react-native';
3+
import BottomSheet from '@gorhom/bottom-sheet';
64
import Button from '../../components/button';
75
import ContactList from '../../components/contactList';
6+
import customFooter from '../../components/customFooter';
7+
import searchHandle from '../../components/searchHandle';
88

99
const FooterExample = () => {
10-
// state
11-
const [fadeBehavior, setFadeBehavior] = useState<'none' | 'fade'>('none');
12-
const [slideBehavior, setSlideBehavior] = useState<'none' | 'slide'>('none');
13-
const [scaleBehavior, setScaleBehavior] = useState<'none' | 'scale'>('none');
14-
1510
// hooks
1611
const bottomSheetRef = useRef<BottomSheet>(null);
17-
const { bottom: bottomSafeArea } = useSafeAreaInsets();
1812

1913
// variables
20-
const snapPoints = useMemo(() => [80, 250], []);
21-
const appearanceBehavior = useMemo(
22-
() => [fadeBehavior, slideBehavior, scaleBehavior],
23-
[fadeBehavior, slideBehavior, scaleBehavior]
24-
);
14+
const snapPoints = useMemo(() => ['25%', '50%'], []);
2515

2616
// callbacks
27-
const handleFadeBehavior = useCallback(() => {
28-
setFadeBehavior(state => (state === 'none' ? 'fade' : 'none'));
29-
}, []);
30-
const handleScaleBehavior = useCallback(() => {
31-
setScaleBehavior(state => (state === 'none' ? 'scale' : 'none'));
32-
}, []);
33-
const handleSlideBehavior = useCallback(() => {
34-
setSlideBehavior(state => (state === 'none' ? 'slide' : 'none'));
35-
}, []);
3617
const handleExpandPress = useCallback(() => {
3718
bottomSheetRef.current?.expand();
3819
}, []);
@@ -46,18 +27,6 @@ const FooterExample = () => {
4627
// renders
4728
return (
4829
<View style={styles.container}>
49-
<Button
50-
label={`Toggle Fade Behavior: ${fadeBehavior}`}
51-
onPress={handleFadeBehavior}
52-
/>
53-
<Button
54-
label={`Toggle Scale Behavior: ${scaleBehavior}`}
55-
onPress={handleScaleBehavior}
56-
/>
57-
<Button
58-
label={`Toggle Slide Behavior: ${slideBehavior}`}
59-
onPress={handleSlideBehavior}
60-
/>
6130
<Button label="Expand" onPress={handleExpandPress} />
6231
<Button label="Collapse" onPress={handleCollapsePress} />
6332
<Button label="Close" onPress={handleClosePress} />
@@ -66,17 +35,15 @@ const FooterExample = () => {
6635
snapPoints={snapPoints}
6736
keyboardBehavior="interactive"
6837
keyboardBlurBehavior="restore"
69-
handleComponent={SearchHandle}
38+
enablePanDownToClose={true}
39+
handleComponent={searchHandle}
40+
footerComponent={customFooter}
7041
>
71-
<ContactList count={10} type="FlatList" />
72-
<BottomSheetFooter
73-
bottomInset={bottomSafeArea}
74-
appearanceBehavior={appearanceBehavior}
75-
>
76-
<View style={styles.footer}>
77-
<Text style={styles.footerText}>this is a footer!</Text>
78-
</View>
79-
</BottomSheetFooter>
42+
<ContactList
43+
count={10}
44+
type="FlatList"
45+
enableFooterMarginAdjustment={false}
46+
/>
8047
</BottomSheet>
8148
</View>
8249
);
@@ -88,13 +55,22 @@ const styles = StyleSheet.create({
8855
padding: 24,
8956
},
9057
footer: {
58+
alignSelf: 'flex-end',
9159
justifyContent: 'center',
9260
alignItems: 'center',
93-
marginHorizontal: 12,
94-
padding: 12,
95-
marginBottom: 12,
96-
borderRadius: 24,
61+
marginHorizontal: 24,
62+
width: 50,
63+
height: 50,
64+
borderRadius: 25,
9765
backgroundColor: '#80f',
66+
shadowOffset: {
67+
width: 0,
68+
height: 12,
69+
},
70+
shadowOpacity: 0.25,
71+
shadowRadius: 8.0,
72+
73+
elevation: 24,
9874
},
9975
footerText: {
10076
fontSize: 16,

example/src/screens/modal/DetachedExample.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ const DetachedExample = () => {
5656
),
5757
[]
5858
);
59+
const renderFooter = useCallback(
60+
props => (
61+
<BottomSheetFooter {...props}>
62+
<View style={styles.footer}>
63+
<Text style={styles.footerText}>this is a footer!</Text>
64+
</View>
65+
</BottomSheetFooter>
66+
),
67+
[]
68+
);
5969
return (
6070
<View style={styles.container}>
6171
<Button label="Present" onPress={handlePresentPress} />
@@ -70,6 +80,7 @@ const DetachedExample = () => {
7080
enablePanDownToClose={true}
7181
style={styles.sheetContainer}
7282
backgroundComponent={null}
83+
footerComponent={renderFooter}
7384
handleComponent={renderHeaderHandle}
7485
detached={true}
7586
>
@@ -78,11 +89,6 @@ const DetachedExample = () => {
7889
onLayout={handleContentLayout}
7990
>
8091
{data.map(renderItem)}
81-
<BottomSheetFooter>
82-
<View style={styles.footer}>
83-
<Text style={styles.footerText}>this is a footer!</Text>
84-
</View>
85-
</BottomSheetFooter>
8692
</BottomSheetView>
8793
</BottomSheetModal>
8894
</View>

src/components/bottomSheet/BottomSheet.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import BottomSheetGestureHandlersProvider from '../bottomSheetGestureHandlersPro
3737
import BottomSheetBackdropContainer from '../bottomSheetBackdropContainer';
3838
import BottomSheetHandleContainer from '../bottomSheetHandleContainer';
3939
import BottomSheetBackgroundContainer from '../bottomSheetBackgroundContainer';
40+
import BottomSheetFooterContainer from '../bottomSheetFooterContainer/BottomSheetFooterContainer';
4041
import BottomSheetDraggableView from '../bottomSheetDraggableView';
4142
// import BottomSheetDebugView from '../bottomSheetDebugView';
4243
import {
@@ -143,6 +144,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
143144
handleComponent,
144145
backdropComponent,
145146
backgroundComponent,
147+
footerComponent,
146148
children,
147149
} = props;
148150
//#endregion
@@ -1462,6 +1464,12 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
14621464
{typeof children === 'function'
14631465
? (children as Function)()
14641466
: children}
1467+
1468+
{footerComponent && (
1469+
<BottomSheetFooterContainer
1470+
footerComponent={footerComponent}
1471+
/>
1472+
)}
14651473
</BottomSheetDraggableView>
14661474
</Animated.View>
14671475
<BottomSheetHandleContainer

src/components/bottomSheet/types.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type { PanGestureHandlerProps } from 'react-native-gesture-handler';
55
import type { BottomSheetHandleProps } from '../bottomSheetHandle';
66
import type { BottomSheetBackdropProps } from '../bottomSheetBackdrop';
77
import type { BottomSheetBackgroundProps } from '../bottomSheetBackground';
8+
import type { BottomSheetFooterProps } from '../bottomSheetFooter';
89
import type {
10+
ANIMATION_SOURCE,
911
KEYBOARD_BEHAVIOR,
1012
KEYBOARD_BLUR_BEHAVIOR,
1113
KEYBOARD_INPUT_MODE,
@@ -231,6 +233,12 @@ export interface BottomSheetProps
231233
* @type React.FC\<BottomSheetBackgroundProps\>
232234
*/
233235
backgroundComponent?: React.FC<BottomSheetBackgroundProps> | null;
236+
/**
237+
* Component to be placed as a footer.
238+
* @see {BottomSheetFooterProps}
239+
* @type React.FC\<BottomSheetFooterProps\>
240+
*/
241+
footerComponent?: React.FC<BottomSheetFooterProps>;
234242
/**
235243
* A scrollable node or normal view.
236244
* @type React.ReactNode[] | React.ReactNode

0 commit comments

Comments
 (0)