├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── .yarnrc ├── README.md ├── apps ├── api │ ├── .gitignore │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── context │ │ │ └── index.ts │ │ ├── index.ts │ │ └── router │ │ │ ├── index.ts │ │ │ └── orpc.ts │ ├── tsconfig.json │ └── tsdown.config.ts ├── app-native │ ├── .gitignore │ ├── .npmrc │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── app.json │ ├── app │ │ ├── (tabs) │ │ │ ├── _layout.tsx │ │ │ ├── explore.tsx │ │ │ └── index.tsx │ │ ├── +not-found.tsx │ │ └── _layout.tsx │ ├── assets │ │ ├── fonts │ │ │ └── SpaceMono-Regular.ttf │ │ └── images │ │ │ ├── adaptive-icon.png │ │ │ ├── favicon.png │ │ │ ├── icon.png │ │ │ ├── partial-react-logo.png │ │ │ ├── react-logo.png │ │ │ ├── react-logo@2x.png │ │ │ ├── react-logo@3x.png │ │ │ └── splash-icon.png │ ├── babel.config.js │ ├── components │ │ ├── Collapsible.tsx │ │ ├── ExternalLink.tsx │ │ ├── HapticTab.tsx │ │ ├── HelloWave.tsx │ │ ├── ParallaxScrollView.tsx │ │ ├── ThemedText.tsx │ │ ├── ThemedView.tsx │ │ └── ui │ │ │ ├── IconSymbol.ios.tsx │ │ │ ├── IconSymbol.tsx │ │ │ ├── TabBarBackground.ios.tsx │ │ │ └── TabBarBackground.tsx │ ├── constants │ │ └── Colors.ts │ ├── eslint.config.mjs │ ├── global.css │ ├── hooks │ │ ├── useColorScheme.ts │ │ ├── useColorScheme.web.ts │ │ └── useThemeColor.ts │ ├── metro.config.js │ ├── nativewind-env.d.ts │ ├── package.json │ ├── scripts │ │ └── reset-project.js │ ├── tailwind.config.js │ ├── trpc │ │ └── index.ts │ └── tsconfig.json ├── web-next │ ├── .gitignore │ ├── .npmrc │ ├── .yarnrc │ ├── README.md │ ├── app │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── eslint.config.mjs │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ └── tsconfig.json ├── web-remix │ ├── .gitignore │ ├── .npmrc │ ├── app │ │ ├── app.css │ │ ├── auto-imports.d.ts │ │ ├── components │ │ │ ├── layout │ │ │ │ ├── Header.tsx │ │ │ │ ├── HeroSection.tsx │ │ │ │ └── Navbar.tsx │ │ │ ├── typing-animation.tsx │ │ │ └── ui │ │ │ │ ├── Global.tsx │ │ │ │ ├── accordion.tsx │ │ │ │ ├── alert-dialog.tsx │ │ │ │ ├── alert.tsx │ │ │ │ ├── aspect-ratio.tsx │ │ │ │ ├── avatar.tsx │ │ │ │ ├── badge.tsx │ │ │ │ ├── breadcrumb.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── calendar.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── carousel.tsx │ │ │ │ ├── chart.tsx │ │ │ │ ├── checkbox.tsx │ │ │ │ ├── collapsible.tsx │ │ │ │ ├── command.tsx │ │ │ │ ├── context-menu.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── drawer.tsx │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ ├── form.tsx │ │ │ │ ├── hover-card.tsx │ │ │ │ ├── input-otp.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── menubar.tsx │ │ │ │ ├── navigation-menu.tsx │ │ │ │ ├── pagination.tsx │ │ │ │ ├── popover.tsx │ │ │ │ ├── progress.tsx │ │ │ │ ├── radio-group.tsx │ │ │ │ ├── resizable.tsx │ │ │ │ ├── scroll-area.tsx │ │ │ │ ├── select.tsx │ │ │ │ ├── separator.tsx │ │ │ │ ├── sheet.tsx │ │ │ │ ├── sidebar.tsx │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── slider.tsx │ │ │ │ ├── sonner.tsx │ │ │ │ ├── switch.tsx │ │ │ │ ├── table.tsx │ │ │ │ ├── tabs.tsx │ │ │ │ ├── textarea.tsx │ │ │ │ ├── toggle-group.tsx │ │ │ │ ├── toggle.tsx │ │ │ │ └── tooltip.tsx │ │ ├── hooks │ │ │ └── use-mobile.ts │ │ ├── lib │ │ │ └── utils.ts │ │ ├── root.tsx │ │ ├── routes.ts │ │ ├── routes │ │ │ ├── _index.tsx │ │ │ └── data.ts │ │ └── welcome │ │ │ ├── logo-dark.svg │ │ │ ├── logo-light.svg │ │ │ └── welcome.tsx │ ├── auto-imports.d.ts │ ├── components.json │ ├── eslint.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── react-router.config.ts │ ├── tailwind.config.mjs │ ├── tsconfig.json │ ├── uno.config.ts │ └── vite.config.ts ├── web-tanstack │ ├── .gitignore │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── orpc │ │ │ └── index.ts │ │ ├── routeTree.gen.ts │ │ ├── router.tsx │ │ ├── routes │ │ │ ├── __root.tsx │ │ │ ├── index.tsx │ │ │ └── posts │ │ │ │ ├── $postId.tsx │ │ │ │ └── index.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts └── web-vite │ ├── .gitignore │ ├── README.md │ ├── eslint.config.mjs │ ├── index.html │ ├── package.json │ ├── public │ ├── logo.svg │ └── vite.svg │ ├── src │ ├── App.tsx │ ├── assets │ │ ├── react.svg │ │ └── styles │ │ │ └── index.css │ ├── auto-imports.d.ts │ ├── main.tsx │ ├── pages │ │ └── index.tsx │ ├── trpc │ │ └── index.ts │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── packages ├── db │ ├── eslint.config.mjs │ ├── package.json │ ├── prisma │ │ ├── db │ │ │ └── sqlite3.db │ │ ├── migrations │ │ │ ├── 20241013024007_init │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ ├── src │ │ └── index.ts │ └── tsdown.config.ts ├── drizzle │ ├── drizzle.config.ts │ ├── eslint.config.mjs │ ├── output │ │ ├── 0000_amusing_midnight.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ └── _journal.json │ ├── package.json │ ├── src │ │ ├── db │ │ │ ├── relations.ts │ │ │ ├── schema.ts │ │ │ └── sqlite3.db │ │ └── index.ts │ └── tsdown.config.ts ├── eslint-config │ ├── expo.mjs │ ├── next.mjs │ ├── node.mjs │ ├── package.json │ └── web.mjs ├── sdk │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── utils │ │ │ └── index.ts │ ├── tests │ │ └── .gitkeep │ ├── tsconfig.json │ └── tsdown.config.ts ├── typescript-config │ ├── base.json │ ├── package.json │ └── react-library.json └── ui │ ├── components.json │ ├── package.json │ ├── src │ ├── components │ │ ├── particle │ │ │ ├── image-particle.d.ts │ │ │ ├── image-particle.js │ │ │ └── index.tsx │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── dialog.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── rainbow-button.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── shimmer-button.tsx │ │ │ ├── sonner.tsx │ │ │ └── tabs.tsx │ └── lib │ │ └── utils.ts │ ├── tailwind.config.mjs │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── turbo.json └── uno.config.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, develop ] 6 | paths: 7 | - 'apps/**/*' 8 | - 'packages/**/*' 9 | pull_request: 10 | branches: [ main, develop ] 11 | types: [ opened, synchronize, reopened ] 12 | 13 | jobs: 14 | ci: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | - uses: pnpm/action-setup@v4 20 | with: 21 | version: 10.5.2 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: '22.x' 25 | - run: pnpm install --no-frozen-lockfile 26 | - run: pnpm run lint 27 | - run: pnpm run build 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | .eslintcache -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | resolve-peers-from-workspace-root=true 5 | public-hoist-pattern[]=*react* 6 | public-hoist-pattern[]=*react-dom* 7 | auto-install-peers=true 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npmjs.org" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 F3-App 2 | 3 | **The Fantastic, Fast, and Fun Monorepo Template!** 4 | 5 | Welcome to F3-App, where we've taken the "F" in Full-stack and tripled it! 🎉 6 | 7 | ## 🌟 What's This All About? 8 | 9 | F3-App is your one-stop shop for kickstarting a modern web application with all the bells and whistles. We've carefully curated a tech stack that's so hot, it might just melt your keyboard! 🔥 10 | 11 | ## 🛠️ Our Fantastic Features 12 | 13 | - 🏎️ **Vite-powered React**: Because waiting for your app to build is so last year! 14 | - 🔮 **tRPC API**: Type-safe APIs that'll make you wonder how you ever lived without them. 15 | - 💾 **Prisma**: Database management so smooth, it feels like cheating. 16 | - 🎨 **UnoCSS**: Atomic CSS on steroids - style your app faster than you can say "responsive"! 17 | - 🚄 **Fastify**: An API server so quick, it'll give Usain Bolt a run for his money. 18 | - 🧰 **Turbo**: Monorepo management that'll make you feel like a 10x developer. 19 | 20 | ## 🚦 Getting Started 21 | 22 | 1. Clone this repo (you're already awesome for choosing it!) 23 | 2. Run `pnpm install` (because we're fancy and use pnpm) 24 | 3. Start coding like there's no tomorrow! 25 | 26 | ## 📋 Todo List (Because Even Awesome Projects Have Room for Improvement) 27 | 28 | - [x] ~~Replace Next.js with Vite React (✨ Done! Who needs server-side rendering anyway?)~~ sometimes we need ssr 29 | - [ ] Implement Vitest (Because testing is important, even if it's not as fun as coding) 30 | - [x] Integrate tRPC API (✅ Type-safety for the win!) 31 | - [x] Set up Prisma (💽 Database management has never been this cool) 32 | - [x] Configure UnoCSS (🎨 Making your app pretty, one utility class at a time) 33 | - [x] Implement Fastify (🏃‍♂️ Gotta go fast!) 34 | - [ ] Add PostgreSQL (🐘 Because elephants never forget... your data) 35 | - [ ] docker deployment 36 | - [ ] supabase 37 | - [ ] CI/CD pipeline 38 | - [ ] extension app 39 | - [x] expo app template 40 | 41 | ## 🤔 Why F3-App? 42 | 43 | - It's **Fantastic**: We've handpicked the best tools so you don't have to. 44 | - It's **Fast**: From development to production, speed is our middle name. 45 | - It's **Fun**: Because if you're not enjoying your stack, you're doing it wrong! 46 | 47 | ## 🎉 Join the F3 Revolution! 48 | 49 | Are you ready to take your web development game to the next level? Dive into F3-App and experience the joy of a well-structured, high-performance monorepo setup. 50 | 51 | Remember, in the world of F3-App, the only limit is your imagination (and maybe your coffee supply ☕). 52 | 53 | Happy coding, and may the force of F3 be with you! 🚀✨ 54 | -------------------------------------------------------------------------------- /apps/api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | -------------------------------------------------------------------------------- /apps/api/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from 'eslint-config/node' 2 | -------------------------------------------------------------------------------- /apps/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "license": "ISC", 8 | "main": "index.js", 9 | "scripts": { 10 | "build": "tsdown", 11 | "dev": "tsx watch ./src/index.ts", 12 | "preview": "node ./dist/index.cjs", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "dependencies": { 16 | "@orpc/server": "catalog:orpc", 17 | "@repo/sdk": "workspace:*", 18 | "@typeschema/valibot": "catalog:", 19 | "db": "workspace:*", 20 | "dotenv": "catalog:", 21 | "eslint-config": "workspace:*", 22 | "fastify": "catalog:" 23 | }, 24 | "devDependencies": { 25 | "@repo/typescript-config": "workspace:*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/api/src/context/index.ts: -------------------------------------------------------------------------------- 1 | import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify' 2 | 3 | export function createContext({ req, res }: CreateFastifyContextOptions) { 4 | const user = { name: req.headers.username ?? 'anonymous' } 5 | return { req, res, user } 6 | } 7 | export type Context = Awaited> 8 | -------------------------------------------------------------------------------- /apps/api/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from 'node:http' 2 | 3 | import { RPCHandler } from '@orpc/server/node' 4 | import { CORSPlugin } from '@orpc/server/plugins' 5 | import type { 6 | FastifyTRPCPluginOptions, 7 | } from '@trpc/server/adapters/fastify' 8 | import { 9 | fastifyTRPCPlugin, 10 | } from '@trpc/server/adapters/fastify' 11 | import consola from 'consola' 12 | import * as dotenv from 'dotenv' 13 | import fastify from 'fastify' 14 | 15 | import { createContext } from './context' 16 | import type { AppRouter } from './router' 17 | import { appRouter } from './router' 18 | import { router } from './router/orpc' 19 | 20 | dotenv.config() 21 | 22 | const handler = new RPCHandler(router, { 23 | plugins: [ 24 | new CORSPlugin(), 25 | ], 26 | }) 27 | 28 | const server = fastify({ 29 | maxParamLength: 5000, 30 | logger: true, 31 | serverFactory: (fastifyHandler) => { 32 | const server = createServer(async (req, res) => { 33 | const { matched } = await handler.handle(req, res, { 34 | context: {}, 35 | prefix: '/orpc', 36 | }) 37 | 38 | if (matched) { 39 | return 40 | } 41 | 42 | fastifyHandler(req, res) 43 | }) 44 | 45 | return server 46 | }, 47 | }) 48 | 49 | server.register(fastifyTRPCPlugin, { 50 | prefix: '/trpc', 51 | trpcOptions: { 52 | router: appRouter, 53 | createContext, 54 | onError({ path, error }) { 55 | // report to error monitoring 56 | consola.error(`Error in tRPC handler on path '${path}':`, error) 57 | }, 58 | } satisfies FastifyTRPCPluginOptions['trpcOptions'], 59 | }) 60 | 61 | ;(async () => { 62 | try { 63 | await server.listen({ port: 5000 }) 64 | consola.success('Server is running on port http://localhost:5000') 65 | } 66 | catch (err) { 67 | consola.error(err) 68 | throw err 69 | } 70 | })() 71 | -------------------------------------------------------------------------------- /apps/api/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC } from '@trpc/server' 2 | import { wrap } from '@typeschema/valibot' 3 | import { prisma } from 'db' 4 | import * as v from 'valibot' 5 | 6 | type User = { 7 | id: string 8 | name: string 9 | bio?: string 10 | } 11 | const users: Record = {} 12 | export const t = initTRPC.create() 13 | export const appRouter = t.router({ 14 | hello: t.procedure.query(() => { 15 | return 'Hello, world!' 16 | }), 17 | getUsers: t.procedure.query(async () => { 18 | return await prisma.user.findMany() 19 | }), 20 | getUserById: t.procedure.input(wrap(v.string())).query((opts) => { 21 | return users[opts.input] // input type is string 22 | }), 23 | createUser: t.procedure 24 | .input(wrap(v.object({ 25 | name: v.pipe(v.string(), v.minLength(3)), 26 | bio: v.pipe(v.string(), v.maxLength(142)), 27 | }))) 28 | .mutation((opts) => { 29 | const id = Date.now().toString() 30 | const user: User = { id, ...opts.input } 31 | users[user.id] = user 32 | return user 33 | }), 34 | }) 35 | // export type definition of API 36 | export type AppRouter = typeof appRouter 37 | -------------------------------------------------------------------------------- /apps/api/src/router/orpc.ts: -------------------------------------------------------------------------------- 1 | import { createRouterClient, os } from '@orpc/server' 2 | 3 | const ping = os.handler(() => 'ping') 4 | const pong = os.handler(() => 'pong') 5 | 6 | export const router = { 7 | ping, 8 | pong, 9 | nested: { ping, pong }, 10 | } 11 | 12 | export const serverClient = createRouterClient(router, { 13 | context: {}, 14 | }) 15 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/base.json", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "moduleResolution": "bundler", 6 | "lib": [ 7 | "ES2022" 8 | ], 9 | "types": [ 10 | "node" 11 | ], 12 | "outDir": "./dist", 13 | "rootDir": "./src", 14 | "declarationMap": false, 15 | "declaration": false 16 | }, 17 | "include": [ 18 | "**/*.ts", 19 | "**/*.tsx" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "dist", 24 | "tsdown.config.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /apps/api/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown' 2 | 3 | export default defineConfig({ 4 | entry: './src/index.ts', 5 | outDir: './dist', 6 | format: ['cjs'], 7 | dts: true, 8 | unbundle: true, 9 | target: 'node12', 10 | sourcemap: false, 11 | skipNodeModulesBundle: true, 12 | outExtensions(context) { 13 | if (context.format === 'cjs') { 14 | return { 15 | js: '.cjs', 16 | } 17 | } 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /apps/app-native/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | .kotlin/ 14 | *.orig.* 15 | *.jks 16 | *.p8 17 | *.p12 18 | *.key 19 | *.mobileprovision 20 | 21 | # Metro 22 | .metro-health-check* 23 | 24 | # debug 25 | npm-debug.* 26 | yarn-debug.* 27 | yarn-error.* 28 | 29 | # macOS 30 | .DS_Store 31 | *.pem 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | app-example 40 | -------------------------------------------------------------------------------- /apps/app-native/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /apps/app-native/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "explicit", 4 | "source.organizeImports": "explicit", 5 | "source.sortMembers": "explicit" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/app-native/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Expo app 👋 2 | 3 | This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). 4 | 5 | ## Get started 6 | 7 | 1. Install dependencies 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | 2. Start the app 14 | 15 | ```bash 16 | npx expo start 17 | ``` 18 | 19 | In the output, you'll find options to open the app in a 20 | 21 | - [development build](https://docs.expo.dev/develop/development-builds/introduction/) 22 | - [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) 23 | - [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) 24 | - [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo 25 | 26 | You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). 27 | 28 | ## Get a fresh project 29 | 30 | When you're ready, run: 31 | 32 | ```bash 33 | npm run reset-project 34 | ``` 35 | 36 | This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. 37 | 38 | ## Learn more 39 | 40 | To learn more about developing your project with Expo, look at the following resources: 41 | 42 | - [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). 43 | - [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. 44 | 45 | ## Join the community 46 | 47 | Join our community of developers creating universal apps. 48 | 49 | - [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. 50 | - [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. 51 | -------------------------------------------------------------------------------- /apps/app-native/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "app-native", 4 | "slug": "app-native", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "appnative", 9 | "userInterfaceStyle": "automatic", 10 | "newArchEnabled": true, 11 | "ios": { 12 | "supportsTablet": true 13 | }, 14 | "android": { 15 | "adaptiveIcon": { 16 | "foregroundImage": "./assets/images/adaptive-icon.png", 17 | "backgroundColor": "#ffffff" 18 | }, 19 | "edgeToEdgeEnabled": true 20 | }, 21 | "web": { 22 | "bundler": "metro", 23 | "output": "static", 24 | "favicon": "./assets/images/favicon.png" 25 | }, 26 | "plugins": [ 27 | "expo-router", 28 | [ 29 | "expo-splash-screen", 30 | { 31 | "image": "./assets/images/splash-icon.png", 32 | "imageWidth": 200, 33 | "resizeMode": "contain", 34 | "backgroundColor": "#ffffff" 35 | } 36 | ] 37 | ], 38 | "experiments": { 39 | "typedRoutes": true 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apps/app-native/app/(tabs)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from 'expo-router' 2 | import * as React from 'react' 3 | import { Platform } from 'react-native' 4 | 5 | import { HapticTab } from '@/components/HapticTab' 6 | import { IconSymbol } from '@/components/ui/IconSymbol' 7 | import TabBarBackground from '@/components/ui/TabBarBackground' 8 | import { Colors } from '@/constants/Colors' 9 | import { useColorScheme } from '@/hooks/useColorScheme' 10 | 11 | export default function TabLayout() { 12 | const colorScheme = useColorScheme() 13 | 14 | return ( 15 | 30 | , 35 | }} 36 | /> 37 | , 42 | }} 43 | /> 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /apps/app-native/app/(tabs)/index.tsx: -------------------------------------------------------------------------------- 1 | import { Image } from 'expo-image' 2 | import { Platform, StyleSheet } from 'react-native' 3 | 4 | import { HelloWave } from '@/components/HelloWave' 5 | import ParallaxScrollView from '@/components/ParallaxScrollView' 6 | import { ThemedText } from '@/components/ThemedText' 7 | import { ThemedView } from '@/components/ThemedView' 8 | 9 | export default function HomeScreen() { 10 | return ( 11 | 18 | )} 19 | > 20 | 21 | Welcome! 22 | 23 | 24 | 25 | Step 1: Try it 26 | 27 | Edit app/(tabs)/index.tsx to see changes. 28 | Press{' '} 29 | 30 | {Platform.select({ 31 | ios: 'cmd + d', 32 | android: 'cmd + m', 33 | web: 'F12', 34 | })} 35 | {' '} 36 | to open developer tools. 37 | 38 | 39 | 40 | Step 2: Explore 41 | 42 | {`Tap the Explore tab to learn more about what's included in this starter app.`} 43 | 44 | 45 | 46 | Step 3: Get a fresh start 47 | 48 | {`When you're ready, run `} 49 | npm run reset-project to get a fresh{' '} 50 | app directory. This will move the current{' '} 51 | app to{' '} 52 | app-example. 53 | 54 | 55 | 56 | ) 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | titleContainer: { 61 | flexDirection: 'row', 62 | alignItems: 'center', 63 | gap: 8, 64 | }, 65 | stepContainer: { 66 | gap: 8, 67 | marginBottom: 8, 68 | }, 69 | reactLogo: { 70 | height: 178, 71 | width: 290, 72 | bottom: 0, 73 | left: 0, 74 | position: 'absolute', 75 | }, 76 | }) 77 | -------------------------------------------------------------------------------- /apps/app-native/app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from 'expo-router' 2 | import { StyleSheet } from 'react-native' 3 | 4 | import { ThemedText } from '@/components/ThemedText' 5 | import { ThemedView } from '@/components/ThemedView' 6 | 7 | export default function NotFoundScreen() { 8 | return ( 9 | <> 10 | 11 | 12 | This screen does not exist. 13 | 14 | Go to home screen! 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | container: { 23 | flex: 1, 24 | alignItems: 'center', 25 | justifyContent: 'center', 26 | padding: 20, 27 | }, 28 | link: { 29 | marginTop: 15, 30 | paddingVertical: 15, 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /apps/app-native/app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native-reanimated' 2 | import '../global.css' 3 | 4 | import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native' 5 | import { QueryClient } from '@tanstack/react-query' 6 | import { httpBatchLink } from '@trpc/react-query' 7 | import { useFonts } from 'expo-font' 8 | import { Stack } from 'expo-router' 9 | import { StatusBar } from 'expo-status-bar' 10 | 11 | import { useColorScheme } from '@/hooks/useColorScheme' 12 | 13 | import { trpc } from '../trpc' 14 | 15 | const queryClient = new QueryClient({}) 16 | 17 | const trpcClient = trpc.createClient({ 18 | links: [ 19 | httpBatchLink({ 20 | url: 'your api url', 21 | headers() { 22 | const token = '' 23 | return token ? { Authorization: `Bearer ${token}` } : {} 24 | }, 25 | // transformer: superjson, 26 | }), 27 | ], 28 | }) 29 | 30 | export default function RootLayout() { 31 | const colorScheme = useColorScheme() 32 | const [loaded] = useFonts({ 33 | SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), 34 | }) 35 | 36 | if (!loaded) { 37 | // Async font loading only occurs in development. 38 | return null 39 | } 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /apps/app-native/assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /apps/app-native/assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/favicon.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/icon.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/react-logo.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /apps/app-native/assets/images/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/app-native/assets/images/splash-icon.png -------------------------------------------------------------------------------- /apps/app-native/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true) 3 | return { 4 | presets: [ 5 | ['babel-preset-expo', { jsxImportSource: 'nativewind' }], 6 | 'nativewind/babel', 7 | ], 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/app-native/components/Collapsible.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react' 2 | import { useState } from 'react' 3 | import { StyleSheet, TouchableOpacity } from 'react-native' 4 | 5 | import { ThemedText } from '@/components/ThemedText' 6 | import { ThemedView } from '@/components/ThemedView' 7 | import { IconSymbol } from '@/components/ui/IconSymbol' 8 | import { Colors } from '@/constants/Colors' 9 | import { useColorScheme } from '@/hooks/useColorScheme' 10 | 11 | export function Collapsible({ children, title }: PropsWithChildren & { title: string }) { 12 | const [isOpen, setIsOpen] = useState(false) 13 | const theme = useColorScheme() ?? 'light' 14 | 15 | return ( 16 | 17 | setIsOpen((value) => !value)} 20 | activeOpacity={0.8} 21 | > 22 | 29 | 30 | {title} 31 | 32 | {isOpen && {children}} 33 | 34 | ) 35 | } 36 | 37 | const styles = StyleSheet.create({ 38 | heading: { 39 | flexDirection: 'row', 40 | alignItems: 'center', 41 | gap: 6, 42 | }, 43 | content: { 44 | marginTop: 6, 45 | marginLeft: 24, 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /apps/app-native/components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import type { Href } from 'expo-router' 2 | import { Link } from 'expo-router' 3 | import { openBrowserAsync } from 'expo-web-browser' 4 | import type { ComponentProps } from 'react' 5 | import { Platform } from 'react-native' 6 | 7 | type Props = Omit, 'href'> & { href: Href & string } 8 | 9 | export function ExternalLink({ href, ...rest }: Props) { 10 | return ( 11 | { 16 | if (Platform.OS !== 'web') { 17 | // Prevent the default behavior of linking to the default browser on native. 18 | event.preventDefault() 19 | // Open the link in an in-app browser. 20 | await openBrowserAsync(href) 21 | } 22 | }} 23 | /> 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /apps/app-native/components/HapticTab.tsx: -------------------------------------------------------------------------------- 1 | import type { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs' 2 | import { PlatformPressable } from '@react-navigation/elements' 3 | import * as Haptics from 'expo-haptics' 4 | 5 | export function HapticTab(props: BottomTabBarButtonProps) { 6 | return ( 7 | { 10 | if (process.env.EXPO_OS === 'ios') { 11 | // Add a soft haptic feedback when pressing down on the tabs. 12 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) 13 | } 14 | props.onPressIn?.(ev) 15 | }} 16 | /> 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /apps/app-native/components/HelloWave.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { StyleSheet } from 'react-native' 3 | import Animated, { 4 | useAnimatedStyle, 5 | useSharedValue, 6 | withRepeat, 7 | withSequence, 8 | withTiming, 9 | } from 'react-native-reanimated' 10 | 11 | import { ThemedText } from '@/components/ThemedText' 12 | 13 | export function HelloWave() { 14 | const rotationAnimation = useSharedValue(0) 15 | 16 | useEffect(() => { 17 | rotationAnimation.value = withRepeat( 18 | withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), 19 | 4, // Run the animation 4 times 20 | ) 21 | }, [rotationAnimation]) 22 | 23 | const animatedStyle = useAnimatedStyle(() => ({ 24 | transform: [{ rotate: `${rotationAnimation.value}deg` }], 25 | })) 26 | 27 | return ( 28 | 29 | 👋 30 | 31 | ) 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | text: { 36 | fontSize: 28, 37 | lineHeight: 32, 38 | marginTop: -6, 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /apps/app-native/components/ParallaxScrollView.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren, ReactElement } from 'react' 2 | import { StyleSheet } from 'react-native' 3 | import Animated, { 4 | interpolate, 5 | useAnimatedRef, 6 | useAnimatedStyle, 7 | useScrollViewOffset, 8 | } from 'react-native-reanimated' 9 | 10 | import { ThemedView } from '@/components/ThemedView' 11 | import { useBottomTabOverflow } from '@/components/ui/TabBarBackground' 12 | import { useColorScheme } from '@/hooks/useColorScheme' 13 | 14 | const HEADER_HEIGHT = 250 15 | 16 | type Props = PropsWithChildren<{ 17 | headerImage: ReactElement 18 | headerBackgroundColor: { dark: string, light: string } 19 | }> 20 | 21 | export default function ParallaxScrollView({ 22 | children, 23 | headerImage, 24 | headerBackgroundColor, 25 | }: Props) { 26 | const colorScheme = useColorScheme() ?? 'light' 27 | const scrollRef = useAnimatedRef() 28 | const scrollOffset = useScrollViewOffset(scrollRef) 29 | const bottom = useBottomTabOverflow() 30 | const headerAnimatedStyle = useAnimatedStyle(() => { 31 | return { 32 | transform: [ 33 | { 34 | translateY: interpolate( 35 | scrollOffset.value, 36 | [-HEADER_HEIGHT, 0, HEADER_HEIGHT], 37 | [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75], 38 | ), 39 | }, 40 | { 41 | scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), 42 | }, 43 | ], 44 | } 45 | }) 46 | 47 | return ( 48 | 49 | 55 | 62 | {headerImage} 63 | 64 | {children} 65 | 66 | 67 | ) 68 | } 69 | 70 | const styles = StyleSheet.create({ 71 | container: { 72 | flex: 1, 73 | }, 74 | header: { 75 | height: HEADER_HEIGHT, 76 | overflow: 'hidden', 77 | }, 78 | content: { 79 | flex: 1, 80 | padding: 32, 81 | gap: 16, 82 | overflow: 'hidden', 83 | }, 84 | }) 85 | -------------------------------------------------------------------------------- /apps/app-native/components/ThemedText.tsx: -------------------------------------------------------------------------------- 1 | import type { TextProps } from 'react-native' 2 | import { StyleSheet, Text } from 'react-native' 3 | 4 | import { useThemeColor } from '@/hooks/useThemeColor' 5 | 6 | export type ThemedTextProps = TextProps & { 7 | lightColor?: string 8 | darkColor?: string 9 | type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link' 10 | } 11 | 12 | export function ThemedText({ 13 | style, 14 | lightColor, 15 | darkColor, 16 | type = 'default', 17 | ...rest 18 | }: ThemedTextProps) { 19 | const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text') 20 | 21 | return ( 22 | 34 | ) 35 | } 36 | 37 | const styles = StyleSheet.create({ 38 | default: { 39 | fontSize: 16, 40 | lineHeight: 24, 41 | }, 42 | defaultSemiBold: { 43 | fontSize: 16, 44 | lineHeight: 24, 45 | fontWeight: '600', 46 | }, 47 | title: { 48 | fontSize: 32, 49 | fontWeight: 'bold', 50 | lineHeight: 32, 51 | }, 52 | subtitle: { 53 | fontSize: 20, 54 | fontWeight: 'bold', 55 | }, 56 | link: { 57 | lineHeight: 30, 58 | fontSize: 16, 59 | color: '#0a7ea4', 60 | }, 61 | }) 62 | -------------------------------------------------------------------------------- /apps/app-native/components/ThemedView.tsx: -------------------------------------------------------------------------------- 1 | import type { ViewProps } from 'react-native' 2 | import { View } from 'react-native' 3 | 4 | import { useThemeColor } from '@/hooks/useThemeColor' 5 | 6 | export type ThemedViewProps = ViewProps & { 7 | lightColor?: string 8 | darkColor?: string 9 | } 10 | 11 | export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { 12 | const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background') 13 | 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /apps/app-native/components/ui/IconSymbol.ios.tsx: -------------------------------------------------------------------------------- 1 | import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; 2 | import { StyleProp, ViewStyle } from 'react-native'; 3 | 4 | export function IconSymbol({ 5 | name, 6 | size = 24, 7 | color, 8 | style, 9 | weight = 'regular', 10 | }: { 11 | name: SymbolViewProps['name']; 12 | size?: number; 13 | color: string; 14 | style?: StyleProp; 15 | weight?: SymbolWeight; 16 | }) { 17 | return ( 18 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /apps/app-native/components/ui/IconSymbol.tsx: -------------------------------------------------------------------------------- 1 | // Fallback for using MaterialIcons on Android and web. 2 | 3 | import MaterialIcons from '@expo/vector-icons/MaterialIcons'; 4 | import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; 5 | import { ComponentProps } from 'react'; 6 | import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; 7 | 8 | type IconMapping = Record['name']>; 9 | type IconSymbolName = keyof typeof MAPPING; 10 | 11 | /** 12 | * Add your SF Symbols to Material Icons mappings here. 13 | * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). 14 | * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. 15 | */ 16 | const MAPPING = { 17 | 'house.fill': 'home', 18 | 'paperplane.fill': 'send', 19 | 'chevron.left.forwardslash.chevron.right': 'code', 20 | 'chevron.right': 'chevron-right', 21 | } as IconMapping; 22 | 23 | /** 24 | * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. 25 | * This ensures a consistent look across platforms, and optimal resource usage. 26 | * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons. 27 | */ 28 | export function IconSymbol({ 29 | name, 30 | size = 24, 31 | color, 32 | style, 33 | }: { 34 | name: IconSymbolName; 35 | size?: number; 36 | color: string | OpaqueColorValue; 37 | style?: StyleProp; 38 | weight?: SymbolWeight; 39 | }) { 40 | return ; 41 | } 42 | -------------------------------------------------------------------------------- /apps/app-native/components/ui/TabBarBackground.ios.tsx: -------------------------------------------------------------------------------- 1 | import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; 2 | import { BlurView } from 'expo-blur'; 3 | import { StyleSheet } from 'react-native'; 4 | 5 | export default function BlurTabBarBackground() { 6 | return ( 7 | 14 | ); 15 | } 16 | 17 | export function useBottomTabOverflow() { 18 | return useBottomTabBarHeight(); 19 | } 20 | -------------------------------------------------------------------------------- /apps/app-native/components/ui/TabBarBackground.tsx: -------------------------------------------------------------------------------- 1 | // This is a shim for web and Android where the tab bar is generally opaque. 2 | export default undefined; 3 | 4 | export function useBottomTabOverflow() { 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /apps/app-native/constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = '#0a7ea4' 7 | const tintColorDark = '#fff' 8 | 9 | export const Colors = { 10 | light: { 11 | text: '#11181C', 12 | background: '#fff', 13 | tint: tintColorLight, 14 | icon: '#687076', 15 | tabIconDefault: '#687076', 16 | tabIconSelected: tintColorLight, 17 | }, 18 | dark: { 19 | text: '#ECEDEE', 20 | background: '#151718', 21 | tint: tintColorDark, 22 | icon: '#9BA1A6', 23 | tabIconDefault: '#9BA1A6', 24 | tabIconSelected: tintColorDark, 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /apps/app-native/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint-config-hyoban' 2 | 3 | export default defineConfig( 4 | { 5 | react: 'expo', 6 | restrictedSyntax: ['jsx', 'tsx'], 7 | strict: true, 8 | tailwindCSS: true, 9 | unocss: false, 10 | ignores: ['**/*.d.ts', '**/components/ui/**', 'package.json'], 11 | }, 12 | { 13 | rules: { 14 | 'react-hooks/rules-of-hooks': 'error', 15 | 'react-hooks/exhaustive-deps': 'error', 16 | 'react-refresh/only-export-components': 'off', 17 | 'react-google-translate/no-conditional-text-nodes-with-siblings': 'off', 18 | 19 | 'unicorn/prefer-add-event-listener': 'off', 20 | 'unicorn/prefer-node-protocol': 'off', 21 | 'unused-imports/no-unused-vars': 'off', 22 | 'unicorn/no-useless-undefined': 'off', 23 | 24 | 'max-params': 'off', 25 | 'no-console': 'off', 26 | 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-unused-expressions': 'off', 29 | 30 | '@eslint-react/naming-convention/filename-extension': 'off', 31 | '@eslint-react/no-complex-conditional-rendering': 'off', 32 | '@eslint-react/dom/no-missing-button-type': 'off', 33 | '@eslint-react/no-array-index-key': 'off', 34 | '@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 'off', 35 | '@eslint-react/jsx-no-undef': 'off', 36 | 37 | '@stylistic/multiline-ternary': 'off', 38 | '@stylistic/arrow-parens': 'off', 39 | '@stylistic/brace-style': 'off', 40 | '@stylistic/jsx-one-expression-per-line': 'off', 41 | }, 42 | }, 43 | ) 44 | -------------------------------------------------------------------------------- /apps/app-native/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/app-native/hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from 'react-native' 2 | -------------------------------------------------------------------------------- /apps/app-native/hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useColorScheme as useRNColorScheme } from 'react-native' 3 | 4 | /** 5 | * To support static rendering, this value needs to be re-calculated on the client side for web 6 | */ 7 | export function useColorScheme() { 8 | const [hasHydrated, setHasHydrated] = useState(false) 9 | 10 | useEffect(() => { 11 | setHasHydrated(true) 12 | }, []) 13 | 14 | const colorScheme = useRNColorScheme() 15 | 16 | if (hasHydrated) { 17 | return colorScheme 18 | } 19 | 20 | return 'light' 21 | } 22 | -------------------------------------------------------------------------------- /apps/app-native/hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { Colors } from '@/constants/Colors' 7 | import { useColorScheme } from '@/hooks/useColorScheme' 8 | 9 | export function useThemeColor( 10 | props: { light?: string, dark?: string }, 11 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark, 12 | ) { 13 | const theme = useColorScheme() ?? 'light' 14 | const colorFromProps = props[theme] 15 | 16 | if (colorFromProps) { 17 | return colorFromProps 18 | } 19 | return Colors[theme][colorName] 20 | } 21 | -------------------------------------------------------------------------------- /apps/app-native/metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require('expo/metro-config') 2 | const { withNativeWind } = require('nativewind/metro') 3 | 4 | const config = getDefaultConfig(__dirname) 5 | 6 | module.exports = withNativeWind(config, { input: './global.css' }) 7 | -------------------------------------------------------------------------------- /apps/app-native/nativewind-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind. -------------------------------------------------------------------------------- /apps/app-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-user", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "expo-router/entry", 6 | "scripts": { 7 | "android": "expo start --android", 8 | "expo:lint": "expo lint", 9 | "ios": "expo start --ios", 10 | "reset-project": "node ./scripts/reset-project.js", 11 | "start": "expo start", 12 | "web": "expo start --web" 13 | }, 14 | "dependencies": { 15 | "@expo/vector-icons": "^14.1.0", 16 | "@react-navigation/bottom-tabs": "^7.3.10", 17 | "@react-navigation/elements": "^2.3.8", 18 | "@react-navigation/native": "^7.1.6", 19 | "expo": "~53.0.20", 20 | "expo-blur": "~14.1.5", 21 | "expo-constants": "~17.1.7", 22 | "expo-font": "~13.3.2", 23 | "expo-haptics": "~14.1.4", 24 | "expo-image": "~2.4.0", 25 | "expo-linking": "~7.1.7", 26 | "expo-router": "~5.1.4", 27 | "expo-splash-screen": "~0.30.10", 28 | "expo-status-bar": "~2.2.3", 29 | "expo-symbols": "~0.4.5", 30 | "expo-system-ui": "~5.0.10", 31 | "expo-web-browser": "~14.2.0", 32 | "nativewind": "^4.1.23", 33 | "react": "catalog:react-rn", 34 | "react-dom": "catalog:react-rn", 35 | "react-native": "0.79.5", 36 | "react-native-gesture-handler": "~2.24.0", 37 | "react-native-reanimated": "~3.17.5", 38 | "react-native-safe-area-context": "5.4.0", 39 | "react-native-screens": "~4.11.1", 40 | "react-native-web": "~0.20.0", 41 | "react-native-webview": "13.13.5", 42 | "superjson": "^1.12.1" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.25.2", 46 | "@tailwindcss/postcss": "^4.1.11", 47 | "eslint": "^9.36.0", 48 | "eslint-config-expo": "~9.2.0", 49 | "postcss": "^8.5.6", 50 | "prettier-plugin-tailwindcss": "^0.5.14", 51 | "tailwindcss": "^3.4.17", 52 | "typescript": "^5.9.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /apps/app-native/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | // NOTE: Update this to include the paths to all files that contain Nativewind classes. 4 | content: ['./App.tsx', './components/**/*.{js,jsx,ts,tsx}'], 5 | presets: [require('nativewind/preset')], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /apps/app-native/trpc/index.ts: -------------------------------------------------------------------------------- 1 | import type { inferReactQueryProcedureOptions } from '@trpc/react-query' 2 | import { createTRPCReact } from '@trpc/react-query' 3 | import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server' 4 | 5 | import type { AppRouter } from '../../api/src/router' 6 | 7 | export const trpc = createTRPCReact() 8 | 9 | export type ReactQueryOptions = inferReactQueryProcedureOptions 10 | export type RouterInputs = inferRouterInputs 11 | export type RouterOutputs = inferRouterOutputs 12 | 13 | export { type AppRouter } from '../../api/src/router' 14 | -------------------------------------------------------------------------------- /apps/app-native/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts", 16 | "nativewind-env.d.ts" 17 | ] 18 | } -------------------------------------------------------------------------------- /apps/web-next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /apps/web-next/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | auto-install-peers=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /apps/web-next/.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npmjs.org" -------------------------------------------------------------------------------- /apps/web-next/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /apps/web-next/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/web-next/app/favicon.ico -------------------------------------------------------------------------------- /apps/web-next/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/web-next/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /apps/web-next/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fisand/f3-app/fa576fed74b674fa92262d4fadd726d5dc01aaaa/apps/web-next/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /apps/web-next/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #ffffff; 3 | --foreground: #171717; 4 | } 5 | 6 | @unocss all; 7 | 8 | @media (prefers-color-scheme: dark) { 9 | :root { 10 | --background: #0a0a0a; 11 | --foreground: #ededed; 12 | } 13 | } 14 | 15 | html, 16 | body { 17 | max-width: 100vw; 18 | overflow-x: hidden; 19 | } 20 | 21 | body { 22 | color: var(--foreground); 23 | background: var(--background); 24 | font-family: Arial, Helvetica, sans-serif; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | * { 30 | box-sizing: border-box; 31 | padding: 0; 32 | margin: 0; 33 | } 34 | 35 | a { 36 | color: inherit; 37 | text-decoration: none; 38 | } 39 | 40 | @media (prefers-color-scheme: dark) { 41 | html { 42 | color-scheme: dark; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/web-next/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import '@unocss/reset/tailwind.css' 2 | import './globals.css' 3 | 4 | import type { Metadata } from 'next' 5 | import localFont from 'next/font/local' 6 | 7 | const geistSans = localFont({ 8 | src: './fonts/GeistVF.woff', 9 | variable: '--font-geist-sans', 10 | weight: '100 900', 11 | }) 12 | const geistMono = localFont({ 13 | src: './fonts/GeistMonoVF.woff', 14 | variable: '--font-geist-mono', 15 | weight: '100 900', 16 | }) 17 | 18 | export const metadata: Metadata = { 19 | title: 'Create Next App', 20 | description: 'Generated by create next app', 21 | } 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode 27 | }>) { 28 | return ( 29 | 30 | 31 | {children} 32 | 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /apps/web-next/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { ShimmerButton } from '@repo/ui/src/components/ui/shimmer-button' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |
    8 |
  1. 9 | Nextjs 🩷 unocss 10 |
  2. 11 |
  3. 12 | Get started by editing app/page.tsx. 13 |
  4. 14 |
  5. Save and see your changes instantly.
  6. 15 |
16 | 17 |
18 | 19 | Deploy now 20 | 21 | 27 | Read our docs 28 | 29 |
30 |
31 | 60 |
61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /apps/web-next/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint-config-hyoban' 2 | 3 | export default defineConfig({ 4 | react: 'next', 5 | unocss: true, 6 | tailwindCSS: false, 7 | ignores: ['**/*.d.ts'], 8 | }, { 9 | rules: {}, 10 | }) 11 | -------------------------------------------------------------------------------- /apps/web-next/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'export', // for cloudflare deploy preview 4 | } 5 | 6 | export default nextConfig 7 | -------------------------------------------------------------------------------- /apps/web-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-next", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "private": true, 6 | "scripts": { 7 | "build": "next build --turbo", 8 | "dev": "next dev --turbo", 9 | "lint": "eslint", 10 | "preview": "pnpm dlx serve out", 11 | "start": "next start" 12 | }, 13 | "dependencies": { 14 | "@repo/ui": "workspace:*", 15 | "@unocss/reset": "~66.5.4", 16 | "eslint-config": "workspace:*", 17 | "next": "^16.0.1", 18 | "react": "^19.2.0", 19 | "react-dom": "^19.2.0" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^22.15.29", 23 | "@unocss/postcss": "~66.3.3", 24 | "eslint-config-next": "16.0.1", 25 | "typescript": "^5.9.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web-next/postcss.config.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { fileURLToPath } from 'node:url' 3 | 4 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 5 | 6 | export default { 7 | plugins: [ 8 | ['@unocss/postcss', { 9 | content: ['./app/**/*.{html,js,ts,jsx,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'], 10 | configFile: '../../uno.config.ts', 11 | cwd: path.resolve(__dirname, '../../'), 12 | }], 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /apps/web-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "./*" 27 | ], 28 | "@ui-internal/*": [ 29 | "../../packages/ui/src/*" 30 | ] 31 | }, 32 | "target": "ES2017" 33 | }, 34 | "include": [ 35 | "next-env.d.ts", 36 | "**/*.ts", 37 | "**/*.tsx", 38 | ".next/types/**/*.ts", 39 | ".next/dev/types/**/*.ts" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /apps/web-remix/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | 4 | # React Router 5 | /.react-router/ 6 | /build/ 7 | -------------------------------------------------------------------------------- /apps/web-remix/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | auto-install-peers=true 3 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /apps/web-remix/app/app.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, 3 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 4 | } 5 | 6 | html, 7 | body { 8 | @apply bg-white dark:bg-gray-950; 9 | 10 | @media (prefers-color-scheme: dark) { 11 | color-scheme: dark; 12 | } 13 | } 14 | 15 | 16 | 17 | @layer base { 18 | :root { 19 | --sidebar-background: 0 0% 98%; 20 | --sidebar-foreground: 240 5.3% 26.1%; 21 | --sidebar-primary: 240 5.9% 10%; 22 | --sidebar-primary-foreground: 0 0% 98%; 23 | --sidebar-accent: 240 4.8% 95.9%; 24 | --sidebar-accent-foreground: 240 5.9% 10%; 25 | --sidebar-border: 220 13% 91%; 26 | --sidebar-ring: 217.2 91.2% 59.8%; 27 | } 28 | .dark { 29 | --sidebar-background: 240 5.9% 10%; 30 | --sidebar-foreground: 240 4.8% 95.9%; 31 | --sidebar-primary: 224.3 76.3% 48%; 32 | --sidebar-primary-foreground: 0 0% 100%; 33 | --sidebar-accent: 240 3.7% 15.9%; 34 | --sidebar-accent-foreground: 240 4.8% 95.9%; 35 | --sidebar-border: 240 3.7% 15.9%; 36 | --sidebar-ring: 217.2 91.2% 59.8%; 37 | } 38 | } 39 | 40 | 41 | 42 | @layer base { 43 | * { 44 | @apply border-border; 45 | } 46 | body { 47 | @apply bg-background text-foreground; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/layout/Header.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | 3 | export function Header({ action }: { action?: ReactNode }) { 4 | return ( 5 |
6 |
7 |
8 | Unoi 9 | 10 | 21 | fisand 22 | 23 | 24 |
25 |
26 | {action} 27 | 33 | 34 | 35 |
36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/layout/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useConnectModal } from '@rainbow-me/rainbowkit' 2 | import { motion } from 'framer-motion' 3 | import type { LucideIcon } from 'lucide-react' 4 | import { useEffect, useState } from 'react' 5 | import { Link } from 'react-router' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | interface NavItem { 10 | name: string 11 | url: string 12 | icon: LucideIcon 13 | } 14 | 15 | interface NavbarProps { 16 | items: NavItem[] 17 | className?: string 18 | } 19 | 20 | export function Navbar({ items, className }: NavbarProps) { 21 | const [activeTab, setActiveTab] = useState(items[0].name) 22 | const [_, setIsMobile] = useState(false) 23 | const { openConnectModal } = useConnectModal() 24 | 25 | useEffect(() => { 26 | const handleResize = () => { 27 | setIsMobile(window.innerWidth < 768) 28 | } 29 | 30 | handleResize() 31 | window.addEventListener('resize', handleResize) 32 | return () => window.removeEventListener('resize', handleResize) 33 | }, []) 34 | 35 | const handleClick = useCallback((item: NavItem) => { 36 | setActiveTab(item.name) 37 | console.info(item.name) 38 | if (item.name.toLowerCase() === 'connect') { 39 | openConnectModal?.() 40 | } 41 | }, [openConnectModal]) 42 | 43 | return ( 44 |
50 |
51 | {items.map((item) => { 52 | const Icon = item.icon 53 | const isActive = activeTab === item.name 54 | 55 | return ( 56 | handleClick(item)} 60 | className={cn( 61 | 'relative cursor-pointer text-sm font-semibold px-6 py-2 rounded-full transition-colors', 62 | 'text-white/40 hover:text-white', 63 | isActive && 'text-white', 64 | )} 65 | > 66 | {item.name} 67 | 68 | 69 | 70 | {isActive && ( 71 | 81 |
82 |
83 |
84 |
85 |
86 | 87 | )} 88 | 89 | ) 90 | })} 91 |
92 |
93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/typing-animation.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-plusplus */ 2 | 3 | import type { MotionProps } from 'motion/react' 4 | import { motion } from 'motion/react' 5 | import { useEffect, useRef, useState } from 'react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | interface TypingAnimationProps extends MotionProps { 10 | children: string 11 | className?: string 12 | duration?: number 13 | delay?: number 14 | as?: React.ElementType 15 | startOnView?: boolean 16 | } 17 | 18 | export function TypingAnimation({ 19 | children, 20 | className, 21 | duration = 100, 22 | delay = 0, 23 | as: Component = 'div', 24 | startOnView = false, 25 | ...props 26 | }: TypingAnimationProps) { 27 | const MotionComponent = motion.create(Component, { 28 | forwardMotionProps: true, 29 | }) 30 | 31 | const [displayedText, setDisplayedText] = useState('') 32 | const [started, setStarted] = useState(false) 33 | const elementRef = useRef(null) 34 | 35 | useEffect(() => { 36 | if (!startOnView) { 37 | const startTimeout = setTimeout(() => { 38 | setStarted(true) 39 | }, delay) 40 | return () => clearTimeout(startTimeout) 41 | } 42 | 43 | const observer = new IntersectionObserver( 44 | ([entry]) => { 45 | if (entry.isIntersecting) { 46 | setTimeout(() => { 47 | setStarted(true) 48 | }, delay) 49 | observer.disconnect() 50 | } 51 | }, 52 | { threshold: 0.1 }, 53 | ) 54 | 55 | if (elementRef.current) { 56 | observer.observe(elementRef.current) 57 | } 58 | 59 | return () => observer.disconnect() 60 | }, [delay, startOnView]) 61 | 62 | useEffect(() => { 63 | if (!started) 64 | return 65 | 66 | let i = 0 67 | const typingEffect = setInterval(() => { 68 | if (i < children.length) { 69 | setDisplayedText(children.slice(0, Math.max(0, i + 1))) 70 | i++ 71 | } 72 | else { 73 | clearInterval(typingEffect) 74 | } 75 | }, duration) 76 | 77 | return () => { 78 | clearInterval(typingEffect) 79 | } 80 | }, [children, duration, started]) 81 | 82 | return ( 83 | 91 | {displayedText} 92 | 93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as AccordionPrimitive from '@radix-ui/react-accordion' 2 | import { ChevronDownIcon } from '@radix-ui/react-icons' 3 | import * as React from 'react' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | const Accordion = AccordionPrimitive.Root 8 | 9 | function AccordionItem({ ref, className, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 10 | return ( 11 | 16 | ) 17 | } 18 | AccordionItem.displayName = 'AccordionItem' 19 | 20 | function AccordionTrigger({ ref, className, children, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 21 | return ( 22 | 23 | svg]:rotate-180', 27 | className, 28 | )} 29 | {...props} 30 | > 31 | {children} 32 | 33 | 34 | 35 | ) 36 | } 37 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 38 | 39 | function AccordionContent({ ref, className, children, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 40 | return ( 41 | 46 |
{children}
47 |
48 | ) 49 | } 50 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 51 | 52 | export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } 53 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import type { VariantProps } from 'class-variance-authority' 2 | import { cva } from 'class-variance-authority' 3 | import * as React from 'react' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | const alertVariants = cva( 8 | 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-background text-foreground', 13 | destructive: 14 | 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', 15 | }, 16 | }, 17 | defaultVariants: { 18 | variant: 'default', 19 | }, 20 | }, 21 | ) 22 | 23 | function Alert({ ref, className, variant, ...props }: React.HTMLAttributes & VariantProps & { ref?: React.RefObject }) { 24 | return ( 25 |
31 | ) 32 | } 33 | Alert.displayName = 'Alert' 34 | 35 | function AlertTitle({ ref, className, ...props }: React.HTMLAttributes & { ref?: React.RefObject }) { 36 | return ( 37 |
42 | ) 43 | } 44 | AlertTitle.displayName = 'AlertTitle' 45 | 46 | function AlertDescription({ ref, className, ...props }: React.HTMLAttributes & { ref?: React.RefObject }) { 47 | return ( 48 |
53 | ) 54 | } 55 | AlertDescription.displayName = 'AlertDescription' 56 | 57 | export { Alert, AlertDescription, AlertTitle } 58 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' 2 | 3 | const AspectRatio = AspectRatioPrimitive.Root 4 | 5 | export { AspectRatio } 6 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as AvatarPrimitive from '@radix-ui/react-avatar' 4 | import * as React from 'react' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Avatar({ ref, className, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 9 | return ( 10 | 18 | ) 19 | } 20 | Avatar.displayName = AvatarPrimitive.Root.displayName 21 | 22 | function AvatarImage({ ref, className, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 23 | return ( 24 | 29 | ) 30 | } 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 32 | 33 | function AvatarFallback({ ref, className, ...props }: React.ComponentPropsWithoutRef & { ref?: React.RefObject | null> }) { 34 | return ( 35 | 43 | ) 44 | } 45 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 46 | 47 | export { Avatar, AvatarFallback, AvatarImage } 48 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import type { VariantProps } from 'class-variance-authority' 2 | import { cva } from 'class-variance-authority' 3 | import * as React from 'react' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | const badgeVariants = cva( 8 | 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', 14 | secondary: 15 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 16 | destructive: 17 | 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', 18 | outline: 'text-foreground', 19 | }, 20 | }, 21 | defaultVariants: { 22 | variant: 'default', 23 | }, 24 | }, 25 | ) 26 | 27 | export interface BadgeProps 28 | extends React.HTMLAttributes, 29 | VariantProps {} 30 | 31 | function Badge({ className, variant, ...props }: BadgeProps) { 32 | return ( 33 |
34 | ) 35 | } 36 | 37 | export { Badge, badgeVariants } 38 | -------------------------------------------------------------------------------- /apps/web-remix/app/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons' 2 | import { Slot } from '@radix-ui/react-slot' 3 | import * as React from 'react' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | function Breadcrumb({ ref, ...props }: React.ComponentPropsWithoutRef<'nav'> & { 8 | separator?: React.ReactNode 9 | } & { ref?: React.RefObject }) { 10 | return