From 6c269a4c5b2bb2d93833cfbb26db7f690c094379 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 7 May 2025 14:29:11 +0530 Subject: [PATCH] add unistyles --- .changeset/weak-carrots-return.md | 5 + apps/cli/README.md | 33 +- apps/cli/src/helpers/api-setup.ts | 12 +- apps/cli/src/helpers/auth-setup.ts | 6 +- apps/cli/src/helpers/create-readme.ts | 19 +- apps/cli/src/helpers/env-setup.ts | 5 +- apps/cli/src/helpers/post-installation.ts | 39 +- apps/cli/src/helpers/template-manager.ts | 88 +++- apps/cli/src/index.ts | 59 ++- apps/cli/src/prompts/backend-framework.ts | 1 - apps/cli/src/prompts/examples.ts | 5 +- apps/cli/src/prompts/frontend-option.ts | 23 +- apps/cli/src/types.ts | 3 +- .../lib/auth-client.ts.hbs} | 0 .../app/(drawer)/index.tsx.hbs | 0 .../components/sign-in.tsx.hbs | 0 .../components/sign-up.tsx.hbs | 0 .../unistyles/app/(drawer)/index.tsx.hbs | 179 ++++++++ .../unistyles/components/sign-in.tsx.hbs | 134 ++++++ .../unistyles/components/sign-up.tsx.hbs | 152 +++++++ .../assets/adaptive-icon.png | Bin .../{ => native-base}/assets/favicon.png | Bin .../native/{ => native-base}/assets/icon.png | Bin .../{ => native-base}/assets/splash.png | Bin .../native/{ => nativewind}/_gitignore | 0 .../native/{ => nativewind}/app-env.d.ts | 0 .../frontend/native/{ => nativewind}/app.json | 0 .../app/(drawer)/(tabs)/_layout.tsx | 0 .../app/(drawer)/(tabs)/index.tsx | 0 .../app/(drawer)/(tabs)/two.tsx | 0 .../{ => nativewind}/app/(drawer)/_layout.tsx | 0 .../app/(drawer)/index.tsx.hbs | 0 .../native/{ => nativewind}/app/+html.tsx | 0 .../{ => nativewind}/app/+not-found.tsx | 0 .../{ => nativewind}/app/_layout.tsx.hbs | 0 .../native/{ => nativewind}/app/modal.tsx | 0 .../native/{ => nativewind}/babel.config.js | 0 .../{ => nativewind}/components/container.tsx | 0 .../components/header-button.tsx | 0 .../components/tabbar-icon.tsx | 0 .../native/{ => nativewind}/global.css | 0 .../lib/android-navigation-bar.tsx | 0 .../native/{ => nativewind}/lib/constants.ts | 0 .../{ => nativewind}/lib/use-color-scheme.ts | 0 .../native/{ => nativewind}/metro.config.js | 0 .../native/{ => nativewind}/package.json | 0 .../{ => nativewind}/tailwind.config.js | 0 .../native/{ => nativewind}/tsconfig.json | 0 .../frontend/native/unistyles/_gitignore | 24 ++ .../frontend/native/unistyles/app.json | 44 ++ .../unistyles/app/(drawer)/(tabs)/_layout.tsx | 34 ++ .../unistyles/app/(drawer)/(tabs)/index.tsx | 29 ++ .../unistyles/app/(drawer)/(tabs)/two.tsx | 29 ++ .../native/unistyles/app/(drawer)/_layout.tsx | 59 +++ .../unistyles/app/(drawer)/index.tsx.hbs | 115 +++++ .../frontend/native/unistyles/app/+html.tsx | 48 +++ .../native/unistyles/app/+not-found.tsx | 34 ++ .../native/unistyles/app/_layout.tsx.hbs | 77 ++++ .../frontend/native/unistyles/app/modal.tsx | 29 ++ .../frontend/native/unistyles/babel.config.js | 20 + .../frontend/native/unistyles/breakpoints.ts | 9 + .../native/unistyles/components/container.tsx | 20 + .../unistyles/components/header-button.tsx | 31 ++ .../unistyles/components/tabbar-icon.tsx | 15 + .../frontend/native/unistyles/expo-env.d.ts | 3 + .../frontend/native/unistyles/index.js | 2 + .../frontend/native/unistyles/metro.config.js | 20 + .../frontend/native/unistyles/package.json | 47 ++ .../frontend/native/unistyles/theme.ts | 35 ++ .../frontend/native/unistyles/tsconfig.json | 12 + .../frontend/native/unistyles/unistyles.ts | 27 ++ .../web-base/src/components/header.tsx.hbs | 11 +- .../app/(home)/_components/StackArchitech.tsx | 408 ++++++++++++------ apps/web/src/lib/constant.ts | 25 +- 74 files changed, 1762 insertions(+), 208 deletions(-) create mode 100644 .changeset/weak-carrots-return.md rename apps/cli/templates/auth/native/{lib/auth-client.ts => native-base/lib/auth-client.ts.hbs} (100%) rename apps/cli/templates/auth/native/{ => nativewind}/app/(drawer)/index.tsx.hbs (100%) rename apps/cli/templates/auth/native/{ => nativewind}/components/sign-in.tsx.hbs (100%) rename apps/cli/templates/auth/native/{ => nativewind}/components/sign-up.tsx.hbs (100%) create mode 100644 apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs create mode 100644 apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs rename apps/cli/templates/frontend/native/{ => native-base}/assets/adaptive-icon.png (100%) rename apps/cli/templates/frontend/native/{ => native-base}/assets/favicon.png (100%) rename apps/cli/templates/frontend/native/{ => native-base}/assets/icon.png (100%) rename apps/cli/templates/frontend/native/{ => native-base}/assets/splash.png (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/_gitignore (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app-env.d.ts (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app.json (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/(drawer)/(tabs)/_layout.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/(drawer)/(tabs)/index.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/(drawer)/(tabs)/two.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/(drawer)/_layout.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/(drawer)/index.tsx.hbs (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/+html.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/+not-found.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/_layout.tsx.hbs (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/app/modal.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/babel.config.js (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/components/container.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/components/header-button.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/components/tabbar-icon.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/global.css (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/lib/android-navigation-bar.tsx (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/lib/constants.ts (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/lib/use-color-scheme.ts (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/metro.config.js (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/package.json (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/tailwind.config.js (100%) rename apps/cli/templates/frontend/native/{ => nativewind}/tsconfig.json (100%) create mode 100644 apps/cli/templates/frontend/native/unistyles/_gitignore create mode 100644 apps/cli/templates/frontend/native/unistyles/app.json create mode 100644 apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/unistyles/app/+html.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/+not-found.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/app/_layout.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/unistyles/app/modal.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/babel.config.js create mode 100644 apps/cli/templates/frontend/native/unistyles/breakpoints.ts create mode 100644 apps/cli/templates/frontend/native/unistyles/components/container.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/components/header-button.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/components/tabbar-icon.tsx create mode 100644 apps/cli/templates/frontend/native/unistyles/expo-env.d.ts create mode 100644 apps/cli/templates/frontend/native/unistyles/index.js create mode 100644 apps/cli/templates/frontend/native/unistyles/metro.config.js create mode 100644 apps/cli/templates/frontend/native/unistyles/package.json create mode 100644 apps/cli/templates/frontend/native/unistyles/theme.ts create mode 100644 apps/cli/templates/frontend/native/unistyles/tsconfig.json create mode 100644 apps/cli/templates/frontend/native/unistyles/unistyles.ts diff --git a/.changeset/weak-carrots-return.md b/.changeset/weak-carrots-return.md new file mode 100644 index 0000000..b2c1367 --- /dev/null +++ b/.changeset/weak-carrots-return.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +add expo with unistyles diff --git a/apps/cli/README.md b/apps/cli/README.md index 6680e4b..aac567c 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -23,21 +23,21 @@ Follow the prompts to configure your project or use the `--yes` flag for default ## Features -| Category | Options | -|----------|---------| -| **TypeScript** | End-to-end type safety across all parts of your application | -| **Frontend** | • React with TanStack Router
• React with React Router
• React with TanStack Start (SSR)
• Next.js
• SvelteKit
• Nuxt (Vue)
• SolidJS
• React Native with Expo
• None | -| **Backend** | • Hono
• Express
• Elysia
• Next.js API routes
• Convex | -| **API Layer** | • tRPC (type-safe APIs)
• oRPC (OpenAPI-compatible type-safe APIs) | -| **Runtime** | • Bun
• Node.js | -| **Database** | • SQLite
• PostgreSQL
• MySQL
• MongoDB
• None | -| **ORM** | • Drizzle (TypeScript-first)
• Prisma (feature-rich)
• Mongoose (for MongoDB)
• None | -| **Database Setup** | • Turso (SQLite)
• Neon (PostgreSQL)
• Prisma Postgres (via Prisma Accelerate)
• MongoDB Atlas
• None (manual setup) | -| **Authentication** | Better-Auth (email/password, with more options coming soon) | -| **Styling** | Tailwind CSS with shadcn/ui components | -| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds) | -| **Examples** | • Todo app
• AI Chat interface (using Vercel AI SDK) | -| **Developer Experience** | • Automatic Git initialization
• Package manager choice (npm, pnpm, bun)
• Automatic dependency installation | +| Category | Options | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **TypeScript** | End-to-end type safety across all parts of your application | +| **Frontend** | • React with TanStack Router
• React with React Router
• React with TanStack Start (SSR)
• Next.js
• SvelteKit
• Nuxt (Vue)
• SolidJS
• React Native with NativeWind (via Expo)
• React Native with Unistyles (via Expo)
• None | +| **Backend** | • Hono
• Express
• Elysia
• Next.js API routes
• Convex
• None | +| **API Layer** | • tRPC (type-safe APIs)
• oRPC (OpenAPI-compatible type-safe APIs)
• None | +| **Runtime** | • Bun
• Node.js | +| **Database** | • SQLite
• PostgreSQL
• MySQL
• MongoDB
• None | +| **ORM** | • Drizzle (TypeScript-first)
• Prisma (feature-rich)
• Mongoose (for MongoDB)
• None | +| **Database Setup** | • Turso (SQLite)
• Neon (PostgreSQL)
• Prisma Postgres (via Prisma Accelerate)
• MongoDB Atlas
• None (manual setup) | +| **Authentication** | Better-Auth (email/password, with more options coming soon) | +| **Styling** | Tailwind CSS with shadcn/ui components | +| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds) | +| **Examples** | • Todo app
• AI Chat interface (using Vercel AI SDK) | +| **Developer Experience** | • Automatic Git initialization
• Package manager choice (npm, pnpm, bun)
• Automatic dependency installation | ## Usage @@ -51,7 +51,7 @@ Options: --orm ORM type (none, drizzle, prisma, mongoose) --auth Include authentication --no-auth Exclude authentication - --frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native, none) + --frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none) --addons Additional addons (pwa, tauri, starlight, biome, husky, turborepo, none) --examples Examples to include (todo, ai, none) --git Initialize git repository @@ -119,6 +119,7 @@ npx create-better-t-stack my-app --addons starlight ## Compatibility Notes - **Convex backend**: Automatically disables authentication, database, ORM, and API options +- **Backend 'none'**: If selected, this option will force related options like API, ORM, database, authentication, and runtime to 'none'. Examples will also be disabled (set to none/empty). - **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer - **PWA support** requires React with TanStack Router, React Router, or SolidJS - **Tauri desktop app** requires React (TanStack Router/React Router), Nuxt, SvelteKit, or SolidJS diff --git a/apps/cli/src/helpers/api-setup.ts b/apps/cli/src/helpers/api-setup.ts index 15791e1..61d338f 100644 --- a/apps/cli/src/helpers/api-setup.ts +++ b/apps/cli/src/helpers/api-setup.ts @@ -125,7 +125,8 @@ export async function setupApi(config: ProjectConfig): Promise { "tanstack-router", "tanstack-start", "next", - "native", + "native-nativewind", + "native-unistyles", ]; const needsSolidQuery = frontend.includes("solid"); const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f)); @@ -137,9 +138,14 @@ export async function setupApi(config: ProjectConfig): Promise { ]; const hasReactWeb = frontend.some( - (f) => f !== "native" && reactBasedFrontends.includes(f), + (f) => + f !== "native-nativewind" && + f !== "native-unistyles" && + reactBasedFrontends.includes(f), ); - const hasNative = frontend.includes("native"); + const hasNative = + frontend.includes("native-nativewind") || + frontend.includes("native-unistyles"); if (hasReactWeb && webDirExists) { const webPkgJsonPath = path.join(webDir, "package.json"); diff --git a/apps/cli/src/helpers/auth-setup.ts b/apps/cli/src/helpers/auth-setup.ts index cccecdc..fd20e52 100644 --- a/apps/cli/src/helpers/auth-setup.ts +++ b/apps/cli/src/helpers/auth-setup.ts @@ -45,7 +45,11 @@ export async function setupAuth(config: ProjectConfig): Promise { }); } - if (frontend.includes("native") && nativeDirExists) { + if ( + (frontend.includes("native-nativewind") || + frontend.includes("native-unistyles")) && + nativeDirExists + ) { await addPackageDependency({ dependencies: ["better-auth", "@better-auth/expo"], projectDir: nativeDir, diff --git a/apps/cli/src/helpers/create-readme.ts b/apps/cli/src/helpers/create-readme.ts index fd01e1a..0e235d0 100644 --- a/apps/cli/src/helpers/create-readme.ts +++ b/apps/cli/src/helpers/create-readme.ts @@ -39,7 +39,9 @@ function generateReadmeContent(options: ProjectConfig): string { const isConvex = backend === "convex"; const hasReactRouter = frontend.includes("react-router"); const hasTanstackRouter = frontend.includes("tanstack-router"); - const hasNative = frontend.includes("native"); + const hasNative = + frontend.includes("native-nativewind") || + frontend.includes("native-unistyles"); const hasNext = frontend.includes("next"); const hasTanstackStart = frontend.includes("tanstack-start"); const hasSvelte = frontend.includes("svelte"); @@ -78,7 +80,16 @@ This project was created with [Better-T-Stack](https://github.com/AmanVarshney01 ## Features -${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api)} +${generateFeaturesList( + database, + auth, + addons, + orm, + runtime, + frontend, + backend, + api, +)} ## Getting Started @@ -207,7 +218,9 @@ function generateFeaturesList( const isConvex = backend === "convex"; const hasTanstackRouter = frontend.includes("tanstack-router"); const hasReactRouter = frontend.includes("react-router"); - const hasNative = frontend.includes("native"); + const hasNative = + frontend.includes("native-nativewind") || + frontend.includes("native-unistyles"); const hasNext = frontend.includes("next"); const hasTanstackStart = frontend.includes("tanstack-start"); const hasSvelte = frontend.includes("svelte"); diff --git a/apps/cli/src/helpers/env-setup.ts b/apps/cli/src/helpers/env-setup.ts index 2e09569..7aa961c 100644 --- a/apps/cli/src/helpers/env-setup.ts +++ b/apps/cli/src/helpers/env-setup.ts @@ -118,7 +118,10 @@ export async function setupEnvironmentVariables( } } - if (frontend.includes("native")) { + if ( + frontend.includes("native-nativewind") || + frontend.includes("native-unistyles") + ) { const nativeDir = path.join(projectDir, "apps/native"); if (await fs.pathExists(nativeDir)) { let envVarName = "EXPO_PUBLIC_SERVER_URL"; diff --git a/apps/cli/src/helpers/post-installation.ts b/apps/cli/src/helpers/post-installation.ts index 1ef36bb..9a0f388 100644 --- a/apps/cli/src/helpers/post-installation.ts +++ b/apps/cli/src/helpers/post-installation.ts @@ -1,11 +1,6 @@ import { consola } from "consola"; import pc from "picocolors"; -import type { - ProjectBackend, - ProjectDatabase, - ProjectOrm, - ProjectRuntime, -} from "../types"; +import type { ProjectDatabase, ProjectOrm, ProjectRuntime } from "../types"; import { getPackageExecutionCommand } from "../utils/get-package-execution-command"; import type { ProjectConfig } from "../types"; @@ -43,9 +38,11 @@ export function displayPostInstallInstructions( const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : ""; - const nativeInstructions = frontend?.includes("native") - ? getNativeInstructions(isConvex) - : ""; + const nativeInstructions = + frontend?.includes("native-nativewind") || + frontend?.includes("native-unistyles") + ? getNativeInstructions(isConvex) + : ""; const pwaInstructions = addons?.includes("pwa") && (frontend?.includes("react-router") || @@ -67,7 +64,9 @@ export function displayPostInstallInstructions( "solid", ].includes(f), ); - const hasNative = frontend?.includes("native"); + const hasNative = + frontend?.includes("native-nativewind") || + frontend?.includes("native-unistyles"); const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb @@ -90,7 +89,9 @@ export function displayPostInstallInstructions( } if (isConvex) { - output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup ${pc.dim("(this will guide you through Convex project setup)")}\n`; + output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup ${pc.dim( + "(this will guide you through Convex project setup)", + )}\n`; output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`; } else { output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`; @@ -101,7 +102,9 @@ export function displayPostInstallInstructions( if (hasWeb) { output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`; } else if (!hasNative && !addons?.includes("starlight")) { - output += `${pc.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)\n`; + output += `${pc.yellow( + "NOTE:", + )} You are creating a backend-only app (no frontend selected)\n`; } if (!isConvex) { @@ -122,8 +125,12 @@ export function displayPostInstallInstructions( if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`; if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`; - output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`; - output += `${pc.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:\n`; + output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan( + tazeCommand, + )}\n\n`; + output += `${pc.bold( + "Like Better-T Stack?", + )} Please consider giving us a star on GitHub:\n`; output += pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack"); consola.box(output); @@ -183,7 +190,9 @@ function getDatabaseInstructions( instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`); if (database === "sqlite") { instructions.push( - `${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`, + `${pc.cyan( + "•", + )} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`, ); } } else if (orm === "none") { diff --git a/apps/cli/src/helpers/template-manager.ts b/apps/cli/src/helpers/template-manager.ts index 2c17529..3936219 100644 --- a/apps/cli/src/helpers/template-manager.ts +++ b/apps/cli/src/helpers/template-manager.ts @@ -70,7 +70,9 @@ export async function setupFrontendTemplates( const hasNuxtWeb = context.frontend.includes("nuxt"); const hasSvelteWeb = context.frontend.includes("svelte"); const hasSolidWeb = context.frontend.includes("solid"); - const hasNative = context.frontend.includes("native"); + const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasUnistyles = context.frontend.includes("native-unistyles"); + const hasNative = hasNativeWind || hasUnistyles; const isConvex = context.backend === "convex"; if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) { @@ -181,16 +183,45 @@ export async function setupFrontendTemplates( } } - if (hasNative) { + if (hasNativeWind || hasUnistyles) { const nativeAppDir = path.join(projectDir, "apps/native"); await fs.ensureDir(nativeAppDir); - const nativeBaseDir = path.join(PKG_ROOT, "templates/frontend/native"); - if (await fs.pathExists(nativeBaseDir)) { - await processAndCopyFiles("**/*", nativeBaseDir, nativeAppDir, context); + const nativeBaseCommonDir = path.join( + PKG_ROOT, + "templates/frontend/native/native-base", + ); + if (await fs.pathExists(nativeBaseCommonDir)) { + await processAndCopyFiles( + "**/*", + nativeBaseCommonDir, + nativeAppDir, + context, + ); } else { } + let nativeFrameworkPath = ""; + if (hasNativeWind) { + nativeFrameworkPath = "nativewind"; + } else if (hasUnistyles) { + nativeFrameworkPath = "unistyles"; + } + + const nativeSpecificDir = path.join( + PKG_ROOT, + `templates/frontend/native/${nativeFrameworkPath}`, + ); + if (await fs.pathExists(nativeSpecificDir)) { + await processAndCopyFiles( + "**/*", + nativeSpecificDir, + nativeAppDir, + context, + true, + ); + } + if (!isConvex && (context.api === "trpc" || context.api === "orpc")) { const apiNativeSrcDir = path.join( PKG_ROOT, @@ -203,7 +234,6 @@ export async function setupFrontendTemplates( nativeAppDir, context, ); - } else { } } } @@ -345,7 +375,9 @@ export async function setupAuthTemplate( const hasNuxtWeb = context.frontend.includes("nuxt"); const hasSvelteWeb = context.frontend.includes("svelte"); const hasSolidWeb = context.frontend.includes("solid"); - const hasNative = context.frontend.includes("native"); + const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasUnistyles = context.frontend.includes("native-unistyles"); + const hasNative = hasNativeWind || hasUnistyles; if (serverAppDirExists) { const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base"); @@ -475,10 +507,39 @@ export async function setupAuthTemplate( } if (hasNative && nativeAppDirExists) { - const authNativeSrc = path.join(PKG_ROOT, "templates/auth/native"); - if (await fs.pathExists(authNativeSrc)) { - await processAndCopyFiles("**/*", authNativeSrc, nativeAppDir, context); - } else { + const authNativeBaseSrc = path.join( + PKG_ROOT, + "templates/auth/native/native-base", + ); + if (await fs.pathExists(authNativeBaseSrc)) { + await processAndCopyFiles( + "**/*", + authNativeBaseSrc, + nativeAppDir, + context, + ); + } + + let nativeFrameworkAuthPath = ""; + if (hasNativeWind) { + nativeFrameworkAuthPath = "nativewind"; + } else if (hasUnistyles) { + nativeFrameworkAuthPath = "unistyles"; + } + + if (nativeFrameworkAuthPath) { + const authNativeFrameworkSrc = path.join( + PKG_ROOT, + `templates/auth/native/${nativeFrameworkAuthPath}`, + ); + if (await fs.pathExists(authNativeFrameworkSrc)) { + await processAndCopyFiles( + "**/*", + authNativeFrameworkSrc, + nativeAppDir, + context, + ); + } } } } @@ -695,6 +756,9 @@ export async function handleExtras( context: ProjectConfig, ): Promise { const extrasDir = path.join(PKG_ROOT, "templates/extras"); + const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasUnistyles = context.frontend.includes("native-unistyles"); + const hasNative = hasNativeWind || hasUnistyles; if (context.packageManager === "pnpm") { const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml"); @@ -706,7 +770,7 @@ export async function handleExtras( if ( context.packageManager === "pnpm" && - (context.frontend.includes("native") || context.frontend.includes("nuxt")) + (hasNative || context.frontend.includes("nuxt")) ) { const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs"); const npmrcDest = path.join(projectDir, ".npmrc"); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index a039652..7d684be 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -84,7 +84,8 @@ async function main() { "tanstack-start", "next", "nuxt", - "native", + "native-nativewind", + "native-unistyles", "svelte", "solid", "none", @@ -303,6 +304,9 @@ async function main() { config.runtime = "none"; config.dbSetup = "none"; config.examples = ["todo"]; + log.info( + "Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo", + ); } else if (config.backend === "none") { config.auth = false; config.database = "none"; @@ -311,10 +315,24 @@ async function main() { config.runtime = "none"; config.dbSetup = "none"; config.examples = []; + log.info( + "Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", + ); } else if (config.database === "none") { config.orm = "none"; + log.info( + "Due to '--database none', '--orm' has been automatically set to 'none'.", + ); + config.auth = false; + log.info( + "Due to '--database none', '--auth' has been automatically set to 'false'.", + ); + config.dbSetup = "none"; + log.info( + "Due to '--database none', '--db-setup' has been automatically set to 'none'.", + ); } log.info( @@ -380,13 +398,16 @@ function processAndValidateFlags( if (options.api) { config.api = options.api as ProjectApi; if (options.api === "none") { - if (options.backend && options.backend !== "convex") { + if ( + options.backend && + options.backend !== "convex" && + options.backend !== "none" + ) { consola.fatal( - `'--api none' is only supported with '--backend convex'. Please choose a different API setting or use '--backend convex'.`, + `'--api none' is only supported with '--backend convex' or '--backend none'. Please choose a different API setting or use '--backend convex' or '--backend none'.`, ); process.exit(1); } - config.backend = "convex"; } } @@ -468,12 +489,22 @@ function processAndValidateFlags( f === "svelte" || f === "solid", ); + const nativeFrontends = validOptions.filter( + (f) => f === "native-nativewind" || f === "native-unistyles", + ); + if (webFrontends.length > 1) { consola.fatal( "Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid", ); process.exit(1); } + if (nativeFrontends.length > 1) { + consola.fatal( + "Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles", + ); + process.exit(1); + } config.frontend = validOptions; } } @@ -595,6 +626,9 @@ function processAndValidateFlags( process.exit(1); } config.examples = []; + log.info( + "Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", + ); } else { const effectiveDatabase = config.database ?? (options.yes ? DEFAULT_CONFIG.database : undefined); @@ -621,6 +655,9 @@ function processAndValidateFlags( process.exit(1); } config.orm = "none"; + log.info( + "Due to '--database none', '--orm' has been automatically set to 'none'.", + ); if (providedFlags.has("auth") && options.auth === true) { consola.fatal( @@ -629,6 +666,9 @@ function processAndValidateFlags( process.exit(1); } config.auth = false; + log.info( + "Due to '--database none', '--auth' has been automatically set to 'false'.", + ); if (providedFlags.has("dbSetup") && options.dbSetup !== "none") { consola.fatal( @@ -637,6 +677,9 @@ function processAndValidateFlags( process.exit(1); } config.dbSetup = "none"; + log.info( + "Due to '--database none', '--db-setup' has been automatically set to 'none'.", + ); } if (config.orm === "mongoose" && !providedFlags.has("database")) { @@ -753,6 +796,9 @@ function processAndValidateFlags( ) { if (config.api !== "none") { config.api = "orpc"; + log.info( + `Due to frontend selection, API has been set to 'orpc'. tRPC is not compatible with Nuxt, Svelte, or Solid Framework`, + ); } } @@ -813,7 +859,8 @@ function processAndValidateFlags( const onlyNativeFrontend = effectiveFrontend && effectiveFrontend.length === 1 && - effectiveFrontend[0] === "native"; + (effectiveFrontend[0] === "native-nativewind" || + effectiveFrontend[0] === "native-unistyles"); if ( onlyNativeFrontend && @@ -822,7 +869,7 @@ function processAndValidateFlags( !config.examples.includes("none") ) { consola.fatal( - "Examples are not supported when only the 'native' frontend is selected.", + "Examples are not supported when only a native frontend (NativeWind or Unistyles) is selected.", ); process.exit(1); } diff --git a/apps/cli/src/prompts/backend-framework.ts b/apps/cli/src/prompts/backend-framework.ts index 404bfbe..355948a 100644 --- a/apps/cli/src/prompts/backend-framework.ts +++ b/apps/cli/src/prompts/backend-framework.ts @@ -48,7 +48,6 @@ export async function getBackendFrameworkChoice( }); } - // Add "None" option backendOptions.push({ value: "none" as const, label: "None", diff --git a/apps/cli/src/prompts/examples.ts b/apps/cli/src/prompts/examples.ts index 3f7d294..ec555b1 100644 --- a/apps/cli/src/prompts/examples.ts +++ b/apps/cli/src/prompts/examples.ts @@ -27,7 +27,10 @@ export async function getExamplesChoice( if (database === "none") return []; const onlyNative = - frontends && frontends.length === 1 && frontends[0] === "native"; + frontends && + frontends.length === 1 && + (frontends[0] === "native-nativewind" || + frontends[0] === "native-unistyles"); if (onlyNative) { return []; } diff --git a/apps/cli/src/prompts/frontend-option.ts b/apps/cli/src/prompts/frontend-option.ts index e7adebf..32ac209 100644 --- a/apps/cli/src/prompts/frontend-option.ts +++ b/apps/cli/src/prompts/frontend-option.ts @@ -95,7 +95,28 @@ export async function getFrontendChoice( } if (frontendTypes.includes("native")) { - result.push("native"); + const nativeFramework = await select({ + message: "Choose native framework", + options: [ + { + value: "native-nativewind" as const, + label: "NativeWind", + hint: "Use Tailwind CSS for React Native", + }, + { + value: "native-unistyles" as const, + label: "Unistyles", + hint: "Consistent styling for React Native", + }, + ], + initialValue: "native-nativewind", + }); + + if (isCancel(nativeFramework)) { + cancel(pc.red("Operation cancelled")); + process.exit(0); + } + result.push(nativeFramework); } return result; diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 4837138..d123dc9 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -29,7 +29,8 @@ export type ProjectFrontend = | "tanstack-start" | "next" | "nuxt" - | "native" + | "native-nativewind" + | "native-unistyles" | "svelte" | "solid" | "none"; diff --git a/apps/cli/templates/auth/native/lib/auth-client.ts b/apps/cli/templates/auth/native/native-base/lib/auth-client.ts.hbs similarity index 100% rename from apps/cli/templates/auth/native/lib/auth-client.ts rename to apps/cli/templates/auth/native/native-base/lib/auth-client.ts.hbs diff --git a/apps/cli/templates/auth/native/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/native/nativewind/app/(drawer)/index.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/app/(drawer)/index.tsx.hbs rename to apps/cli/templates/auth/native/nativewind/app/(drawer)/index.tsx.hbs diff --git a/apps/cli/templates/auth/native/components/sign-in.tsx.hbs b/apps/cli/templates/auth/native/nativewind/components/sign-in.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/components/sign-in.tsx.hbs rename to apps/cli/templates/auth/native/nativewind/components/sign-in.tsx.hbs diff --git a/apps/cli/templates/auth/native/components/sign-up.tsx.hbs b/apps/cli/templates/auth/native/nativewind/components/sign-up.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/components/sign-up.tsx.hbs rename to apps/cli/templates/auth/native/nativewind/components/sign-up.tsx.hbs diff --git a/apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs new file mode 100644 index 0000000..b7c6193 --- /dev/null +++ b/apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs @@ -0,0 +1,179 @@ +import { authClient } from "@/lib/auth-client"; +import { useQuery } from "@tanstack/react-query"; +import { ScrollView, Text, TouchableOpacity, View } from "react-native"; +import { StyleSheet } from "react-native-unistyles"; + +import { Container } from "@/components/container"; +import { SignIn } from "@/components/sign-in"; +import { SignUp } from "@/components/sign-up"; +{{#if (eq api "orpc")}} +import { queryClient, orpc } from "@/utils/orpc"; +{{/if}} +{{#if (eq api "trpc")}} +import { queryClient, trpc } from "@/utils/trpc"; +{{/if}} + +export default function Home() { + {{#if (eq api "orpc")}} + const healthCheck = useQuery(orpc.healthCheck.queryOptions()); + const privateData = useQuery(orpc.privateData.queryOptions()); + {{/if}} + {{#if (eq api "trpc")}} + const healthCheck = useQuery(trpc.healthCheck.queryOptions()); + const privateData = useQuery(trpc.privateData.queryOptions()); + {{/if}} + const { data: session } = authClient.useSession(); + + return ( + + + + BETTER T STACK + {session?.user ? ( + + + + Welcome,{" "} + {session.user.name} + + + {session.user.email} + + { + authClient.signOut(); + queryClient.invalidateQueries(); + }} + > + Sign Out + + + ) : null} + + API Status + + + + {healthCheck.isLoading + ? "Checking..." + : healthCheck.data + ? "Connected to API" + : "API Disconnected"} + + + + + Private Data + {privateData && ( + + + {privateData.data?.message} + + + )} + + {!session?.user && ( + <> + + + + )} + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + pageContainer: { + paddingHorizontal: 8, + }, + headerTitle: { + color: theme?.colors?.typography, + fontSize: 30, + fontWeight: "bold", + marginBottom: 16, + }, + sessionInfoCard: { + marginBottom: 24, + padding: 16, + borderRadius: 8, + borderWidth: 1, + borderColor: theme?.colors?.border, + }, + sessionUserRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: 8, + }, + welcomeText: { + color: theme?.colors?.typography, + fontSize: 16, + }, + userNameText: { + fontWeight: "500", + color: theme?.colors?.typography, + }, + emailText: { + color: theme?.colors?.typography, + fontSize: 14, + marginBottom: 16, + }, + signOutButton: { + backgroundColor: theme?.colors?.destructive, + paddingVertical: 8, + paddingHorizontal: 16, + borderRadius: 6, + alignSelf: "flex-start", + }, + signOutButtonText: { + fontWeight: "500", + }, + apiStatusCard: { + marginBottom: 24, + borderRadius: 8, + borderWidth: 1, + borderColor: theme?.colors?.border, + padding: 16, + }, + cardTitle: { + marginBottom: 12, + fontWeight: "500", + color: theme?.colors?.typography, + }, + apiStatusRow: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + statusIndicatorDot: { + height: 12, + width: 12, + borderRadius: 9999, + }, + statusIndicatorGreen: { + backgroundColor: theme.colors.success, + }, + statusIndicatorRed: { + backgroundColor: theme.colors.destructive, + }, + mutedText: { + color: theme?.colors?.typography, + }, + privateDataCard: { + marginBottom: 24, + borderRadius: 8, + borderWidth: 1, + borderColor: theme?.colors?.border, + padding: 16, + }, +})); diff --git a/apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs b/apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs new file mode 100644 index 0000000..c63395b --- /dev/null +++ b/apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs @@ -0,0 +1,134 @@ +import { authClient } from "@/lib/auth-client"; +{{#if (eq api "trpc")}} +import { queryClient } from "@/utils/trpc"; +{{/if}} +{{#if (eq api "orpc")}} +import { queryClient } from "@/utils/orpc"; +{{/if}} +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { StyleSheet } from "react-native-unistyles"; + +export function SignIn() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleLogin = async () => { + setIsLoading(true); + setError(null); + + await authClient.signIn.email( + { + email, + password, + }, + { + onError: (error) => { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess: () => { + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished: () => { + setIsLoading(false); + }, + }, + ); + }; + + return ( + + Sign In + + {error && ( + + {error} + + )} + + + + + + + {isLoading ? ( + + ) : ( + Sign In + )} + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + container: { + marginTop: 24, + padding: 16, + borderRadius: 8, + borderWidth: 1, + borderColor: theme.colors.border, + }, + title: { + fontSize: 18, + fontWeight: "600", + color: theme.colors.typography, + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 6, + }, + errorText: { + color: theme.colors.destructive, + fontSize: 14, + }, + input: { + marginBottom: 12, + padding: 16, + borderRadius: 6, + color: theme.colors.typography, + borderWidth: 1, + borderColor: theme.colors.border, + }, + button: { + backgroundColor: theme.colors.primary, + padding: 16, + borderRadius: 6, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + }, + buttonText: { + fontWeight: "500", + }, +})); diff --git a/apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs b/apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs new file mode 100644 index 0000000..67f0a1b --- /dev/null +++ b/apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs @@ -0,0 +1,152 @@ +import { authClient } from "@/lib/auth-client"; +{{#if (eq api "trpc")}} +import { queryClient } from "@/utils/trpc"; +{{/if}} +{{#if (eq api "orpc")}} +import { queryClient } from "@/utils/orpc"; +{{/if}} +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { StyleSheet } from "react-native-unistyles"; + +export function SignUp() { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSignUp = async () => { + setIsLoading(true); + setError(null); + + await authClient.signUp.email( + { + name, + email, + password, + }, + { + onError: (error) => { + setError(error.error?.message || "Failed to sign up"); + setIsLoading(false); + }, + onSuccess: () => { + setName(""); + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished: () => { + setIsLoading(false); + }, + }, + ); + }; + + return ( + + Create Account + + {error && ( + + {error} + + )} + + + + + + + + + {isLoading ? ( + + ) : ( + Sign Up + )} + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + container: { + marginTop: 24, + padding: 16, + borderRadius: 8, + borderWidth: 1, + borderColor: theme.colors.border, + }, + title: { + fontSize: 18, + fontWeight: "600", + color: theme.colors.typography, + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 6, + }, + errorText: { + color: theme.colors.destructive, + fontSize: 14, + }, + input: { + marginBottom: 12, + padding: 16, + borderRadius: 6, + color: theme.colors.typography, + borderWidth: 1, + borderColor: theme.colors.border, + }, + inputLast: { + marginBottom: 16, + padding: 16, + borderRadius: 6, + color: theme.colors.typography, + borderWidth: 1, + borderColor: theme.colors.border, + }, + button: { + backgroundColor: theme.colors.primary, + padding: 16, + borderRadius: 6, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + }, + buttonText: { + fontWeight: "500", + }, +})); diff --git a/apps/cli/templates/frontend/native/assets/adaptive-icon.png b/apps/cli/templates/frontend/native/native-base/assets/adaptive-icon.png similarity index 100% rename from apps/cli/templates/frontend/native/assets/adaptive-icon.png rename to apps/cli/templates/frontend/native/native-base/assets/adaptive-icon.png diff --git a/apps/cli/templates/frontend/native/assets/favicon.png b/apps/cli/templates/frontend/native/native-base/assets/favicon.png similarity index 100% rename from apps/cli/templates/frontend/native/assets/favicon.png rename to apps/cli/templates/frontend/native/native-base/assets/favicon.png diff --git a/apps/cli/templates/frontend/native/assets/icon.png b/apps/cli/templates/frontend/native/native-base/assets/icon.png similarity index 100% rename from apps/cli/templates/frontend/native/assets/icon.png rename to apps/cli/templates/frontend/native/native-base/assets/icon.png diff --git a/apps/cli/templates/frontend/native/assets/splash.png b/apps/cli/templates/frontend/native/native-base/assets/splash.png similarity index 100% rename from apps/cli/templates/frontend/native/assets/splash.png rename to apps/cli/templates/frontend/native/native-base/assets/splash.png diff --git a/apps/cli/templates/frontend/native/_gitignore b/apps/cli/templates/frontend/native/nativewind/_gitignore similarity index 100% rename from apps/cli/templates/frontend/native/_gitignore rename to apps/cli/templates/frontend/native/nativewind/_gitignore diff --git a/apps/cli/templates/frontend/native/app-env.d.ts b/apps/cli/templates/frontend/native/nativewind/app-env.d.ts similarity index 100% rename from apps/cli/templates/frontend/native/app-env.d.ts rename to apps/cli/templates/frontend/native/nativewind/app-env.d.ts diff --git a/apps/cli/templates/frontend/native/app.json b/apps/cli/templates/frontend/native/nativewind/app.json similarity index 100% rename from apps/cli/templates/frontend/native/app.json rename to apps/cli/templates/frontend/native/nativewind/app.json diff --git a/apps/cli/templates/frontend/native/app/(drawer)/(tabs)/_layout.tsx b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/(drawer)/(tabs)/_layout.tsx rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx diff --git a/apps/cli/templates/frontend/native/app/(drawer)/(tabs)/index.tsx b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/(drawer)/(tabs)/index.tsx rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx diff --git a/apps/cli/templates/frontend/native/app/(drawer)/(tabs)/two.tsx b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/(drawer)/(tabs)/two.tsx rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx diff --git a/apps/cli/templates/frontend/native/app/(drawer)/_layout.tsx b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/(drawer)/_layout.tsx rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx diff --git a/apps/cli/templates/frontend/native/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs similarity index 100% rename from apps/cli/templates/frontend/native/app/(drawer)/index.tsx.hbs rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs diff --git a/apps/cli/templates/frontend/native/app/+html.tsx b/apps/cli/templates/frontend/native/nativewind/app/+html.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/+html.tsx rename to apps/cli/templates/frontend/native/nativewind/app/+html.tsx diff --git a/apps/cli/templates/frontend/native/app/+not-found.tsx b/apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/+not-found.tsx rename to apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx diff --git a/apps/cli/templates/frontend/native/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs similarity index 100% rename from apps/cli/templates/frontend/native/app/_layout.tsx.hbs rename to apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs diff --git a/apps/cli/templates/frontend/native/app/modal.tsx b/apps/cli/templates/frontend/native/nativewind/app/modal.tsx similarity index 100% rename from apps/cli/templates/frontend/native/app/modal.tsx rename to apps/cli/templates/frontend/native/nativewind/app/modal.tsx diff --git a/apps/cli/templates/frontend/native/babel.config.js b/apps/cli/templates/frontend/native/nativewind/babel.config.js similarity index 100% rename from apps/cli/templates/frontend/native/babel.config.js rename to apps/cli/templates/frontend/native/nativewind/babel.config.js diff --git a/apps/cli/templates/frontend/native/components/container.tsx b/apps/cli/templates/frontend/native/nativewind/components/container.tsx similarity index 100% rename from apps/cli/templates/frontend/native/components/container.tsx rename to apps/cli/templates/frontend/native/nativewind/components/container.tsx diff --git a/apps/cli/templates/frontend/native/components/header-button.tsx b/apps/cli/templates/frontend/native/nativewind/components/header-button.tsx similarity index 100% rename from apps/cli/templates/frontend/native/components/header-button.tsx rename to apps/cli/templates/frontend/native/nativewind/components/header-button.tsx diff --git a/apps/cli/templates/frontend/native/components/tabbar-icon.tsx b/apps/cli/templates/frontend/native/nativewind/components/tabbar-icon.tsx similarity index 100% rename from apps/cli/templates/frontend/native/components/tabbar-icon.tsx rename to apps/cli/templates/frontend/native/nativewind/components/tabbar-icon.tsx diff --git a/apps/cli/templates/frontend/native/global.css b/apps/cli/templates/frontend/native/nativewind/global.css similarity index 100% rename from apps/cli/templates/frontend/native/global.css rename to apps/cli/templates/frontend/native/nativewind/global.css diff --git a/apps/cli/templates/frontend/native/lib/android-navigation-bar.tsx b/apps/cli/templates/frontend/native/nativewind/lib/android-navigation-bar.tsx similarity index 100% rename from apps/cli/templates/frontend/native/lib/android-navigation-bar.tsx rename to apps/cli/templates/frontend/native/nativewind/lib/android-navigation-bar.tsx diff --git a/apps/cli/templates/frontend/native/lib/constants.ts b/apps/cli/templates/frontend/native/nativewind/lib/constants.ts similarity index 100% rename from apps/cli/templates/frontend/native/lib/constants.ts rename to apps/cli/templates/frontend/native/nativewind/lib/constants.ts diff --git a/apps/cli/templates/frontend/native/lib/use-color-scheme.ts b/apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts similarity index 100% rename from apps/cli/templates/frontend/native/lib/use-color-scheme.ts rename to apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts diff --git a/apps/cli/templates/frontend/native/metro.config.js b/apps/cli/templates/frontend/native/nativewind/metro.config.js similarity index 100% rename from apps/cli/templates/frontend/native/metro.config.js rename to apps/cli/templates/frontend/native/nativewind/metro.config.js diff --git a/apps/cli/templates/frontend/native/package.json b/apps/cli/templates/frontend/native/nativewind/package.json similarity index 100% rename from apps/cli/templates/frontend/native/package.json rename to apps/cli/templates/frontend/native/nativewind/package.json diff --git a/apps/cli/templates/frontend/native/tailwind.config.js b/apps/cli/templates/frontend/native/nativewind/tailwind.config.js similarity index 100% rename from apps/cli/templates/frontend/native/tailwind.config.js rename to apps/cli/templates/frontend/native/nativewind/tailwind.config.js diff --git a/apps/cli/templates/frontend/native/tsconfig.json b/apps/cli/templates/frontend/native/nativewind/tsconfig.json similarity index 100% rename from apps/cli/templates/frontend/native/tsconfig.json rename to apps/cli/templates/frontend/native/nativewind/tsconfig.json diff --git a/apps/cli/templates/frontend/native/unistyles/_gitignore b/apps/cli/templates/frontend/native/unistyles/_gitignore new file mode 100644 index 0000000..2ff55da --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/_gitignore @@ -0,0 +1,24 @@ +node_modules/ +.expo/ +dist/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +web-build/ +# expo router +expo-env.d.ts + + + +ios +android + +# macOS +.DS_Store + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/unistyles/app.json b/apps/cli/templates/frontend/native/unistyles/app.json new file mode 100644 index 0000000..d2856dc --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app.json @@ -0,0 +1,44 @@ +{ + "expo": { + "name": "my-better-t-app", + "slug": "my-better-t-app", + "version": "1.0.0", + "newArchEnabled": true, + "scheme": "my-better-t-app", + "web": { + "bundler": "metro", + "output": "static", + "favicon": "./assets/favicon.png" + }, + "plugins": [ + "expo-router", + "react-native-edge-to-edge", + "expo-secure-store" + ], + "experiments": { + "typedRoutes": true, + "tsconfigPaths": true, + "reactCompiler": true + }, + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "automatic", + "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" + } + } +} diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx new file mode 100644 index 0000000..e4a5e2b --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx @@ -0,0 +1,34 @@ +import { Tabs } from "expo-router"; +import { useUnistyles } from "react-native-unistyles"; + +import { TabBarIcon } from "@/components/tabbar-icon"; + +export default function TabLayout() { + const { theme } = useUnistyles(); + + return ( + + , + }} + /> + , + }} + /> + + ); +} diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx new file mode 100644 index 0000000..52f73f4 --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx @@ -0,0 +1,29 @@ +import { Stack } from "expo-router"; +import { StyleSheet } from "react-native-unistyles"; +import { Container } from "@/components/container"; +import { Text, View } from "react-native"; + +export default function Home() { + return ( + <> + + + + Tab One + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + text: { + color: theme.colors.typography, + }, + container: { + flex: 1, + paddingBottom: 100, + justifyContent: "center", + alignItems: "center", + }, +})); diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx new file mode 100644 index 0000000..b75640e --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx @@ -0,0 +1,29 @@ +import { Stack } from "expo-router"; +import { StyleSheet } from "react-native-unistyles"; +import { Container } from "@/components/container"; +import { Text, View } from "react-native"; + +export default function Home() { + return ( + <> + + + + Tab Two + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + text: { + color: theme.colors.typography, + }, + container: { + flex: 1, + paddingBottom: 100, + justifyContent: "center", + alignItems: "center", + }, +})); diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx new file mode 100644 index 0000000..fc0d630 --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx @@ -0,0 +1,59 @@ +import { Ionicons, MaterialIcons } from "@expo/vector-icons"; +import { Link } from "expo-router"; +import { Drawer } from "expo-router/drawer"; +import { useUnistyles } from "react-native-unistyles"; + +import { HeaderButton } from "../../components/header-button"; + +const DrawerLayout = () => { + const { theme } = useUnistyles(); + + return ( + + ( + + ), + }} + /> + ( + + ), + headerRight: () => ( + + + + ), + }} + /> + + ); +}; + +export default DrawerLayout; diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs new file mode 100644 index 0000000..3eca269 --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs @@ -0,0 +1,115 @@ +import { ScrollView, Text, View } from "react-native"; +import { StyleSheet } from "react-native-unistyles"; +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 ( + + + BETTER T STACK + + + API Status + + + + {{#if (or (eq api "orpc") (eq api "trpc"))}} + {healthCheck.isLoading + ? "Checking..." + : healthCheck.data + ? "Connected" + : "Disconnected"} + {{/if}} + {{#if (eq backend "convex")}} + {healthCheck === undefined + ? "Checking..." + : healthCheck === "OK" + ? "Connected" + : "Error"} + {{/if}} + + + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + pageContainer: { + paddingHorizontal: 8, + }, + headerTitle: { + color: theme?.colors?.typography, + fontSize: 30, + fontWeight: "bold", + marginBottom: 16, + }, + apiStatusCard: { + marginBottom: 24, + borderRadius: 8, + borderWidth: 1, + borderColor: theme?.colors?.border, + padding: 16, + }, + cardTitle: { + marginBottom: 12, + fontWeight: "500", + color: theme?.colors?.typography, + }, + apiStatusRow: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + statusIndicatorDot: { + height: 12, + width: 12, + borderRadius: 9999, + }, + statusIndicatorGreen: { + backgroundColor: theme.colors.success, + }, + statusIndicatorRed: { + backgroundColor: theme.colors.destructive, + }, + statusText: { + color: theme?.colors?.typography, + }, +})); diff --git a/apps/cli/templates/frontend/native/unistyles/app/+html.tsx b/apps/cli/templates/frontend/native/unistyles/app/+html.tsx new file mode 100644 index 0000000..c495253 --- /dev/null +++ b/apps/cli/templates/frontend/native/unistyles/app/+html.tsx @@ -0,0 +1,48 @@ +import { ScrollViewStyleReset } from 'expo-router/html'; + +import '../unistyles'; + +// 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: React.ReactNode }) { + return ( + + + + + + {/* + 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: + + */} + + {/* + 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. + */} + + + {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} +