Skip to content

Commit 7397014

Browse files
committed
feat: add themed bottom sheet modal
1 parent 3b632ba commit 7397014

File tree

2 files changed

+123
-7
lines changed

2 files changed

+123
-7
lines changed

apps/mobile/app/_layout.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useAppearanceSetting } from '@flow/hooks'
22
import { PlaybackService, setupPlayer } from '@flow/player'
33
import { useSettingStore, useTrackStore } from '@flow/store'
4+
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
45
import { DarkTheme as NavDarkTheme, DefaultTheme as NavLightTheme, ThemeProvider } from '@react-navigation/native'
56
import { ErrorBoundary, Slot } from 'expo-router'
67
import * as SplashScreen from 'expo-splash-screen'
@@ -48,13 +49,15 @@ export default function RootLayout() {
4849
<PaperProvider theme={paperTheme}>
4950
<ThemeProvider value={effectiveColorScheme === 'dark' ? DarkTheme : LightTheme}>
5051
<GestureHandlerRootView style={{ flex: 1 }}>
51-
<ToastProvider>
52-
<ThemedView style={{ flex: 1 }} testID="root-surface">
53-
<StatusBar style={statusBarStyle} />
54-
<Slot />
55-
<Player />
56-
</ThemedView>
57-
</ToastProvider>
52+
<BottomSheetModalProvider>
53+
<ToastProvider>
54+
<ThemedView style={{ flex: 1 }} testID="root-surface">
55+
<StatusBar style={statusBarStyle} />
56+
<Slot />
57+
<Player />
58+
</ThemedView>
59+
</ToastProvider>
60+
</BottomSheetModalProvider>
5861
</GestureHandlerRootView>
5962
</ThemeProvider>
6063
</PaperProvider>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { BottomSheetBackdropProps, BottomSheetModalProps } from '@gorhom/bottom-sheet'
2+
import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet'
3+
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
4+
import { StyleSheet } from 'react-native'
5+
import { useTheme } from 'react-native-paper'
6+
import { useBackHandler } from '@/hooks/useBackHandler'
7+
8+
interface ThemedBottomSheetModalProps extends BottomSheetModalProps {
9+
children: React.ReactNode
10+
enablePanDownToClose?: boolean
11+
enableDynamicSizing?: boolean
12+
backgroundStyle?: object
13+
handleIndicatorStyle?: object
14+
visible?: boolean
15+
onDismiss?: () => void
16+
}
17+
18+
export function ThemedBottomSheetModal({
19+
children,
20+
enablePanDownToClose = true,
21+
enableDynamicSizing = false,
22+
snapPoints: customSnapPoints,
23+
backgroundStyle,
24+
handleIndicatorStyle,
25+
visible = false,
26+
onDismiss,
27+
...rest
28+
}: ThemedBottomSheetModalProps) {
29+
const ref = useRef<BottomSheetModal>(null)
30+
const { colors } = useTheme()
31+
32+
useEffect(() => {
33+
if (visible) {
34+
ref.current?.present()
35+
}
36+
else {
37+
ref.current?.dismiss()
38+
}
39+
}, [visible])
40+
41+
const renderBackdrop = useCallback(
42+
(props: BottomSheetBackdropProps) => (
43+
<BottomSheetBackdrop
44+
{...props}
45+
disappearsOnIndex={-1}
46+
appearsOnIndex={0}
47+
opacity={0.5}
48+
pressBehavior="close"
49+
/>
50+
),
51+
[],
52+
)
53+
54+
const materialBackgroundStyle = useMemo(
55+
() => [
56+
{
57+
backgroundColor: colors.surfaceVariant,
58+
borderTopLeftRadius: 16,
59+
borderTopRightRadius: 16,
60+
},
61+
backgroundStyle,
62+
],
63+
[colors.surfaceVariant, backgroundStyle],
64+
)
65+
66+
const materialHandleIndicatorStyle = useMemo(
67+
() => [
68+
{
69+
backgroundColor: colors.onSurfaceVariant,
70+
opacity: 0.4,
71+
},
72+
handleIndicatorStyle,
73+
],
74+
[colors.onSurfaceVariant, handleIndicatorStyle],
75+
)
76+
77+
const handleDismiss = useCallback(() => {
78+
onDismiss?.()
79+
ref.current?.dismiss()
80+
}, [onDismiss])
81+
82+
useBackHandler(visible, handleDismiss)
83+
84+
return (
85+
<BottomSheetModal
86+
ref={ref}
87+
snapPoints={customSnapPoints ?? ['50%', '90%']}
88+
enablePanDownToClose={enablePanDownToClose}
89+
enableDynamicSizing={enableDynamicSizing}
90+
backgroundStyle={materialBackgroundStyle}
91+
handleIndicatorStyle={materialHandleIndicatorStyle}
92+
backdropComponent={renderBackdrop}
93+
onDismiss={handleDismiss}
94+
style={styles.bottomSheet}
95+
{...rest}
96+
>
97+
{children}
98+
</BottomSheetModal>
99+
)
100+
}
101+
102+
const styles = StyleSheet.create({
103+
bottomSheet: {
104+
shadowColor: '#000',
105+
shadowOffset: {
106+
width: 0,
107+
height: -4,
108+
},
109+
shadowOpacity: 0.1,
110+
shadowRadius: 8,
111+
elevation: 8,
112+
},
113+
})

0 commit comments

Comments
 (0)