├── 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 |
--------------------------------------------------------------------------------