diff --git a/packages/expo-template/README.md b/packages/expo-template/README.md
new file mode 100644
index 0000000..48dd63f
--- /dev/null
+++ b/packages/expo-template/README.md
@@ -0,0 +1,50 @@
+# Welcome to your Expo app š
+
+This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
+
+## Get started
+
+1. Install dependencies
+
+ ```bash
+ npm install
+ ```
+
+2. Start the app
+
+ ```bash
+ npx expo start
+ ```
+
+In the output, you'll find options to open the app in a
+
+- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
+- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
+- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
+- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
+
+You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
+
+## Get a fresh project
+
+When you're ready, run:
+
+```bash
+npm run reset-project
+```
+
+This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
+
+## Learn more
+
+To learn more about developing your project with Expo, look at the following resources:
+
+- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
+- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
+
+## Join the community
+
+Join our community of developers creating universal apps.
+
+- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
+- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
diff --git a/packages/expo-template/app.json b/packages/expo-template/app.json
new file mode 100644
index 0000000..6ba124e
--- /dev/null
+++ b/packages/expo-template/app.json
@@ -0,0 +1,41 @@
+{
+ "expo": {
+ "name": "HelloWorld",
+ "slug": "expo-template-default",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/images/icon.png",
+ "scheme": "myapp",
+ "userInterfaceStyle": "automatic",
+ "newArchEnabled": true,
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/images/adaptive-icon.png",
+ "backgroundColor": "#ffffff"
+ }
+ },
+ "web": {
+ "bundler": "metro",
+ "output": "static",
+ "favicon": "./assets/images/favicon.png"
+ },
+ "plugins": [
+ "expo-router",
+ [
+ "expo-splash-screen",
+ {
+ "image": "./assets/images/splash-icon.png",
+ "imageWidth": 200,
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ }
+ ]
+ ],
+ "experiments": {
+ "typedRoutes": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/expo-template/app/(tabs)/_layout.tsx b/packages/expo-template/app/(tabs)/_layout.tsx
new file mode 100644
index 0000000..d1d39bd
--- /dev/null
+++ b/packages/expo-template/app/(tabs)/_layout.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { withLayoutContext } from 'expo-router';
+import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation';
+
+const Tabs = withLayoutContext(createNativeBottomTabNavigator().Navigator);
+
+export default function TabLayout() {
+ return (
+
+ ({ sfSymbol: 'house.fill' }),
+ }}
+ />
+ ({ sfSymbol: 'paperplane.fill' }),
+ }}
+ />
+
+ );
+}
diff --git a/packages/expo-template/app/(tabs)/explore.tsx b/packages/expo-template/app/(tabs)/explore.tsx
new file mode 100644
index 0000000..9c9715d
--- /dev/null
+++ b/packages/expo-template/app/(tabs)/explore.tsx
@@ -0,0 +1,129 @@
+import { StyleSheet, Image, Platform } from 'react-native';
+
+import { Collapsible } from '@/components/Collapsible';
+import { ExternalLink } from '@/components/ExternalLink';
+import ParallaxScrollView from '@/components/ParallaxScrollView';
+import { ThemedText } from '@/components/ThemedText';
+import { ThemedView } from '@/components/ThemedView';
+import { IconSymbol } from '@/components/ui/IconSymbol';
+
+export default function TabTwoScreen() {
+ return (
+
+ }
+ >
+
+ Explore
+
+
+ This app includes example code to help you get started.
+
+
+
+ This app has two screens:{' '}
+ app/(tabs)/index.tsx{' '}
+ and{' '}
+ app/(tabs)/explore.tsx
+
+
+ The layout file in{' '}
+ app/(tabs)/_layout.tsx{' '}
+ sets up the tab navigator.
+
+
+ Learn more
+
+
+
+
+ You can open this project on Android, iOS, and the web. To open the
+ web version, press w{' '}
+ in the terminal running this project.
+
+
+
+
+ For static images, you can use the{' '}
+ @2x and{' '}
+ @3x suffixes to
+ provide files for different screen densities
+
+
+
+ Learn more
+
+
+
+
+ Open app/_layout.tsx{' '}
+ to see how to load{' '}
+
+ custom fonts such as this one.
+
+
+
+ Learn more
+
+
+
+
+ This template has light and dark mode support. The{' '}
+ useColorScheme() hook
+ lets you inspect what the user's current color scheme is, and so you
+ can adjust UI colors accordingly.
+
+
+ Learn more
+
+
+
+
+ This template includes an example of an animated component. The{' '}
+
+ components/HelloWave.tsx
+ {' '}
+ component uses the powerful{' '}
+
+ react-native-reanimated
+ {' '}
+ library to create a waving hand animation.
+
+ {Platform.select({
+ ios: (
+
+ The{' '}
+
+ components/ParallaxScrollView.tsx
+ {' '}
+ component provides a parallax effect for the header image.
+
+ ),
+ })}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ headerImage: {
+ color: '#808080',
+ bottom: -90,
+ left: -35,
+ position: 'absolute',
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+});
diff --git a/packages/expo-template/app/(tabs)/index.tsx b/packages/expo-template/app/(tabs)/index.tsx
new file mode 100644
index 0000000..08d122b
--- /dev/null
+++ b/packages/expo-template/app/(tabs)/index.tsx
@@ -0,0 +1,78 @@
+import { Image, StyleSheet, Platform } from 'react-native';
+
+import { HelloWave } from '@/components/HelloWave';
+import ParallaxScrollView from '@/components/ParallaxScrollView';
+import { ThemedText } from '@/components/ThemedText';
+import { ThemedView } from '@/components/ThemedView';
+
+export default function HomeScreen() {
+ return (
+
+ }
+ >
+
+ Welcome!
+
+
+
+ Step 1: Try it
+
+ Edit{' '}
+ app/(tabs)/index.tsx{' '}
+ to see changes. Press{' '}
+
+ {Platform.select({
+ ios: 'cmd + d',
+ android: 'cmd + m',
+ web: 'F12',
+ })}
+ {' '}
+ to open developer tools.
+
+
+
+ Step 2: Explore
+
+ Tap the Explore tab to learn more about what's included in this
+ starter app.
+
+
+
+ Step 3: Get a fresh start
+
+ When you're ready, run{' '}
+ npm run reset-project{' '}
+ to get a fresh app{' '}
+ directory. This will move the current{' '}
+ app to{' '}
+ app-example.
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ titleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ stepContainer: {
+ gap: 8,
+ marginBottom: 8,
+ },
+ reactLogo: {
+ height: 178,
+ width: 290,
+ bottom: 0,
+ left: 0,
+ position: 'absolute',
+ },
+});
diff --git a/packages/expo-template/app/+not-found.tsx b/packages/expo-template/app/+not-found.tsx
new file mode 100644
index 0000000..963b04f
--- /dev/null
+++ b/packages/expo-template/app/+not-found.tsx
@@ -0,0 +1,32 @@
+import { Link, Stack } from 'expo-router';
+import { StyleSheet } from 'react-native';
+
+import { ThemedText } from '@/components/ThemedText';
+import { ThemedView } from '@/components/ThemedView';
+
+export default function NotFoundScreen() {
+ return (
+ <>
+
+
+ This screen doesn't exist.
+
+ Go to home screen!
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 20,
+ },
+ link: {
+ marginTop: 15,
+ paddingVertical: 15,
+ },
+});
diff --git a/packages/expo-template/app/_layout.tsx b/packages/expo-template/app/_layout.tsx
new file mode 100644
index 0000000..6109bcc
--- /dev/null
+++ b/packages/expo-template/app/_layout.tsx
@@ -0,0 +1,43 @@
+import {
+ DarkTheme,
+ DefaultTheme,
+ ThemeProvider,
+} from '@react-navigation/native';
+import { useFonts } from 'expo-font';
+import { Stack } from 'expo-router';
+import * as SplashScreen from 'expo-splash-screen';
+import { StatusBar } from 'expo-status-bar';
+import { useEffect } from 'react';
+import 'react-native-reanimated';
+
+import { useColorScheme } from '@/hooks/useColorScheme';
+
+// Prevent the splash screen from auto-hiding before asset loading is complete.
+SplashScreen.preventAutoHideAsync();
+
+export default function RootLayout() {
+ const colorScheme = useColorScheme();
+ const [loaded] = useFonts({
+ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
+ });
+
+ useEffect(() => {
+ if (loaded) {
+ SplashScreen.hideAsync();
+ }
+ }, [loaded]);
+
+ if (!loaded) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/expo-template/assets/fonts/SpaceMono-Regular.ttf b/packages/expo-template/assets/fonts/SpaceMono-Regular.ttf
new file mode 100755
index 0000000..28d7ff7
Binary files /dev/null and b/packages/expo-template/assets/fonts/SpaceMono-Regular.ttf differ
diff --git a/packages/expo-template/assets/images/adaptive-icon.png b/packages/expo-template/assets/images/adaptive-icon.png
new file mode 100644
index 0000000..03d6f6b
Binary files /dev/null and b/packages/expo-template/assets/images/adaptive-icon.png differ
diff --git a/packages/expo-template/assets/images/favicon.png b/packages/expo-template/assets/images/favicon.png
new file mode 100644
index 0000000..e75f697
Binary files /dev/null and b/packages/expo-template/assets/images/favicon.png differ
diff --git a/packages/expo-template/assets/images/icon.png b/packages/expo-template/assets/images/icon.png
new file mode 100644
index 0000000..a0b1526
Binary files /dev/null and b/packages/expo-template/assets/images/icon.png differ
diff --git a/packages/expo-template/assets/images/partial-react-logo.png b/packages/expo-template/assets/images/partial-react-logo.png
new file mode 100644
index 0000000..66fd957
Binary files /dev/null and b/packages/expo-template/assets/images/partial-react-logo.png differ
diff --git a/packages/expo-template/assets/images/react-logo.png b/packages/expo-template/assets/images/react-logo.png
new file mode 100644
index 0000000..9d72a9f
Binary files /dev/null and b/packages/expo-template/assets/images/react-logo.png differ
diff --git a/packages/expo-template/assets/images/react-logo@2x.png b/packages/expo-template/assets/images/react-logo@2x.png
new file mode 100644
index 0000000..2229b13
Binary files /dev/null and b/packages/expo-template/assets/images/react-logo@2x.png differ
diff --git a/packages/expo-template/assets/images/react-logo@3x.png b/packages/expo-template/assets/images/react-logo@3x.png
new file mode 100644
index 0000000..a99b203
Binary files /dev/null and b/packages/expo-template/assets/images/react-logo@3x.png differ
diff --git a/packages/expo-template/assets/images/splash-icon.png b/packages/expo-template/assets/images/splash-icon.png
new file mode 100644
index 0000000..03d6f6b
Binary files /dev/null and b/packages/expo-template/assets/images/splash-icon.png differ
diff --git a/packages/expo-template/components/Collapsible.tsx b/packages/expo-template/components/Collapsible.tsx
new file mode 100644
index 0000000..652afd3
--- /dev/null
+++ b/packages/expo-template/components/Collapsible.tsx
@@ -0,0 +1,49 @@
+import { PropsWithChildren, useState } from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
+
+import { ThemedText } from '@/components/ThemedText';
+import { ThemedView } from '@/components/ThemedView';
+import { IconSymbol } from '@/components/ui/IconSymbol';
+import { Colors } from '@/constants/Colors';
+import { useColorScheme } from '@/hooks/useColorScheme';
+
+export function Collapsible({
+ children,
+ title,
+}: PropsWithChildren & { title: string }) {
+ const [isOpen, setIsOpen] = useState(false);
+ const theme = useColorScheme() ?? 'light';
+
+ return (
+
+ setIsOpen((value) => !value)}
+ activeOpacity={0.8}
+ >
+
+
+ {title}
+
+ {isOpen && {children}}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ heading: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 6,
+ },
+ content: {
+ marginTop: 6,
+ marginLeft: 24,
+ },
+});
diff --git a/packages/expo-template/components/ExternalLink.tsx b/packages/expo-template/components/ExternalLink.tsx
new file mode 100644
index 0000000..8f05675
--- /dev/null
+++ b/packages/expo-template/components/ExternalLink.tsx
@@ -0,0 +1,24 @@
+import { Link } from 'expo-router';
+import { openBrowserAsync } from 'expo-web-browser';
+import { type ComponentProps } from 'react';
+import { Platform } from 'react-native';
+
+type Props = Omit, 'href'> & { href: string };
+
+export function ExternalLink({ href, ...rest }: Props) {
+ return (
+ {
+ if (Platform.OS !== 'web') {
+ // Prevent the default behavior of linking to the default browser on native.
+ event.preventDefault();
+ // Open the link in an in-app browser.
+ await openBrowserAsync(href);
+ }
+ }}
+ />
+ );
+}
diff --git a/packages/expo-template/components/HelloWave.tsx b/packages/expo-template/components/HelloWave.tsx
new file mode 100644
index 0000000..05264ff
--- /dev/null
+++ b/packages/expo-template/components/HelloWave.tsx
@@ -0,0 +1,43 @@
+import { useEffect } from 'react';
+import { StyleSheet } from 'react-native';
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withTiming,
+ withRepeat,
+ withSequence,
+} from 'react-native-reanimated';
+
+import { ThemedText } from '@/components/ThemedText';
+
+export function HelloWave() {
+ const rotationAnimation = useSharedValue(0);
+
+ useEffect(() => {
+ rotationAnimation.value = withRepeat(
+ withSequence(
+ withTiming(25, { duration: 150 }),
+ withTiming(0, { duration: 150 })
+ ),
+ 4 // Run the animation 4 times
+ );
+ }, []);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [{ rotate: `${rotationAnimation.value}deg` }],
+ }));
+
+ return (
+
+ š
+
+ );
+}
+
+const styles = StyleSheet.create({
+ text: {
+ fontSize: 28,
+ lineHeight: 32,
+ marginTop: -6,
+ },
+});
diff --git a/packages/expo-template/components/ParallaxScrollView.tsx b/packages/expo-template/components/ParallaxScrollView.tsx
new file mode 100644
index 0000000..7b947ae
--- /dev/null
+++ b/packages/expo-template/components/ParallaxScrollView.tsx
@@ -0,0 +1,85 @@
+import type { PropsWithChildren, ReactElement } from 'react';
+import { StyleSheet } from 'react-native';
+import Animated, {
+ interpolate,
+ useAnimatedRef,
+ useAnimatedStyle,
+ useScrollViewOffset,
+} from 'react-native-reanimated';
+
+import { ThemedView } from '@/components/ThemedView';
+import { useColorScheme } from '@/hooks/useColorScheme';
+
+const HEADER_HEIGHT = 250;
+
+type Props = PropsWithChildren<{
+ headerImage: ReactElement;
+ headerBackgroundColor: { dark: string; light: string };
+}>;
+
+export default function ParallaxScrollView({
+ children,
+ headerImage,
+ headerBackgroundColor,
+}: Props) {
+ const colorScheme = useColorScheme() ?? 'light';
+ const scrollRef = useAnimatedRef();
+ const scrollOffset = useScrollViewOffset(scrollRef);
+ const headerAnimatedStyle = useAnimatedStyle(() => {
+ return {
+ transform: [
+ {
+ translateY: interpolate(
+ scrollOffset.value,
+ [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
+ [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
+ ),
+ },
+ {
+ scale: interpolate(
+ scrollOffset.value,
+ [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
+ [2, 1, 1]
+ ),
+ },
+ ],
+ };
+ });
+
+ return (
+
+
+
+ {headerImage}
+
+ {children}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ header: {
+ height: HEADER_HEIGHT,
+ overflow: 'hidden',
+ },
+ content: {
+ flex: 1,
+ padding: 32,
+ gap: 16,
+ overflow: 'hidden',
+ },
+});
diff --git a/packages/expo-template/components/ThemedText.tsx b/packages/expo-template/components/ThemedText.tsx
new file mode 100644
index 0000000..c0e1a78
--- /dev/null
+++ b/packages/expo-template/components/ThemedText.tsx
@@ -0,0 +1,60 @@
+import { Text, type TextProps, StyleSheet } from 'react-native';
+
+import { useThemeColor } from '@/hooks/useThemeColor';
+
+export type ThemedTextProps = TextProps & {
+ lightColor?: string;
+ darkColor?: string;
+ type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
+};
+
+export function ThemedText({
+ style,
+ lightColor,
+ darkColor,
+ type = 'default',
+ ...rest
+}: ThemedTextProps) {
+ const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
+
+ return (
+
+ );
+}
+
+const styles = StyleSheet.create({
+ default: {
+ fontSize: 16,
+ lineHeight: 24,
+ },
+ defaultSemiBold: {
+ fontSize: 16,
+ lineHeight: 24,
+ fontWeight: '600',
+ },
+ title: {
+ fontSize: 32,
+ fontWeight: 'bold',
+ lineHeight: 32,
+ },
+ subtitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ },
+ link: {
+ lineHeight: 30,
+ fontSize: 16,
+ color: '#0a7ea4',
+ },
+});
diff --git a/packages/expo-template/components/ThemedView.tsx b/packages/expo-template/components/ThemedView.tsx
new file mode 100644
index 0000000..af42a9f
--- /dev/null
+++ b/packages/expo-template/components/ThemedView.tsx
@@ -0,0 +1,22 @@
+import { View, type ViewProps } from 'react-native';
+
+import { useThemeColor } from '@/hooks/useThemeColor';
+
+export type ThemedViewProps = ViewProps & {
+ lightColor?: string;
+ darkColor?: string;
+};
+
+export function ThemedView({
+ style,
+ lightColor,
+ darkColor,
+ ...otherProps
+}: ThemedViewProps) {
+ const backgroundColor = useThemeColor(
+ { light: lightColor, dark: darkColor },
+ 'background'
+ );
+
+ return ;
+}
diff --git a/packages/expo-template/components/__tests__/ThemedText-test.tsx b/packages/expo-template/components/__tests__/ThemedText-test.tsx
new file mode 100644
index 0000000..1ac3225
--- /dev/null
+++ b/packages/expo-template/components/__tests__/ThemedText-test.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react';
+import renderer from 'react-test-renderer';
+
+import { ThemedText } from '../ThemedText';
+
+it(`renders correctly`, () => {
+ const tree = renderer.create(Snapshot test!).toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
diff --git a/packages/expo-template/components/__tests__/__snapshots__/ThemedText-test.tsx.snap b/packages/expo-template/components/__tests__/__snapshots__/ThemedText-test.tsx.snap
new file mode 100644
index 0000000..b68e53e
--- /dev/null
+++ b/packages/expo-template/components/__tests__/__snapshots__/ThemedText-test.tsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ Snapshot test!
+
+`;
diff --git a/packages/expo-template/components/ui/IconSymbol.ios.tsx b/packages/expo-template/components/ui/IconSymbol.ios.tsx
new file mode 100644
index 0000000..9177f4d
--- /dev/null
+++ b/packages/expo-template/components/ui/IconSymbol.ios.tsx
@@ -0,0 +1,32 @@
+import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
+import { StyleProp, ViewStyle } from 'react-native';
+
+export function IconSymbol({
+ name,
+ size = 24,
+ color,
+ style,
+ weight = 'regular',
+}: {
+ name: SymbolViewProps['name'];
+ size?: number;
+ color: string;
+ style?: StyleProp;
+ weight?: SymbolWeight;
+}) {
+ return (
+
+ );
+}
diff --git a/packages/expo-template/components/ui/IconSymbol.tsx b/packages/expo-template/components/ui/IconSymbol.tsx
new file mode 100644
index 0000000..9f6bf48
--- /dev/null
+++ b/packages/expo-template/components/ui/IconSymbol.tsx
@@ -0,0 +1,50 @@
+// This file is a fallback for using MaterialIcons on Android and web.
+
+import MaterialIcons from '@expo/vector-icons/MaterialIcons';
+import { SymbolWeight } from 'expo-symbols';
+import React from 'react';
+import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native';
+
+// Add your SFSymbol to MaterialIcons mappings here.
+const MAPPING = {
+ // See MaterialIcons here: https://icons.expo.fyi
+ // See SF Symbols in the SF Symbols app on Mac.
+ 'house.fill': 'home',
+ 'paperplane.fill': 'send',
+ 'chevron.left.forwardslash.chevron.right': 'code',
+ 'chevron.right': 'chevron-right',
+} as Partial<
+ Record<
+ import('expo-symbols').SymbolViewProps['name'],
+ React.ComponentProps['name']
+ >
+>;
+
+export type IconSymbolName = keyof typeof MAPPING;
+
+/**
+ * An icon component that uses native SFSymbols on iOS, and MaterialIcons on Android and web. This ensures a consistent look across platforms, and optimal resource usage.
+ *
+ * Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons.
+ */
+export function IconSymbol({
+ name,
+ size = 24,
+ color,
+ style,
+}: {
+ name: IconSymbolName;
+ size?: number;
+ color: string | OpaqueColorValue;
+ style?: StyleProp;
+ weight?: SymbolWeight;
+}) {
+ return (
+
+ );
+}
diff --git a/packages/expo-template/constants/Colors.ts b/packages/expo-template/constants/Colors.ts
new file mode 100644
index 0000000..14e6784
--- /dev/null
+++ b/packages/expo-template/constants/Colors.ts
@@ -0,0 +1,26 @@
+/**
+ * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
+ * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
+ */
+
+const tintColorLight = '#0a7ea4';
+const tintColorDark = '#fff';
+
+export const Colors = {
+ light: {
+ text: '#11181C',
+ background: '#fff',
+ tint: tintColorLight,
+ icon: '#687076',
+ tabIconDefault: '#687076',
+ tabIconSelected: tintColorLight,
+ },
+ dark: {
+ text: '#ECEDEE',
+ background: '#151718',
+ tint: tintColorDark,
+ icon: '#9BA1A6',
+ tabIconDefault: '#9BA1A6',
+ tabIconSelected: tintColorDark,
+ },
+};
diff --git a/packages/expo-template/gitignore b/packages/expo-template/gitignore
new file mode 100644
index 0000000..c9d575d
--- /dev/null
+++ b/packages/expo-template/gitignore
@@ -0,0 +1,38 @@
+# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
+
+# dependencies
+node_modules/
+
+# Expo
+.expo/
+dist/
+web-build/
+expo-env.d.ts
+
+# Native
+*.orig.*
+*.jks
+*.p8
+*.p12
+*.key
+*.mobileprovision
+
+# Metro
+.metro-health-check*
+
+# debug
+npm-debug.*
+yarn-debug.*
+yarn-error.*
+
+# macOS
+.DS_Store
+*.pem
+
+# local env files
+.env*.local
+
+# typescript
+*.tsbuildinfo
+
+app-example
diff --git a/packages/expo-template/hooks/useColorScheme.ts b/packages/expo-template/hooks/useColorScheme.ts
new file mode 100644
index 0000000..17e3c63
--- /dev/null
+++ b/packages/expo-template/hooks/useColorScheme.ts
@@ -0,0 +1 @@
+export { useColorScheme } from 'react-native';
diff --git a/packages/expo-template/hooks/useColorScheme.web.ts b/packages/expo-template/hooks/useColorScheme.web.ts
new file mode 100644
index 0000000..7eb1c1b
--- /dev/null
+++ b/packages/expo-template/hooks/useColorScheme.web.ts
@@ -0,0 +1,21 @@
+import { useEffect, useState } from 'react';
+import { useColorScheme as useRNColorScheme } from 'react-native';
+
+/**
+ * To support static rendering, this value needs to be re-calculated on the client side for web
+ */
+export function useColorScheme() {
+ const [hasHydrated, setHasHydrated] = useState(false);
+
+ useEffect(() => {
+ setHasHydrated(true);
+ }, []);
+
+ const colorScheme = useRNColorScheme();
+
+ if (hasHydrated) {
+ return colorScheme;
+ }
+
+ return 'light';
+}
diff --git a/packages/expo-template/hooks/useThemeColor.ts b/packages/expo-template/hooks/useThemeColor.ts
new file mode 100644
index 0000000..0608e73
--- /dev/null
+++ b/packages/expo-template/hooks/useThemeColor.ts
@@ -0,0 +1,21 @@
+/**
+ * Learn more about light and dark modes:
+ * https://docs.expo.dev/guides/color-schemes/
+ */
+
+import { Colors } from '@/constants/Colors';
+import { useColorScheme } from '@/hooks/useColorScheme';
+
+export function useThemeColor(
+ props: { light?: string; dark?: string },
+ colorName: keyof typeof Colors.light & keyof typeof Colors.dark
+) {
+ const theme = useColorScheme() ?? 'light';
+ const colorFromProps = props[theme];
+
+ if (colorFromProps) {
+ return colorFromProps;
+ } else {
+ return Colors[theme][colorName];
+ }
+}
diff --git a/packages/expo-template/package.json b/packages/expo-template/package.json
new file mode 100644
index 0000000..8a2f409
--- /dev/null
+++ b/packages/expo-template/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "@bottom-tabs/expo-template",
+ "main": "expo-router/entry",
+ "version": "0.0.1",
+ "scripts": {
+ "start": "expo start",
+ "reset-project": "node ./scripts/reset-project.js",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "test": "jest --watchAll",
+ "lint": "expo lint"
+ },
+ "jest": {
+ "preset": "jest-expo"
+ },
+ "dependencies": {
+ "@expo/vector-icons": "^14.0.2",
+ "react-native-bottom-tabs": "0.8.1",
+ "@bottom-tabs/react-navigation": "0.8.1",
+ "@react-navigation/native": "^7.0.14",
+ "expo": "~52.0.11",
+ "expo-blur": "~14.0.1",
+ "expo-constants": "~17.0.3",
+ "expo-font": "~13.0.1",
+ "expo-haptics": "~14.0.0",
+ "expo-linking": "~7.0.3",
+ "expo-router": "~4.0.9",
+ "expo-splash-screen": "~0.29.13",
+ "expo-status-bar": "~2.0.0",
+ "expo-symbols": "~0.2.0",
+ "expo-system-ui": "~4.0.4",
+ "expo-web-browser": "~14.0.1",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "react-native": "0.76.3",
+ "react-native-gesture-handler": "~2.20.2",
+ "react-native-reanimated": "~3.16.1",
+ "react-native-safe-area-context": "4.12.0",
+ "react-native-screens": "~4.4.0",
+ "react-native-web": "~0.19.13",
+ "react-native-webview": "13.12.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.25.2",
+ "@types/jest": "^29.5.12",
+ "@types/react": "~18.3.12",
+ "@types/react-test-renderer": "^18.3.0",
+ "jest": "^29.2.1",
+ "jest-expo": "~52.0.2",
+ "react-test-renderer": "18.3.1",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/expo-template/scripts/reset-project.js b/packages/expo-template/scripts/reset-project.js
new file mode 100755
index 0000000..5f81463
--- /dev/null
+++ b/packages/expo-template/scripts/reset-project.js
@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+
+/**
+ * This script is used to reset the project to a blank state.
+ * It moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.
+ * You can remove the `reset-project` script from package.json and safely delete this file after running it.
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+const root = process.cwd();
+const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
+const newDir = "app-example";
+const newAppDir = "app";
+const newDirPath = path.join(root, newDir);
+
+const indexContent = `import { Text, View } from "react-native";
+
+export default function Index() {
+ return (
+
+ Edit app/index.tsx to edit this screen.
+
+ );
+}
+`;
+
+const layoutContent = `import { Stack } from "expo-router";
+
+export default function RootLayout() {
+ return ;
+}
+`;
+
+const moveDirectories = async () => {
+ try {
+ // Create the app-example directory
+ await fs.promises.mkdir(newDirPath, { recursive: true });
+ console.log(`š /${newDir} directory created.`);
+
+ // Move old directories to new app-example directory
+ for (const dir of oldDirs) {
+ const oldDirPath = path.join(root, dir);
+ const newDirPath = path.join(root, newDir, dir);
+ if (fs.existsSync(oldDirPath)) {
+ await fs.promises.rename(oldDirPath, newDirPath);
+ console.log(`ā”ļø /${dir} moved to /${newDir}/${dir}.`);
+ } else {
+ console.log(`ā”ļø /${dir} does not exist, skipping.`);
+ }
+ }
+
+ // Create new /app directory
+ const newAppDirPath = path.join(root, newAppDir);
+ await fs.promises.mkdir(newAppDirPath, { recursive: true });
+ console.log("\nš New /app directory created.");
+
+ // Create index.tsx
+ const indexPath = path.join(newAppDirPath, "index.tsx");
+ await fs.promises.writeFile(indexPath, indexContent);
+ console.log("š app/index.tsx created.");
+
+ // Create _layout.tsx
+ const layoutPath = path.join(newAppDirPath, "_layout.tsx");
+ await fs.promises.writeFile(layoutPath, layoutContent);
+ console.log("š app/_layout.tsx created.");
+
+ console.log("\nā
Project reset complete. Next steps:");
+ console.log(
+ "1. Run `npx expo start` to start a development server.\n2. Edit app/index.tsx to edit the main screen.\n3. Delete the /app-example directory when you're done referencing it."
+ );
+ } catch (error) {
+ console.error(`Error during script execution: ${error}`);
+ }
+};
+
+moveDirectories();
diff --git a/packages/expo-template/tsconfig.json b/packages/expo-template/tsconfig.json
new file mode 100644
index 0000000..909e901
--- /dev/null
+++ b/packages/expo-template/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "expo/tsconfig.base",
+ "compilerOptions": {
+ "strict": true,
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ".expo/types/**/*.ts",
+ "expo-env.d.ts"
+ ]
+}