Chore: Migrate ActionSheets to `react-native-bottom-sheet` and make them dynamic (#4193)

* Install react-native-bottom-sheet

* Migrate ActionSheets to react-native-bottom-sheet

* Remove unnecessary props

* Minor fixes

* Enable OverDrag

* Fix position in landscape mode

* Prefix interface with I

* Remove react-native-scroll-bottom-sheet

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Danish Ahmed Mirza 2022-05-19 22:23:45 +05:30 committed by GitHub
parent 9d514690f0
commit f5625cd5f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 153 deletions

View File

@ -1,32 +1,21 @@
import { useBackHandler } from '@react-native-community/hooks'; import { useBackHandler } from '@react-native-community/hooks';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react';
import { Keyboard, Text } from 'react-native'; import { Keyboard } from 'react-native';
import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler'; import { Easing } from 'react-native-reanimated';
import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet';
import { themes } from '../../lib/constants';
import { useDimensions, useOrientation } from '../../dimensions'; import { useDimensions, useOrientation } from '../../dimensions';
import I18n from '../../i18n';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as List from '../List';
import { Button } from './Button';
import { Handle } from './Handle'; import { Handle } from './Handle';
import { IActionSheetItem, Item } from './Item'; import { TActionSheetOptions } from './Provider';
import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider'; import BottomSheetContent from './BottomSheetContent';
import styles, { ITEM_HEIGHT } from './styles'; import styles, { ITEM_HEIGHT } from './styles';
const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index
});
const HANDLE_HEIGHT = isIOS ? 40 : 56; const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MAX_SNAP_HEIGHT = 16; const MIN_SNAP_HEIGHT = 16;
const CANCEL_HEIGHT = 64; const CANCEL_HEIGHT = 64;
const ANIMATION_DURATION = 250; const ANIMATION_DURATION = 250;
@ -39,27 +28,26 @@ const ANIMATION_CONFIG = {
const ActionSheet = React.memo( const ActionSheet = React.memo(
forwardRef(({ children }: { children: React.ReactElement }, ref) => { forwardRef(({ children }: { children: React.ReactElement }, ref) => {
const { theme } = useTheme(); const { colors } = useTheme();
const bottomSheetRef = useRef<ScrollBottomSheet<TActionSheetOptionsItem>>(null); const bottomSheetRef = useRef<BottomSheet>(null);
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions); const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const { height } = useDimensions(); const { height } = useDimensions();
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const maxSnap = Math.max( const maxSnap = Math.min(
height -
// Items height // Items height
ITEM_HEIGHT * (data?.options?.length || 0) - ITEM_HEIGHT * (data?.options?.length || 0) +
// Handle height // Handle height
HANDLE_HEIGHT - HANDLE_HEIGHT +
// Custom header height // Custom header height
(data?.headerHeight || 0) - (data?.headerHeight || 0) +
// Insets bottom height (Notch devices) // Insets bottom height (Notch devices)
insets.bottom - insets.bottom +
// Cancel button height // Cancel button height
(data?.hasCancel ? CANCEL_HEIGHT : 0), (data?.hasCancel ? CANCEL_HEIGHT : 0),
MAX_SNAP_HEIGHT height - MIN_SNAP_HEIGHT
); );
/* /*
@ -69,14 +57,13 @@ const ActionSheet = React.memo(
* we'll provide more one snap * we'll provide more one snap
* that point 50% of the whole screen * that point 50% of the whole screen
*/ */
const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height]; const snaps = maxSnap > height * 0.6 && !isLandscape && !data.snaps ? [height * 0.5, maxSnap] : [maxSnap];
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
const closedSnapIndex = snaps.length - 1;
const toggleVisible = () => setVisible(!isVisible); const toggleVisible = () => setVisible(!isVisible);
const hide = () => { const hide = () => {
bottomSheetRef.current?.snapTo(closedSnapIndex); bottomSheetRef.current?.close();
toggleVisible();
}; };
const show = (options: TActionSheetOptions) => { const show = (options: TActionSheetOptions) => {
@ -84,12 +71,6 @@ const ActionSheet = React.memo(
toggleVisible(); toggleVisible();
}; };
const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
if (nativeEvent.oldState === State.ACTIVE) {
hide();
}
};
useBackHandler(() => { useBackHandler(() => {
if (isVisible) { if (isVisible) {
hide(); hide();
@ -101,7 +82,6 @@ const ActionSheet = React.memo(
if (isVisible) { if (isVisible) {
Keyboard.dismiss(); Keyboard.dismiss();
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
bottomSheetRef.current?.snapTo(openedSnapIndex);
} }
}, [isVisible]); }, [isVisible]);
@ -122,26 +102,18 @@ const ActionSheet = React.memo(
</> </>
); );
const renderFooter = () => const renderBackdrop = useCallback(
data?.hasCancel ? ( props => (
<Button <BottomSheetBackdrop
onPress={hide} {...props}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]} appearsOnIndex={0}
// TODO: Remove when migrate Touch // Backdrop should be visible all the time bottom sheet is open
theme={theme} disappearsOnIndex={-1}
accessibilityLabel={I18n.t('Cancel')}> opacity={colors.backdropOpacity}
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text> />
</Button> ),
) : null; []
);
const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
const animatedPosition = React.useRef(new Value(0));
const opacity = interpolateNode(animatedPosition.current, {
inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP
}) as any; // The function's return differs from the expected type of opacity, however this problem is something related to lib, maybe when updating the types will be fixed.
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {}; const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
@ -149,42 +121,19 @@ const ActionSheet = React.memo(
<> <>
{children} {children}
{isVisible && ( {isVisible && (
<> <BottomSheet
<TapGestureHandler onHandlerStateChange={onBackdropPressed}>
<Animated.View
testID='action-sheet-backdrop'
style={[
styles.backdrop,
{
backgroundColor: themes[theme].backdropColor,
opacity
}
]}
/>
</TapGestureHandler>
<ScrollBottomSheet<TActionSheetOptionsItem>
testID='action-sheet'
ref={bottomSheetRef} ref={bottomSheetRef}
componentType='FlatList' snapPoints={data?.snaps ? data.snaps : snaps}
snapPoints={snaps} animationConfigs={ANIMATION_CONFIG}
initialSnapIndex={closedSnapIndex} animateOnMount={true}
renderHandle={renderHandle} backdropComponent={renderBackdrop}
onSettle={index => index === closedSnapIndex && toggleVisible()} handleComponent={renderHandle}
animatedPosition={animatedPosition.current} enablePanDownToClose
containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }} style={{ ...styles.container, ...bottomSheet }}
animationConfig={ANIMATION_CONFIG} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
data={data.options} onChange={index => index === -1 && toggleVisible()}>
renderItem={renderItem} <BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
keyExtractor={item => item.title} </BottomSheet>
style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator}
ListFooterComponent={renderFooter}
getItemLayout={getItemLayout}
removeClippedSubviews={isIOS}
/>
</>
)} )}
</> </>
); );

View File

@ -0,0 +1,58 @@
import { Text } from 'react-native';
import React from 'react';
import { BottomSheetView, BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { Button } from './Button';
import I18n from '../../i18n';
import { useTheme } from '../../theme';
import { IActionSheetItem, Item } from './Item';
import { TActionSheetOptionsItem } from './Provider';
import styles from './styles';
import * as List from '../List';
interface IBottomSheetContentProps {
hasCancel?: boolean;
options?: TActionSheetOptionsItem[];
hide: () => void;
children?: React.ReactElement | null;
}
const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: IBottomSheetContentProps) => {
const { theme, colors } = useTheme();
const renderFooter = () =>
hasCancel ? (
<Button
onPress={hide}
style={[styles.button, { backgroundColor: colors.auxiliaryBackground }]}
// TODO: Remove when migrate Touch
theme={theme}
accessibilityLabel={I18n.t('Cancel')}>
<Text style={[styles.text, { color: colors.bodyText }]}>{I18n.t('Cancel')}</Text>
</Button>
) : null;
const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
if (options) {
return (
<BottomSheetFlatList
data={options}
refreshing={false}
keyExtractor={item => item.title}
bounces={true}
renderItem={renderItem}
style={{ backgroundColor: colors.focusedBackground }}
keyboardDismissMode='interactive'
indicatorStyle='black'
contentContainerStyle={styles.content}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator}
ListFooterComponent={renderFooter}
/>
);
}
return <BottomSheetView>{children}</BottomSheetView>;
});
export default BottomSheetContent;

