├── .npmrc
├── apps
├── spa
│ ├── README.md
│ ├── sst-env.d.ts
│ ├── turbo.json
│ ├── postcss.config.js
│ ├── .gitignore
│ ├── tsconfig.dev.json
│ ├── src
│ │ ├── sst-env.d.ts
│ │ ├── routes
│ │ │ ├── _layout
│ │ │ │ ├── _layout-2
│ │ │ │ │ ├── layout-a.tsx
│ │ │ │ │ └── layout-b.tsx
│ │ │ │ └── _layout-2.tsx
│ │ │ ├── posts.index.tsx
│ │ │ ├── _layout.tsx
│ │ │ ├── index.tsx
│ │ │ ├── posts.$postId.tsx
│ │ │ ├── posts.tsx
│ │ │ └── __root.tsx
│ │ ├── main.tsx
│ │ ├── posts.tsx
│ │ └── routeTree.gen.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ ├── index.html
│ ├── vite.config.js
│ ├── playwright.config.ts
│ ├── package.json
│ └── tests
│ │ └── app.spec.ts
├── astro
│ ├── src
│ │ ├── env.d.ts
│ │ ├── layouts
│ │ │ └── Layout.astro
│ │ ├── components
│ │ │ └── Card.astro
│ │ └── pages
│ │ │ └── index.astro
│ ├── tsconfig.json
│ ├── turbo.json
│ ├── astro.config.mjs
│ ├── package.json
│ ├── public
│ │ └── favicon.svg
│ └── README.md
└── api
│ ├── src
│ ├── types.ts
│ ├── middleware
│ │ ├── errorMiddleware.ts
│ │ └── responseLoggerMiddleware.ts
│ ├── __tests__
│ │ └── server.test.ts
│ ├── utils
│ │ ├── logger.ts
│ │ └── errorHandler.ts
│ ├── index.ts
│ └── index.test.ts
│ ├── sst-env.d.ts
│ ├── turbo.json
│ ├── wrangler.toml
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── package.json
├── .env.example
├── .markdownlint.jsonc
├── infra
├── src
│ ├── index.ts
│ └── pages.ts
├── bun.lockb
├── tsconfig.json
├── tsup.config.ts
└── package.json
├── packages
├── ui
│ ├── bunfig.toml
│ ├── postcss.config.js
│ ├── src
│ │ ├── index.tsx
│ │ ├── lib
│ │ │ └── utils.ts
│ │ ├── components
│ │ │ ├── ui
│ │ │ │ ├── aspect-ratio.tsx
│ │ │ │ ├── skeleton.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── toaster.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── textarea.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── progress.tsx
│ │ │ │ ├── sonner.tsx
│ │ │ │ ├── checkbox.tsx
│ │ │ │ ├── slider.tsx
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── switch.tsx
│ │ │ │ ├── tooltip.tsx
│ │ │ │ ├── hover-card.tsx
│ │ │ │ ├── popover.tsx
│ │ │ │ ├── toggle.tsx
│ │ │ │ ├── avatar.tsx
│ │ │ │ ├── radio-group.tsx
│ │ │ │ ├── alert.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── resizable.tsx
│ │ │ │ ├── toggle-group.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── tabs.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── accordion.tsx
│ │ │ │ ├── input-otp.tsx
│ │ │ │ ├── calendar.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── pagination.tsx
│ │ │ │ ├── table.tsx
│ │ │ │ ├── drawer.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── use-toast.ts
│ │ │ │ ├── sheet.tsx
│ │ │ │ ├── form.tsx
│ │ │ │ ├── alert-dialog.tsx
│ │ │ │ ├── toast.tsx
│ │ │ │ ├── command.tsx
│ │ │ │ ├── navigation-menu.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── carousel.tsx
│ │ │ │ ├── context-menu.tsx
│ │ │ │ └── dropdown-menu.tsx
│ │ │ ├── counter-button
│ │ │ │ ├── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ └── link
│ │ │ │ ├── index.test.tsx
│ │ │ │ └── index.tsx
│ │ ├── hooks
│ │ │ ├── use-media-query.ts
│ │ │ ├── use-enter-submit.ts
│ │ │ └── use-resize-observer.ts
│ │ └── styles
│ │ │ └── globals.css
│ ├── turbo.json
│ ├── happydom.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── components.json
│ ├── tailwind.config.ts
│ └── package.json
├── _packages-template
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── turbo.json
│ ├── tsup.config.ts
│ └── package.json
├── core
│ ├── tsconfig.json
│ ├── turbo.json
│ ├── src
│ │ └── index.ts
│ ├── tsup.config.ts
│ └── package.json
├── db
│ ├── tsconfig.json
│ ├── README.md
│ ├── turbo.json
│ ├── tsup.config.ts
│ ├── drizzle.config.ts
│ ├── src
│ │ ├── index.ts
│ │ ├── client.ts
│ │ └── schema
│ │ │ └── index.ts
│ └── package.json
└── config-typescript
│ ├── astro.json
│ ├── package.json
│ ├── nextjs.json
│ ├── vite.json
│ ├── react.json
│ ├── node.json
│ └── base.json
├── tsconfig.json
├── bun.lockb
├── env.d.ts
├── sst-env.d.ts
├── .vscode
├── settings.json
└── extensions.json
├── .gitignore
├── .github
└── workflows
│ ├── lint.yml
│ └── template-sync.yml
├── turbo.json
├── biome.jsonc
├── TODO.md
├── sst.config.ts
├── package.json
├── prompts
└── explainer
├── .cursorrules
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers = true
2 |
--------------------------------------------------------------------------------
/apps/spa/README.md:
--------------------------------------------------------------------------------
1 | # Tanstack Router
2 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | CLOUDFLARE_API_TOKEN=
2 | DATABASE_URL=
--------------------------------------------------------------------------------
/.markdownlint.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "MD033": false
3 | }
4 |
--------------------------------------------------------------------------------
/infra/src/index.ts:
--------------------------------------------------------------------------------
1 | export { pagesProject } from './pages'
2 |
--------------------------------------------------------------------------------
/packages/ui/bunfig.toml:
--------------------------------------------------------------------------------
1 | [test]
2 | preload = "./happydom.ts"
--------------------------------------------------------------------------------
/packages/_packages-template/src/index.ts:
--------------------------------------------------------------------------------
1 | const index = 'index'
2 |
--------------------------------------------------------------------------------
/apps/astro/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/api/src/types.ts:
--------------------------------------------------------------------------------
1 | export type Bindings = {
2 | DATABASE_URL: string
3 | }
4 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/austinm911/tanstack-monorepo/HEAD/bun.lockb
--------------------------------------------------------------------------------
/infra/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/austinm911/tanstack-monorepo/HEAD/infra/bun.lockb
--------------------------------------------------------------------------------
/infra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json",
3 | "include": [".", "../env.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/apps/api/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | ///
4 | export {}
5 |
--------------------------------------------------------------------------------
/apps/api/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/astro/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/astro.json",
3 | "include": [".", "../../env.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/apps/spa/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | ///
4 | export {}
5 |
--------------------------------------------------------------------------------
/apps/spa/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'bun' {
2 | interface Env {
3 | CLOUDFLARE_ACCOUNT_ID: string
4 | DATABASE_URL: string
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json",
3 | "include": [".", "../../env.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/db/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json",
3 | "include": [".", "../../env.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/apps/api/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "api"
2 | main = "src/index.ts"
3 | compatibility_date = "2024-03-07"
4 |
5 | [dev]
6 | port = 5001
7 |
--------------------------------------------------------------------------------
/apps/astro/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/db/README.md:
--------------------------------------------------------------------------------
1 | # Database Package
2 |
3 | [Drizzle Postgres](https://orm.drizzle.team/docs/get-started-postgresql#postgresjs)
4 |
--------------------------------------------------------------------------------
/packages/db/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { Link } from './components/link'
2 | export { CounterButton } from './components/counter-button'
3 |
--------------------------------------------------------------------------------
/packages/ui/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/_packages-template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json",
3 | "include": [".", "../../env.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/apps/astro/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config'
2 |
3 | // https://astro.build/config
4 | export default defineConfig({})
5 |
--------------------------------------------------------------------------------
/packages/_packages-template/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { db } from '@repo/db'
2 |
3 | if (!db) {
4 | throw new Error('db is not defined')
5 | }
6 |
7 | console.log(db)
8 |
--------------------------------------------------------------------------------
/apps/spa/postcss.config.js:
--------------------------------------------------------------------------------
1 | // @ts-expect-error - No types for postcss
2 | import postcss from '@repo/ui/postcss.config'
3 |
4 | export default postcss
5 |
--------------------------------------------------------------------------------
/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | import "sst"
4 | declare module "sst" {
5 | export interface Resource {
6 | }
7 | }
8 | export {}
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "biomejs.biome",
3 | "editor.formatOnSave": true,
4 | "typescript.preferences.importModuleSpecifierEnding": "minimal"
5 | }
6 |
--------------------------------------------------------------------------------
/apps/spa/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 |
7 | /test-results/
8 | /playwright-report/
9 | /blob-report/
10 | /playwright/.cache/
11 |
--------------------------------------------------------------------------------
/apps/spa/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "composite": true,
3 | "extends": "../../../tsconfig.base.json",
4 |
5 | "files": ["src/main.tsx"],
6 | "include": ["src", "__tests__/**/*.test.*"]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "yzhang.markdown-all-in-one",
4 | "biomejs.biome",
5 | "astro-build.astro-vscode",
6 | "DavidAnson.vscode-markdownlint"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/spa/src/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | interface ImportMetaEnv {
3 | readonly VITE_API_URL: string
4 | }
5 | interface ImportMeta {
6 | readonly env: ImportMetaEnv
7 | }
--------------------------------------------------------------------------------
/packages/ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/apps/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/base.json",
3 | "compilerOptions": {
4 | "types": ["@cloudflare/workers-types", "@types/bun"]
5 | },
6 | "include": [".", "../../env.d.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/config-typescript/astro.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Astro",
4 | "extends": "./react.json",
5 | "compilerOptions": {
6 | "jsx": "preserve"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/config-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/config-typescript",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/packages/config-typescript/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./react.json",
5 | "compilerOptions": {
6 | "plugins": [{ "name": "next" }]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/infra/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entryPoints: ['src/index.ts'],
5 | target: 'es2022',
6 | format: ['esm'],
7 | ...options,
8 | }))
9 |
--------------------------------------------------------------------------------
/packages/config-typescript/vite.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Vite",
4 | "extends": "./react.json",
5 | "compilerOptions": {
6 | "types": ["vite/client", "@types/bun"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/spa/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import baseConfig from '@repo/ui/tailwind.config'
2 | import type { Config } from 'tailwindcss'
3 |
4 | export default {
5 | content: ['./src/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'],
6 | presets: [baseConfig],
7 | } satisfies Config
8 |
--------------------------------------------------------------------------------
/apps/spa/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/vite.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["src/*"]
7 | }
8 | },
9 | "include": [".", "../../env.d.ts"],
10 | "exclude": ["node_modules", "dist"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/config-typescript/react.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Application",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react-jsx",
7 | "lib": ["es2022", "dom", "dom.iterable"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/happydom.ts:
--------------------------------------------------------------------------------
1 | // https://bun.sh/guides/test/happy-dom
2 | //! only able to get this to work when the bunfig.toml and this file are in the subpackages, not in the root
3 |
4 | import { GlobalRegistrator } from '@happy-dom/global-registrator'
5 |
6 | GlobalRegistrator.register()
7 |
--------------------------------------------------------------------------------
/packages/config-typescript/node.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Node",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "module": "NodeNext",
7 | "moduleResolution": "NodeNext",
8 | "lib": ["es2022"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config-typescript/react.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["src/*"]
7 | }
8 | },
9 | "include": [".", "../../env.d.ts"],
10 | "exclude": ["node_modules", "dist", "build"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/_layout/_layout-2/layout-a.tsx:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from '@tanstack/react-router'
2 |
3 | export const Route = createFileRoute('/_layout/_layout-2/layout-a')({
4 | component: LayoutAComponent,
5 | })
6 |
7 | function LayoutAComponent() {
8 | return
I'm layout A!
9 | }
10 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/_layout/_layout-2/layout-b.tsx:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from '@tanstack/react-router'
2 |
3 | export const Route = createFileRoute('/_layout/_layout-2/layout-b')({
4 | component: LayoutBComponent,
5 | })
6 |
7 | function LayoutBComponent() {
8 | return I'm layout B!
9 | }
10 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/posts.index.tsx:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from '@tanstack/react-router'
2 | import * as React from 'react'
3 |
4 | export const Route = createFileRoute('/posts/')({
5 | component: PostsIndexComponent,
6 | })
7 |
8 | function PostsIndexComponent() {
9 | return Select a post.
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | .turbo
4 | *.log
5 | .next
6 | dist
7 | dist-ssr
8 | *.local
9 | .env
10 | .cache
11 | server/dist
12 | public/dist
13 | tsconfig.tsbuildinfo
14 | # generated types
15 | .astro/
16 |
17 | # sst
18 | .sst
19 |
20 | # Cloudflare
21 | .wrangler
22 |
23 | vite.config.js.timestamp-*
24 |
--------------------------------------------------------------------------------
/apps/api/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entryPoints: ['src/index.ts'],
5 | dts: {
6 | compilerOptions: {
7 | composite: false,
8 | },
9 | },
10 | target: 'es2022',
11 | format: ['esm'],
12 | ...options,
13 | }))
14 |
--------------------------------------------------------------------------------
/packages/core/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entryPoints: ['src/index.ts'],
5 | dts: {
6 | compilerOptions: {
7 | composite: false,
8 | },
9 | },
10 | target: 'es2022',
11 | format: ['esm'],
12 | ...options,
13 | }))
14 |
--------------------------------------------------------------------------------
/packages/db/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entryPoints: ['src/index.ts'],
5 | dts: {
6 | compilerOptions: {
7 | composite: false,
8 | },
9 | },
10 | target: 'es2022',
11 | format: ['esm'],
12 | ...options,
13 | }))
14 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/apps/spa/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vite App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/spa/vite.config.js:
--------------------------------------------------------------------------------
1 | import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
2 | import react from '@vitejs/plugin-react'
3 | import { defineConfig } from 'vite'
4 | import tsconfigPaths from 'vite-tsconfig-paths'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [TanStackRouterVite(), react(), tsconfigPaths()],
9 | })
10 |
--------------------------------------------------------------------------------
/packages/ui/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entry: ['./src/index.tsx'],
5 | format: ['esm'],
6 | dts: {
7 | compilerOptions: {
8 | composite: false,
9 | },
10 | },
11 | target: 'es2022',
12 | external: ['react'],
13 | banner: {
14 | js: "'use client'",
15 | },
16 | ...options,
17 | }))
18 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet, createFileRoute } from '@tanstack/react-router'
2 |
3 | export const Route = createFileRoute('/_layout')({
4 | component: LayoutComponent,
5 | })
6 |
7 | function LayoutComponent() {
8 | return (
9 |
10 |
I'm a layout
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/packages/db/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'drizzle-kit'
2 |
3 | if (!process.env.DATABASE_URL) {
4 | throw new Error('DATABASE_URL is not set')
5 | }
6 |
7 | export default defineConfig({
8 | dialect: 'postgresql',
9 | out: 'src/migrations',
10 | dbCredentials: {
11 | url: process.env.DATABASE_URL,
12 | },
13 | schema: 'src/schema/index.ts',
14 | verbose: true,
15 | strict: true,
16 | })
17 |
--------------------------------------------------------------------------------
/packages/ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/packages/ui/src/components/counter-button/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'bun:test'
2 | import { createRoot } from 'react-dom/client'
3 | import { CounterButton } from '.'
4 |
5 | describe('CounterButton', () => {
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div')
8 | const root = createRoot(div)
9 | root.render()
10 | root.unmount()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@repo/ui/components/ui/button'
2 | import { createFileRoute } from '@tanstack/react-router'
3 |
4 | export const Route = createFileRoute('/')({
5 | component: Home,
6 | })
7 |
8 | function Home() {
9 | return (
10 |
11 |
Welcome Home!
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/_packages-template/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { type Options, defineConfig } from 'tsup'
2 |
3 | export default defineConfig((options: Options) => ({
4 | entryPoints: ['src/index.ts'],
5 | dts: {
6 | // @see https://github.com/unjs/unbuild/issues/304#issuecomment-1688027556 - composite breaks dts
7 | compilerOptions: {
8 | composite: false,
9 | },
10 | },
11 | target: 'esnext',
12 | format: ['esm'],
13 | ...options,
14 | }))
15 |
--------------------------------------------------------------------------------
/packages/ui/src/components/link/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'bun:test'
2 |
3 | import { createRoot } from 'react-dom/client'
4 | import { Link } from '.'
5 |
6 | describe('Link', () => {
7 | it('renders without crashing', () => {
8 | const div = document.createElement('div')
9 | const root = createRoot(div)
10 | root.render(Turborepo Docs)
11 | root.unmount()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/infra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/infra",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "license": "MIT",
7 |
8 | "scripts": {
9 | "clean": "rm -rf dist node_modules .turbo",
10 | "typecheck": "tsc --noEmit",
11 | "lint": "biome check --write",
12 | "outdated": "bun outdated"
13 | },
14 | "dependencies": {
15 | "pulumi": "latest",
16 | "@pulumi/cloudflare": "latest"
17 | },
18 | "devDependencies": {}
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | lint:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v3
13 |
14 | - name: Setup Bun
15 | uses: oven-sh/setup-bun@v2
16 | with:
17 | bun-version: latest
18 |
19 | - name: Install dependencies
20 | run: bun install
21 |
22 | - name: Run lint script
23 | run: bun run lint
24 |
--------------------------------------------------------------------------------
/apps/api/src/middleware/errorMiddleware.ts:
--------------------------------------------------------------------------------
1 | import type { Context, Next } from 'hono'
2 | import { handleApiError } from '../utils/errorHandler'
3 | import { customLogger } from '../utils/logger'
4 |
5 | export async function errorMiddleware(c: Context, next: Next) {
6 | try {
7 | await next()
8 | } catch (error) {
9 | customLogger(
10 | `Error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
11 | )
12 | return handleApiError(c, error)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/infra/src/pages.ts:
--------------------------------------------------------------------------------
1 | import * as cloudflare from '@pulumi/cloudflare'
2 | import * as pulumi from '@pulumi/pulumi'
3 |
4 | const projectName = 'spa-cf'
5 | const CLOUDFLARE_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID
6 |
7 | // Cloudflare Pages Project
8 | export const pagesProject = new cloudflare.PagesProject(
9 | projectName,
10 | {
11 | accountId: CLOUDFLARE_ACCOUNT_ID,
12 | name: projectName,
13 | productionBranch: 'main',
14 | },
15 | {
16 | protect: true,
17 | },
18 | )
19 |
--------------------------------------------------------------------------------
/apps/api/src/__tests__/server.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'bun:test'
2 | import { testClient } from 'hono/testing'
3 | import app from '..'
4 |
5 | describe('Server', () => {
6 | const client = testClient(app)
7 |
8 | it.skip('health check returns 200 with hello world', async () => {
9 | // @ts-expect-error - testClient types not working
10 | const res = await client.status.$get()
11 | expect(res.status).toBe(200)
12 | expect(await res.json()).toEqual({ hello: 'world' })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/apps/api/src/utils/logger.ts:
--------------------------------------------------------------------------------
1 | export function customLogger(message: string, ...rest: string[]) {
2 | const date = new Date()
3 | const formattedDate = date
4 | .toLocaleString('en-US', {
5 | year: 'numeric',
6 | month: '2-digit',
7 | day: '2-digit',
8 | hour: '2-digit',
9 | minute: '2-digit',
10 | second: '2-digit',
11 | hour12: true,
12 | timeZone: 'America/Los_Angeles',
13 | })
14 | .replace(/(\d+)\/(\d+)\/(\d+),/, '$3-$1-$2')
15 | console.log(`[${formattedDate}] ${message}`, ...rest)
16 | }
17 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = useState(false)
5 |
6 | useEffect(() => {
7 | function onChange(event: MediaQueryListEvent) {
8 | setValue(event.matches)
9 | }
10 |
11 | const result = matchMedia(query)
12 | result.addEventListener('change', onChange)
13 | setValue(result.matches)
14 |
15 | return () => result.removeEventListener('change', onChange)
16 | }, [query])
17 |
18 | return value
19 | }
20 |
--------------------------------------------------------------------------------
/apps/api/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Hono } from 'hono'
2 | import { cors } from 'hono/cors'
3 | import { errorMiddleware } from './middleware/errorMiddleware'
4 | import { responseLoggerMiddleware } from './middleware/responseLoggerMiddleware'
5 | import type { Bindings } from './types'
6 |
7 | const app = new Hono<{ Bindings: Bindings }>()
8 |
9 | const routes = app
10 | .use('*', cors())
11 | .use(errorMiddleware)
12 | .use(responseLoggerMiddleware)
13 | .get('/', (c) => c.json({ hello: 'world' }))
14 |
15 | export default app
16 | export type AppType = typeof routes
17 |
--------------------------------------------------------------------------------
/packages/ui/src/components/link/index.tsx:
--------------------------------------------------------------------------------
1 | import type { AnchorHTMLAttributes, ReactNode } from 'react'
2 |
3 | interface LinkProps extends AnchorHTMLAttributes {
4 | children: ReactNode
5 | newTab?: boolean
6 | href: string
7 | }
8 |
9 | export function Link({
10 | children,
11 | href,
12 | newTab,
13 | ...other
14 | }: LinkProps): JSX.Element {
15 | return (
16 |
22 | {children}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/apps/api/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'bun:test'
2 | import app from '.'
3 |
4 | describe('Hono API', () => {
5 | it('should return 200 for root path', async () => {
6 | const res = await app.request('/')
7 | expect(res.status).toBe(200)
8 | const json = await res.json()
9 | expect(json).toEqual({ hello: 'world' })
10 | })
11 |
12 | it('should return 200 for health check', async () => {
13 | const res = await app.request('/')
14 | expect(res.status).toBe(200)
15 | const json = await res.json()
16 | expect(json).toEqual({ hello: 'world' })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/apps/astro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/astro",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "astro dev",
7 | "start": "astro dev",
8 | "build": "astro check && astro build",
9 | "clean": "rm -rf node_modules .astro .turbo dist",
10 | "preview": "astro preview",
11 | "lint": "biome check --write",
12 | "format": "biome format",
13 | "typecheck": "tsc --noEmit",
14 | "test": "bun test",
15 | "astro": "astro"
16 | },
17 | "dependencies": {
18 | "astro": "^4.14.5"
19 | },
20 | "devDependencies": {
21 | "@astrojs/check": "^0.9.3",
22 | "typescript": "^5.5.4"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/core",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "license": "MIT",
7 | "exports": {
8 | ".": "./src/index.ts",
9 | "./*": "./src/*.ts"
10 | },
11 | "scripts": {
12 | "build": "tsup",
13 | "clean": "rm -rf dist node_modules .turbo",
14 | "dev": "tsup --watch",
15 | "typecheck": "tsc --noEmit",
16 | "lint": "biome check --write",
17 | "test": "bun test",
18 | "outdated": "bun outdated"
19 | },
20 | "dependencies": {
21 | "@repo/db": "workspace:*"
22 | },
23 | "devDependencies": {
24 | "tsup": "^8.2.4",
25 | "typescript": "^5.5.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-enter-submit.ts:
--------------------------------------------------------------------------------
1 | import { type RefObject, useRef } from 'react'
2 |
3 | export function useEnterSubmit(): {
4 | formRef: RefObject
5 | onKeyDown: (event: React.KeyboardEvent) => void
6 | } {
7 | const formRef = useRef(null)
8 |
9 | const handleKeyDown = (
10 | event: React.KeyboardEvent,
11 | ): void => {
12 | if (
13 | event.key === 'Enter' &&
14 | !event.shiftKey &&
15 | !event.nativeEvent.isComposing
16 | ) {
17 | formRef.current?.requestSubmit()
18 | event.preventDefault()
19 | }
20 | }
21 |
22 | return { formRef, onKeyDown: handleKeyDown }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/spa/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from '@playwright/test'
2 |
3 | /**
4 | * See https://playwright.dev/docs/test-configuration.
5 | */
6 | export default defineConfig({
7 | testDir: './tests',
8 |
9 | reporter: [['line']],
10 |
11 | use: {
12 | /* Base URL to use in actions like `await page.goto('/')`. */
13 | baseURL: 'http://localhost:3001/',
14 | },
15 |
16 | webServer: {
17 | command: 'pnpm run dev',
18 | url: 'http://localhost:3001',
19 | reuseExistingServer: !process.env.CI,
20 | stdout: 'pipe',
21 | },
22 |
23 | projects: [
24 | {
25 | name: 'chromium',
26 | use: { ...devices['Desktop Chrome'] },
27 | },
28 | ],
29 | })
30 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-resize-observer.ts:
--------------------------------------------------------------------------------
1 | import { type RefObject, useEffect, useState } from 'react'
2 |
3 | export function useResizeObserver(
4 | elementRef: RefObject,
5 | ): ResizeObserverEntry | undefined {
6 | const [entry, setEntry] = useState()
7 |
8 | useEffect(() => {
9 | const node = elementRef?.current
10 | if (!node) return
11 |
12 | const updateEntry = ([entry]: ResizeObserverEntry[]): void => {
13 | setEntry(entry)
14 | }
15 |
16 | const observer = new ResizeObserver((entries) => {
17 | updateEntry(entries)
18 | })
19 |
20 | observer.observe(node)
21 |
22 | return () => observer.disconnect()
23 | }, [elementRef])
24 |
25 | return entry
26 | }
27 |
--------------------------------------------------------------------------------
/apps/api/src/utils/errorHandler.ts:
--------------------------------------------------------------------------------
1 | import type { Context } from 'hono'
2 |
3 | export function handleApiError(c: Context, error: unknown) {
4 | console.error('API Error:', error)
5 |
6 | const errorMessage =
7 | error instanceof Error ? error.message : 'Unknown error occurred'
8 | const statusCode = error instanceof ApiError ? error.statusCode : 500
9 |
10 | const responseInit: ResponseInit = { status: statusCode }
11 |
12 | return c.json(
13 | { message: 'An error occurred', error: errorMessage },
14 | responseInit,
15 | )
16 | }
17 |
18 | export class ApiError extends Error {
19 | constructor(
20 | public override message: string,
21 | public statusCode: number,
22 | ) {
23 | super(message)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/api/src/middleware/responseLoggerMiddleware.ts:
--------------------------------------------------------------------------------
1 | import type { Context, Next } from 'hono'
2 | import { customLogger } from '../utils/logger'
3 |
4 | export async function responseLoggerMiddleware(c: Context, next: Next) {
5 | await next()
6 |
7 | const { method, url } = c.req
8 | const status = c.res.status
9 | const contentLength = c.res.headers.get('Content-Length')
10 |
11 | let responseBody = ''
12 | if (c.res.headers.get('Content-Type')?.includes('application/json')) {
13 | const clonedResponse = c.res.clone()
14 | responseBody = await clonedResponse.text()
15 | }
16 |
17 | customLogger(
18 | `${method} ${url} - Status: ${status}, Content-Length: ${contentLength}`,
19 | responseBody ? `Response: ${responseBody}` : '',
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
7 | "dependsOn": ["^build"],
8 | "outputs": [
9 | "build/**",
10 | ".vercel/**",
11 | "dist/**",
12 | ".next/**",
13 | "!.next/cache/**",
14 | ".sst/**"
15 | ]
16 | },
17 | "test": {
18 | "outputs": ["coverage/**"],
19 | "dependsOn": []
20 | },
21 | "lint": {
22 | "dependsOn": ["^build"]
23 | },
24 | "typecheck": {
25 | "dependsOn": ["^build"]
26 | },
27 | "dev": {
28 | "dependsOn": ["^build"],
29 | "cache": false,
30 | "persistent": true
31 | },
32 | "outdated": {},
33 | "clean": {
34 | "cache": false
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/_layout/_layout-2.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
2 |
3 | export const Route = createFileRoute('/_layout/_layout-2')({
4 | component: LayoutComponent,
5 | })
6 |
7 | function LayoutComponent() {
8 | return (
9 |
10 |
I'm a nested layout
11 |
12 |
18 | Layout A
19 |
20 |
26 | Layout B
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/packages/config-typescript/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Base Configuration",
4 | "compilerOptions": {
5 | "skipLibCheck": true,
6 | "allowJs": true,
7 | "target": "es2022",
8 | "module": "ESNext",
9 | "moduleResolution": "Bundler",
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "moduleDetection": "force",
13 | "isolatedModules": true,
14 | "verbatimModuleSyntax": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "strict": true,
17 | "noUncheckedIndexedAccess": true,
18 | "noImplicitOverride": true,
19 | "sourceMap": true,
20 | "declaration": true,
21 | "declarationMap": true,
22 | "composite": false,
23 | "types": ["@types/bun"],
24 | "noEmit": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/_packages-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/packages-template",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "license": "MIT",
7 | "sideEffects": false,
8 | "main": "./dist/index.js",
9 | "module": "./dist/index.mjs",
10 | "types": "./dist/index.d.ts",
11 | "files": ["dist/**"],
12 | "exports": {
13 | ".": "./src/index.ts",
14 | "./*": "./src/*.ts"
15 | },
16 | "scripts": {
17 | "build": "tsup",
18 | "clean": "rm -rf dist node_modules .turbo",
19 | "dev": "tsup --watch",
20 | "typecheck": "tsc --noEmit",
21 | "lint": "biome check --write",
22 | "test": "bun test",
23 | "outdated": "bun outdated"
24 | },
25 | "dependencies": {},
26 | "devDependencies": {
27 | "tsup": "^8.2.4",
28 | "typescript": "^5.5.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/astro/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/biome.jsonc:
--------------------------------------------------------------------------------
1 | // @see https://biomejs.dev/guides/big-projects/#monorepos
2 | {
3 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
4 | "linter": {
5 | "enabled": true,
6 | "rules": { "recommended": true }
7 | },
8 | "organizeImports": {
9 | "enabled": true
10 | },
11 | "javascript": {
12 | "formatter": {
13 | "semicolons": "asNeeded",
14 | "jsxQuoteStyle": "single",
15 | "quoteStyle": "single"
16 | }
17 | },
18 | "json": {
19 | "formatter": {
20 | "enabled": true,
21 | "indentWidth": 4,
22 | "indentStyle": "tab"
23 | }
24 | },
25 | "files": {
26 | "ignore": [
27 | "build/**",
28 | "dist/**",
29 | ".sst/**",
30 | ".turbo/**",
31 | "sst-env.d.ts",
32 | ".wrangler/**",
33 | "prompts/**",
34 | "routeTree.gen.ts"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from '@/components/ui/toast'
11 | import { useToast } from '@/components/ui/use-toast'
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(({ id, title, description, action, ...props }) => (
19 |
20 |
21 | {title && {title}}
22 | {description && {description}}
23 |
24 | {action}
25 |
26 |
27 | ))}
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/apps/spa/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider, createRouter } from '@tanstack/react-router'
2 | import React from 'react'
3 | import ReactDOM from 'react-dom/client'
4 | import { routeTree } from './routeTree.gen'
5 | import '@repo/ui/globals.css'
6 |
7 | // Set up a Router instance
8 | const router = createRouter({
9 | routeTree,
10 | defaultPreload: 'intent',
11 | defaultStaleTime: 5000,
12 | })
13 |
14 | // Register things for typesafety
15 | declare module '@tanstack/react-router' {
16 | interface Register {
17 | router: typeof router
18 | }
19 | }
20 |
21 | // biome-ignore lint/style/noNonNullAssertion: standard
22 | const rootElement = document.getElementById('app')!
23 |
24 | if (!rootElement.innerHTML) {
25 | const root = ReactDOM.createRoot(rootElement)
26 | root.render()
27 | }
28 |
--------------------------------------------------------------------------------
/apps/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/api",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "exports": "./src/index.ts",
7 | "scripts": {
8 | "dev": "sst dev",
9 | "wrangler:dev": "wrangler dev src/index.ts",
10 | "build": "tsup",
11 | "clean": "rm -rf node_modules .turbo .wrangler",
12 | "deploy": "wrangler deploy --minify src/index.ts",
13 | "lint": "biome check --write",
14 | "format": "biome format",
15 | "typecheck": "tsc --noEmit",
16 | "test": "bun test",
17 | "outdated": "bun outdated"
18 | },
19 | "dependencies": {
20 | "@repo/core": "workspace:*",
21 | "@repo/db": "workspace:*",
22 | "hono": "^4.5.8"
23 | },
24 | "devDependencies": {
25 | "@cloudflare/workers-types": "^4.20240821.1",
26 | "@repo/config-typescript": "*",
27 | "wrangler": "^3.72.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as LabelPrimitive from '@radix-ui/react-label'
4 | import { type VariantProps, cva } from 'class-variance-authority'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const labelVariants = cva(
10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { cn } from '@/lib/utils'
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | },
21 | )
22 | Textarea.displayName = 'Textarea'
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as SeparatorPrimitive from '@radix-ui/react-separator'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = 'horizontal', decorative = true, ...props },
14 | ref,
15 | ) => (
16 |
27 | ),
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { cn } from '@/lib/utils'
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | },
22 | )
23 | Input.displayName = 'Input'
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as ProgressPrimitive from '@radix-ui/react-progress'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # Todo
2 |
3 | This is a Turborepo starter template with Tanstack Router
4 |
5 | ## Known Issues
6 |
7 | - [ ] Deploying StaticSite to Cloudflare with SST does not work?
8 |
9 | ## TODO
10 |
11 | - [ ] Replace blog package with Astro
12 | - [ ] Add Tanstack Start App
13 | - [ ] Add Expo Mobile Package
14 | - Having issues deploying to CF with SST, but works with AWS SST provider.
15 | - [ ] Try with Cloudflare Pulumi
16 | - [ ] Determine if tsup can be removed to simplify repo
17 | - [x] Replace Jest with Bun's built-in test runner
18 | - [x] Add Drizzle ORM DB Package
19 | - [x] Add Tanstack Router Vite SPA App
20 | - [x] Replace Express Server with Hono
21 | - [x] Add Core Package for Business Logic
22 | - [x] Replace ESLint/Prettier with Biome, add biome format, lint, check scripts
23 | - [x] Add Biome
24 | - [x] Remove Eslint
25 |
26 | ## Lower Priority
27 |
28 | - [ ] Add [T3 Env](https://env.t3.gg/)
29 | - [ ] Add common utilities like Knip
30 |
--------------------------------------------------------------------------------
/packages/db/src/index.ts:
--------------------------------------------------------------------------------
1 | import { type Db, createDb, createDbWithConnection } from './client'
2 | import * as schema from './schema'
3 |
4 | export { createDb, createDbWithConnection, schema }
5 | export type { Db, DbWithConnection, Transaction, DbOrTx } from './client'
6 |
7 | let db: Db | undefined
8 |
9 | // Export the db instance only if process.env is available
10 | // This allows usage in Cloudflare Workers
11 | try {
12 | if (typeof process === 'undefined') {
13 | throw new Error(
14 | 'Process is undefined. If process is undefined, you are probably in a browser and instead can use the createDb function with a connection string to create a db instance.',
15 | )
16 | }
17 | if (!process.env.DATABASE_URL) {
18 | throw new Error(
19 | 'DATABASE_URL is not set. Please check your environment variables.',
20 | )
21 | }
22 | db = createDb(process.env.DATABASE_URL)
23 | } catch (error) {
24 | console.error(error)
25 | }
26 |
27 | export { db }
28 |
--------------------------------------------------------------------------------
/packages/db/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/db",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "sideEffects": false,
7 | "type": "module",
8 | "exports": {
9 | ".": "./src/index.ts",
10 | "./*": "./src/*.ts"
11 | },
12 | "scripts": {
13 | "build": "tsup",
14 | "clean": "rm -rf dist node_modules .turbo",
15 | "dev": "tsup --watch",
16 | "typecheck": "tsc --noEmit",
17 | "lint": "biome check --write",
18 | "test": "bun test",
19 | "generate": "drizzle-kit generate",
20 | "check": "drizzle-kit check",
21 | "push": "bun --env-file=../../.env drizzle-kit push",
22 | "migrate": "drizzle-kit migrate",
23 | "outdated": "bun outdated"
24 | },
25 | "dependencies": {
26 | "drizzle-orm": "^0.33.0",
27 | "drizzle-zod": "^0.5.1",
28 | "postgres": "^3.4.4",
29 | "zod": "^3.23.8"
30 | },
31 | "devDependencies": {
32 | "drizzle-kit": "^0.24.1",
33 | "tsup": "^8.2.4",
34 | "typescript": "^5.5.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/sst.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | export default $config({
4 | app(input) {
5 | return {
6 | name: 'tanstack-monorepo',
7 | removal: input?.stage === 'production' ? 'retain' : 'remove',
8 | home: 'cloudflare',
9 | providers: {
10 | cloudflare: {},
11 | aws: {},
12 | },
13 | }
14 | },
15 | async run() {
16 | const hono = new sst.cloudflare.Worker('tanstack-monorepo-api', {
17 | url: true,
18 | handler: 'apps/api/src/index.ts',
19 | })
20 |
21 | // ! Tried to deploy to Cloudflare but doesn't appear to work
22 | const spa = new sst.aws.StaticSite('tanstack-monorepo-spa-', {
23 | path: 'apps/spa',
24 | build: {
25 | command: 'bun run build',
26 | output: 'dist',
27 | },
28 | environment: {
29 | VITE_API_URL: hono.url.apply((url) => url?.toString() ?? ''),
30 | },
31 | })
32 |
33 | return {
34 | api: hono.url,
35 | spa: spa.url,
36 | }
37 | },
38 | })
39 |
--------------------------------------------------------------------------------
/apps/spa/src/posts.tsx:
--------------------------------------------------------------------------------
1 | import { CounterButton } from '@repo/ui' //? auto import works
2 | import { notFound } from '@tanstack/react-router'
3 | import axios from 'redaxios'
4 |
5 | export type PostType = {
6 | id: string
7 | title: string
8 | body: string
9 | }
10 | export const fetchPost = async (postId: string) => {
11 | console.info(`Fetching post with id ${postId}...`)
12 | await new Promise((r) => setTimeout(r, 500))
13 | const post = await axios
14 | .get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
15 | .then((r) => r.data)
16 | .catch((err) => {
17 | if (err.status === 404) {
18 | throw notFound()
19 | }
20 | throw err
21 | })
22 |
23 | return post
24 | }
25 |
26 | export const fetchPosts = async () => {
27 | console.info('Fetching posts......')
28 | await new Promise((r) => setTimeout(r, 500))
29 | return axios
30 | .get>('https://jsonplaceholder.typicode.com/posts')
31 | .then((r) => r.data.slice(0, 10))
32 | }
33 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useTheme } from 'next-themes'
4 | import { Toaster as Sonner } from 'sonner'
5 |
6 | type ToasterProps = React.ComponentProps
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = 'system' } = useTheme()
10 |
11 | return (
12 |
28 | )
29 | }
30 |
31 | export { Toaster }
32 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/posts.$postId.tsx:
--------------------------------------------------------------------------------
1 | import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
2 | import type { ErrorComponentProps } from '@tanstack/react-router'
3 | import * as React from 'react'
4 | import { fetchPost } from '../posts'
5 |
6 | export const Route = createFileRoute('/posts/$postId')({
7 | loader: async ({ params: { postId } }) => fetchPost(postId),
8 | // biome-ignore lint/suspicious/noExplicitAny:
9 | errorComponent: PostErrorComponent as any,
10 | notFoundComponent: () => {
11 | return Post not found
12 | },
13 | component: PostComponent,
14 | })
15 |
16 | export function PostErrorComponent({ error }: ErrorComponentProps) {
17 | return
18 | }
19 |
20 | function PostComponent() {
21 | const post = Route.useLoaderData()
22 |
23 | return (
24 |
25 |
{post.title}
26 |
{post.body}
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/template-sync.yml:
--------------------------------------------------------------------------------
1 | # @see https://github.com/AndreasAugustin/actions-template-sync
2 | # .templatesyncignore will ignore certain files from being synced like .gitignore
3 | name: Template Sync
4 |
5 | on:
6 | workflow_dispatch:
7 |
8 | jobs:
9 | repo-sync:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | pull-requests: write
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | # https://github.com/actions/checkout#usage
18 | # uncomment if you use submodules within the repository
19 | # with:
20 | # submodules: true
21 |
22 | - name: actions-template-sync
23 | uses: AndreasAugustin/actions-template-sync@v2
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | source_repo_path: austinm911/tanstack-monorepo
27 | upstream_branch: main
28 |
--------------------------------------------------------------------------------
/apps/spa/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/spa",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "serve": "vite preview",
9 | "start": "vite",
10 | "clean": "rm -rf node_modules .vite .turbo dist",
11 | "lint": "biome check --write",
12 | "format": "biome format",
13 | "typecheck": "tsc --noEmit",
14 | "test": "bun test",
15 | "test:e2e": "playwright test --project=chromium"
16 | },
17 | "dependencies": {
18 | "@repo/ui": "workspace:*",
19 | "@tanstack/react-router": "^1.49.7",
20 | "@tanstack/router-devtools": "^1.49.7",
21 | "@tanstack/router-plugin": "^1.49.3",
22 | "immer": "^10.1.1",
23 | "react": "^18.2.0",
24 | "react-dom": "^18.2.0",
25 | "redaxios": "^0.5.1",
26 | "zod": "^3.23.8"
27 | },
28 | "devDependencies": {
29 | "@playwright/test": "^1.45.3",
30 | "@repo/config-typescript": "workspace:*",
31 | "@types/react": "^18.2.47",
32 | "@types/react-dom": "^18.2.18",
33 | "@vitejs/plugin-react": "^4.3.1",
34 | "vite": "^5.3.5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/db/src/client.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from 'drizzle-orm/postgres-js'
2 | import postgres from 'postgres'
3 | import * as schema from './schema'
4 |
5 | function createConnection(connectionString: string) {
6 | return postgres(connectionString)
7 | }
8 |
9 | export function createDb(
10 | connectionString: string,
11 | options: { logger?: boolean } = { logger: false },
12 | ) {
13 | const connection = createConnection(connectionString)
14 | return drizzle(connection, { schema: schema, logger: options.logger })
15 | }
16 |
17 | // Allows connection to be exported for running local scripts (to close the connection manually)
18 | export function createDbWithConnection(connectionString: string) {
19 | const connection = createConnection(connectionString)
20 | const db = drizzle(connection, { schema: schema, logger: true })
21 | return { db, connection }
22 | }
23 |
24 | export type Db = ReturnType
25 | export type DbWithConnection = ReturnType
26 | export type Transaction = Parameters[0]>[0]
27 | export type DbOrTx = Db | Transaction
28 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/posts.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
2 | import * as React from 'react'
3 | import { fetchPosts } from '../posts'
4 |
5 | export const Route = createFileRoute('/posts')({
6 | loader: fetchPosts,
7 | component: PostsComponent,
8 | })
9 |
10 | function PostsComponent() {
11 | const posts = Route.useLoaderData()
12 |
13 | return (
14 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/packages/ui/src/components/counter-button/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from 'react'
4 |
5 | export function CounterButton(): JSX.Element {
6 | const [count, setCount] = useState(0)
7 |
8 | return (
9 |
17 |
18 | This component is from{' '}
19 |
26 | ui
27 |
28 |
29 |
30 |
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tanstack-sst-monorepo",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo run build",
6 | "clean": "turbo run clean && rm -rf node_modules .turbo .sst",
7 | "dev": "turbo run dev",
8 | "format": "biome format --write",
9 | "lint": "turbo run lint",
10 | "test": "turbo run test",
11 | "turbo:generate": "turbo gen workspace",
12 | "typecheck": "turbo run typecheck",
13 | "sst:dev": "sst dev",
14 | "sst:unlock": "sst unlock",
15 | "api": "bun --filter @repo/api dev",
16 | "spa": "bun --filter @repo/spa dev",
17 | "astro": "bun --filter @repo/astro dev",
18 | "ui:add-component": "bunx shadcn-ui@latest add --cwd packages/ui",
19 | "outdated": "turbo run outdated",
20 | "update-deps": "bunx taze --interactive --recursive"
21 | },
22 | "dependencies": {
23 | "sst": "3.0.68"
24 | },
25 | "devDependencies": {
26 | "@biomejs/biome": "^1.8.3",
27 | "@happy-dom/global-registrator": "^15.0.0",
28 | "@types/bun": "^1.1.7",
29 | "turbo": "^2.0.14"
30 | },
31 | "engines": {
32 | "node": ">=20"
33 | },
34 | "packageManager": "bun@1.1.26",
35 | "workspaces": ["apps/*", "packages/*", "infra/*"]
36 | }
37 |
--------------------------------------------------------------------------------
/apps/astro/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | title: string
4 | }
5 |
6 | const { title } = Astro.props
7 | ---
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {title}
18 |
19 |
20 |
21 |
22 |
23 |
51 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
4 | import { Check } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as SliderPrimitive from '@radix-ui/react-slider'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import { type VariantProps, cva } from 'class-variance-authority'
2 | import type * as React from 'react'
3 |
4 | import { cn } from '@/lib/utils'
5 |
6 | const badgeVariants = cva(
7 | 'inline-flex items-center rounded-full 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',
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
13 | secondary:
14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
15 | destructive:
16 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
17 | outline: 'text-foreground',
18 | },
19 | },
20 | defaultVariants: {
21 | variant: 'default',
22 | },
23 | },
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as SwitchPrimitives from '@radix-ui/react-switch'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as TooltipPrimitive from '@radix-ui/react-tooltip'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/prompts/explainer:
--------------------------------------------------------------------------------
1 | # Explain Code and Recent Changes
2 |
3 | You are an AI assistant tasked with explaining code and recent changes to a user with an intermediate level of programming knowledge. Please follow these guidelines:
4 |
5 | 1. Analyze the code or changes provided in previous responses.
6 | 2. Use a step-by-step approach to break down your explanation.
7 | 3. Show a concise diff of the changes (previous vs current) of the code you are explaining.
8 | 4. Provide context and reasoning for each part of your explanation.
9 | 5. Use technical terms appropriate for an intermediate-level programmer, but briefly explain any complex concepts.
10 | 6. Highlight any best practices or potential improvements you notice.
11 | 7. If relevant, mention how the changes might affect the overall system or other parts of the codebase.
12 | 8. Ask clarifying questions if any part of the code or changes is unclear.
13 |
14 | Remember to:
15 |
16 | - Think through your explanation carefully before responding.
17 | - Use clear and concise language.
18 | - Provide examples or analogies where appropriate to aid understanding.
19 | - Offer suggestions for further learning or exploration related to the topic.
20 |
21 | Please respond with your step-by-step explanation, including your thought process for each step.
22 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as PopoverPrimitive from '@radix-ui/react-popover'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/apps/astro/src/components/Card.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | title: string
4 | body: string
5 | href: string
6 | }
7 |
8 | const { href, title, body } = Astro.props
9 | ---
10 |
11 |
12 |
13 |
14 | {title}
15 | →
16 |
17 |
18 | {body}
19 |
20 |
21 |
22 |
62 |
--------------------------------------------------------------------------------
/apps/spa/tests/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test.beforeEach(async ({ page }) => {
4 | await page.goto('/')
5 | })
6 |
7 | test('Navigating to a post page', async ({ page }) => {
8 | await page.getByRole('link', { name: 'Posts' }).click()
9 | await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
10 | await expect(page.getByRole('heading')).toContainText('sunt aut facere')
11 | })
12 |
13 | test('Navigating nested layouts', async ({ page }) => {
14 | await page.goto('/')
15 | await page.getByRole('link', { name: 'Layout', exact: true }).click()
16 |
17 | await expect(page.locator('#app')).toContainText("I'm a layout")
18 | await expect(page.locator('#app')).toContainText("I'm a nested layout")
19 |
20 | await page.getByRole('link', { name: 'Layout A' }).click()
21 | await expect(page.locator('#app')).toContainText("I'm layout A!")
22 |
23 | await page.getByRole('link', { name: 'Layout B' }).click()
24 | await expect(page.locator('#app')).toContainText("I'm layout B!")
25 | })
26 |
27 | test('Navigating to a not-found route', async ({ page }) => {
28 | await page.getByRole('link', { name: 'This Route Does Not Exist' }).click()
29 | await expect(page.getByRole('paragraph')).toContainText(
30 | 'This is the notFoundComponent configured on root route',
31 | )
32 | await page.getByRole('link', { name: 'Start Over' }).click()
33 | await expect(page.getByRole('heading')).toContainText('Welcome Home!')
34 | })
35 |
--------------------------------------------------------------------------------
/apps/spa/src/routes/__root.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
2 | import { TanStackRouterDevtools } from '@tanstack/router-devtools'
3 | import * as React from 'react'
4 |
5 | export const Route = createRootRoute({
6 | component: RootComponent,
7 | notFoundComponent: () => {
8 | return (
9 |
10 |
This is the notFoundComponent configured on root route
11 |
Start Over
12 |
13 | )
14 | },
15 | })
16 |
17 | function RootComponent() {
18 | return (
19 | <>
20 |
21 |
28 | Home
29 | {' '}
30 |
36 | Posts
37 | {' '}
38 |
44 | Layout
45 | {' '}
46 |
53 | This Route Does Not Exist
54 |
55 |
56 |
57 |
58 | {/* Start rendering router matches */}
59 |
60 | >
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as TogglePrimitive from '@radix-ui/react-toggle'
4 | import { type VariantProps, cva } from 'class-variance-authority'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const toggleVariants = cva(
10 | 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
11 | {
12 | variants: {
13 | variant: {
14 | default: 'bg-transparent',
15 | outline:
16 | 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
17 | },
18 | size: {
19 | default: 'h-10 px-3',
20 | sm: 'h-9 px-2.5',
21 | lg: 'h-11 px-5',
22 | },
23 | },
24 | defaultVariants: {
25 | variant: 'default',
26 | size: 'default',
27 | },
28 | },
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/packages/ui/src/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 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
4 | import { Circle } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------
1 | # Cursor Rules
2 |
3 | ## Whenever you need a React component
4 |
5 | 1. Carefully consider the component's purpose, functionality, and design
6 | 2. Think slowly, step by step, and outline your reasoning
7 | 3. Check if a similar component already exists in any of the following locations
8 | 1. packages/ui/src/components
9 | 2. apps/spa/src/components
10 | 4. If it doesn't exist, generate a detailed prompt for the component, including:
11 | - Component name and purpose
12 | - Desired props and their types
13 | - Any specific styling or behavior requirements
14 | - Mention of using Tailwind CSS for styling
15 | - Request for TypeScript usage
16 | 5. URL encode the prompt.
17 | 6. Create a clickable link in this format:
18 | [ComponentName](https://v0.dev/chat?q={encoded_prompt})
19 | 7. After generating, adapt the component to fit our project structure:
20 | - Import
21 | - common shadcn/ui components from @repo/ui/components/ui/
22 | - app specific components from @/components
23 | - Ensure it follows our existing component patterns
24 | - Add any necessary custom logic or state management
25 |
26 | Example prompt template:
27 | "Create a React component named {ComponentName} using TypeScript and Tailwind CSS.
28 | It should {description of functionality}. Props should include {list of props with types}.
29 | The component should {any specific styling or behavior notes}. Please provide the full component code."
30 |
31 | Remember to replace placeholders like and with the actual values used in your project.
32 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import { type VariantProps, cva } from 'class-variance-authority'
2 | import * as React from 'react'
3 |
4 | import { cn } from '@/lib/utils'
5 |
6 | const alertVariants = cva(
7 | 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
8 | {
9 | variants: {
10 | variant: {
11 | default: 'bg-background text-foreground',
12 | destructive:
13 | 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
14 | },
15 | },
16 | defaultVariants: {
17 | variant: 'default',
18 | },
19 | },
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = 'Alert'
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = 'AlertTitle'
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = 'AlertDescription'
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = 'vertical', ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { GripVertical } from 'lucide-react'
4 | import * as ResizablePrimitive from 'react-resizable-panels'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const ResizablePanelGroup = ({
9 | className,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
19 | )
20 |
21 | const ResizablePanel = ResizablePrimitive.Panel
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }: React.ComponentProps & {
28 | withHandle?: boolean
29 | }) => (
30 | div]:rotate-90',
33 | className,
34 | )}
35 | {...props}
36 | >
37 | {withHandle && (
38 |
39 |
40 |
41 | )}
42 |
43 | )
44 |
45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
46 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
4 | import type { VariantProps } from 'class-variance-authority'
5 | import * as React from 'react'
6 |
7 | import { toggleVariants } from '@/components/ui/toggle'
8 | import { cn } from '@/lib/utils'
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: 'default',
14 | variant: 'default',
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |
54 | {children}
55 |
56 | )
57 | })
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
60 |
61 | export { ToggleGroup, ToggleGroupItem }
62 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot'
2 | import { type VariantProps, cva } from 'class-variance-authority'
3 | import * as React from 'react'
4 |
5 | import { cn } from '@/lib/utils'
6 |
7 | const buttonVariants = cva(
8 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9 | {
10 | variants: {
11 | variant: {
12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13 | destructive:
14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15 | outline:
16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
17 | secondary:
18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19 | ghost: 'hover:bg-accent hover:text-accent-foreground',
20 | link: 'text-primary underline-offset-4 hover:underline',
21 | },
22 | size: {
23 | default: 'h-10 px-4 py-2',
24 | sm: 'h-9 rounded-md px-3',
25 | lg: 'h-11 rounded-md px-8',
26 | icon: 'h-10 w-10',
27 | },
28 | },
29 | defaultVariants: {
30 | variant: 'default',
31 | size: 'default',
32 | },
33 | },
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : 'button'
45 | return (
46 |
51 | )
52 | },
53 | )
54 | Button.displayName = 'Button'
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/packages/db/src/schema/index.ts:
--------------------------------------------------------------------------------
1 | import { date, integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core'
2 | import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
3 | import type { z } from 'zod'
4 |
5 | //? TIP: Separate the tables into different files as they get more complex
6 |
7 | export const users = pgTable('users', {
8 | id: integer('id').primaryKey().generatedAlwaysAsIdentity({ startWith: 1000 }),
9 | username: text('username').notNull().unique(),
10 | email: text('email').notNull().unique(),
11 | createdAt: timestamp('created_at').defaultNow(),
12 | })
13 |
14 | export const userSchema = createSelectSchema(users)
15 | export const insertUserSchema = createInsertSchema(users)
16 | export type User = z.infer
17 | export type InsertUser = z.infer
18 |
19 | export const tasks = pgTable('tasks', {
20 | id: integer('id').primaryKey().generatedAlwaysAsIdentity({ startWith: 1000 }),
21 | userId: integer('user_id').references(() => users.id),
22 | title: text('title').notNull(),
23 | description: text('description'),
24 | dueDate: date('due_date'),
25 | status: text('status').notNull(),
26 | createdAt: timestamp('created_at').defaultNow(),
27 | })
28 |
29 | export const taskSchema = createSelectSchema(tasks)
30 | export const insertTaskSchema = createInsertSchema(tasks)
31 | export type Task = z.infer
32 | export type InsertTask = z.infer
33 |
34 | export const comments = pgTable('comments', {
35 | id: integer('id').primaryKey().generatedAlwaysAsIdentity({ startWith: 1000 }),
36 | taskId: integer('task_id').references(() => tasks.id),
37 | userId: integer('user_id').references(() => users.id),
38 | content: text('content').notNull(),
39 | createdAt: timestamp('created_at').defaultNow(),
40 | })
41 |
42 | export const commentSchema = createSelectSchema(comments)
43 | export const insertCommentSchema = createInsertSchema(comments)
44 | export type Comment = z.infer
45 | export type InsertComment = z.infer
46 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as TabsPrimitive from '@radix-ui/react-tabs'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { cn } from '@/lib/utils'
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = 'Card'
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = 'CardHeader'
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = 'CardTitle'
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = 'CardDescription'
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = 'CardContent'
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = 'CardFooter'
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as AccordionPrimitive from '@radix-ui/react-accordion'
4 | import { ChevronDown } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = 'AccordionItem'
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180',
32 | className,
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ))
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/packages/ui/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | export default {
4 | darkMode: ['class'],
5 | content: [
6 | './pages/**/*.{ts,tsx}',
7 | './components/**/*.{ts,tsx}',
8 | './app/**/*.{ts,tsx}',
9 | './src/**/*.{ts,tsx}',
10 | ],
11 | prefix: '',
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: '2rem',
16 | screens: {
17 | '2xl': '1400px',
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: 'hsl(var(--border))',
23 | input: 'hsl(var(--input))',
24 | ring: 'hsl(var(--ring))',
25 | background: 'hsl(var(--background))',
26 | foreground: 'hsl(var(--foreground))',
27 | primary: {
28 | DEFAULT: 'hsl(var(--primary))',
29 | foreground: 'hsl(var(--primary-foreground))',
30 | },
31 | secondary: {
32 | DEFAULT: 'hsl(var(--secondary))',
33 | foreground: 'hsl(var(--secondary-foreground))',
34 | },
35 | destructive: {
36 | DEFAULT: 'hsl(var(--destructive))',
37 | foreground: 'hsl(var(--destructive-foreground))',
38 | },
39 | muted: {
40 | DEFAULT: 'hsl(var(--muted))',
41 | foreground: 'hsl(var(--muted-foreground))',
42 | },
43 | accent: {
44 | DEFAULT: 'hsl(var(--accent))',
45 | foreground: 'hsl(var(--accent-foreground))',
46 | },
47 | popover: {
48 | DEFAULT: 'hsl(var(--popover))',
49 | foreground: 'hsl(var(--popover-foreground))',
50 | },
51 | card: {
52 | DEFAULT: 'hsl(var(--card))',
53 | foreground: 'hsl(var(--card-foreground))',
54 | },
55 | },
56 | borderRadius: {
57 | lg: 'var(--radius)',
58 | md: 'calc(var(--radius) - 2px)',
59 | sm: 'calc(var(--radius) - 4px)',
60 | },
61 | keyframes: {
62 | 'accordion-down': {
63 | from: { height: '0' },
64 | to: { height: 'var(--radix-accordion-content-height)' },
65 | },
66 | 'accordion-up': {
67 | from: { height: 'var(--radix-accordion-content-height)' },
68 | to: { height: '0' },
69 | },
70 | },
71 | animation: {
72 | 'accordion-down': 'accordion-down 0.2s ease-out',
73 | 'accordion-up': 'accordion-up 0.2s ease-out',
74 | },
75 | },
76 | },
77 | plugins: [require('tailwindcss-animate')],
78 | } satisfies Config
79 |
--------------------------------------------------------------------------------
/packages/ui/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 222.2 84% 4.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 222.2 84% 4.9%;
13 | --primary: 222.2 47.4% 11.2%;
14 | --primary-foreground: 210 40% 98%;
15 | --secondary: 210 40% 96.1%;
16 | --secondary-foreground: 222.2 47.4% 11.2%;
17 | --muted: 210 40% 96.1%;
18 | --muted-foreground: 215.4 16.3% 46.9%;
19 | --accent: 210 40% 96.1%;
20 | --accent-foreground: 222.2 47.4% 11.2%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 210 40% 98%;
23 | --border: 214.3 31.8% 91.4%;
24 | --input: 214.3 31.8% 91.4%;
25 | --ring: 222.2 84% 4.9%;
26 | --radius: 0.5rem;
27 | --chart-1: 12 76% 61%;
28 | --chart-2: 173 58% 39%;
29 | --chart-3: 197 37% 24%;
30 | --chart-4: 43 74% 66%;
31 | --chart-5: 27 87% 67%;
32 | }
33 |
34 | .dark {
35 | --background: 222.2 84% 4.9%;
36 | --foreground: 210 40% 98%;
37 | --card: 222.2 84% 4.9%;
38 | --card-foreground: 210 40% 98%;
39 | --popover: 222.2 84% 4.9%;
40 | --popover-foreground: 210 40% 98%;
41 | --primary: 210 40% 98%;
42 | --primary-foreground: 222.2 47.4% 11.2%;
43 | --secondary: 217.2 32.6% 17.5%;
44 | --secondary-foreground: 210 40% 98%;
45 | --muted: 217.2 32.6% 17.5%;
46 | --muted-foreground: 215 20.2% 65.1%;
47 | --accent: 217.2 32.6% 17.5%;
48 | --accent-foreground: 210 40% 98%;
49 | --destructive: 0 62.8% 30.6%;
50 | --destructive-foreground: 210 40% 98%;
51 | --border: 217.2 32.6% 17.5%;
52 | --input: 217.2 32.6% 17.5%;
53 | --ring: 212.7 26.8% 83.9%;
54 | --chart-1: 220 70% 50%;
55 | --chart-2: 160 60% 45%;
56 | --chart-3: 30 80% 55%;
57 | --chart-4: 280 65% 60%;
58 | --chart-5: 340 75% 55%;
59 | }
60 | }
61 |
62 | @layer base {
63 | * {
64 | @apply border-border;
65 | }
66 | body {
67 | @apply bg-background text-foreground;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/apps/astro/README.md:
--------------------------------------------------------------------------------
1 | # Astro Starter Kit: Basics
2 |
3 | ```sh
4 | npm create astro@latest -- --template basics
5 | ```
6 |
7 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
8 | [](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
9 | [](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
10 |
11 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
12 |
13 | 
14 |
15 | ## 🚀 Project Structure
16 |
17 | Inside of your Astro project, you'll see the following folders and files:
18 |
19 | ```text
20 | /
21 | ├── public/
22 | │ └── favicon.svg
23 | ├── src/
24 | │ ├── components/
25 | │ │ └── Card.astro
26 | │ ├── layouts/
27 | │ │ └── Layout.astro
28 | │ └── pages/
29 | │ └── index.astro
30 | └── package.json
31 | ```
32 |
33 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
34 |
35 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
36 |
37 | Any static assets, like images, can be placed in the `public/` directory.
38 |
39 | ## 🧞 Commands
40 |
41 | All commands are run from the root of the project, from a terminal:
42 |
43 | | Command | Action |
44 | | :------------------------ | :----------------------------------------------- |
45 | | `npm install` | Installs dependencies |
46 | | `npm run dev` | Starts local dev server at `localhost:4321` |
47 | | `npm run build` | Build your production site to `./dist/` |
48 | | `npm run preview` | Preview your build locally, before deploying |
49 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
50 | | `npm run astro -- --help` | Get help using the Astro CLI |
51 |
52 | ## 👀 Want to learn more?
53 |
54 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
55 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/input-otp.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { OTPInput, OTPInputContext } from 'input-otp'
4 | import { Dot } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const InputOTP = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, containerClassName, ...props }, ref) => (
13 |
22 | ))
23 | InputOTP.displayName = 'InputOTP'
24 |
25 | const InputOTPGroup = React.forwardRef<
26 | React.ElementRef<'div'>,
27 | React.ComponentPropsWithoutRef<'div'>
28 | >(({ className, ...props }, ref) => (
29 |
30 | ))
31 | InputOTPGroup.displayName = 'InputOTPGroup'
32 |
33 | const InputOTPSlot = React.forwardRef<
34 | React.ElementRef<'div'>,
35 | React.ComponentPropsWithoutRef<'div'> & { index: number }
36 | >(({ index, className, ...props }, ref) => {
37 | const inputOTPContext = React.useContext(OTPInputContext)
38 | const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] ?? {}
39 |
40 | return (
41 |
50 | {char}
51 | {hasFakeCaret && (
52 |
55 | )}
56 |
57 | )
58 | })
59 | InputOTPSlot.displayName = 'InputOTPSlot'
60 |
61 | const InputOTPSeparator = React.forwardRef<
62 | React.ElementRef<'div'>,
63 | React.ComponentPropsWithoutRef<'div'>
64 | >(({ ...props }, ref) => (
65 |
68 | role='separator'
69 | {...props}
70 | >
71 |
72 |
73 | ))
74 | InputOTPSeparator.displayName = 'InputOTPSeparator'
75 |
76 | export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
77 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { ChevronLeft, ChevronRight } from 'lucide-react'
4 | import type * as React from 'react'
5 | import { DayPicker } from 'react-day-picker'
6 |
7 | import { buttonVariants } from '@/components/ui/button'
8 | import { cn } from '@/lib/utils'
9 |
10 | export type CalendarProps = React.ComponentProps
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | ,
58 | IconRight: ({ ...props }) => ,
59 | }}
60 | {...props}
61 | />
62 | )
63 | }
64 | Calendar.displayName = 'Calendar'
65 |
66 | export { Calendar }
67 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "type": "module",
7 | "exports": {
8 | ".": "./src/index.tsx",
9 | "./*": "./src/*.tsx",
10 | "./components/*": "./src/components/*.tsx",
11 | "./lib/*": "./src/lib/*.ts",
12 | "./hooks/*": "./src/hooks/*.ts",
13 | "./globals.css": "./src/styles/globals.css",
14 | "./tailwind.config": "./tailwind.config.ts",
15 | "./postcss.config": "./postcss.config.js"
16 | },
17 | "sideEffects": false,
18 | "scripts": {
19 | "build": "tsup",
20 | "clean": "rm -rf dist node_modules .turbo",
21 | "dev": "tsup --watch",
22 | "typecheck": "tsc --noEmit",
23 | "lint": "biome check --write",
24 | "test": "bun test",
25 | "add-component": "bunx shadcn-ui@latest add",
26 | "outdated": "bun outdated"
27 | },
28 | "devDependencies": {
29 | "@repo/config-typescript": "*",
30 | "@types/react": "^18.3.4",
31 | "@types/react-dom": "^18.3.0",
32 | "autoprefixer": "^10.4.20",
33 | "postcss": "^8.4.41",
34 | "react": "^18.3.1",
35 | "react-dom": "^18.3.1",
36 | "tailwindcss": "^3.4.10",
37 | "tsup": "^8.2.4",
38 | "typescript": "^5.5.4"
39 | },
40 | "dependencies": {
41 | "@hookform/resolvers": "^3.9.0",
42 | "@radix-ui/react-accordion": "^1.2.0",
43 | "@radix-ui/react-alert-dialog": "^1.1.1",
44 | "@radix-ui/react-aspect-ratio": "^1.1.0",
45 | "@radix-ui/react-avatar": "^1.1.0",
46 | "@radix-ui/react-checkbox": "^1.1.1",
47 | "@radix-ui/react-collapsible": "^1.1.0",
48 | "@radix-ui/react-context-menu": "^2.2.1",
49 | "@radix-ui/react-dialog": "^1.1.1",
50 | "@radix-ui/react-dropdown-menu": "^2.1.1",
51 | "@radix-ui/react-hover-card": "^1.1.1",
52 | "@radix-ui/react-label": "^2.1.0",
53 | "@radix-ui/react-menubar": "^1.1.1",
54 | "@radix-ui/react-navigation-menu": "^1.2.0",
55 | "@radix-ui/react-popover": "^1.1.1",
56 | "@radix-ui/react-progress": "^1.1.0",
57 | "@radix-ui/react-radio-group": "^1.2.0",
58 | "@radix-ui/react-scroll-area": "^1.1.0",
59 | "@radix-ui/react-select": "^2.1.1",
60 | "@radix-ui/react-separator": "^1.1.0",
61 | "@radix-ui/react-slider": "^1.2.0",
62 | "@radix-ui/react-slot": "^1.1.0",
63 | "@radix-ui/react-switch": "^1.1.0",
64 | "@radix-ui/react-tabs": "^1.1.0",
65 | "@radix-ui/react-toast": "^1.2.1",
66 | "@radix-ui/react-toggle": "^1.1.0",
67 | "@radix-ui/react-toggle-group": "^1.1.0",
68 | "@radix-ui/react-tooltip": "^1.1.2",
69 | "class-variance-authority": "^0.7.0",
70 | "clsx": "^2.1.1",
71 | "cmdk": "1.0.0",
72 | "date-fns": "^3.6.0",
73 | "embla-carousel-react": "^8.2.0",
74 | "input-otp": "^1.2.4",
75 | "lucide-react": "^0.436.0",
76 | "next-themes": "^0.3.0",
77 | "react-day-picker": "8.10.1",
78 | "react-hook-form": "^7.53.0",
79 | "react-resizable-panels": "^2.1.1",
80 | "recharts": "^2.12.7",
81 | "sonner": "^1.5.0",
82 | "tailwind-merge": "^2.5.2",
83 | "tailwindcss-animate": "^1.0.7",
84 | "vaul": "^0.9.1",
85 | "zod": "^3.23.8"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot'
2 | import { ChevronRight, MoreHorizontal } from 'lucide-react'
3 | import * as React from 'react'
4 |
5 | import { cn } from '@/lib/utils'
6 |
7 | const Breadcrumb = React.forwardRef<
8 | HTMLElement,
9 | React.ComponentPropsWithoutRef<'nav'> & {
10 | separator?: React.ReactNode
11 | }
12 | >(({ ...props }, ref) => )
13 | Breadcrumb.displayName = 'Breadcrumb'
14 |
15 | const BreadcrumbList = React.forwardRef<
16 | HTMLOListElement,
17 | React.ComponentPropsWithoutRef<'ol'>
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | BreadcrumbList.displayName = 'BreadcrumbList'
29 |
30 | const BreadcrumbItem = React.forwardRef<
31 | HTMLLIElement,
32 | React.ComponentPropsWithoutRef<'li'>
33 | >(({ className, ...props }, ref) => (
34 |
39 | ))
40 | BreadcrumbItem.displayName = 'BreadcrumbItem'
41 |
42 | const BreadcrumbLink = React.forwardRef<
43 | HTMLAnchorElement,
44 | React.ComponentPropsWithoutRef<'a'> & {
45 | asChild?: boolean
46 | }
47 | >(({ asChild, className, ...props }, ref) => {
48 | const Comp = asChild ? Slot : 'a'
49 |
50 | return (
51 |
56 | )
57 | })
58 | BreadcrumbLink.displayName = 'BreadcrumbLink'
59 |
60 | const BreadcrumbPage = React.forwardRef<
61 | HTMLSpanElement,
62 | React.ComponentPropsWithoutRef<'span'>
63 | >(({ className, ...props }, ref) => (
64 |
72 | ))
73 | BreadcrumbPage.displayName = 'BreadcrumbPage'
74 |
75 | const BreadcrumbSeparator = ({
76 | children,
77 | className,
78 | ...props
79 | }: React.ComponentProps<'li'>) => (
80 | svg]:size-3.5', className)}
84 | {...props}
85 | >
86 | {children ?? }
87 |
88 | )
89 | BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
90 |
91 | const BreadcrumbEllipsis = ({
92 | className,
93 | ...props
94 | }: React.ComponentProps<'span'>) => (
95 |
101 |
102 | More
103 |
104 | )
105 | BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
106 |
107 | export {
108 | Breadcrumb,
109 | BreadcrumbList,
110 | BreadcrumbItem,
111 | BreadcrumbLink,
112 | BreadcrumbPage,
113 | BreadcrumbSeparator,
114 | BreadcrumbEllipsis,
115 | }
116 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'
2 | import * as React from 'react'
3 |
4 | import { type ButtonProps, buttonVariants } from '@/components/ui/button'
5 | import { cn } from '@/lib/utils'
6 |
7 | const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
8 |
14 | )
15 | Pagination.displayName = 'Pagination'
16 |
17 | const PaginationContent = React.forwardRef<
18 | HTMLUListElement,
19 | React.ComponentProps<'ul'>
20 | >(({ className, ...props }, ref) => (
21 |
26 | ))
27 | PaginationContent.displayName = 'PaginationContent'
28 |
29 | const PaginationItem = React.forwardRef<
30 | HTMLLIElement,
31 | React.ComponentProps<'li'>
32 | >(({ className, ...props }, ref) => (
33 |
34 | ))
35 | PaginationItem.displayName = 'PaginationItem'
36 |
37 | type PaginationLinkProps = {
38 | isActive?: boolean
39 | } & Pick &
40 | React.ComponentProps<'a'>
41 |
42 | const PaginationLink = ({
43 | className,
44 | isActive,
45 | size = 'icon',
46 | ...props
47 | }: PaginationLinkProps) => (
48 |
59 | )
60 | PaginationLink.displayName = 'PaginationLink'
61 |
62 | const PaginationPrevious = ({
63 | className,
64 | ...props
65 | }: React.ComponentProps) => (
66 |
72 |
73 | Previous
74 |
75 | )
76 | PaginationPrevious.displayName = 'PaginationPrevious'
77 |
78 | const PaginationNext = ({
79 | className,
80 | ...props
81 | }: React.ComponentProps) => (
82 |
88 | Next
89 |
90 |
91 | )
92 | PaginationNext.displayName = 'PaginationNext'
93 |
94 | const PaginationEllipsis = ({
95 | className,
96 | ...props
97 | }: React.ComponentProps<'span'>) => (
98 |
103 |
104 | More pages
105 |
106 | )
107 | PaginationEllipsis.displayName = 'PaginationEllipsis'
108 |
109 | export {
110 | Pagination,
111 | PaginationContent,
112 | PaginationEllipsis,
113 | PaginationItem,
114 | PaginationLink,
115 | PaginationNext,
116 | PaginationPrevious,
117 | }
118 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { cn } from '@/lib/utils'
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = 'Table'
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = 'TableHeader'
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = 'TableBody'
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0',
47 | className,
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = 'TableFooter'
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = 'TableRow'
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = 'TableHead'
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = 'TableCell'
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = 'TableCaption'
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { Drawer as DrawerPrimitive } from 'vaul'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const Drawer = ({
9 | shouldScaleBackground = true,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
16 | )
17 | Drawer.displayName = 'Drawer'
18 |
19 | const DrawerTrigger = DrawerPrimitive.Trigger
20 |
21 | const DrawerPortal = DrawerPrimitive.Portal
22 |
23 | const DrawerClose = DrawerPrimitive.Close
24 |
25 | const DrawerOverlay = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
34 | ))
35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
36 |
37 | const DrawerContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, children, ...props }, ref) => (
41 |
42 |
43 |
51 |
52 | {children}
53 |
54 |
55 | ))
56 | DrawerContent.displayName = 'DrawerContent'
57 |
58 | const DrawerHeader = ({
59 | className,
60 | ...props
61 | }: React.HTMLAttributes) => (
62 |
66 | )
67 | DrawerHeader.displayName = 'DrawerHeader'
68 |
69 | const DrawerFooter = ({
70 | className,
71 | ...props
72 | }: React.HTMLAttributes) => (
73 |
77 | )
78 | DrawerFooter.displayName = 'DrawerFooter'
79 |
80 | const DrawerTitle = React.forwardRef<
81 | React.ElementRef,
82 | React.ComponentPropsWithoutRef
83 | >(({ className, ...props }, ref) => (
84 |
92 | ))
93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName
94 |
95 | const DrawerDescription = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ))
105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName
106 |
107 | export {
108 | Drawer,
109 | DrawerPortal,
110 | DrawerOverlay,
111 | DrawerTrigger,
112 | DrawerClose,
113 | DrawerContent,
114 | DrawerHeader,
115 | DrawerFooter,
116 | DrawerTitle,
117 | DrawerDescription,
118 | }
119 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as DialogPrimitive from '@radix-ui/react-dialog'
4 | import { X } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = 'DialogHeader'
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = 'DialogFooter'
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from 'react'
5 |
6 | import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
7 |
8 | const TOAST_LIMIT = 1
9 | const TOAST_REMOVE_DELAY = 1000000
10 |
11 | type ToasterToast = ToastProps & {
12 | id: string
13 | title?: React.ReactNode
14 | description?: React.ReactNode
15 | action?: ToastActionElement
16 | }
17 |
18 | const actionTypes = {
19 | ADD_TOAST: 'ADD_TOAST',
20 | UPDATE_TOAST: 'UPDATE_TOAST',
21 | DISMISS_TOAST: 'DISMISS_TOAST',
22 | REMOVE_TOAST: 'REMOVE_TOAST',
23 | } as const
24 |
25 | let count = 0
26 |
27 | function genId() {
28 | count = (count + 1) % Number.MAX_SAFE_INTEGER
29 | return count.toString()
30 | }
31 |
32 | type ActionType = typeof actionTypes
33 |
34 | type Action =
35 | | {
36 | type: ActionType['ADD_TOAST']
37 | toast: ToasterToast
38 | }
39 | | {
40 | type: ActionType['UPDATE_TOAST']
41 | toast: Partial
42 | }
43 | | {
44 | type: ActionType['DISMISS_TOAST']
45 | toastId?: ToasterToast['id']
46 | }
47 | | {
48 | type: ActionType['REMOVE_TOAST']
49 | toastId?: ToasterToast['id']
50 | }
51 |
52 | interface State {
53 | toasts: ToasterToast[]
54 | }
55 |
56 | const toastTimeouts = new Map>()
57 |
58 | const addToRemoveQueue = (toastId: string) => {
59 | if (toastTimeouts.has(toastId)) {
60 | return
61 | }
62 |
63 | const timeout = setTimeout(() => {
64 | toastTimeouts.delete(toastId)
65 | dispatch({
66 | type: 'REMOVE_TOAST',
67 | toastId: toastId,
68 | })
69 | }, TOAST_REMOVE_DELAY)
70 |
71 | toastTimeouts.set(toastId, timeout)
72 | }
73 |
74 | export const reducer = (state: State, action: Action): State => {
75 | switch (action.type) {
76 | case 'ADD_TOAST':
77 | return {
78 | ...state,
79 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
80 | }
81 |
82 | case 'UPDATE_TOAST':
83 | return {
84 | ...state,
85 | toasts: state.toasts.map((t) =>
86 | t.id === action.toast.id ? { ...t, ...action.toast } : t,
87 | ),
88 | }
89 |
90 | case 'DISMISS_TOAST': {
91 | const { toastId } = action
92 |
93 | // ! Side effects ! - This could be extracted into a dismissToast() action,
94 | // but I'll keep it here for simplicity
95 | if (toastId) {
96 | addToRemoveQueue(toastId)
97 | } else {
98 | for (const toast of state.toasts) {
99 | addToRemoveQueue(toast.id)
100 | }
101 | }
102 |
103 | return {
104 | ...state,
105 | toasts: state.toasts.map((t) =>
106 | t.id === toastId || toastId === undefined
107 | ? {
108 | ...t,
109 | open: false,
110 | }
111 | : t,
112 | ),
113 | }
114 | }
115 | case 'REMOVE_TOAST':
116 | if (action.toastId === undefined) {
117 | return {
118 | ...state,
119 | toasts: [],
120 | }
121 | }
122 | return {
123 | ...state,
124 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
125 | }
126 | }
127 | }
128 |
129 | const listeners: Array<(state: State) => void> = []
130 |
131 | let memoryState: State = { toasts: [] }
132 |
133 | function dispatch(action: Action) {
134 | memoryState = reducer(memoryState, action)
135 | for (const listener of listeners) {
136 | listener(memoryState)
137 | }
138 | }
139 |
140 | type Toast = Omit
141 |
142 | function toast({ ...props }: Toast) {
143 | const id = genId()
144 |
145 | const update = (props: ToasterToast) =>
146 | dispatch({
147 | type: 'UPDATE_TOAST',
148 | toast: { ...props, id },
149 | })
150 | const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
151 |
152 | dispatch({
153 | type: 'ADD_TOAST',
154 | toast: {
155 | ...props,
156 | id,
157 | open: true,
158 | onOpenChange: (open) => {
159 | if (!open) dismiss()
160 | },
161 | },
162 | })
163 |
164 | return {
165 | id: id,
166 | dismiss,
167 | update,
168 | }
169 | }
170 |
171 | function useToast() {
172 | const [state, setState] = React.useState(memoryState)
173 |
174 | React.useEffect(() => {
175 | listeners.push(setState)
176 | return () => {
177 | const index = listeners.indexOf(setState)
178 | if (index > -1) {
179 | listeners.splice(index, 1)
180 | }
181 | }
182 | }, [])
183 |
184 | return {
185 | ...state,
186 | toast,
187 | dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
188 | }
189 | }
190 |
191 | export { useToast, toast }
192 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as SheetPrimitive from '@radix-ui/react-dialog'
4 | import { type VariantProps, cva } from 'class-variance-authority'
5 | import { X } from 'lucide-react'
6 | import * as React from 'react'
7 |
8 | import { cn } from '@/lib/utils'
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
35 | {
36 | variants: {
37 | side: {
38 | top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
39 | bottom:
40 | 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
41 | left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
42 | right:
43 | 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
44 | },
45 | },
46 | defaultVariants: {
47 | side: 'right',
48 | },
49 | },
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = 'right', className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = 'SheetHeader'
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = 'SheetFooter'
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import type * as LabelPrimitive from '@radix-ui/react-label'
4 | import { Slot } from '@radix-ui/react-slot'
5 | import * as React from 'react'
6 | import {
7 | Controller,
8 | type ControllerProps,
9 | type FieldPath,
10 | type FieldValues,
11 | FormProvider,
12 | useFormContext,
13 | } from 'react-hook-form'
14 |
15 | import { Label } from '@/components/ui/label'
16 | import { cn } from '@/lib/utils'
17 |
18 | const Form = FormProvider
19 |
20 | type FormFieldContextValue<
21 | TFieldValues extends FieldValues = FieldValues,
22 | TName extends FieldPath = FieldPath,
23 | > = {
24 | name: TName
25 | }
26 |
27 | const FormFieldContext = React.createContext(
28 | {} as FormFieldContextValue,
29 | )
30 |
31 | const FormField = <
32 | TFieldValues extends FieldValues = FieldValues,
33 | TName extends FieldPath = FieldPath,
34 | >({
35 | ...props
36 | }: ControllerProps) => {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const useFormField = () => {
45 | const fieldContext = React.useContext(FormFieldContext)
46 | const itemContext = React.useContext(FormItemContext)
47 | const { getFieldState, formState } = useFormContext()
48 |
49 | const fieldState = getFieldState(fieldContext.name, formState)
50 |
51 | if (!fieldContext) {
52 | throw new Error('useFormField should be used within ')
53 | }
54 |
55 | const { id } = itemContext
56 |
57 | return {
58 | id,
59 | name: fieldContext.name,
60 | formItemId: `${id}-form-item`,
61 | formDescriptionId: `${id}-form-item-description`,
62 | formMessageId: `${id}-form-item-message`,
63 | ...fieldState,
64 | }
65 | }
66 |
67 | type FormItemContextValue = {
68 | id: string
69 | }
70 |
71 | const FormItemContext = React.createContext(
72 | {} as FormItemContextValue,
73 | )
74 |
75 | const FormItem = React.forwardRef<
76 | HTMLDivElement,
77 | React.HTMLAttributes
78 | >(({ className, ...props }, ref) => {
79 | const id = React.useId()
80 |
81 | return (
82 |
83 |
84 |
85 | )
86 | })
87 | FormItem.displayName = 'FormItem'
88 |
89 | const FormLabel = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => {
93 | const { error, formItemId } = useFormField()
94 |
95 | return (
96 |
102 | )
103 | })
104 | FormLabel.displayName = 'FormLabel'
105 |
106 | const FormControl = React.forwardRef<
107 | React.ElementRef,
108 | React.ComponentPropsWithoutRef
109 | >(({ ...props }, ref) => {
110 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111 |
112 | return (
113 |
124 | )
125 | })
126 | FormControl.displayName = 'FormControl'
127 |
128 | const FormDescription = React.forwardRef<
129 | HTMLParagraphElement,
130 | React.HTMLAttributes
131 | >(({ className, ...props }, ref) => {
132 | const { formDescriptionId } = useFormField()
133 |
134 | return (
135 |
141 | )
142 | })
143 | FormDescription.displayName = 'FormDescription'
144 |
145 | const FormMessage = React.forwardRef<
146 | HTMLParagraphElement,
147 | React.HTMLAttributes
148 | >(({ className, children, ...props }, ref) => {
149 | const { error, formMessageId } = useFormField()
150 | const body = error ? String(error?.message) : children
151 |
152 | if (!body) {
153 | return null
154 | }
155 |
156 | return (
157 |
163 | {body}
164 |
165 | )
166 | })
167 | FormMessage.displayName = 'FormMessage'
168 |
169 | export {
170 | useFormField,
171 | Form,
172 | FormItem,
173 | FormLabel,
174 | FormControl,
175 | FormDescription,
176 | FormMessage,
177 | FormField,
178 | }
179 |
--------------------------------------------------------------------------------
/apps/astro/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Card from '../components/Card.astro'
3 | import Layout from '../layouts/Layout.astro'
4 | ---
5 |
6 |
7 |
8 |
36 | Welcome to Astro
37 |
38 | To get started, open the directory src/pages in your project.
39 | Code Challenge: Tweak the "Welcome to Astro" message above.
40 |
41 |
63 |
64 |
65 |
66 |
124 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
4 | import * as React from 'react'
5 |
6 | import { buttonVariants } from '@/components/ui/button'
7 | import { cn } from '@/lib/utils'
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = 'AlertDialogHeader'
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = 'AlertDialogFooter'
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as ToastPrimitives from '@radix-ui/react-toast'
4 | import { type VariantProps, cva } from 'class-variance-authority'
5 | import { X } from 'lucide-react'
6 | import * as React from 'react'
7 |
8 | import { cn } from '@/lib/utils'
9 |
10 | const ToastProvider = ToastPrimitives.Provider
11 |
12 | const ToastViewport = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, ...props }, ref) => (
16 |
24 | ))
25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26 |
27 | const toastVariants = cva(
28 | 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
29 | {
30 | variants: {
31 | variant: {
32 | default: 'border bg-background text-foreground',
33 | destructive:
34 | 'destructive group border-destructive bg-destructive text-destructive-foreground',
35 | },
36 | },
37 | defaultVariants: {
38 | variant: 'default',
39 | },
40 | },
41 | )
42 |
43 | const Toast = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef &
46 | VariantProps
47 | >(({ className, variant, ...props }, ref) => {
48 | return (
49 |
54 | )
55 | })
56 | Toast.displayName = ToastPrimitives.Root.displayName
57 |
58 | const ToastAction = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
70 | ))
71 | ToastAction.displayName = ToastPrimitives.Action.displayName
72 |
73 | const ToastClose = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >(({ className, ...props }, ref) => (
77 |
86 |
87 |
88 | ))
89 | ToastClose.displayName = ToastPrimitives.Close.displayName
90 |
91 | const ToastTitle = React.forwardRef<
92 | React.ElementRef,
93 | React.ComponentPropsWithoutRef
94 | >(({ className, ...props }, ref) => (
95 |
100 | ))
101 | ToastTitle.displayName = ToastPrimitives.Title.displayName
102 |
103 | const ToastDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | ToastDescription.displayName = ToastPrimitives.Description.displayName
114 |
115 | type ToastProps = React.ComponentPropsWithoutRef
116 |
117 | type ToastActionElement = React.ReactElement
118 |
119 | export {
120 | type ToastProps,
121 | type ToastActionElement,
122 | ToastProvider,
123 | ToastViewport,
124 | Toast,
125 | ToastTitle,
126 | ToastDescription,
127 | ToastClose,
128 | ToastAction,
129 | }
130 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import type { DialogProps } from '@radix-ui/react-dialog'
4 | import { Command as CommandPrimitive } from 'cmdk'
5 | import { Search } from 'lucide-react'
6 | import * as React from 'react'
7 |
8 | import { Dialog, DialogContent } from '@/components/ui/dialog'
9 | import { cn } from '@/lib/utils'
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | interface CommandDialogProps extends DialogProps {}
27 |
28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29 | return (
30 |
37 | )
38 | }
39 |
40 | const CommandInput = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
45 |
46 |
54 |
55 | ))
56 |
57 | CommandInput.displayName = CommandPrimitive.Input.displayName
58 |
59 | const CommandList = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, ...props }, ref) => (
63 |
68 | ))
69 |
70 | CommandList.displayName = CommandPrimitive.List.displayName
71 |
72 | const CommandEmpty = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >((props, ref) => (
76 |
81 | ))
82 |
83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84 |
85 | const CommandGroup = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(({ className, ...props }, ref) => (
89 |
97 | ))
98 |
99 | CommandGroup.displayName = CommandPrimitive.Group.displayName
100 |
101 | const CommandSeparator = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112 |
113 | const CommandItem = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
125 | ))
126 |
127 | CommandItem.displayName = CommandPrimitive.Item.displayName
128 |
129 | const CommandShortcut = ({
130 | className,
131 | ...props
132 | }: React.HTMLAttributes) => {
133 | return (
134 |
141 | )
142 | }
143 | CommandShortcut.displayName = 'CommandShortcut'
144 |
145 | export {
146 | Command,
147 | CommandDialog,
148 | CommandInput,
149 | CommandList,
150 | CommandEmpty,
151 | CommandGroup,
152 | CommandItem,
153 | CommandShortcut,
154 | CommandSeparator,
155 | }
156 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'
2 | import { cva } from 'class-variance-authority'
3 | import { ChevronDown } from 'lucide-react'
4 | import * as React from 'react'
5 |
6 | import { cn } from '@/lib/utils'
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{' '}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/apps/spa/src/routeTree.gen.ts:
--------------------------------------------------------------------------------
1 | /* prettier-ignore-start */
2 |
3 | /* eslint-disable */
4 |
5 | // @ts-nocheck
6 |
7 | // noinspection JSUnusedGlobalSymbols
8 |
9 | // This file is auto-generated by TanStack Router
10 |
11 | // Import Routes
12 |
13 | import { Route as rootRoute } from './routes/__root'
14 | import { Route as PostsImport } from './routes/posts'
15 | import { Route as LayoutImport } from './routes/_layout'
16 | import { Route as IndexImport } from './routes/index'
17 | import { Route as PostsIndexImport } from './routes/posts.index'
18 | import { Route as PostsPostIdImport } from './routes/posts.$postId'
19 | import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2'
20 | import { Route as LayoutLayout2LayoutBImport } from './routes/_layout/_layout-2/layout-b'
21 | import { Route as LayoutLayout2LayoutAImport } from './routes/_layout/_layout-2/layout-a'
22 |
23 | // Create/Update Routes
24 |
25 | const PostsRoute = PostsImport.update({
26 | path: '/posts',
27 | getParentRoute: () => rootRoute,
28 | } as any)
29 |
30 | const LayoutRoute = LayoutImport.update({
31 | id: '/_layout',
32 | getParentRoute: () => rootRoute,
33 | } as any)
34 |
35 | const IndexRoute = IndexImport.update({
36 | path: '/',
37 | getParentRoute: () => rootRoute,
38 | } as any)
39 |
40 | const PostsIndexRoute = PostsIndexImport.update({
41 | path: '/',
42 | getParentRoute: () => PostsRoute,
43 | } as any)
44 |
45 | const PostsPostIdRoute = PostsPostIdImport.update({
46 | path: '/$postId',
47 | getParentRoute: () => PostsRoute,
48 | } as any)
49 |
50 | const LayoutLayout2Route = LayoutLayout2Import.update({
51 | id: '/_layout-2',
52 | getParentRoute: () => LayoutRoute,
53 | } as any)
54 |
55 | const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBImport.update({
56 | path: '/layout-b',
57 | getParentRoute: () => LayoutLayout2Route,
58 | } as any)
59 |
60 | const LayoutLayout2LayoutARoute = LayoutLayout2LayoutAImport.update({
61 | path: '/layout-a',
62 | getParentRoute: () => LayoutLayout2Route,
63 | } as any)
64 |
65 | // Populate the FileRoutesByPath interface
66 |
67 | declare module '@tanstack/react-router' {
68 | interface FileRoutesByPath {
69 | '/': {
70 | id: '/'
71 | path: '/'
72 | fullPath: '/'
73 | preLoaderRoute: typeof IndexImport
74 | parentRoute: typeof rootRoute
75 | }
76 | '/_layout': {
77 | id: '/_layout'
78 | path: ''
79 | fullPath: ''
80 | preLoaderRoute: typeof LayoutImport
81 | parentRoute: typeof rootRoute
82 | }
83 | '/posts': {
84 | id: '/posts'
85 | path: '/posts'
86 | fullPath: '/posts'
87 | preLoaderRoute: typeof PostsImport
88 | parentRoute: typeof rootRoute
89 | }
90 | '/_layout/_layout-2': {
91 | id: '/_layout/_layout-2'
92 | path: ''
93 | fullPath: ''
94 | preLoaderRoute: typeof LayoutLayout2Import
95 | parentRoute: typeof LayoutImport
96 | }
97 | '/posts/$postId': {
98 | id: '/posts/$postId'
99 | path: '/$postId'
100 | fullPath: '/posts/$postId'
101 | preLoaderRoute: typeof PostsPostIdImport
102 | parentRoute: typeof PostsImport
103 | }
104 | '/posts/': {
105 | id: '/posts/'
106 | path: '/'
107 | fullPath: '/posts/'
108 | preLoaderRoute: typeof PostsIndexImport
109 | parentRoute: typeof PostsImport
110 | }
111 | '/_layout/_layout-2/layout-a': {
112 | id: '/_layout/_layout-2/layout-a'
113 | path: '/layout-a'
114 | fullPath: '/layout-a'
115 | preLoaderRoute: typeof LayoutLayout2LayoutAImport
116 | parentRoute: typeof LayoutLayout2Import
117 | }
118 | '/_layout/_layout-2/layout-b': {
119 | id: '/_layout/_layout-2/layout-b'
120 | path: '/layout-b'
121 | fullPath: '/layout-b'
122 | preLoaderRoute: typeof LayoutLayout2LayoutBImport
123 | parentRoute: typeof LayoutLayout2Import
124 | }
125 | }
126 | }
127 |
128 | // Create and export the route tree
129 |
130 | export const routeTree = rootRoute.addChildren({
131 | IndexRoute,
132 | LayoutRoute: LayoutRoute.addChildren({
133 | LayoutLayout2Route: LayoutLayout2Route.addChildren({
134 | LayoutLayout2LayoutARoute,
135 | LayoutLayout2LayoutBRoute,
136 | }),
137 | }),
138 | PostsRoute: PostsRoute.addChildren({ PostsPostIdRoute, PostsIndexRoute }),
139 | })
140 |
141 | /* prettier-ignore-end */
142 |
143 | /* ROUTE_MANIFEST_START
144 | {
145 | "routes": {
146 | "__root__": {
147 | "filePath": "__root.tsx",
148 | "children": [
149 | "/",
150 | "/_layout",
151 | "/posts"
152 | ]
153 | },
154 | "/": {
155 | "filePath": "index.tsx"
156 | },
157 | "/_layout": {
158 | "filePath": "_layout.tsx",
159 | "children": [
160 | "/_layout/_layout-2"
161 | ]
162 | },
163 | "/posts": {
164 | "filePath": "posts.tsx",
165 | "children": [
166 | "/posts/$postId",
167 | "/posts/"
168 | ]
169 | },
170 | "/_layout/_layout-2": {
171 | "filePath": "_layout/_layout-2.tsx",
172 | "parent": "/_layout",
173 | "children": [
174 | "/_layout/_layout-2/layout-a",
175 | "/_layout/_layout-2/layout-b"
176 | ]
177 | },
178 | "/posts/$postId": {
179 | "filePath": "posts.$postId.tsx",
180 | "parent": "/posts"
181 | },
182 | "/posts/": {
183 | "filePath": "posts.index.tsx",
184 | "parent": "/posts"
185 | },
186 | "/_layout/_layout-2/layout-a": {
187 | "filePath": "_layout/_layout-2/layout-a.tsx",
188 | "parent": "/_layout/_layout-2"
189 | },
190 | "/_layout/_layout-2/layout-b": {
191 | "filePath": "_layout/_layout-2/layout-b.tsx",
192 | "parent": "/_layout/_layout-2"
193 | }
194 | }
195 | }
196 | ROUTE_MANIFEST_END */
197 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as SelectPrimitive from '@radix-ui/react-select'
4 | import { Check, ChevronDown, ChevronUp } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1',
23 | className,
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = 'popper', ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ))
135 | SelectItem.displayName = SelectPrimitive.Item.displayName
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ))
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | }
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Turborepo Monorepo (Tanstack/Hono/SST/Drizzle)
2 |
3 | ## Goals
4 |
5 | - Simplicity over complexity
6 | - Modularity
7 | - Taking a bet on the best "rising" libraries offering the best developer experience
8 |
9 | ## Known Issues
10 |
11 | See [TODO.md](./TODO.md) for more details
12 |
13 | ## Using this example
14 |
15 | Add Environment Variables:
16 | Copy the `.env.example` file to `.env` and add your environment variables.
17 |
18 | ```sh
19 | CLOUDFLARE_ACCOUNT_ID=
20 | ```
21 |
22 | Add new packages to the repo with Turborepo:
23 |
24 | ```sh
25 | bun turbo gen workspace
26 | # alternatively: `bun turbo gen workspace --type ` to specify the type of package to create
27 | ```
28 |
29 | Update dependencies across the monorepo:
30 |
31 | ```sh
32 | bunx taze -I -r
33 | # or
34 | bunx taze --interactive --recursive
35 | bunx taze --include lodash,webpack # filter by package name
36 | ```
37 |
38 | ## What's inside?
39 |
40 | This Turborepo includes the following packages and apps:
41 |
42 | **Apps**
43 |
44 | - **api**: A [Hono](https://hono.dev/) server compatible with Cloudflare Workers
45 | - **astro**: A [Astro](https://astro.build/) for content-driven websites
46 | - **spa**: A [Tanstack Router](https://tanstack.com/router) SPA
47 |
48 | **Packages**
49 |
50 | - **ui**: React UI library initialized with Shadcn common components, tsconfig, and globals.css, which are exported to the spa app
51 | - **typescript-config**: tsconfig.json's used throughout the monorepo
52 | - **core**: Core package for business logic
53 | - **db**: Database package for [Drizzle ORM](https://drizzle.dev/)
54 |
55 | Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
56 |
57 | ### Utilities
58 |
59 | This Turborepo has some additional tools already setup for you:
60 |
61 | - [SST](https://sst.dev/) for Infrastructure as Code
62 | - [Bun](https://bun.sh/) for package management, monorepo workspace, test runner
63 | - [Biome](https://biomejs.dev/) for formatting & linting
64 | - [TypeScript](https://www.typescriptlang.org/) for static type checking
65 | - [taze](https://github.com/antfu-collective/taze) for updating dependencies across the monorepo
66 |
67 | #### Other Utilities to Consider
68 |
69 | - [Knip](https://github.com/webpro/knip) - Find unused code
70 | - [date-fns](https://date-fns.org/) - Date utilities
71 | - [mattpocock/ts-reset: A 'CSS reset' for TypeScript, improving types for common JavaScript API's](https://github.com/mattpocock/ts-reset)
72 | - [ts-essentials/ts-essentials: All essential TypeScript types in one place 🤙](https://github.com/ts-essentials/ts-essentials)
73 | - Auth
74 | - WorkOS
75 | - Clerk
76 | - Supabase
77 | - Email
78 | - [Resend](https://resend.com/) - Transaction & Marketing Emails
79 |
80 | ## Syncing Github Template
81 |
82 | Refer to [AndreasAugustin/actions-template-sync](https://github.com/AndreasAugustin/actions-template-sync) for a github action to sync the template. This repo uses the [.github/workflows/template-sync.yml](./.github/workflows/template-sync.yml) file to sync the template, which you can manually trigger the action from the Actions tab in Github. You can configure this to automatically sync with a cron schedule if you'd like.
83 |
84 | ## Resources
85 |
86 | - Example Monorepos
87 |
88 | - [vercel/turborepo/examples/kitchen-sink](https://github.com/vercel/turborepo/tree/ca29f0fa75ad2cf4c9640e8ffdef406e63961472/examples/kitchen-sink)
89 | - best monorepo with turbo [midday-ai/midday](https://github.com/midday-ai/midday) - a monorepo for my personal website and projects, built with Turborepo + pnpm 📚
90 | - [mattpocock/total-typescript-monorepo](https://github.com/mattpocock/total-typescript-monorepo) / [total-typescript-monorepo-template](https://github.com/mattpocock/total-typescript-monorepo-template)
91 | - [byCedric/expo-monorepo-example](https://github.com/byCedric/expo-monorepo-example) Expo + Web
92 | - [saasfly/saasfly](https://github.com/saasfly/saasfly) - Next.JS template
93 | - [enjidev/enji.dev](https://github.com/enjidev/enji.dev) - a monorepo for my personal website and projects, built with Turborepo + pnpm 📚
94 | - Misc
95 | - [hamlim/template-monorepo](https://github.com/hamlim/template-monorepo) - A quick and simple monorepo starter template (Bun, Next.js, Turbo) / Biome
96 | - [breezemm/breeze-web](https://github.com/breezemm/breeze-web) - Monorepo for Breeze Web - init with Shadcn in UI package, consumed elsewhere
97 | - [dangvanthanh/nuxt-turborepo-boilerplate](https://github.com/dangvanthanh/nuxt-turborepo-boilerplate) - review biome config
98 | - SST
99 | - [sst/ion/tree/dev/examples/aws-monorepo](https://github.com/sst/ion/tree/dev/examples/aws-monorepo)
100 | - [sayandedotcom/refhired.com](https://github.com/sayandedotcom/refhired.com) - SST + Turborepo
101 | - [marcotheo/qwik-sst-deployments](https://github.com/marcotheo/qwik-sst-deployments) - see example of SST + Cloudflare Pages deployment with Pulumi
102 | - Pulumi
103 | - [martypenner/surface-2-air-site](https://github.com/martypenner/surface-2-air-site) - example of Pulumi usage
104 | - [ginetta/ginetta-tonic-festival](https://github.com/ginetta/ginetta-tonic-festival/blob/bcbefd4b3acf6df100b687295f73fc469b984e55/infrastructure/package.json) - see Pulumi usage
105 |
106 | - Turborepo Generators
107 | - [turbo-generator](https://github.com/eXodes/turbo-generator) - example generators for a Next.JS app
108 | - [turborepo-template](https://github.com/dhoniaridho/turborepo-template/tree/main/turbo/generators) - example templates with various app packages
109 |
110 | - tsup
111 | - [treeshaking-with-tsup](https://dorshinar.me/posts/treeshaking-with-tsup)
112 |
113 | ### Syncing Github Template Notes
114 |
115 | - [GitHub templates and repository sync | 0xDC.me](https://0xdc.me/blog/github-templates-and-repository-sync/)
116 | - [actions-template-sync](https://github.com/AndreasAugustin/actions-template-sync) - seems most used
117 | - [template-sync](https://github.com/template-tools/template-sync)
118 | - [coopTilleuls/template-sync](https://github.com/coopTilleuls/template-sync)
119 |
120 | ## Notes
121 |
122 | - Package.json settings
123 | - Refer to [Live Types in a TypeScript Monorepo](https://colinhacks.com/essays/live-types-typescript-monorepo?q=1) and [treeshaking-with-tsup](https://dorshinar.me/posts/treeshaking-with-tsup) - either use `exports` to export the `index.tsx/ts` file or use baseUrl/paths in base tsconfig.json to point to the `index.tsx/ts` file
124 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import useEmblaCarousel, {
4 | type UseEmblaCarouselType,
5 | } from 'embla-carousel-react'
6 | import { ArrowLeft, ArrowRight } from 'lucide-react'
7 | import * as React from 'react'
8 |
9 | import { Button } from '@/components/ui/button'
10 | import { cn } from '@/lib/utils'
11 |
12 | type CarouselApi = UseEmblaCarouselType[1]
13 | type UseCarouselParameters = Parameters
14 | type CarouselOptions = UseCarouselParameters[0]
15 | type CarouselPlugin = UseCarouselParameters[1]
16 |
17 | type CarouselProps = {
18 | opts?: CarouselOptions
19 | plugins?: CarouselPlugin
20 | orientation?: 'horizontal' | 'vertical'
21 | setApi?: (api: CarouselApi) => void
22 | }
23 |
24 | type CarouselContextProps = {
25 | carouselRef: ReturnType[0]
26 | api: ReturnType[1]
27 | scrollPrev: () => void
28 | scrollNext: () => void
29 | canScrollPrev: boolean
30 | canScrollNext: boolean
31 | } & CarouselProps
32 |
33 | const CarouselContext = React.createContext(null)
34 |
35 | function useCarousel() {
36 | const context = React.useContext(CarouselContext)
37 |
38 | if (!context) {
39 | throw new Error('useCarousel must be used within a ')
40 | }
41 |
42 | return context
43 | }
44 |
45 | const Carousel = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes & CarouselProps
48 | >(
49 | (
50 | {
51 | orientation = 'horizontal',
52 | opts,
53 | setApi,
54 | plugins,
55 | className,
56 | children,
57 | ...props
58 | },
59 | ref,
60 | ) => {
61 | const [carouselRef, api] = useEmblaCarousel(
62 | {
63 | ...opts,
64 | axis: orientation === 'horizontal' ? 'x' : 'y',
65 | },
66 | plugins,
67 | )
68 | const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69 | const [canScrollNext, setCanScrollNext] = React.useState(false)
70 |
71 | const onSelect = React.useCallback((api: CarouselApi) => {
72 | if (!api) {
73 | return
74 | }
75 |
76 | setCanScrollPrev(api.canScrollPrev())
77 | setCanScrollNext(api.canScrollNext())
78 | }, [])
79 |
80 | const scrollPrev = React.useCallback(() => {
81 | api?.scrollPrev()
82 | }, [api])
83 |
84 | const scrollNext = React.useCallback(() => {
85 | api?.scrollNext()
86 | }, [api])
87 |
88 | const handleKeyDown = React.useCallback(
89 | (event: React.KeyboardEvent) => {
90 | if (event.key === 'ArrowLeft') {
91 | event.preventDefault()
92 | scrollPrev()
93 | } else if (event.key === 'ArrowRight') {
94 | event.preventDefault()
95 | scrollNext()
96 | }
97 | },
98 | [scrollPrev, scrollNext],
99 | )
100 |
101 | React.useEffect(() => {
102 | if (!api || !setApi) {
103 | return
104 | }
105 |
106 | setApi(api)
107 | }, [api, setApi])
108 |
109 | React.useEffect(() => {
110 | if (!api) {
111 | return
112 | }
113 |
114 | onSelect(api)
115 | api.on('reInit', onSelect)
116 | api.on('select', onSelect)
117 |
118 | return () => {
119 | api?.off('select', onSelect)
120 | }
121 | }, [api, onSelect])
122 |
123 | return (
124 |
137 |
145 | {children}
146 |
147 |
148 | )
149 | },
150 | )
151 | Carousel.displayName = 'Carousel'
152 |
153 | const CarouselContent = React.forwardRef<
154 | HTMLDivElement,
155 | React.HTMLAttributes
156 | >(({ className, ...props }, ref) => {
157 | const { carouselRef, orientation } = useCarousel()
158 |
159 | return (
160 |
171 | )
172 | })
173 | CarouselContent.displayName = 'CarouselContent'
174 |
175 | const CarouselItem = React.forwardRef<
176 | HTMLDivElement,
177 | React.HTMLAttributes
178 | >(({ className, ...props }, ref) => {
179 | const { orientation } = useCarousel()
180 |
181 | return (
182 |
193 | )
194 | })
195 | CarouselItem.displayName = 'CarouselItem'
196 |
197 | const CarouselPrevious = React.forwardRef<
198 | HTMLButtonElement,
199 | React.ComponentProps
200 | >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
201 | const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202 |
203 | return (
204 |
222 | )
223 | })
224 | CarouselPrevious.displayName = 'CarouselPrevious'
225 |
226 | const CarouselNext = React.forwardRef<
227 | HTMLButtonElement,
228 | React.ComponentProps
229 | >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
230 | const { orientation, scrollNext, canScrollNext } = useCarousel()
231 |
232 | return (
233 |
251 | )
252 | })
253 | CarouselNext.displayName = 'CarouselNext'
254 |
255 | export {
256 | type CarouselApi,
257 | Carousel,
258 | CarouselContent,
259 | CarouselItem,
260 | CarouselPrevious,
261 | CarouselNext,
262 | }
263 |
--------------------------------------------------------------------------------
/packages/ui/src/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
4 | import { Check, ChevronRight, Circle } from 'lucide-react'
5 | import * as React from 'react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const ContextMenu = ContextMenuPrimitive.Root
10 |
11 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger
12 |
13 | const ContextMenuGroup = ContextMenuPrimitive.Group
14 |
15 | const ContextMenuPortal = ContextMenuPrimitive.Portal
16 |
17 | const ContextMenuSub = ContextMenuPrimitive.Sub
18 |
19 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
20 |
21 | const ContextMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
41 |
42 | const ContextMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ))
55 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
56 |
57 | const ContextMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef