├── assets ├── icon.png ├── favicon.png ├── splash.png └── adaptive-icon.png ├── app ├── _layout.tsx └── (app) │ ├── (tabs) │ ├── (search,saved) │ │ ├── _layout.tsx │ │ ├── [id].tsx │ │ ├── saved.tsx │ │ └── index.tsx │ ├── post.tsx │ ├── account.tsx │ └── _layout.tsx │ ├── modal.tsx │ ├── [...missing].tsx │ └── _layout.tsx ├── prettier.config.js ├── theme ├── Box.tsx ├── Text.tsx ├── index.ts └── theme.ts ├── babel.config.js ├── components ├── Container.tsx ├── TabBarIcon.tsx ├── ScreenContent.tsx ├── EditScreenInfo.tsx ├── HeaderButton.tsx └── Button.tsx ├── metro.config.js ├── tsconfig.json ├── .gitignore ├── cesconfig.json ├── app.json ├── utils └── firebase.ts └── package.json /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKempin/expo-router-modal/main/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKempin/expo-router-modal/main/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKempin/expo-router-modal/main/assets/splash.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKempin/expo-router-modal/main/assets/adaptive-icon.png -------------------------------------------------------------------------------- /app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from 'expo-router'; 2 | 3 | export default function RootLayout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | bracketSameLine: true, 6 | trailingComma: 'es5', 7 | }; 8 | -------------------------------------------------------------------------------- /theme/Box.tsx: -------------------------------------------------------------------------------- 1 | import { createBox } from '@shopify/restyle'; 2 | 3 | import { Theme } from './theme'; 4 | 5 | const Box = createBox(); 6 | 7 | export default Box; 8 | -------------------------------------------------------------------------------- /theme/Text.tsx: -------------------------------------------------------------------------------- 1 | import { createText } from '@shopify/restyle'; 2 | 3 | import { Theme } from './theme'; 4 | 5 | const Text = createText(); 6 | 7 | export default Text; 8 | -------------------------------------------------------------------------------- /theme/index.ts: -------------------------------------------------------------------------------- 1 | import Box from './Box'; 2 | import Text from './Text'; 3 | import theme, { useTheme, Theme, makeStyles } from './theme'; 4 | 5 | export { theme, Box, Text, useTheme, Theme, makeStyles }; 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | const plugins = []; 4 | 5 | return { 6 | presets: ['babel-preset-expo'], 7 | 8 | plugins, 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/(search,saved)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router'; 2 | import React from 'react'; 3 | 4 | export default function StackLayout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /components/Container.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from 'theme'; 2 | 3 | export const Container = ({ children }: { children: React.ReactNode }) => { 4 | return ( 5 | 6 | {children} 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require('expo/metro-config'); 2 | 3 | /** @type {import('expo/metro-config').MetroConfig} */ 4 | // eslint-disable-next-line no-undef 5 | const config = getDefaultConfig(__dirname); 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | 6 | "baseUrl": ".", 7 | "paths": { 8 | "~/*": ["*"] 9 | } 10 | }, 11 | "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /app/(app)/modal.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import { Platform, Text } from 'react-native'; 3 | 4 | export default function Modal() { 5 | return ( 6 | <> 7 | MODAL SCREEN 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/(app)/[...missing].tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from 'expo-router'; 2 | import { Text } from 'react-native'; 3 | 4 | export default function NotFoundScreen() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | NOT FOUND 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | # expo router 13 | expo-env.d.ts 14 | 15 | # firebase/supabase/vexo 16 | .env 17 | 18 | ios 19 | android 20 | 21 | # macOS 22 | .DS_Store 23 | 24 | # Temporary files created by Metro to check the health of the file watcher 25 | .metro-health-check* -------------------------------------------------------------------------------- /app/(app)/(tabs)/post.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | export default function Post() { 5 | return ( 6 | <> 7 | 8 | 9 | POST SCREEN 10 | 11 | 12 | ); 13 | } 14 | 15 | const styles = StyleSheet.create({ 16 | container: { 17 | flex: 1, 18 | padding: 24, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /components/TabBarIcon.tsx: -------------------------------------------------------------------------------- 1 | import FontAwesome from '@expo/vector-icons/FontAwesome'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | export const TabBarIcon = (props: { 5 | name: React.ComponentProps['name']; 6 | color: string; 7 | }) => { 8 | return ; 9 | }; 10 | 11 | export const styles = StyleSheet.create({ 12 | tabBarIcon: { 13 | marginBottom: -3, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/(search,saved)/[id].tsx: -------------------------------------------------------------------------------- 1 | import { useLocalSearchParams, Stack } from 'expo-router'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | export default function Two() { 5 | const { id } = useLocalSearchParams(); 6 | 7 | return ( 8 | <> 9 | 10 | 11 | DETAILS SCREEN 12 | Details of user {id} 13 | 14 | 15 | ); 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flex: 1, 21 | padding: 24, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /app/(app)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '@shopify/restyle'; 2 | import { Stack } from 'expo-router'; 3 | import { theme } from 'theme'; 4 | 5 | export const unstable_settings = { 6 | // Ensure that reloading on `/modal` keeps a back button present. 7 | initialRouteName: '(tabs)', 8 | }; 9 | 10 | export default function RootLayout() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/ScreenContent.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text } from 'theme'; 2 | 3 | import { EditScreenInfo } from './EditScreenInfo'; 4 | 5 | type ScreenContentProps = { 6 | title: string; 7 | path: string; 8 | children?: React.ReactNode; 9 | }; 10 | 11 | export const ScreenContent = ({ title, path, children }: ScreenContentProps) => { 12 | return ( 13 | 14 | {title} 15 | 16 | 17 | {children} 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/account.tsx: -------------------------------------------------------------------------------- 1 | import { Stack, Link } from 'expo-router'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | import { HeaderButton } from '../../../components/HeaderButton'; 5 | 6 | export default function Account() { 7 | return ( 8 | <> 9 | ( 12 | 13 | 14 | 15 | ), 16 | }} 17 | /> 18 | 19 | ACCOUNT SCREEN 20 | 21 | 22 | ); 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | container: { 27 | flex: 1, 28 | padding: 24, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /components/EditScreenInfo.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text } from 'theme'; 2 | 3 | export const EditScreenInfo = ({ path }: { path: string }) => { 4 | const title = 'Open up the code for this screen:'; 5 | const description = 6 | 'Change any of the text, save the file, and your app will automatically update.'; 7 | 8 | return ( 9 | 10 | 11 | {title} 12 | 13 | 14 | {path} 15 | 16 | 17 | {description} 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/(search,saved)/saved.tsx: -------------------------------------------------------------------------------- 1 | import { Stack, Link } from 'expo-router'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | export default function Saved() { 5 | return ( 6 | <> 7 | 8 | 9 | SAVED screen - (search)/saved.tsx 10 | 15 | View dynamic ID screen 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | flex: 1, 25 | padding: 24, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /cesconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "cesVersion": "2.11.25", 3 | "projectName": "my-expo-app", 4 | "packages": [ 5 | { 6 | "name": "expo-router", 7 | "type": "navigation", 8 | "options": { 9 | "type": "tabs" 10 | } 11 | }, 12 | { 13 | "name": "restyle", 14 | "type": "styling" 15 | }, 16 | { 17 | "name": "firebase", 18 | "type": "authentication" 19 | } 20 | ], 21 | "flags": { 22 | "noGit": false, 23 | "noInstall": false, 24 | "overwrite": false, 25 | "importAlias": true, 26 | "packageManager": "npm", 27 | "eas": false 28 | }, 29 | "packageManager": { 30 | "type": "npm", 31 | "version": "10.8.2" 32 | }, 33 | "os": { 34 | "type": "Darwin", 35 | "platform": "darwin", 36 | "arch": "arm64", 37 | "kernelVersion": "23.6.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/HeaderButton.tsx: -------------------------------------------------------------------------------- 1 | import FontAwesome from '@expo/vector-icons/FontAwesome'; 2 | import { forwardRef } from 'react'; 3 | import { Pressable, StyleSheet } from 'react-native'; 4 | 5 | export const HeaderButton = forwardRef void }>( 6 | ({ onPress }, ref) => { 7 | return ( 8 | 9 | {({ pressed }) => ( 10 | 21 | )} 22 | 23 | ); 24 | } 25 | ); 26 | 27 | export const styles = StyleSheet.create({ 28 | headerRight: { 29 | marginRight: 15, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "my-expo-app", 4 | "slug": "my-expo-app", 5 | "version": "1.0.0", 6 | 7 | "scheme": "my-expo-app", 8 | "web": { 9 | "bundler": "metro", 10 | "output": "static", 11 | "favicon": "./assets/favicon.png" 12 | }, 13 | "plugins": ["expo-router"], 14 | "experiments": { 15 | "typedRoutes": true, 16 | 17 | "tsconfigPaths": true 18 | }, 19 | 20 | "orientation": "portrait", 21 | "icon": "./assets/icon.png", 22 | 23 | "userInterfaceStyle": "light", 24 | 25 | "splash": { 26 | "image": "./assets/splash.png", 27 | "resizeMode": "contain", 28 | "backgroundColor": "#ffffff" 29 | }, 30 | "assetBundlePatterns": ["**/*"], 31 | "ios": { 32 | "supportsTablet": true 33 | }, 34 | "android": { 35 | "adaptiveIcon": { 36 | "foregroundImage": "./assets/adaptive-icon.png", 37 | "backgroundColor": "#ffffff" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/(search,saved)/index.tsx: -------------------------------------------------------------------------------- 1 | import { Stack, Link } from 'expo-router'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | import { HeaderButton } from '../../../../components/HeaderButton'; 5 | 6 | export default function Home() { 7 | return ( 8 | <> 9 | ( 13 | 14 | 15 | 16 | ), 17 | }} 18 | /> 19 | 20 | Search screen - (search)/index.tsx 21 | 22 | 27 | View dynamic ID screen 28 | 29 | 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flex: 1, 37 | padding: 24, 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /utils/firebase.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app'; 2 | 3 | // Optionally import the services that you want to use 4 | // import {...} from "firebase/auth"; 5 | // import {...} from "firebase/database"; 6 | // import {...} from "firebase/firestore"; 7 | // import {...} from "firebase/functions"; 8 | // import {...} from "firebase/storage"; 9 | 10 | // Initialize Firebase 11 | const firebaseConfig = { 12 | apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, 13 | authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN, 14 | databaseURL: process.env.EXPO_PUBLIC_FIREBASE_DATABASE_URL, 15 | projectId: process.env.EXPO_PUBLIC_PROJECT_ID, 16 | storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET, 17 | messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, 18 | appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID, 19 | measurementId: process.env.EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID, 20 | }; 21 | 22 | const firebase = initializeApp(firebaseConfig); 23 | // For more information on how to access Firebase in your project, 24 | // see the Firebase documentation: https://firebase.google.com/docs/web/setup#access-firebase 25 | 26 | export default firebase; 27 | -------------------------------------------------------------------------------- /components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import { TouchableOpacity, TouchableOpacityProps } from 'react-native'; 3 | import { Text, makeStyles } from 'theme'; 4 | 5 | type ButtonProps = { 6 | title?: string; 7 | } & TouchableOpacityProps; 8 | 9 | export const Button = forwardRef( 10 | ({ title, ...touchableProps }, ref) => { 11 | const styles = useStyles(); 12 | 13 | return ( 14 | 15 | 16 | {title} 17 | 18 | 19 | ); 20 | } 21 | ); 22 | 23 | const useStyles = makeStyles((theme) => ({ 24 | button: { 25 | alignItems: 'center', 26 | backgroundColor: theme.colors.purple, 27 | borderRadius: theme.borderRadii.xl_24, 28 | elevation: 5, 29 | flexDirection: 'row', 30 | justifyContent: 'center', 31 | padding: theme.spacing.m_16, 32 | shadowColor: theme.colors.black, 33 | shadowOffset: { 34 | height: 2, 35 | width: 0, 36 | }, 37 | shadowOpacity: 0.25, 38 | shadowRadius: 3.84, 39 | }, 40 | })); 41 | -------------------------------------------------------------------------------- /app/(app)/(tabs)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from 'expo-router'; 2 | 3 | import { TabBarIcon } from '../../../components/TabBarIcon'; 4 | 5 | export default function TabLayout() { 6 | return ( 7 | 11 | , 15 | headerShown: false, 16 | title: 'Search', 17 | }} 18 | /> 19 | , 23 | headerShown: false, 24 | title: 'Saved', 25 | }} 26 | /> 27 | 28 | , 32 | title: 'Post', 33 | }} 34 | /> 35 | , 39 | title: 'Account', 40 | }} 41 | /> 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /theme/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, useTheme as useRestyleTheme } from '@shopify/restyle'; 2 | import { ImageStyle, TextStyle, ViewStyle } from 'react-native'; 3 | 4 | type NamedStyles = { 5 | [P in keyof T]: ViewStyle | TextStyle | ImageStyle; 6 | }; 7 | 8 | const palette = { 9 | gray: '#808080', 10 | blue: '#007AFF', 11 | darkGray: '#38434D', 12 | white: '#FFFFFF', 13 | black: '#000000', 14 | purple: '#6366F1', 15 | }; 16 | 17 | const theme = createTheme({ 18 | colors: { 19 | ...palette, 20 | }, 21 | spacing: { 22 | xs_4: 4, 23 | s_8: 8, 24 | sm_12: 12, 25 | m_16: 16, 26 | ml_24: 24, 27 | l_32: 32, 28 | xl_64: 64, 29 | }, 30 | borderRadii: { 31 | s_3: 3, 32 | m_6: 6, 33 | l_12: 12, 34 | xl_24: 24, 35 | }, 36 | textVariants: { 37 | body: { 38 | fontSize: 16, 39 | }, 40 | title: { fontSize: 20, fontWeight: 'bold' }, 41 | large: { 42 | fontSize: 36, 43 | }, 44 | extra_large: { 45 | fontSize: 64, 46 | fontWeight: 'bold', 47 | }, 48 | defaults: { 49 | // We can define a default text variant here. 50 | }, 51 | }, 52 | }); 53 | 54 | export const useTheme = () => { 55 | return useRestyleTheme(); 56 | }; 57 | 58 | export const makeStyles = | NamedStyles>( 59 | styles: (theme: Theme) => T 60 | ) => { 61 | return () => { 62 | return styles(theme); 63 | }; 64 | }; 65 | 66 | export type Theme = typeof theme; 67 | export default theme; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-expo-app", 3 | "version": "1.0.0", 4 | "main": "expo-router/entry", 5 | "scripts": { 6 | "android": "expo start --android", 7 | "ios": "expo start --ios", 8 | "start": "expo start", 9 | "lint": "eslint \"**/*.{js,jsx,ts,tsx}\" && prettier -c \"**/*.{js,jsx,ts,tsx,json}\"", 10 | "format": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix && prettier \"**/*.{js,jsx,ts,tsx,json}\" --write", 11 | "web": "expo start --web" 12 | }, 13 | "dependencies": { 14 | "@expo/vector-icons": "^14.0.0", 15 | "@react-navigation/native": "^6.1.7", 16 | "@shopify/restyle": "^2.4.2", 17 | "expo": "^51.0.38", 18 | "expo-constants": "~16.0.1", 19 | "expo-linking": "~6.3.1", 20 | "expo-router": "^3.5.23", 21 | "expo-status-bar": "~1.12.1", 22 | "expo-system-ui": "~3.0.4", 23 | "expo-web-browser": "~13.0.3", 24 | "firebase": "^10.5.2", 25 | "react": "18.2.0", 26 | "react-dom": "18.2.0", 27 | "react-native": "0.74.5", 28 | "react-native-gesture-handler": "~2.16.1", 29 | "react-native-reanimated": "~3.10.1", 30 | "react-native-safe-area-context": "4.10.1", 31 | "react-native-screens": "3.31.1", 32 | "react-native-web": "~0.19.10" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.20.0", 36 | "@types/react": "~18.2.45", 37 | "@typescript-eslint/eslint-plugin": "^7.7.0", 38 | "@typescript-eslint/parser": "^7.7.0", 39 | "eslint": "^8.57.0", 40 | "eslint-config-universe": "^12.0.1", 41 | "prettier": "^3.2.5", 42 | "typescript": "~5.3.3" 43 | }, 44 | "eslintConfig": { 45 | "extends": "universe/native", 46 | "root": true 47 | }, 48 | "expo": { 49 | "install": { 50 | "exclude": [ 51 | "react-native-safe-area-context" 52 | ] 53 | } 54 | }, 55 | "private": true 56 | } 57 | --------------------------------------------------------------------------------