View File

@ -13,10 +13,13 @@ export type TActionSheetOptionsItem = {
}; };
export type TActionSheetOptions = { export type TActionSheetOptions = {
options: TActionSheetOptionsItem[]; options?: TActionSheetOptionsItem[];
headerHeight?: number; headerHeight?: number;
customHeader?: React.ReactElement | null; customHeader?: React.ReactElement | null;
hasCancel?: boolean; hasCancel?: boolean;
type?: string;
children?: React.ReactElement | null;
snaps?: string[] | number[];
}; };
interface IActionSheetProvider { interface IActionSheetProvider {
showActionSheet: (item: TActionSheetOptions) => void; showActionSheet: (item: TActionSheetOptions) => void;

View File

@ -46,8 +46,7 @@ export default StyleSheet.create({
}, },
bottomSheet: { bottomSheet: {
width: '50%', width: '50%',
alignSelf: 'center', marginHorizontal: '25%'
left: '25%'
}, },
button: { button: {
marginHorizontal: 16, marginHorizontal: 16,

View File

@ -30,6 +30,7 @@
"dependencies": { "dependencies": {
"@bugsnag/react-native": "^7.10.5", "@bugsnag/react-native": "^7.10.5",
"@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1",
"@gorhom/bottom-sheet": "^4",
"@nozbe/watermelondb": "0.23.0", "@nozbe/watermelondb": "0.23.0",
"@react-native-clipboard/clipboard": "^1.8.5", "@react-native-clipboard/clipboard": "^1.8.5",
"@react-native-community/art": "^1.2.0", "@react-native-community/art": "^1.2.0",
@ -111,7 +112,6 @@
"react-native-restart": "0.0.22", "react-native-restart": "0.0.22",
"react-native-safe-area-context": "3.2.0", "react-native-safe-area-context": "3.2.0",
"react-native-screens": "2.9.0", "react-native-screens": "2.9.0",
"react-native-scroll-bottom-sheet": "0.6.2",
"react-native-scrollable-tab-view": "^1.0.0", "react-native-scrollable-tab-view": "^1.0.0",
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0", "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0",
"react-native-slowlog": "^1.0.2", "react-native-slowlog": "^1.0.2",

View File

@ -1,42 +0,0 @@
diff --git a/node_modules/react-native-scroll-bottom-sheet/src/index.tsx b/node_modules/react-native-scroll-bottom-sheet/src/index.tsx
index c4dbcce..b545c03 100644
--- a/node_modules/react-native-scroll-bottom-sheet/src/index.tsx
+++ b/node_modules/react-native-scroll-bottom-sheet/src/index.tsx
@@ -518,6 +518,28 @@ export class ScrollBottomSheet<T extends any> extends Component<Props<T>> {
clockRunning(this.animationClock)
),
[
+ this.didScrollUpAndPullDown,
+ this.setTranslationY,
+ set(this.tempDestSnapPoint, add(snapPoints[0], this.extraOffset)),
+ cond(not(this.isManuallySetValue), set(this.nextSnapIndex, 0)),
+ set(
+ this.destSnapPoint,
+ cond(
+ this.isManuallySetValue,
+ this.manualYOffset,
+ this.calculateNextSnapPoint()
+ )
+ ),
+ cond(this.isManuallySetValue, [
+ set(this.animationFinished, 0)
+ ]),
+ set(
+ this.lastSnap,
+ sub(
+ this.destSnapPoint,
+ cond(eq(this.scrollUpAndPullDown, 1), this.lastStartScrollY, 0)
+ )
+ ),
runTiming({
clock: this.animationClock,
from: cond(
@@ -550,7 +572,7 @@ export class ScrollBottomSheet<T extends any> extends Component<Props<T>> {
);
this.position = interpolate(this.translateY, {
- inputRange: [openPosition, closedPosition],
+ inputRange: [snapPoints[snapPoints.length - 2], closedPosition],
outputRange: [1, 0],
extrapolate: Extrapolate.CLAMP,
});

View File

@ -2931,6 +2931,23 @@
base64-js "^1.2.3" base64-js "^1.2.3"
xmlbuilder "^14.0.0" xmlbuilder "^14.0.0"
"@gorhom/bottom-sheet@^4":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.1.5.tgz#35341d45799de28082c380db6639537b04fa0b26"
integrity sha512-3F5P8jK3NXwT2lGwkAdkdLwDVHaRvMZalUTXjK6Ogf0Tki6idffJ3TNlQZlg8k6+OnXAx0+i80f4XaI+J4GFrA==
dependencies:
"@gorhom/portal" "^1.0.11"
invariant "^2.2.4"
nanoid "^3.1.20"
react-native-redash "^16.1.1"
"@gorhom/portal@^1.0.11":
version "1.0.12"
resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.12.tgz#1c0deabb3f9057c736352a88bae9ca891a100346"
integrity sha512-JOYe85RUwiksgdMbhLWDCLpH3kgFFz+LCu1lnxOMMBQSfAKtL5kkTKVrhtmQ3Lq3lJM2paGnLc4wJrlVuaC5Jw==
dependencies:
nanoid "^3.1.23"
"@hapi/hoek@^9.0.0": "@hapi/hoek@^9.0.0":
version "9.2.1" version "9.2.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17"
@ -13067,6 +13084,11 @@ nanoid@3.1.23, nanoid@^3.1.15:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
nanoid@^3.1.20, nanoid@^3.1.23:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -14972,6 +14994,15 @@ react-native-redash@^12.0.3:
parse-svg-path "^0.1.2" parse-svg-path "^0.1.2"
use-memo-one "^1.1.1" use-memo-one "^1.1.1"
react-native-redash@^16.1.1:
version "16.2.3"
resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-16.2.3.tgz#ee63e100c60f83275116e57d4e8bc79f26349db9"
integrity sha512-vSjHA6/mBY3IpDYPish3DlG06PKNLkb/b89hw7nsDM3yxAJ7Db+yMnEL3pp2YsoYblDc3s+0+wBRlvxay4X4vQ==
dependencies:
abs-svg-path "^0.1.1"
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
react-native-restart@0.0.22: react-native-restart@0.0.22:
version "0.0.22" version "0.0.22"
resolved "https://registry.yarnpkg.com/react-native-restart/-/react-native-restart-0.0.22.tgz#81fcb7f31e35951d85410c68b9556acf3ab88705" resolved "https://registry.yarnpkg.com/react-native-restart/-/react-native-restart-0.0.22.tgz#81fcb7f31e35951d85410c68b9556acf3ab88705"
@ -14987,13 +15018,6 @@ react-native-screens@2.9.0:
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.9.0.tgz#ead2843107ba00fee259aa377582e457c74f1f3b" resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.9.0.tgz#ead2843107ba00fee259aa377582e457c74f1f3b"
integrity sha512-5MaiUD6HA3nzY3JbVI8l3V7pKedtxQF3d8qktTVI0WmWXTI4QzqOU8r8fPVvfKo3MhOXwhWBjr+kQ7DZaIQQeg== integrity sha512-5MaiUD6HA3nzY3JbVI8l3V7pKedtxQF3d8qktTVI0WmWXTI4QzqOU8r8fPVvfKo3MhOXwhWBjr+kQ7DZaIQQeg==
react-native-scroll-bottom-sheet@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/react-native-scroll-bottom-sheet/-/react-native-scroll-bottom-sheet-0.6.2.tgz#f68ea26cb10171ccc8eacf3b917a0e84c7508b82"
integrity sha512-VesAAzsv0xxtGKftFVRqbWiB1AaG3YttvilV16rpvbA45z0ow89tG5FZQtznH9ypKVY17O0bFFHocdji7bvZLg==
dependencies:
utility-types "^3.10.0"
react-native-scrollable-tab-view@^1.0.0: react-native-scrollable-tab-view@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-1.0.0.tgz#87319896067f7bb643ecd7fba2cba4d6d8f9e18b" resolved "https://registry.yarnpkg.com/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-1.0.0.tgz#87319896067f7bb643ecd7fba2cba4d6d8f9e18b"
@ -17748,11 +17772,6 @@ utila@^0.4.0, utila@~0.4:
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
utility-types@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
utils-merge@1.0.1: utils-merge@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"