Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions with-router-rnr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Expo Router and React Native Reusables

Use [Expo Router](https://docs.expo.dev/router/introduction/) with [React Native Reusables](https://github.com/mrzachnugent/react-native-reusables/tree/main) components. This works like a universal shadcn/ui, but for all platforms. It leverages nativewind for styling and combines a number of community libraries to provide a consistent experience across web, iOS, and Android.

## 🚀 How to use

```sh
npx create-expo-app -e with-router-rnr
```

Read the RNR docs to learn [how to add components](https://www.reactnativereusables.com/getting-started/initial-setup/).

## Deploy

Deploy on all platforms with Expo Application Services (EAS).

- Deploy the website: `npx eas-cli deploy` — [Learn more](https://docs.expo.dev/eas/hosting/get-started/)
- Deploy on iOS and Android using: `npx eas-cli build` — [Learn more](https://expo.dev/eas)
11 changes: 11 additions & 0 deletions with-router-rnr/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"expo": {
"scheme": "acme",
"userInterfaceStyle": "automatic",
"orientation": "default",
"web": {
"output": "static"
},
"plugins": ["expo-router"]
}
}
9 changes: 9 additions & 0 deletions with-router-rnr/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
1 change: 1 addition & 0 deletions with-router-rnr/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="nativewind/types" />
6 changes: 6 additions & 0 deletions with-router-rnr/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, { input: "./src/global.css" });
3 changes: 3 additions & 0 deletions with-router-rnr/nativewind-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// <reference types="nativewind/types" />

// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
64 changes: 64 additions & 0 deletions with-router-rnr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "with-router-rnr",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"deploy": "npx expo export -p web && npx eas-cli@latest deploy"
},
"dependencies": {
"@bacons/apple-colors": "^0.0.8",
"@gorhom/bottom-sheet": "^5.1.4",
"@hookform/resolvers": "^3.3.4",
"@react-native-community/slider": "4.5.6",
"@react-navigation/material-top-tabs": "^7.0.0",
"@react-navigation/native": "^7.0.0",
"@rn-primitives/aspect-ratio": "~1.2.0",
"@rn-primitives/avatar": "~1.2.0",
"@rn-primitives/collapsible": "~1.2.0",
"@rn-primitives/label": "~1.2.0",
"@rn-primitives/portal": "~1.3.0",
"@rn-primitives/progress": "~1.2.0",
"@rn-primitives/slider": "~1.2.0",
"@rn-primitives/toast": "~1.2.0",
"@rn-primitives/toolbar": "~1.2.0",
"@rn-primitives/tooltip": "~1.2.0",
"@shopify/flash-list": "1.7.6",
"@tanstack/react-table": "^8.11.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"expo": "^53.0.9",
"expo-font": "~13.3.1",
"expo-haptics": "~14.1.4",
"expo-linking": "~7.1.5",
"expo-navigation-bar": "^4.2.6",
"expo-router": "~5.0.7",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"expo-system-ui": "~5.0.7",
"lucide-react-native": "^0.511.0",
"nativewind": "^4.1.23",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.49.2",
"react-native": "0.79.2",
"react-native-calendars": "^1.1302.0",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.5",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.10.0",
"react-native-svg": "15.11.2",
"react-native-tab-view": "^3.5.2",
"react-native-toast-message": "^2.2.0",
"react-native-web": "~0.20.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~19.0.10",
"tailwindcss": "3.3.5",
"typescript": "~5.8.3"
}
}
68 changes: 68 additions & 0 deletions with-router-rnr/src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import "@/global.css";

import {
DarkTheme,
DefaultTheme,
Theme,
ThemeProvider,
} from "@react-navigation/native";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import * as React from "react";
import { NAV_THEME } from "@/lib/constants";
import { useColorScheme } from "@/lib/useColorScheme";
import { PortalHost } from "@rn-primitives/portal";
import { ThemeToggle } from "@/components/ThemeToggle";
import { useGlobals } from "@/lib/useGlobals";
import { NativeStackNavigationOptions } from "@react-navigation/native-stack";

const LIGHT_THEME: Theme = {
...DefaultTheme,
colors: NAV_THEME.light,
};
const DARK_THEME: Theme = {
...DarkTheme,
colors: NAV_THEME.dark,
};

export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from "expo-router";

// These are the default stack options for iOS, they disable on other platforms.
const DEFAULT_STACK_HEADER: NativeStackNavigationOptions =
process.env.EXPO_OS !== "ios"
? {}
: {
headerTransparent: true,
headerBlurEffect: "systemChromeMaterial",
headerShadowVisible: true,
headerLargeTitleShadowVisible: false,
headerLargeStyle: {
backgroundColor: "transparent",
},
headerLargeTitle: true,
};

export default function RootLayout() {
useGlobals();

const { isDarkColorScheme } = useColorScheme();

return (
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<Stack screenOptions={DEFAULT_STACK_HEADER}>
<Stack.Screen
name="index"
options={{
title: "Starter Base",
headerRight: () => <ThemeToggle />,
}}
/>
</Stack>
<PortalHost />
</ThemeProvider>
);
}
156 changes: 156 additions & 0 deletions with-router-rnr/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import * as React from "react";
import { View } from "react-native";
import Animated, {
FadeInUp,
FadeOutDown,
LayoutAnimationConfig,
} from "react-native-reanimated";
import { Info } from "@/lib/icons/Info";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Text } from "@/components/ui/text";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { BodyScrollView } from "@/components/ui/body";

