mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add unistyles
This commit is contained in:
25
apps/cli/templates/frontend/native/nativewind/_gitignore
Normal file
25
apps/cli/templates/frontend/native/nativewind/_gitignore
Normal file
@@ -0,0 +1,25 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
# expo router
|
||||
expo-env.d.ts
|
||||
|
||||
.env
|
||||
.cache
|
||||
|
||||
ios
|
||||
android
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
2
apps/cli/templates/frontend/native/nativewind/app-env.d.ts
vendored
Normal file
2
apps/cli/templates/frontend/native/nativewind/app-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// @ts-ignore
|
||||
/// <reference types="nativewind/types" />
|
||||
46
apps/cli/templates/frontend/native/nativewind/app.json
Normal file
46
apps/cli/templates/frontend/native/nativewind/app.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "my-better-t-app",
|
||||
"slug": "my-better-t-app",
|
||||
"version": "1.0.0",
|
||||
"scheme": "my-better-t-app",
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-secure-store",
|
||||
"expo-web-browser"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"tsconfigPaths": true
|
||||
},
|
||||
"newArchEnabled": true,
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.amanvarshney01.mybettertapp"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.amanvarshney01.mybettertapp",
|
||||
"edgeToEdgeEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
import { TabBarIcon } from "@/components/tabbar-icon";
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: "black",
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Tab One",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="two"
|
||||
options={{
|
||||
title: "Tab Two",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function TabOne() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="p-6 flex-1 justify-center">
|
||||
<Text className="text-2xl font-bold text-foreground text-center mb-4">
|
||||
Tab One
|
||||
</Text>
|
||||
<Text className="text-foreground text-center">
|
||||
This is the first tab of the application.
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function TabTwo() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="p-6 flex-1 justify-center">
|
||||
<Text className="text-2xl font-bold text-foreground text-center mb-4">
|
||||
Tab Two
|
||||
</Text>
|
||||
<Text className="text-foreground text-center">
|
||||
This is the second tab of the application.
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { Link } from "expo-router";
|
||||
import { Drawer } from "expo-router/drawer";
|
||||
|
||||
import { HeaderButton } from "@/components/header-button";
|
||||
|
||||
const DrawerLayout = () => {
|
||||
return (
|
||||
<Drawer>
|
||||
<Drawer.Screen
|
||||
name="index"
|
||||
options={{
|
||||
headerTitle: "Home",
|
||||
drawerLabel: "Home",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons name="home-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="(tabs)"
|
||||
options={{
|
||||
headerTitle: "Tabs",
|
||||
drawerLabel: "Tabs",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<MaterialIcons name="border-bottom" size={size} color={color} />
|
||||
),
|
||||
headerRight: () => (
|
||||
<Link href="/modal" asChild>
|
||||
<HeaderButton />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawerLayout;
|
||||
@@ -0,0 +1,74 @@
|
||||
import { View, Text, ScrollView } from "react-native";
|
||||
import { Container } from "@/components/container";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { trpc } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
{{#if (eq backend "convex")}}
|
||||
import { useQuery } from "convex/react";
|
||||
import { api } from "@{{ projectName }}/backend/convex/_generated/api.js";
|
||||
{{/if}}
|
||||
|
||||
export default function Home() {
|
||||
{{#if (eq api "orpc")}}
|
||||
const healthCheck = useQuery(orpc.healthCheck.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq backend "convex")}}
|
||||
const healthCheck = useQuery(api.healthCheck.get);
|
||||
{{/if}}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ScrollView className="py-4 flex-1">
|
||||
<Text className="font-mono text-foreground text-2xl font-bold mb-6">
|
||||
BETTER T STACK
|
||||
</Text>
|
||||
|
||||
<View className="rounded-lg border border-foreground p-4">
|
||||
<Text className="mb-2 font-medium text-foreground">API Status</Text>
|
||||
<View className="flex-row items-center gap-2">
|
||||
<View
|
||||
className={`h-2.5 w-2.5 rounded-full ${
|
||||
{{#if (or (eq api "orpc") (eq api "trpc"))}}
|
||||
healthCheck.data ? "bg-green-500" : "bg-red-500"
|
||||
{{else}}
|
||||
healthCheck ? "bg-green-500" : "bg-red-500"
|
||||
{{/if}}
|
||||
}`}
|
||||
/>
|
||||
<Text className="text-sm text-foreground">
|
||||
{{#if (eq api "orpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
{{#if (eq backend "convex")}}
|
||||
{healthCheck === undefined
|
||||
? "Checking..."
|
||||
: healthCheck === "OK"
|
||||
? "Connected"
|
||||
: "Error"}
|
||||
{{/if}}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
47
apps/cli/templates/frontend/native/nativewind/app/+html.tsx
Normal file
47
apps/cli/templates/frontend/native/nativewind/app/+html.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ScrollViewStyleReset } from 'expo-router/html';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
// This file is web-only and used to configure the root HTML for every
|
||||
// web page during static rendering.
|
||||
// The contents of this function only run in Node.js environments and
|
||||
// do not have access to the DOM or browser APIs.
|
||||
export default function Root({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta content="IE=edge" httpEquiv="X-UA-Compatible" />
|
||||
|
||||
{/*
|
||||
This viewport disables scaling which makes the mobile website act more like a native app.
|
||||
However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
*/}
|
||||
<meta
|
||||
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"
|
||||
name="viewport"
|
||||
/>
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<Container>
|
||||
<Text className="text-xl font-bold">This screen doesn't exist.</Text>
|
||||
<Link href="/" className="mt-4 pt-4">
|
||||
<Text className="text-base text-[#2e78b7]">Go to home screen!</Text>
|
||||
</Link>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{{#if (eq backend "convex")}}
|
||||
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
||||
{{else}}
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
{{/if}}
|
||||
import { Stack } from "expo-router";
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
type Theme,
|
||||
ThemeProvider,
|
||||
} from "@react-navigation/native";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import "../global.css";
|
||||
{{#if (eq api "trpc")}}
|
||||
import { queryClient } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
import { queryClient } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
import { NAV_THEME } from "@/lib/constants";
|
||||
import React, { useRef } from "react";
|
||||
import { useColorScheme } from "@/lib/use-color-scheme";
|
||||
import { Platform } from "react-native";
|
||||
import { setAndroidNavigationBar } from "@/lib/android-navigation-bar";
|
||||
|
||||
const LIGHT_THEME: Theme = {
|
||||
...DefaultTheme,
|
||||
colors: NAV_THEME.light,
|
||||
};
|
||||
const DARK_THEME: Theme = {
|
||||
...DarkTheme,
|
||||
colors: NAV_THEME.dark,
|
||||
};
|
||||
|
||||
export const unstable_settings = {
|
||||
initialRouteName: "(drawer)",
|
||||
};
|
||||
|
||||
{{#if (eq backend "convex")}}
|
||||
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, {
|
||||
unsavedChangesWarning: false,
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
export default function RootLayout() {
|
||||
const hasMounted = useRef(false);
|
||||
const { colorScheme, isDarkColorScheme } = useColorScheme();
|
||||
const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (hasMounted.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
document.documentElement.classList.add("bg-background");
|
||||
}
|
||||
setAndroidNavigationBar(colorScheme);
|
||||
setIsColorSchemeLoaded(true);
|
||||
hasMounted.current = true;
|
||||
}, []);
|
||||
|
||||
if (!isColorSchemeLoaded) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
{{#if (eq backend "convex")}}
|
||||
<ConvexProvider client={convex}>
|
||||
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
|
||||
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
|
||||
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
name="modal"
|
||||
options=\{{ title: "Modal", presentation: "modal" }}
|
||||
/>
|
||||
</Stack>
|
||||
</GestureHandlerRootView>
|
||||
</ThemeProvider>
|
||||
</ConvexProvider>
|
||||
{{else}}
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
|
||||
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
|
||||
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
name="modal"
|
||||
options=\{{ title: "Modal", presentation: "modal" }}
|
||||
/>
|
||||
</Stack>
|
||||
</GestureHandlerRootView>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
{{/if}}
|
||||
);
|
||||
}
|
||||
|
||||
const useIsomorphicLayoutEffect =
|
||||
Platform.OS === "web" && typeof window === "undefined"
|
||||
? React.useEffect
|
||||
: React.useLayoutEffect;
|
||||
12
apps/cli/templates/frontend/native/nativewind/app/modal.tsx
Normal file
12
apps/cli/templates/frontend/native/nativewind/app/modal.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function Modal() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="flex-1 justify-center items-center">
|
||||
<Text className="text-xl font-bold text-foreground">Modal View</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
const plugins = [];
|
||||
|
||||
plugins.push('react-native-reanimated/plugin');
|
||||
|
||||
return {
|
||||
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
|
||||
plugins,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SafeAreaView } from "react-native";
|
||||
|
||||
export const Container = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<SafeAreaView className="flex flex-1 p-4 bg-background">
|
||||
{children}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { forwardRef } from 'react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
|
||||
export const HeaderButton = forwardRef<typeof Pressable, { onPress?: () => void }>(
|
||||
({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={25}
|
||||
color="gray"
|
||||
style={[
|
||||
styles.headerRight,
|
||||
{
|
||||
opacity: pressed ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
headerRight: {
|
||||
marginRight: 15,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export const TabBarIcon = (props: {
|
||||
name: React.ComponentProps<typeof FontAwesome>['name'];
|
||||
color: string;
|
||||
}) => {
|
||||
return <FontAwesome size={28} style={styles.tabBarIcon} {...props} />;
|
||||
};
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
tabBarIcon: {
|
||||
marginBottom: -3,
|
||||
},
|
||||
});
|
||||
25
apps/cli/templates/frontend/native/nativewind/global.css
Normal file
25
apps/cli/templates/frontend/native/nativewind/global.css
Normal file
@@ -0,0 +1,25 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--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%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
}
|
||||
|
||||
.dark:root {
|
||||
--background: 240 10% 3.9%;
|
||||
--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%;
|
||||
--destructive: 0 72% 51%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as NavigationBar from "expo-navigation-bar";
|
||||
import { Platform } from "react-native";
|
||||
import { NAV_THEME } from "@/lib/constants";
|
||||
|
||||
export async function setAndroidNavigationBar(theme: "light" | "dark") {
|
||||
if (Platform.OS !== "android") return;
|
||||
await NavigationBar.setButtonStyleAsync(theme === "dark" ? "light" : "dark");
|
||||
await NavigationBar.setBackgroundColorAsync(
|
||||
theme === "dark" ? NAV_THEME.dark.background : NAV_THEME.light.background,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export const NAV_THEME = {
|
||||
light: {
|
||||
background: "hsl(0 0% 100%)",
|
||||
border: "hsl(240 5.9% 90%)",
|
||||
card: "hsl(0 0% 100%)",
|
||||
notification: "hsl(0 84.2% 60.2%)",
|
||||
primary: "hsl(240 5.9% 10%)",
|
||||
text: "hsl(240 10% 3.9%)",
|
||||
},
|
||||
dark: {
|
||||
background: "hsl(240 10% 3.9%)",
|
||||
border: "hsl(240 3.7% 15.9%)",
|
||||
card: "hsl(240 10% 3.9%)",
|
||||
notification: "hsl(0 72% 51%)",
|
||||
primary: "hsl(0 0% 98%)",
|
||||
text: "hsl(0 0% 98%)",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useColorScheme as useNativewindColorScheme } from "nativewind";
|
||||
|
||||
export function useColorScheme() {
|
||||
const { colorScheme, setColorScheme, toggleColorScheme } =
|
||||
useNativewindColorScheme();
|
||||
return {
|
||||
colorScheme: colorScheme ?? "dark",
|
||||
isDarkColorScheme: colorScheme === "dark",
|
||||
setColorScheme,
|
||||
toggleColorScheme,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { FileStore } = require("metro-cache");
|
||||
const { withNativeWind } = require("nativewind/metro");
|
||||
const path = require("node:path");
|
||||
|
||||
const config = withTurborepoManagedCache(
|
||||
withMonorepoPaths(
|
||||
withNativeWind(getDefaultConfig(__dirname), {
|
||||
input: "./global.css",
|
||||
configPath: "./tailwind.config.js",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
config.resolver.unstable_enablePackageExports = true;
|
||||
|
||||
config.resolver.disableHierarchicalLookup = true;
|
||||
|
||||
module.exports = config;
|
||||
|
||||
/**
|
||||
* Add the monorepo paths to the Metro config.
|
||||
* This allows Metro to resolve modules from the monorepo.
|
||||
*
|
||||
* @see https://docs.expo.dev/guides/monorepos/#modify-the-metro-config
|
||||
* @param {import('expo/metro-config').MetroConfig} config
|
||||
* @returns {import('expo/metro-config').MetroConfig}
|
||||
*/
|
||||
function withMonorepoPaths(config) {
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "../..");
|
||||
|
||||
// #1 - Watch all files in the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
|
||||
// #2 - Resolve modules within the project's `node_modules` first, then all monorepo modules
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the Metro cache to the `.cache/metro` folder.
|
||||
* If you have any environment variables, you can configure Turborepo to invalidate it when needed.
|
||||
*
|
||||
* @see https://turbo.build/repo/docs/reference/configuration#env
|
||||
* @param {import('expo/metro-config').MetroConfig} config
|
||||
* @returns {import('expo/metro-config').MetroConfig}
|
||||
*/
|
||||
function withTurborepoManagedCache(config) {
|
||||
config.cacheStores = [
|
||||
new FileStore({ root: path.join(__dirname, ".cache/metro") }),
|
||||
];
|
||||
return config;
|
||||
}
|
||||
45
apps/cli/templates/frontend/native/nativewind/package.json
Normal file
45
apps/cli/templates/frontend/native/nativewind/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "native",
|
||||
"version": "1.0.0",
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
"dev": "expo start --clear",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"prebuild": "expo prebuild",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.4",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/drawer": "^7.1.1",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@tanstack/react-form": "^1.0.5",
|
||||
"@tanstack/react-query": "^5.69.2",
|
||||
"expo": "^53.0.4",
|
||||
"expo-constants": "~17.1.4",
|
||||
"expo-linking": "~7.1.4",
|
||||
"expo-navigation-bar": "~4.2.3",
|
||||
"expo-router": "~5.0.3",
|
||||
"expo-secure-store": "~14.2.3",
|
||||
"expo-status-bar": "~2.2.3",
|
||||
"expo-system-ui": "~5.0.6",
|
||||
"expo-web-browser": "~14.1.6",
|
||||
"nativewind": "^4.1.23",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.1",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "5.3.0",
|
||||
"react-native-screens": "~4.10.0",
|
||||
"react-native-web": "^0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@types/react": "~19.0.10",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.8.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const { hairlineWidth } = require("nativewind/theme");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
content: ["./app/**/*.{js,ts,tsx}", "./components/**/*.{js,ts,tsx}"],
|
||||
|
||||
presets: [require("nativewind/preset")],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
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))",
|
||||
},
|
||||
},
|
||||
borderWidth: {
|
||||
hairline: hairlineWidth(),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
18
apps/cli/templates/frontend/native/nativewind/tsconfig.json
Normal file
18
apps/cli/templates/frontend/native/nativewind/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts",
|
||||
"nativewind-env.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user