create-restack-pattern 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/dist/index.js +73 -0
  4. package/package.json +26 -0
  5. package/template/backend/.env.example +8 -0
  6. package/template/backend/README.md +8 -0
  7. package/template/backend/api/index.ts +9 -0
  8. package/template/backend/drizzle/0000_grey_lethal_legion.sql +19 -0
  9. package/template/backend/drizzle/meta/0000_snapshot.json +137 -0
  10. package/template/backend/drizzle/meta/_journal.json +13 -0
  11. package/template/backend/drizzle.config.ts +11 -0
  12. package/template/backend/gitignore +31 -0
  13. package/template/backend/package.json +34 -0
  14. package/template/backend/public/.gitkeep +0 -0
  15. package/template/backend/src/config/.gitkeep +0 -0
  16. package/template/backend/src/config/env.ts +22 -0
  17. package/template/backend/src/domains/auth/.gitkeep +0 -0
  18. package/template/backend/src/domains/auth/auth.model.ts +20 -0
  19. package/template/backend/src/domains/auth/auth.repo.ts +51 -0
  20. package/template/backend/src/domains/auth/auth.routes.ts +93 -0
  21. package/template/backend/src/domains/auth/auth.service.test.ts +143 -0
  22. package/template/backend/src/domains/auth/auth.service.ts +97 -0
  23. package/template/backend/src/domains/auth/auth.types.ts +7 -0
  24. package/template/backend/src/domains/user/.gitkeep +0 -0
  25. package/template/backend/src/domains/user/user.model.ts +14 -0
  26. package/template/backend/src/domains/user/user.repo.ts +29 -0
  27. package/template/backend/src/domains/user/user.routes.test.ts +82 -0
  28. package/template/backend/src/domains/user/user.routes.ts +48 -0
  29. package/template/backend/src/domains/user/user.service.ts +40 -0
  30. package/template/backend/src/hono-app.ts +42 -0
  31. package/template/backend/src/index.ts +13 -0
  32. package/template/backend/src/shared/db/.gitkeep +0 -0
  33. package/template/backend/src/shared/db/index.ts +11 -0
  34. package/template/backend/src/shared/logger/logger.ts +15 -0
  35. package/template/backend/src/shared/middlewares/.gitkeep +0 -0
  36. package/template/backend/src/shared/middlewares/rate-limit.ts +43 -0
  37. package/template/backend/src/shared/middlewares/require-auth.test.ts +51 -0
  38. package/template/backend/src/shared/middlewares/require-auth.ts +31 -0
  39. package/template/backend/tsconfig.json +16 -0
  40. package/template/backend/vercel.json +6 -0
  41. package/template/frontend/AGENTS.md +5 -0
  42. package/template/frontend/CLAUDE.md +1 -0
  43. package/template/frontend/README.md +36 -0
  44. package/template/frontend/app/(auth)/login/page.tsx +24 -0
  45. package/template/frontend/app/(auth)/register/page.tsx +24 -0
  46. package/template/frontend/app/dashboard/page.tsx +29 -0
  47. package/template/frontend/app/globals.css +131 -0
  48. package/template/frontend/app/icon.png +0 -0
  49. package/template/frontend/app/layout.tsx +36 -0
  50. package/template/frontend/app/page.tsx +33 -0
  51. package/template/frontend/app/profile/page.tsx +23 -0
  52. package/template/frontend/components/ui/button.tsx +67 -0
  53. package/template/frontend/components/ui/card.tsx +103 -0
  54. package/template/frontend/components/ui/input.tsx +19 -0
  55. package/template/frontend/components/ui/label.tsx +24 -0
  56. package/template/frontend/components.json +24 -0
  57. package/template/frontend/domains/auth/components/.gitkeep +0 -0
  58. package/template/frontend/domains/auth/components/login-form.tsx +75 -0
  59. package/template/frontend/domains/auth/components/logout-button.tsx +30 -0
  60. package/template/frontend/domains/auth/components/register-form.tsx +87 -0
  61. package/template/frontend/domains/auth/hooks/.gitkeep +0 -0
  62. package/template/frontend/domains/auth/hooks/use-login.ts +17 -0
  63. package/template/frontend/domains/auth/hooks/use-register.ts +17 -0
  64. package/template/frontend/domains/auth/services/.gitkeep +0 -0
  65. package/template/frontend/domains/auth/services/auth.api.ts +27 -0
  66. package/template/frontend/domains/user/components/.gitkeep +0 -0
  67. package/template/frontend/domains/user/components/profile-card.tsx +33 -0
  68. package/template/frontend/domains/user/hooks/use-profile.ts +39 -0
  69. package/template/frontend/domains/user/services/user.api.ts +21 -0
  70. package/template/frontend/eslint.config.mjs +18 -0
  71. package/template/frontend/gitignore +43 -0
  72. package/template/frontend/lib/utils.ts +11 -0
  73. package/template/frontend/next.config.ts +7 -0
  74. package/template/frontend/package.json +35 -0
  75. package/template/frontend/postcss.config.mjs +7 -0
  76. package/template/frontend/public/favicon.ico +0 -0
  77. package/template/frontend/public/logo.png +0 -0
  78. package/template/frontend/shared/components/require-auth.tsx +25 -0
  79. package/template/frontend/shared/components/ui/.gitkeep +0 -0
  80. package/template/frontend/shared/hooks/.gitkeep +0 -0
  81. package/template/frontend/shared/hooks/use-auth-mutation.ts +37 -0
  82. package/template/frontend/shared/lib/.gitkeep +0 -0
  83. package/template/frontend/shared/lib/api-client.ts +73 -0
  84. package/template/frontend/tsconfig.json +34 -0
  85. package/template/gitignore +35 -0
  86. package/template/package.json +13 -0
  87. package/template/packages/shared/package.json +24 -0
  88. package/template/packages/shared/src/index.ts +2 -0
  89. package/template/packages/shared/src/schemas/auth.schema.ts +20 -0
  90. package/template/packages/shared/src/schemas/user.schema.ts +16 -0
  91. package/template/packages/shared/tsconfig.json +51 -0
  92. package/template/pnpm-workspace.yaml +4 -0
@@ -0,0 +1,31 @@
1
+ import type { MiddlewareHandler } from "hono"
2
+ import { getCookie } from "hono/cookie"
3
+ import { authService } from "../../domains/auth/auth.service.js"
4
+ import type { AuthTokenPayload } from "../../domains/auth/auth.types.js"
5
+
6
+ export type AuthVariables = { user: AuthTokenPayload }
7
+
8
+ /**
9
+ * Authentication guard middleware.
10
+ * Enforces the presence and validity of the short-lived JWT access token in request cookies.
11
+ * Resolves the payload and binds it to Hono context variables (`c.get("user")`).
12
+ *
13
+ * Usage:
14
+ * Apply to any endpoint requiring authentication (e.g. `userRoutes.get("/me", requireAuth, ...)`).
15
+ */
16
+ export const requireAuth: MiddlewareHandler<{ Variables: AuthVariables }> = async (c, next) => {
17
+ // Extract token from cookie (JWT)
18
+ const token = getCookie(c, "access_token")
19
+ if (!token) return c.json({ error: "Unauthorized" }, 401)
20
+
21
+ try {
22
+ // Decode and verify signature & expiration
23
+ const payload = await authService.verifyAccessToken(token)
24
+ c.set("user", payload)
25
+ } catch {
26
+ // Return standard 401 response on invalid / expired signature
27
+ return c.json({ error: "Unauthorized" }, 401)
28
+ }
29
+
30
+ await next()
31
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "strict": true,
6
+ "verbatimModuleSyntax": true,
7
+ "skipLibCheck": true,
8
+ "types": [
9
+ "node"
10
+ ],
11
+ "jsx": "react-jsx",
12
+ "jsxImportSource": "hono/jsx",
13
+ "outDir": "./dist"
14
+ },
15
+ "exclude": ["node_modules", "**/*.test.ts"]
16
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "framework": null,
4
+ "installCommand": "pnpm install && pnpm --filter @restack/shared build",
5
+ "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
6
+ }
@@ -0,0 +1,5 @@
1
+ <!-- BEGIN:nextjs-agent-rules -->
2
+ # This is NOT the Next.js you know
3
+
4
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
+ <!-- END:nextjs-agent-rules -->
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,24 @@
1
+ import Image from "next/image"
2
+ import { LoginForm } from "@/domains/auth/components/login-form"
3
+ import logo from "@/public/logo.png"
4
+
5
+ /**
6
+ * Next.js Router Page: Login
7
+ *
8
+ * Semi-DDD rules:
9
+ * - Files in `frontend/app/` are strictly for layout, routing config, metadata, or entry gates.
10
+ * - Do not implement custom JSX layouts, forms, state, or complex components here.
11
+ * - Always import and delegate rendering to domain feature components (e.g. `<LoginForm />`).
12
+ */
13
+ export default function LoginPage() {
14
+ return (
15
+ <main className="flex min-h-screen flex-col items-center justify-center bg-muted/40 px-4">
16
+ <div className="mb-8 text-center flex flex-col items-center">
17
+ <Image src={logo} alt="Logo" className="w-12 h-auto mb-3 object-contain" priority />
18
+ <h1 className="text-2xl font-semibold tracking-tight">RestackPattern</h1>
19
+ <p className="text-sm text-muted-foreground mt-1">Full-stack monorepo starter</p>
20
+ </div>
21
+ <LoginForm />
22
+ </main>
23
+ )
24
+ }
@@ -0,0 +1,24 @@
1
+ import Image from "next/image"
2
+ import { RegisterForm } from "@/domains/auth/components/register-form"
3
+ import logo from "@/public/logo.png"
4
+
5
+ /**
6
+ * Next.js Router Page: Register
7
+ *
8
+ * Semi-DDD rules:
9
+ * - Files in `frontend/app/` are strictly for layout, routing config, metadata, or entry gates.
10
+ * - Do not implement custom JSX layouts, forms, state, or complex components here.
11
+ * - Always import and delegate rendering to domain feature components (e.g. `<RegisterForm />`).
12
+ */
13
+ export default function RegisterPage() {
14
+ return (
15
+ <main className="flex min-h-screen flex-col items-center justify-center bg-muted/40 px-4">
16
+ <div className="mb-8 text-center flex flex-col items-center">
17
+ <Image src={logo} alt="Logo" className="w-12 h-auto mb-3 object-contain" priority />
18
+ <h1 className="text-2xl font-semibold tracking-tight">RestackPattern</h1>
19
+ <p className="text-sm text-muted-foreground mt-1">Full-stack monorepo starter</p>
20
+ </div>
21
+ <RegisterForm />
22
+ </main>
23
+ )
24
+ }
@@ -0,0 +1,29 @@
1
+ import Link from "next/link"
2
+ import { LogoutButton } from "@/domains/auth/components/logout-button"
3
+ import { Button } from "@/components/ui/button"
4
+ import { RequireAuth } from "@/shared/components/require-auth"
5
+
6
+ /**
7
+ * Next.js Router Page: Dashboard
8
+ *
9
+ * Semi-DDD rules:
10
+ * - Files in `frontend/app/` are strictly for layout, routing config, metadata, or entry gates.
11
+ * - Do not implement custom JSX layouts, forms, state, or complex components here.
12
+ * - Always import and delegate rendering to domain feature components (e.g. `<LogoutButton />`).
13
+ */
14
+ export default function DashboardPage() {
15
+ return (
16
+ <RequireAuth>
17
+ <main className="p-8">
18
+ <div className="flex items-center justify-between">
19
+ <h1 className="text-2xl font-bold">Dashboard</h1>
20
+ <LogoutButton />
21
+ </div>
22
+ <p className="text-muted-foreground mt-2">You are logged in.</p>
23
+ <Button asChild className="mt-6">
24
+ <Link href="/profile">Profile</Link>
25
+ </Button>
26
+ </main>
27
+ </RequireAuth>
28
+ )
29
+ }
@@ -0,0 +1,131 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --font-sans: var(--font-sans);
11
+ --font-mono: var(--font-geist-mono);
12
+ --font-heading: var(--font-sans);
13
+ --color-sidebar-ring: var(--sidebar-ring);
14
+ --color-sidebar-border: var(--sidebar-border);
15
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
16
+ --color-sidebar-accent: var(--sidebar-accent);
17
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
18
+ --color-sidebar-primary: var(--sidebar-primary);
19
+ --color-sidebar-foreground: var(--sidebar-foreground);
20
+ --color-sidebar: var(--sidebar);
21
+ --color-chart-5: var(--chart-5);
22
+ --color-chart-4: var(--chart-4);
23
+ --color-chart-3: var(--chart-3);
24
+ --color-chart-2: var(--chart-2);
25
+ --color-chart-1: var(--chart-1);
26
+ --color-ring: var(--ring);
27
+ --color-input: var(--input);
28
+ --color-border: var(--border);
29
+ --color-destructive: var(--destructive);
30
+ --color-accent-foreground: var(--accent-foreground);
31
+ --color-accent: var(--accent);
32
+ --color-muted-foreground: var(--muted-foreground);
33
+ --color-muted: var(--muted);
34
+ --color-secondary-foreground: var(--secondary-foreground);
35
+ --color-secondary: var(--secondary);
36
+ --color-primary-foreground: var(--primary-foreground);
37
+ --color-primary: var(--primary);
38
+ --color-popover-foreground: var(--popover-foreground);
39
+ --color-popover: var(--popover);
40
+ --color-card-foreground: var(--card-foreground);
41
+ --color-card: var(--card);
42
+ --radius-sm: calc(var(--radius) * 0.6);
43
+ --radius-md: calc(var(--radius) * 0.8);
44
+ --radius-lg: var(--radius);
45
+ --radius-xl: calc(var(--radius) * 1.4);
46
+ --radius-2xl: calc(var(--radius) * 1.8);
47
+ --radius-3xl: calc(var(--radius) * 2.2);
48
+ --radius-4xl: calc(var(--radius) * 2.6);
49
+ }
50
+
51
+ :root {
52
+ --font-sans: var(--font-geist-sans);
53
+ --background: oklch(1 0 0);
54
+ --foreground: oklch(0.145 0 0);
55
+ --card: oklch(1 0 0);
56
+ --card-foreground: oklch(0.145 0 0);
57
+ --popover: oklch(1 0 0);
58
+ --popover-foreground: oklch(0.145 0 0);
59
+ --primary: oklch(0.205 0 0);
60
+ --primary-foreground: oklch(0.985 0 0);
61
+ --secondary: oklch(0.97 0 0);
62
+ --secondary-foreground: oklch(0.205 0 0);
63
+ --muted: oklch(0.97 0 0);
64
+ --muted-foreground: oklch(0.556 0 0);
65
+ --accent: oklch(0.97 0 0);
66
+ --accent-foreground: oklch(0.205 0 0);
67
+ --destructive: oklch(0.577 0.245 27.325);
68
+ --border: oklch(0.922 0 0);
69
+ --input: oklch(0.922 0 0);
70
+ --ring: oklch(0.708 0 0);
71
+ --chart-1: oklch(0.87 0 0);
72
+ --chart-2: oklch(0.556 0 0);
73
+ --chart-3: oklch(0.439 0 0);
74
+ --chart-4: oklch(0.371 0 0);
75
+ --chart-5: oklch(0.269 0 0);
76
+ --radius: 0.625rem;
77
+ --sidebar: oklch(0.985 0 0);
78
+ --sidebar-foreground: oklch(0.145 0 0);
79
+ --sidebar-primary: oklch(0.205 0 0);
80
+ --sidebar-primary-foreground: oklch(0.985 0 0);
81
+ --sidebar-accent: oklch(0.97 0 0);
82
+ --sidebar-accent-foreground: oklch(0.205 0 0);
83
+ --sidebar-border: oklch(0.922 0 0);
84
+ --sidebar-ring: oklch(0.708 0 0);
85
+ }
86
+
87
+ .dark {
88
+ --background: oklch(0.145 0 0);
89
+ --foreground: oklch(0.985 0 0);
90
+ --card: oklch(0.205 0 0);
91
+ --card-foreground: oklch(0.985 0 0);
92
+ --popover: oklch(0.205 0 0);
93
+ --popover-foreground: oklch(0.985 0 0);
94
+ --primary: oklch(0.922 0 0);
95
+ --primary-foreground: oklch(0.205 0 0);
96
+ --secondary: oklch(0.269 0 0);
97
+ --secondary-foreground: oklch(0.985 0 0);
98
+ --muted: oklch(0.269 0 0);
99
+ --muted-foreground: oklch(0.708 0 0);
100
+ --accent: oklch(0.269 0 0);
101
+ --accent-foreground: oklch(0.985 0 0);
102
+ --destructive: oklch(0.704 0.191 22.216);
103
+ --border: oklch(1 0 0 / 10%);
104
+ --input: oklch(1 0 0 / 15%);
105
+ --ring: oklch(0.556 0 0);
106
+ --chart-1: oklch(0.87 0 0);
107
+ --chart-2: oklch(0.556 0 0);
108
+ --chart-3: oklch(0.439 0 0);
109
+ --chart-4: oklch(0.371 0 0);
110
+ --chart-5: oklch(0.269 0 0);
111
+ --sidebar: oklch(0.205 0 0);
112
+ --sidebar-foreground: oklch(0.985 0 0);
113
+ --sidebar-primary: oklch(0.488 0.243 264.376);
114
+ --sidebar-primary-foreground: oklch(0.985 0 0);
115
+ --sidebar-accent: oklch(0.269 0 0);
116
+ --sidebar-accent-foreground: oklch(0.985 0 0);
117
+ --sidebar-border: oklch(1 0 0 / 10%);
118
+ --sidebar-ring: oklch(0.556 0 0);
119
+ }
120
+
121
+ @layer base {
122
+ * {
123
+ @apply border-border outline-ring/50;
124
+ }
125
+ body {
126
+ @apply bg-background text-foreground;
127
+ }
128
+ html {
129
+ @apply font-sans;
130
+ }
131
+ }
Binary file
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const geistSans = Geist({
6
+ variable: "--font-geist-sans",
7
+ subsets: ["latin"],
8
+ });
9
+
10
+ const geistMono = Geist_Mono({
11
+ variable: "--font-geist-mono",
12
+ subsets: ["latin"],
13
+ });
14
+
15
+ export const metadata: Metadata = {
16
+ title: "RestackPattern",
17
+ description: "Full-stack monorepo starter",
18
+ icons: {
19
+ icon: "/favicon.ico",
20
+ },
21
+ };
22
+
23
+ export default function RootLayout({
24
+ children,
25
+ }: Readonly<{
26
+ children: React.ReactNode;
27
+ }>) {
28
+ return (
29
+ <html
30
+ lang="en"
31
+ className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
32
+ >
33
+ <body className="min-h-full flex flex-col">{children}</body>
34
+ </html>
35
+ );
36
+ }
@@ -0,0 +1,33 @@
1
+ import Link from "next/link"
2
+ import Image from "next/image"
3
+ import { Button } from "@/components/ui/button"
4
+ import logo from "@/public/logo.png"
5
+
6
+ /**
7
+ * Next.js Router Page: Landing Page (root)
8
+ *
9
+ * Semi-DDD rules:
10
+ * - Files in `frontend/app/` are strictly for layout, routing config, metadata, or entry gates.
11
+ * - Do not implement custom JSX layouts, forms, state, or complex components here.
12
+ */
13
+ export default function Home() {
14
+ return (
15
+ <main className="flex min-h-screen flex-col items-center justify-center bg-muted/40 px-4">
16
+ <div className="text-center max-w-md">
17
+ <Image src={logo} alt="Logo" className="w-16 h-auto mx-auto mb-4 object-contain" priority />
18
+ <h1 className="text-4xl font-semibold tracking-tight">RestackPattern</h1>
19
+ <p className="mt-3 text-muted-foreground text-lg">
20
+ Full-stack monorepo starter with Next.js, Hono, and Drizzle ORM.
21
+ </p>
22
+ <div className="mt-8 flex gap-3 justify-center">
23
+ <Button asChild>
24
+ <Link href="/login">Login</Link>
25
+ </Button>
26
+ <Button asChild variant="outline">
27
+ <Link href="/register">Register</Link>
28
+ </Button>
29
+ </div>
30
+ </div>
31
+ </main>
32
+ )
33
+ }
@@ -0,0 +1,23 @@
1
+ import { Button } from "@/components/ui/button";
2
+ import { ProfileCard } from "@/domains/user/components/profile-card"
3
+ import Link from "next/link";
4
+
5
+ /**
6
+ * Next.js Router Page: Profile
7
+ *
8
+ * Semi-DDD rules:
9
+ * - Files in `frontend/app/` are strictly for layout, routing config, metadata, or entry gates.
10
+ * - Do not implement custom JSX layouts, forms, state, or complex components here.
11
+ * - Always import and delegate rendering to domain feature components (e.g. `<ProfileCard />`).
12
+ */
13
+ export default function ProfilePage() {
14
+ return (
15
+ <main className="p-8">
16
+ <h1 className="text-2xl font-bold mb-6">Profile</h1>
17
+ <ProfileCard />
18
+ <Button asChild variant="outline" className="mt-4">
19
+ <Link href="/dashboard">Back to Dashboard</Link>
20
+ </Button>
21
+ </main>
22
+ )
23
+ }
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
13
+ outline:
14
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
15
+ secondary:
16
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
17
+ ghost:
18
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
19
+ destructive:
20
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default:
25
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
26
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
29
+ icon: "size-8",
30
+ "icon-xs":
31
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
32
+ "icon-sm":
33
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
34
+ "icon-lg": "size-9",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ }
42
+ )
43
+
44
+ function Button({
45
+ className,
46
+ variant = "default",
47
+ size = "default",
48
+ asChild = false,
49
+ ...props
50
+ }: React.ComponentProps<"button"> &
51
+ VariantProps<typeof buttonVariants> & {
52
+ asChild?: boolean
53
+ }) {
54
+ const Comp = asChild ? Slot.Root : "button"
55
+
56
+ return (
57
+ <Comp
58
+ data-slot="button"
59
+ data-variant={variant}
60
+ data-size={size}
61
+ className={cn(buttonVariants({ variant, size, className }))}
62
+ {...props}
63
+ />
64
+ )
65
+ }
66
+
67
+ export { Button, buttonVariants }
@@ -0,0 +1,103 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn(
15
+ "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="card-title"
40
+ className={cn(
41
+ "font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
50
+ return (
51
+ <div
52
+ data-slot="card-description"
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="card-action"
63
+ className={cn(
64
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="card-content"
76
+ className={cn("px-(--card-spacing)", className)}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="card-footer"
86
+ className={cn(
87
+ "flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ export {
96
+ Card,
97
+ CardHeader,
98
+ CardFooter,
99
+ CardTitle,
100
+ CardAction,
101
+ CardDescription,
102
+ CardContent,
103
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Input }
@@ -0,0 +1,24 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Label as LabelPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Label }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "rtl": false,
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/shared/hooks"
20
+ },
21
+ "menuColor": "default",
22
+ "menuAccent": "subtle",
23
+ "registries": {}
24
+ }