const GITHUB_AVATAR_URI = "https://github.com/expo.png";

export default function Screen() {
const [progress, setProgress] = React.useState(78);

function updateProgressValue() {
setProgress(Math.floor(Math.random() * 100));
}
return (
<BodyScrollView
className="flex-1 bg-secondary/30"
contentContainerClassName="gap-4 p-4 sm:px-6 md:gap-8"
>
<Card className="w-full p-6 rounded-2xl">
<CardHeader className="items-center">
<Avatar alt="Rick Sanchez's Avatar" className="w-24 h-24">
<AvatarImage source={{ uri: GITHUB_AVATAR_URI }} />
<AvatarFallback>
<Text>RS</Text>
</AvatarFallback>
</Avatar>
<View className="p-3" />
<CardTitle className="pb-2 text-center">Rick Sanchez</CardTitle>
<View className="flex-row">
<CardDescription className="text-base font-semibold">
Scientist
</CardDescription>
<Tooltip delayDuration={150}>
<TooltipTrigger className="px-2 pb-0.5 active:opacity-50">
<Info
size={14}
strokeWidth={2.5}
className="w-4 h-4 text-foreground/70"
/>
</TooltipTrigger>
<TooltipContent className="py-2 px-4 shadow">
<Text className="native:text-lg">Freelance</Text>
</TooltipContent>
</Tooltip>
</View>
</CardHeader>
<CardContent>
<View className="flex-row justify-around gap-3">
<View className="items-center">
<Text className="text-sm text-muted-foreground">Dimension</Text>
<Text className="text-xl font-semibold">C-137</Text>
</View>
<View className="items-center">
<Text className="text-sm text-muted-foreground">Age</Text>
<Text className="text-xl font-semibold">70</Text>
</View>
<View className="items-center">
<Text className="text-sm text-muted-foreground">Species</Text>
<Text className="text-xl font-semibold">Human</Text>
</View>
</View>
</CardContent>
<CardFooter className="flex-col gap-3 pb-0">
<View className="flex-row items-center overflow-hidden">
<Text className="text-sm text-muted-foreground">Productivity:</Text>
<LayoutAnimationConfig skipEntering>
<Animated.View
key={progress}
entering={FadeInUp}
exiting={FadeOutDown}
className="w-11 items-center"
>
<Text className="text-sm font-bold text-sky-600">
{progress}%
</Text>
</Animated.View>
</LayoutAnimationConfig>
</View>
<Progress
value={progress}
className="h-2"
indicatorClassName="bg-sky-600"
/>
<View />
<Button
variant="outline"
className="shadow shadow-foreground/5"
onPress={updateProgressValue}
>
<Text>Update</Text>
</Button>
</CardFooter>
</Card>

<Card className="sm:col-span-2 rounded-2xl">
<CardHeader className="pb-3">
<CardTitle>Your Orders</CardTitle>
<CardDescription className="max-w-lg text-balance leading-relaxed">
Introducing Our Dynamic Orders Dashboard for Seamless Management and
Insightful Analysis.
</CardDescription>
</CardHeader>

<CardFooter>
<Button
onPress={() => {
// TODO...
// notify();
}}
>
Create New Order
</Button>
</CardFooter>
</Card>

<Card className="sm:col-span-2 rounded-2xl">
<CardHeader className="pb-2">
<CardDescription>This Month</CardDescription>

<CardTitle className="text-4xl">$5,329</CardTitle>
</CardHeader>
<CardContent>
<Text className="text-xs text-muted-foreground">
+10% from last month
</Text>
</CardContent>
<CardFooter className="gap-2 flex-wrap">
<Progress value={12} aria-label="12% increase" />
<Button onPress={() => {}}>See More</Button>
</CardFooter>
</Card>
</BodyScrollView>
);
}
30 changes: 30 additions & 0 deletions with-router-rnr/src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Pressable, View } from "react-native";
import { setAndroidNavigationBar } from "@/lib/android-navigation-bar";
import { MoonStar } from "@/lib/icons/MoonStar";
import { Sun } from "@/lib/icons/Sun";
import { useColorScheme } from "@/lib/useColorScheme";

export function ThemeToggle() {
const { isDarkColorScheme, setColorScheme } = useColorScheme();

function toggleColorScheme() {
const newTheme = isDarkColorScheme ? "light" : "dark";
setColorScheme(newTheme);
setAndroidNavigationBar(newTheme);
}

return (
<Pressable
onPress={toggleColorScheme}
className="web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2 active:opacity-70"
>
<View className="flex-1 aspect-square pt-0.5 justify-center items-start web:px-5">
{isDarkColorScheme ? (
<MoonStar className="text-foreground" size={23} strokeWidth={1.25} />
) : (
<Sun className="text-foreground" size={24} strokeWidth={1.25} />
)}
</View>
</Pressable>
);
}
Loading