xref: /expo/home/menu/DevMenuBottomSheet.tsx (revision cd926c41)
1import BottomSheet, {
2  BottomSheetBackdrop,
3  BottomSheetView,
4  useBottomSheetDynamicSnapPoints,
5} from '@gorhom/bottom-sheet';
6import React, { useCallback, useEffect, useMemo, useRef } from 'react';
7import { StyleSheet, View } from 'react-native';
8
9import DevMenuBottomSheetContext from './DevMenuBottomSheetContext';
10import * as DevMenu from './DevMenuModule';
11
12type Props = {
13  uuid: string;
14  children?: React.ReactNode;
15};
16
17function DevMenuBottomSheet({ children, uuid }: Props) {
18  const bottomSheetRef = useRef<BottomSheet | null>(null);
19
20  const onCollapse = useCallback(
21    () =>
22      new Promise<void>((resolve) => {
23        bottomSheetRef.current?.close();
24
25        // still no way to wait for animation to end before the callback, so we wait for 300ms
26        setTimeout(() => {
27          resolve();
28          DevMenu.closeAsync();
29        }, 300);
30      }),
31    []
32  );
33
34  const onExpand = useCallback(
35    () =>
36      new Promise<void>((resolve) => {
37        bottomSheetRef.current?.expand();
38        setTimeout(() => {
39          resolve();
40        }, 300);
41      }),
42    []
43  );
44
45  const onChange = useCallback((index: number) => {
46    if (index === -1) {
47      DevMenu.closeAsync();
48    }
49  }, []);
50
51  const initialSnapPoints = useMemo(() => ['CONTENT_HEIGHT'], []);
52
53  useEffect(() => {
54    bottomSheetRef.current?.expand();
55  }, [uuid]);
56
57  useEffect(() => {
58    const closeSubscription = DevMenu.listenForCloseRequests(() => {
59      bottomSheetRef.current?.collapse();
60      return new Promise<void>((resolve) => {
61        resolve();
62      });
63    });
64    return () => {
65      closeSubscription.remove();
66    };
67  }, []);
68
69  const { animatedHandleHeight, animatedSnapPoints, animatedContentHeight, handleContentLayout } =
70    useBottomSheetDynamicSnapPoints(initialSnapPoints);
71
72  return (
73    <BottomSheet
74      key={uuid}
75      ref={bottomSheetRef}
76      backdropComponent={(props) => (
77        <BottomSheetBackdrop {...props} opacity={0.5} appearsOnIndex={0} disappearsOnIndex={-1} />
78      )}
79      handleComponent={null}
80      snapPoints={animatedSnapPoints}
81      handleHeight={animatedHandleHeight}
82      contentHeight={animatedContentHeight}
83      backgroundStyle={styles.bottomSheetBackground}
84      enablePanDownToClose
85      onChange={onChange}>
86      <DevMenuBottomSheetContext.Provider value={{ collapse: onCollapse, expand: onExpand }}>
87        <BottomSheetView style={styles.contentContainerStyle} onLayout={handleContentLayout}>
88          {children}
89        </BottomSheetView>
90      </DevMenuBottomSheetContext.Provider>
91      {/* Adds bottom offset so that no empty space is shown on overdrag */}
92      <View style={{ height: 100 }} />
93    </BottomSheet>
94  );
95}
96
97const styles = StyleSheet.create({
98  bottomSheetContainer: {
99    flex: 1,
100  },
101  contentContainerStyle: {},
102  bottomSheetBackground: {
103    backgroundColor: '#f8f8fa',
104  },
105});
106
107export default DevMenuBottomSheet;
108