diff --git a/with-router-rnr/README.md b/with-router-rnr/README.md
new file mode 100644
index 00000000..4a3d27c5
--- /dev/null
+++ b/with-router-rnr/README.md
@@ -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)
diff --git a/with-router-rnr/app.json b/with-router-rnr/app.json
new file mode 100644
index 00000000..55db85cb
--- /dev/null
+++ b/with-router-rnr/app.json
@@ -0,0 +1,11 @@
+{
+ "expo": {
+ "scheme": "acme",
+ "userInterfaceStyle": "automatic",
+ "orientation": "default",
+ "web": {
+ "output": "static"
+ },
+ "plugins": ["expo-router"]
+ }
+}
diff --git a/with-router-rnr/babel.config.js b/with-router-rnr/babel.config.js
new file mode 100644
index 00000000..f3c649bb
--- /dev/null
+++ b/with-router-rnr/babel.config.js
@@ -0,0 +1,9 @@
+module.exports = function (api) {
+ api.cache(true);
+ return {
+ presets: [
+ ["babel-preset-expo", { jsxImportSource: "nativewind" }],
+ "nativewind/babel",
+ ],
+ };
+};
diff --git a/with-router-rnr/global.d.ts b/with-router-rnr/global.d.ts
new file mode 100644
index 00000000..a13e3136
--- /dev/null
+++ b/with-router-rnr/global.d.ts
@@ -0,0 +1 @@
+///
diff --git a/with-router-rnr/metro.config.js b/with-router-rnr/metro.config.js
new file mode 100644
index 00000000..34bf631d
--- /dev/null
+++ b/with-router-rnr/metro.config.js
@@ -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" });
diff --git a/with-router-rnr/nativewind-env.d.ts b/with-router-rnr/nativewind-env.d.ts
new file mode 100644
index 00000000..c0d83807
--- /dev/null
+++ b/with-router-rnr/nativewind-env.d.ts
@@ -0,0 +1,3 @@
+///
+
+// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
\ No newline at end of file
diff --git a/with-router-rnr/package.json b/with-router-rnr/package.json
new file mode 100644
index 00000000..e2e4256b
--- /dev/null
+++ b/with-router-rnr/package.json
@@ -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"
+ }
+}
diff --git a/with-router-rnr/src/app/_layout.tsx b/with-router-rnr/src/app/_layout.tsx
new file mode 100644
index 00000000..26f197f0
--- /dev/null
+++ b/with-router-rnr/src/app/_layout.tsx
@@ -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 (
+
+
+
+ ,
+ }}
+ />
+
+
+
+ );
+}
diff --git a/with-router-rnr/src/app/index.tsx b/with-router-rnr/src/app/index.tsx
new file mode 100644
index 00000000..17782ba5
--- /dev/null
+++ b/with-router-rnr/src/app/index.tsx
@@ -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 (
+
+
+
+
+
+
+ RS
+
+
+
+ Rick Sanchez
+
+
+ Scientist
+
+
+
+
+
+
+ Freelance
+
+
+
+
+
+
+
+ Dimension
+ C-137
+
+
+ Age
+ 70
+
+
+ Species
+ Human
+
+
+
+
+
+ Productivity:
+
+
+
+ {progress}%
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Orders
+
+ Introducing Our Dynamic Orders Dashboard for Seamless Management and
+ Insightful Analysis.
+
+
+
+
+
+
+
+
+
+
+ This Month
+
+ $5,329
+
+
+
+ +10% from last month
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/with-router-rnr/src/components/ThemeToggle.tsx b/with-router-rnr/src/components/ThemeToggle.tsx
new file mode 100644
index 00000000..ce38913b
--- /dev/null
+++ b/with-router-rnr/src/components/ThemeToggle.tsx
@@ -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 (
+
+
+ {isDarkColorScheme ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/with-router-rnr/src/components/ui/avatar.tsx b/with-router-rnr/src/components/ui/avatar.tsx
new file mode 100644
index 00000000..bff7cac0
--- /dev/null
+++ b/with-router-rnr/src/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+import * as AvatarPrimitive from "@rn-primitives/avatar";
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+function Avatar({
+ className,
+ ...props
+}: AvatarPrimitive.RootProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: AvatarPrimitive.ImageProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: AvatarPrimitive.FallbackProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarFallback, AvatarImage };
diff --git a/with-router-rnr/src/components/ui/body.tsx b/with-router-rnr/src/components/ui/body.tsx
new file mode 100644
index 00000000..31382e4f
--- /dev/null
+++ b/with-router-rnr/src/components/ui/body.tsx
@@ -0,0 +1,15 @@
+import { ScrollViewProps } from "react-native";
+import Animated from "react-native-reanimated";
+
+export function BodyScrollView(
+ props: ScrollViewProps & { ref?: React.Ref }
+) {
+ return (
+
+ );
+}
diff --git a/with-router-rnr/src/components/ui/button.tsx b/with-router-rnr/src/components/ui/button.tsx
new file mode 100644
index 00000000..7c533c55
--- /dev/null
+++ b/with-router-rnr/src/components/ui/button.tsx
@@ -0,0 +1,112 @@
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+import { Pressable, Text } from "react-native";
+import { TextClassContext } from "@/components/ui/text";
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "group flex items-center justify-center rounded-md 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",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary web:hover:opacity-90 active:opacity-90",
+ destructive: "bg-destructive web:hover:opacity-90 active:opacity-90",
+ outline:
+ "border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent",
+ secondary: "bg-secondary web:hover:opacity-80 active:opacity-80",
+ ghost:
+ "web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent",
+ link: "web:underline-offset-4 web:hover:underline web:focus:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2 native:h-12 native:px-5 native:py-3",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8 native:h-14",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+const buttonTextVariants = cva(
+ "web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors",
+ {
+ variants: {
+ variant: {
+ default: "text-primary-foreground",
+ destructive: "text-destructive-foreground",
+ outline: "group-active:text-accent-foreground",
+ secondary:
+ "text-secondary-foreground group-active:text-secondary-foreground",
+ ghost: "group-active:text-accent-foreground",
+ link: "text-primary group-active:underline",
+ },
+ size: {
+ default: "",
+ sm: "",
+ lg: "native:text-lg",
+ icon: "",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+type ButtonProps = React.ComponentProps &
+ VariantProps;
+
+function Button({
+ ref,
+ className,
+ variant,
+ size,
+ children,
+ ...props
+}: ButtonProps) {
+ return (
+
+
+ {wrapChildren(children)}
+
+
+ );
+}
+
+import * as AC from "@bacons/apple-colors";
+
+function wrapChildren(children: T): T | React.ReactNode {
+ if (children instanceof Function) {
+ return children;
+ }
+ return React.Children.map(children, (child) => {
+ if (typeof child === "string" || typeof child === "number") {
+ return {child};
+ }
+
+ return child;
+ });
+}
+
+export { Button, buttonTextVariants, buttonVariants };
+export type { ButtonProps };
diff --git a/with-router-rnr/src/components/ui/card.tsx b/with-router-rnr/src/components/ui/card.tsx
new file mode 100644
index 00000000..3c14a2c5
--- /dev/null
+++ b/with-router-rnr/src/components/ui/card.tsx
@@ -0,0 +1,104 @@
+import * as React from "react";
+import { Text, TextProps, View, ViewProps } from "react-native";
+import { TextClassContext } from "@/components/ui/text";
+import { cn } from "@/lib/utils";
+
+function Card({
+ className,
+ ...props
+}: ViewProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function CardHeader({
+ className,
+ ...props
+}: ViewProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function CardTitle({
+ className,
+ ...props
+}: TextProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function CardDescription({
+ className,
+ ...props
+}: TextProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+function CardContent({
+ className,
+ ...props
+}: ViewProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+
+
+ );
+}
+
+function CardFooter({
+ className,
+ ...props
+}: ViewProps & {
+ ref?: React.RefObject;
+}) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+};
diff --git a/with-router-rnr/src/components/ui/progress.tsx b/with-router-rnr/src/components/ui/progress.tsx
new file mode 100644
index 00000000..4567e4e4
--- /dev/null
+++ b/with-router-rnr/src/components/ui/progress.tsx
@@ -0,0 +1,84 @@
+import * as ProgressPrimitive from "@rn-primitives/progress";
+import * as React from "react";
+import { View } from "react-native";
+import Animated, {
+ Extrapolation,
+ interpolate,
+ useAnimatedStyle,
+ useDerivedValue,
+ withSpring,
+} from "react-native-reanimated";
+import { cn } from "@/lib/utils";
+
+function Progress({
+ className,
+ value,
+ indicatorClassName,
+ ...props
+}: ProgressPrimitive.RootProps & {
+ ref?: React.RefObject;
+ indicatorClassName?: string;
+}) {
+ return (
+
+
+
+ );
+}
+
+export { Progress };
+
+function Indicator({
+ value,
+ className,
+}: {
+ value: number | undefined | null;
+ className?: string;
+}) {
+ const progress = useDerivedValue(() => value ?? 0);
+
+ const indicator = useAnimatedStyle(() => {
+ return {
+ width: withSpring(
+ `${interpolate(
+ progress.value,
+ [0, 100],
+ [1, 100],
+ Extrapolation.CLAMP
+ )}%`,
+ { overshootClamping: true }
+ ),
+ };
+ });
+
+ if (process.env.EXPO_OS === "web") {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/with-router-rnr/src/components/ui/text.tsx b/with-router-rnr/src/components/ui/text.tsx
new file mode 100644
index 00000000..5aad6e7f
--- /dev/null
+++ b/with-router-rnr/src/components/ui/text.tsx
@@ -0,0 +1,30 @@
+import * as Slot from "@rn-primitives/slot";
+import * as React from "react";
+import { Text as RNText } from "react-native";
+import { cn } from "@/lib/utils";
+
+const TextClassContext = React.createContext(undefined);
+
+function Text({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps & {
+ ref?: React.RefObject;
+ asChild?: boolean;
+}) {
+ const textClass = React.use(TextClassContext);
+ const Component = asChild ? Slot.Text : RNText;
+ return (
+
+ );
+}
+
+export { Text, TextClassContext };
diff --git a/with-router-rnr/src/components/ui/tooltip.tsx b/with-router-rnr/src/components/ui/tooltip.tsx
new file mode 100644
index 00000000..ebe25f53
--- /dev/null
+++ b/with-router-rnr/src/components/ui/tooltip.tsx
@@ -0,0 +1,48 @@
+import * as TooltipPrimitive from "@rn-primitives/tooltip";
+import * as React from "react";
+import { Platform, StyleSheet } from "react-native";
+import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
+import { TextClassContext } from "@/components/ui/text";
+import { cn } from "@/lib/utils";
+
+const Tooltip = TooltipPrimitive.Root;
+
+const TooltipTrigger = TooltipPrimitive.Trigger;
+
+function TooltipContent({
+ className,
+ sideOffset = 4,
+ portalHost,
+ ...props
+}: TooltipPrimitive.ContentProps & {
+ ref?: React.RefObject;
+ portalHost?: string;
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export { Tooltip, TooltipContent, TooltipTrigger };
diff --git a/with-router-rnr/src/global.css b/with-router-rnr/src/global.css
new file mode 100644
index 00000000..525e2e9e
--- /dev/null
+++ b/with-router-rnr/src/global.css
@@ -0,0 +1,49 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 5.9% 10%;
+ }
+
+ .dark:root {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 72% 51%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ }
+}
\ No newline at end of file
diff --git a/with-router-rnr/src/lib/android-navigation-bar.ts b/with-router-rnr/src/lib/android-navigation-bar.ts
new file mode 100644
index 00000000..f0f70551
--- /dev/null
+++ b/with-router-rnr/src/lib/android-navigation-bar.ts
@@ -0,0 +1,10 @@
+import * as NavigationBar from "expo-navigation-bar";
+import { NAV_THEME } from "@/lib/constants";
+
+export async function setAndroidNavigationBar(theme: "light" | "dark") {
+ if (process.env.EXPO_OS !== "android") return;
+ await NavigationBar.setButtonStyleAsync(theme === "dark" ? "light" : "dark");
+ await NavigationBar.setBackgroundColorAsync(
+ theme === "dark" ? NAV_THEME.dark.background : NAV_THEME.light.background
+ );
+}
diff --git a/with-router-rnr/src/lib/constants.ts b/with-router-rnr/src/lib/constants.ts
new file mode 100644
index 00000000..c6822a37
--- /dev/null
+++ b/with-router-rnr/src/lib/constants.ts
@@ -0,0 +1,20 @@
+import * as AC from "@bacons/apple-colors";
+
+export const NAV_THEME = {
+ light: {
+ background: AC.systemBackground, // background
+ border: AC.separator, // border
+ card: AC.secondarySystemBackground, // card
+ notification: AC.systemRed, // destructive
+ primary: AC.secondaryLabel, // primary
+ text: AC.label, // foreground
+ },
+ dark: {
+ background: AC.systemBackground, // background
+ border: AC.separator, // border
+ card: AC.secondarySystemBackground, // card
+ notification: AC.systemRed, // destructive
+ primary: AC.secondaryLabel, // primary
+ text: AC.label, // foreground
+ },
+};
diff --git a/with-router-rnr/src/lib/icons/Info.tsx b/with-router-rnr/src/lib/icons/Info.tsx
new file mode 100644
index 00000000..70282e4b
--- /dev/null
+++ b/with-router-rnr/src/lib/icons/Info.tsx
@@ -0,0 +1,4 @@
+import { Info } from 'lucide-react-native';
+import { iconWithClassName } from './iconWithClassName';
+iconWithClassName(Info);
+export { Info };
\ No newline at end of file
diff --git a/with-router-rnr/src/lib/icons/MoonStar.tsx b/with-router-rnr/src/lib/icons/MoonStar.tsx
new file mode 100644
index 00000000..c884cd1a
--- /dev/null
+++ b/with-router-rnr/src/lib/icons/MoonStar.tsx
@@ -0,0 +1,4 @@
+import { MoonStar } from 'lucide-react-native';
+import { iconWithClassName } from './iconWithClassName';
+iconWithClassName(MoonStar);
+export { MoonStar };
\ No newline at end of file
diff --git a/with-router-rnr/src/lib/icons/Sun.tsx b/with-router-rnr/src/lib/icons/Sun.tsx
new file mode 100644
index 00000000..3e2a642d
--- /dev/null
+++ b/with-router-rnr/src/lib/icons/Sun.tsx
@@ -0,0 +1,4 @@
+import { Sun } from 'lucide-react-native';
+import { iconWithClassName } from './iconWithClassName';
+iconWithClassName(Sun);
+export { Sun };
\ No newline at end of file
diff --git a/with-router-rnr/src/lib/icons/iconWithClassName.ts b/with-router-rnr/src/lib/icons/iconWithClassName.ts
new file mode 100644
index 00000000..c475ed11
--- /dev/null
+++ b/with-router-rnr/src/lib/icons/iconWithClassName.ts
@@ -0,0 +1,14 @@
+import type { LucideIcon } from 'lucide-react-native';
+import { cssInterop } from 'nativewind';
+
+export function iconWithClassName(icon: LucideIcon) {
+ cssInterop(icon, {
+ className: {
+ target: 'style',
+ nativeStyleToProp: {
+ color: true,
+ opacity: true,
+ },
+ },
+ });
+}
diff --git a/with-router-rnr/src/lib/useColorScheme.tsx b/with-router-rnr/src/lib/useColorScheme.tsx
new file mode 100644
index 00000000..8e171b6e
--- /dev/null
+++ b/with-router-rnr/src/lib/useColorScheme.tsx
@@ -0,0 +1,11 @@
+import { useColorScheme as useNativewindColorScheme } from 'nativewind';
+
+export function useColorScheme() {
+ const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme();
+ return {
+ colorScheme: colorScheme ?? 'dark',
+ isDarkColorScheme: colorScheme === 'dark',
+ setColorScheme,
+ toggleColorScheme,
+ };
+}
diff --git a/with-router-rnr/src/lib/useGlobals.android.ts b/with-router-rnr/src/lib/useGlobals.android.ts
new file mode 100644
index 00000000..20a065f5
--- /dev/null
+++ b/with-router-rnr/src/lib/useGlobals.android.ts
@@ -0,0 +1,10 @@
+import { setAndroidNavigationBar } from "./android-navigation-bar";
+import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
+import { Appearance } from "react-native";
+
+// A hook that runs in the root layout to set global styles and behaviors.
+export function useGlobals() {
+ useIsomorphicLayoutEffect(() => {
+ setAndroidNavigationBar(Appearance.getColorScheme() ?? "light");
+ }, []);
+}
diff --git a/with-router-rnr/src/lib/useGlobals.ts b/with-router-rnr/src/lib/useGlobals.ts
new file mode 100644
index 00000000..e990f423
--- /dev/null
+++ b/with-router-rnr/src/lib/useGlobals.ts
@@ -0,0 +1,2 @@
+// A hook that runs in the root layout to set global styles and behaviors.
+export function useGlobals() {}
diff --git a/with-router-rnr/src/lib/useGlobals.web.ts b/with-router-rnr/src/lib/useGlobals.web.ts
new file mode 100644
index 00000000..c759e29f
--- /dev/null
+++ b/with-router-rnr/src/lib/useGlobals.web.ts
@@ -0,0 +1,9 @@
+import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
+
+// A hook that runs in the root layout to set global styles and behaviors.
+export function useGlobals() {
+ useIsomorphicLayoutEffect(() => {
+ // Adds the background color to the html element to prevent white background on overscroll.
+ document.documentElement.classList.add("bg-background");
+ }, []);
+}
diff --git a/with-router-rnr/src/lib/useIsomorphicLayoutEffect.ts b/with-router-rnr/src/lib/useIsomorphicLayoutEffect.ts
new file mode 100644
index 00000000..19de1174
--- /dev/null
+++ b/with-router-rnr/src/lib/useIsomorphicLayoutEffect.ts
@@ -0,0 +1,4 @@
+import * as React from "react";
+
+export const useIsomorphicLayoutEffect =
+ typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
diff --git a/with-router-rnr/src/lib/utils.ts b/with-router-rnr/src/lib/utils.ts
new file mode 100644
index 00000000..2819a830
--- /dev/null
+++ b/with-router-rnr/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/with-router-rnr/tailwind.config.js b/with-router-rnr/tailwind.config.js
new file mode 100644
index 00000000..4e08c947
--- /dev/null
+++ b/with-router-rnr/tailwind.config.js
@@ -0,0 +1,68 @@
+const { hairlineWidth } = require("nativewind/theme");
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: "class",
+ content: ["./src/**/*.{js,jsx,ts,tsx}", "./node_modules/@rnr/**/*.{ts,tsx}"],
+ presets: [require("nativewind/preset")],
+ theme: {
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderWidth: {
+ hairline: hairlineWidth(),
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ },
+ },
+ },
+ future: {
+ hoverOnlyWhenSupported: true,
+ },
+ plugins: [require("tailwindcss-animate")],
+};
diff --git a/with-router-rnr/tsconfig.json b/with-router-rnr/tsconfig.json
new file mode 100644
index 00000000..decdf0b6
--- /dev/null
+++ b/with-router-rnr/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ]
+ }
+ },
+ "extends": "expo/tsconfig.base",
+ "include": [
+ "global.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "nativewind-env.d.ts"
+ ]
+}
\ No newline at end of file