├── .gitignore ├── README.md ├── app.json ├── app ├── (tabs) │ ├── (home) │ │ ├── _layout.tsx │ │ ├── another.tsx │ │ └── index.tsx │ ├── _layout.tsx │ └── profile │ │ ├── _layout.tsx │ │ └── index.tsx ├── +html.tsx ├── [...missing].tsx ├── _layout.tsx └── modal.tsx ├── assets ├── fonts │ └── SpaceMono-Regular.ttf └── images │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── components ├── EditScreenInfo.tsx ├── ExternalLink.tsx ├── StyledText.tsx ├── Themed.tsx └── __tests__ │ └── StyledText-test.js ├── constants └── Colors.ts ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 38 | # The following patterns were generated by expo-cli 39 | 40 | expo-env.d.ts 41 | # @end expo-cli -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expo Router with Bottom Sheet Example 2 | 3 | An example project to showcase the usage of [Expo Router](https://expo.github.io/router/docs/) with [React Native Bottom Sheet](https://gorhom.github.io/react-native-bottom-sheet/). 4 | 5 | 6 | 7 | https://github.com/pzatorski/expo-router-with-bottom-sheet/assets/11720377/dbe50db2-18dd-49bd-b979-c2f5eabaf72d 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "expo-router-with-bottom-sheet", 4 | "slug": "expo-router-with-bottom-sheet", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "assetBundlePatterns": [ 16 | "**/*" 17 | ], 18 | "ios": { 19 | "supportsTablet": true 20 | }, 21 | "android": { 22 | "adaptiveIcon": { 23 | "foregroundImage": "./assets/images/adaptive-icon.png", 24 | "backgroundColor": "#ffffff" 25 | } 26 | }, 27 | "web": { 28 | "bundler": "metro", 29 | "output": "static", 30 | "favicon": "./assets/images/favicon.png" 31 | }, 32 | "plugins": [ 33 | "expo-router" 34 | ], 35 | "experiments": { 36 | "typedRoutes": true 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/(tabs)/(home)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { FontAwesome } from '@expo/vector-icons'; 2 | import { 3 | BottomSheetModal, 4 | BottomSheetModalProvider, 5 | } from '@gorhom/bottom-sheet'; 6 | import { Link, Stack } from 'expo-router'; 7 | import { useEffect, useMemo, useRef } from 'react'; 8 | import { Pressable, StyleSheet, View } from 'react-native'; 9 | import MapView from 'react-native-maps'; 10 | 11 | export default function HomeLayout() { 12 | const bottomSheetModalRef = useRef(null); 13 | const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); 14 | 15 | useEffect(() => { 16 | bottomSheetModalRef.current?.present(); 17 | }, []); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | ( 38 | 39 | 40 | {({ pressed }) => ( 41 | 49 | )} 50 | 51 | 52 | ), 53 | }} 54 | /> 55 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | flex: 1, 68 | }, 69 | map: { 70 | width: '100%', 71 | height: '100%', 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /app/(tabs)/(home)/another.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from 'react-native'; 2 | import React from 'react'; 3 | 4 | export default function AnotherScreen() { 5 | return ( 6 | 7 | AnotherScreen 8 | 9 | ); 10 | } 11 | 12 | const styles = StyleSheet.create({ 13 | container: { 14 | flex: 1, 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /app/(tabs)/(home)/index.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from 'react-native'; 2 | import React from 'react'; 3 | import { BottomSheetScrollView } from '@gorhom/bottom-sheet'; 4 | 5 | const data = Array.from({ length: 50 }).map((_, index) => ({ 6 | id: index, 7 | title: `Item ${index}`, 8 | })); 9 | 10 | export default function HomeScreen() { 11 | return ( 12 | 13 | Data: 14 | 15 | {data.map((item) => ( 16 | 17 | {item.title} 18 | 19 | ))} 20 | 21 | ); 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | padding: 16, 27 | }, 28 | title: { 29 | fontSize: 16, 30 | fontWeight: '600', 31 | marginBottom: 8, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /app/(tabs)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import FontAwesome from '@expo/vector-icons/FontAwesome'; 2 | import { Link, Tabs } from 'expo-router'; 3 | import { Pressable, useColorScheme } from 'react-native'; 4 | 5 | import Colors from '../../constants/Colors'; 6 | 7 | /** 8 | * You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ 9 | */ 10 | function TabBarIcon(props: { 11 | name: React.ComponentProps['name']; 12 | color: string; 13 | }) { 14 | return ( 15 | 16 | ); 17 | } 18 | 19 | export default function TabLayout() { 20 | const colorScheme = useColorScheme(); 21 | 22 | return ( 23 | 28 | ( 33 | 34 | ), 35 | headerRight: () => ( 36 | 37 | 38 | {({ pressed }) => ( 39 | 48 | )} 49 | 50 | 51 | ), 52 | }} 53 | /> 54 | ( 59 | 60 | ), 61 | }} 62 | /> 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /app/(tabs)/profile/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from 'expo-router'; 2 | 3 | export default function ProfileLayout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/(tabs)/profile/index.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from 'react-native'; 2 | import React from 'react'; 3 | 4 | export default function ProfileScreen() { 5 | return ( 6 | 7 | ProfileScreen 8 | 9 | ); 10 | } 11 | 12 | const styles = StyleSheet.create({ 13 | container: { 14 | flex: 1, 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /app/+html.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollViewStyleReset } from 'expo-router/html'; 2 | 3 | // This file is web-only and used to configure the root HTML for every 4 | // web page during static rendering. 5 | // The contents of this function only run in Node.js environments and 6 | // do not have access to the DOM or browser APIs. 7 | export default function Root({ children }: { children: React.ReactNode }) { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | {/* 15 | This viewport disables scaling which makes the mobile website act more like a native app. 16 | However this does reduce built-in accessibility. If you want to enable scaling, use this instead: 17 | 18 | */} 19 | 23 | {/* 24 | Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 25 | However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 26 | */} 27 | 28 | 29 | {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 30 |