create-pnpm-custom-app 1.0.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 (77) hide show
  1. package/README.md +217 -0
  2. package/bin/cli.js +185 -0
  3. package/package.json +39 -0
  4. package/templates/.github/copilot-instructions.md +184 -0
  5. package/templates/.nvmrc +1 -0
  6. package/templates/.vscode/settings.json +51 -0
  7. package/templates/CONTRIBUTING.md +184 -0
  8. package/templates/LICENSE +21 -0
  9. package/templates/README.md +324 -0
  10. package/templates/apps/api/.env.example +36 -0
  11. package/templates/apps/api/.prettierrc.json +7 -0
  12. package/templates/apps/api/eslint.config.js +17 -0
  13. package/templates/apps/api/gitignore +45 -0
  14. package/templates/apps/api/jest.config.ts +22 -0
  15. package/templates/apps/api/package.json +49 -0
  16. package/templates/apps/api/src/app.ts +121 -0
  17. package/templates/apps/api/src/config/config.ts +38 -0
  18. package/templates/apps/api/src/config/logger.ts +57 -0
  19. package/templates/apps/api/src/db/mongo.ts +30 -0
  20. package/templates/apps/api/src/index.ts +40 -0
  21. package/templates/apps/api/src/middlewares/middleware.ts +75 -0
  22. package/templates/apps/api/src/models/example.model.ts +89 -0
  23. package/templates/apps/api/src/routes/routes.ts +54 -0
  24. package/templates/apps/api/src/schemas/swagger.schema.ts +58 -0
  25. package/templates/apps/api/src/services/example.service.ts +63 -0
  26. package/templates/apps/api/src/tests/health.test.ts +90 -0
  27. package/templates/apps/api/src/tests/helpers/test-helpers.ts +40 -0
  28. package/templates/apps/api/src/tests/mocks/mocks.ts +29 -0
  29. package/templates/apps/api/src/tests/setup.ts +11 -0
  30. package/templates/apps/api/src/types/fastify.d.ts +44 -0
  31. package/templates/apps/api/tsconfig.json +24 -0
  32. package/templates/apps/web/.env.example +25 -0
  33. package/templates/apps/web/.prettierignore +7 -0
  34. package/templates/apps/web/.prettierrc +9 -0
  35. package/templates/apps/web/app/ICONS.md +42 -0
  36. package/templates/apps/web/app/[locale]/(routes)/layout.tsx +13 -0
  37. package/templates/apps/web/app/[locale]/(routes)/page.tsx +49 -0
  38. package/templates/apps/web/app/[locale]/[...not-found]/page.tsx +8 -0
  39. package/templates/apps/web/app/[locale]/layout.tsx +35 -0
  40. package/templates/apps/web/app/[locale]/not-found.tsx +12 -0
  41. package/templates/apps/web/app/components/layout/Footer.component.tsx +30 -0
  42. package/templates/apps/web/app/components/layout/Nav.component.tsx +34 -0
  43. package/templates/apps/web/app/components/ui/README.md +39 -0
  44. package/templates/apps/web/app/components/ui/atoms/README.md +55 -0
  45. package/templates/apps/web/app/components/ui/molecules/README.md +51 -0
  46. package/templates/apps/web/app/globals.css +104 -0
  47. package/templates/apps/web/app/icon.svg +5 -0
  48. package/templates/apps/web/app/layout.tsx +37 -0
  49. package/templates/apps/web/app/manifest.json +22 -0
  50. package/templates/apps/web/app/providers.tsx +25 -0
  51. package/templates/apps/web/app/robots.ts +12 -0
  52. package/templates/apps/web/app/sitemap.ts +18 -0
  53. package/templates/apps/web/eslint.config.mjs +16 -0
  54. package/templates/apps/web/gitignore +56 -0
  55. package/templates/apps/web/hooks/README.md +25 -0
  56. package/templates/apps/web/i18n/config.ts +9 -0
  57. package/templates/apps/web/i18n/request.ts +15 -0
  58. package/templates/apps/web/interfaces/README.md +5 -0
  59. package/templates/apps/web/lib/README.md +45 -0
  60. package/templates/apps/web/lib/utils.ts +18 -0
  61. package/templates/apps/web/messages/en.json +34 -0
  62. package/templates/apps/web/messages/es.json +34 -0
  63. package/templates/apps/web/next.config.ts +50 -0
  64. package/templates/apps/web/package.json +34 -0
  65. package/templates/apps/web/postcss.config.mjs +7 -0
  66. package/templates/apps/web/proxy.ts +17 -0
  67. package/templates/apps/web/public/README.md +7 -0
  68. package/templates/apps/web/tsconfig.json +27 -0
  69. package/templates/apps/web/types/README.md +3 -0
  70. package/templates/docs/README.md +13 -0
  71. package/templates/gitignore-root +51 -0
  72. package/templates/package.json +30 -0
  73. package/templates/packages/shared/eslint.config.js +26 -0
  74. package/templates/packages/shared/package.json +22 -0
  75. package/templates/packages/shared/src/index.ts +39 -0
  76. package/templates/packages/shared/tsconfig.json +19 -0
  77. package/templates/pnpm-workspace.yaml +3 -0
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
2
+ import { buildApp } from '../app.js';
3
+ // import { connectMongo, disconnectMongo } from '../db/mongo.js';
4
+ import type { FastifyInstance } from 'fastify';
5
+
6
+ /**
7
+ * Example Test Suite
8
+ *
9
+ * This file demonstrates how to write tests for your API endpoints.
10
+ *
11
+ * @remarks
12
+ * Testing best practices:
13
+ * - Use descriptive test names
14
+ * - Test both success and error cases
15
+ * - Mock external dependencies
16
+ * - Clean up test data after each test
17
+ * - Use beforeAll/afterAll for setup/teardown
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * describe('User API', () => {
22
+ * let app: FastifyInstance;
23
+ *
24
+ * beforeAll(async () => {
25
+ * await connectMongo(process.env.MONGODB_URI_TEST!);
26
+ * app = await buildApp();
27
+ * });
28
+ *
29
+ * afterAll(async () => {
30
+ * await app.close();
31
+ * await disconnectMongo();
32
+ * });
33
+ *
34
+ * it('should create a new user', async () => {
35
+ * const response = await app.inject({
36
+ * method: 'POST',
37
+ * url: '/users',
38
+ * payload: {
39
+ * email: 'test@example.com',
40
+ * password: 'password123',
41
+ * name: 'Test User',
42
+ * },
43
+ * });
44
+ *
45
+ * expect(response.statusCode).toBe(201);
46
+ * expect(response.json()).toMatchObject({
47
+ * email: 'test@example.com',
48
+ * name: 'Test User',
49
+ * });
50
+ * });
51
+ *
52
+ * it('should return 400 for invalid email', async () => {
53
+ * const response = await app.inject({
54
+ * method: 'POST',
55
+ * url: '/users',
56
+ * payload: {
57
+ * email: 'invalid-email',
58
+ * password: 'password123',
59
+ * },
60
+ * });
61
+ *
62
+ * expect(response.statusCode).toBe(400);
63
+ * });
64
+ * });
65
+ * ```
66
+ */
67
+
68
+ describe('Health Check API', () => {
69
+ let app: FastifyInstance;
70
+
71
+ beforeAll(async () => {
72
+ app = await buildApp();
73
+ });
74
+
75
+ afterAll(async () => {
76
+ await app.close();
77
+ });
78
+
79
+ it('should return health status', async () => {
80
+ const response = await app.inject({
81
+ method: 'GET',
82
+ url: '/health',
83
+ });
84
+
85
+ expect(response.statusCode).toBe(200);
86
+ const body = response.json();
87
+ expect(body).toHaveProperty('status', 'ok');
88
+ expect(body).toHaveProperty('timestamp');
89
+ });
90
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Test Helpers
3
+ *
4
+ * Utility functions to help with testing.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // Create test data
9
+ * export function createTestUser(overrides = {}) {
10
+ * return {
11
+ * email: 'test@example.com',
12
+ * password: 'password123',
13
+ * name: 'Test User',
14
+ * role: 'user',
15
+ * ...overrides,
16
+ * };
17
+ * }
18
+ *
19
+ * // Generate JWT token for testing
20
+ * export function generateTestToken(userId: string, app: FastifyInstance) {
21
+ * return app.jwt.sign({ id: userId });
22
+ * }
23
+ *
24
+ * // Clean up test database
25
+ * export async function clearDatabase() {
26
+ * const collections = mongoose.connection.collections;
27
+ * for (const key in collections) {
28
+ * await collections[key].deleteMany({});
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+
34
+ export function generateRandomEmail(): string {
35
+ return `test-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`;
36
+ }
37
+
38
+ export function sleep(ms: number): Promise<void> {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Test Mocks
3
+ *
4
+ * Mock data and functions for testing.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // Mock user data
9
+ * export const mockUser = {
10
+ * id: '507f1f77bcf86cd799439011',
11
+ * email: 'test@example.com',
12
+ * name: 'Test User',
13
+ * role: 'user',
14
+ * createdAt: new Date('2024-01-01'),
15
+ * updatedAt: new Date('2024-01-01'),
16
+ * };
17
+ *
18
+ * // Mock service response
19
+ * export const mockUserService = {
20
+ * findUserById: jest.fn().mockResolvedValue(mockUser),
21
+ * createUser: jest.fn().mockResolvedValue(mockUser),
22
+ * };
23
+ * ```
24
+ */
25
+
26
+ export const mockHealthResponse = {
27
+ status: 'ok',
28
+ timestamp: '2024-01-01T00:00:00.000Z',
29
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Jest Test Setup
3
+ *
4
+ * This file runs before any tests and sets up the test environment.
5
+ * It configures environment variables needed for testing.
6
+ */
7
+
8
+ // Set test environment variables before any modules are loaded
9
+ process.env.NODE_ENV = 'test';
10
+ process.env.JWT_SECRET = 'test-secret-key-for-testing-only';
11
+ process.env.MONGODB_URI = 'mongodb://localhost:27017/test-db';
@@ -0,0 +1,44 @@
1
+ import '@fastify/jwt';
2
+
3
+ /**
4
+ * Extend Fastify types with custom properties
5
+ *
6
+ * This file augments Fastify's type definitions to include custom properties
7
+ * like user authentication data from JWT.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * declare module '@fastify/jwt' {
12
+ * interface FastifyJWT {
13
+ * user: {
14
+ * id: string;
15
+ * email: string;
16
+ * role: 'user' | 'admin';
17
+ * };
18
+ * }
19
+ * }
20
+ * ```
21
+ *
22
+ * This allows TypeScript to recognize `request.user` in route handlers:
23
+ * ```typescript
24
+ * app.get('/profile', {
25
+ * preHandler: [authenticate],
26
+ * handler: async (request) => {
27
+ * // TypeScript knows request.user has id, email, role
28
+ * const userId = request.user.id;
29
+ * return { userId };
30
+ * },
31
+ * });
32
+ * ```
33
+ */
34
+
35
+ declare module '@fastify/jwt' {
36
+ interface FastifyJWT {
37
+ user: {
38
+ id: string;
39
+ // Add your JWT payload properties here
40
+ // email?: string;
41
+ // role?: string;
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
24
+ }
@@ -0,0 +1,25 @@
1
+ # ==================================================
2
+ # API CONFIGURATION
3
+ # ==================================================
4
+ # URL of the backend API
5
+ # Development: http://localhost:3002
6
+ # Production: https://your-api-domain.com
7
+ NEXT_PUBLIC_API_URL=http://localhost:3002
8
+
9
+ # ==================================================
10
+ # AUTHENTICATION (if using NextAuth or similar)
11
+ # ==================================================
12
+ # NEXTAUTH_URL=http://localhost:3000
13
+ # NEXTAUTH_SECRET=your-secret-key-change-this
14
+
15
+ # ==================================================
16
+ # FEATURE FLAGS
17
+ # ==================================================
18
+ # NEXT_PUBLIC_FEATURE_ANALYTICS=false
19
+
20
+ # ==================================================
21
+ # THIRD-PARTY SERVICES
22
+ # ==================================================
23
+ # Add your API keys and secrets here
24
+ # NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=
25
+ # NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .next
3
+ build
4
+ dist
5
+ coverage
6
+ .turbo
7
+ pnpm-lock.yaml
@@ -0,0 +1,9 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "trailingComma": "es5",
6
+ "printWidth": 100,
7
+ "arrowParens": "always",
8
+ "endOfLine": "lf"
9
+ }
@@ -0,0 +1,42 @@
1
+ # App Icons and Metadata
2
+
3
+ This directory contains the app metadata files required by Next.js.
4
+
5
+ ## Required Icons
6
+
7
+ Replace the default placeholder icons with your own branded images:
8
+
9
+ ### Social Media & SEO Images
10
+
11
+ > The images shown below are NOT included with the boilerplate and must be added for a correct application experience. Maintain sizes.
12
+
13
+ - **opengraph-image.png** (1200 x 630) - Open Graph image for social media sharing
14
+ - **twitter-image.png** (1200 x 600) - Twitter/X card image
15
+
16
+ ### App Icons
17
+
18
+ - **icon.svg** - Vector icon (already included, customize as needed)
19
+
20
+ > The icons shown below are NOT included with the boilerplate and must be added for a correct application experience. Maintain sizes.
21
+
22
+ - **icon.png** (512 x 512) - PNG fallback icon
23
+ - **apple-icon.png** (180 x 180) - Apple touch icon for iOS devices
24
+ - **favicon.ico** (48 x 48) - Browser favicon
25
+
26
+ ## Current Status
27
+
28
+ The project includes `icon.svg` as a placeholder. You should replace it and add the missing images before deploying to production.
29
+
30
+ ## Generating Icons
31
+
32
+ You can use tools like:
33
+
34
+ - [Favicon Generator](https://realfavicongenerator.net/)
35
+ - [PWA Asset Generator](https://github.com/elegantapp/pwa-asset-generator)
36
+ - [Figma Export](https://www.figma.com/) for design assets
37
+
38
+ ## Next.js Metadata
39
+
40
+ These files are automatically detected by Next.js and used in the app metadata. See:
41
+
42
+ - <https://nextjs.org/docs/app/api-reference/file-conventions/metadata>
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { Nav } from '@/app/components/layout/Nav.component';
3
+ import { Footer } from '@/app/components/layout/Footer.component';
4
+
5
+ export default function RoutesLayout({ children }: { children: ReactNode }) {
6
+ return (
7
+ <>
8
+ <Nav />
9
+ <main className="min-h-screen">{children}</main>
10
+ <Footer />
11
+ </>
12
+ );
13
+ }
@@ -0,0 +1,49 @@
1
+ import { useTranslations } from 'next-intl';
2
+ import Link from 'next/link';
3
+
4
+ export default function HomePage() {
5
+ const t = useTranslations('home');
6
+
7
+ return (
8
+ <div className="container mx-auto px-4 py-16">
9
+ <div className="mx-auto max-w-3xl text-center">
10
+ <h1 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
11
+ {t('title')}
12
+ </h1>
13
+ <p className="mt-6 text-lg text-muted-foreground">{t('description')}</p>
14
+
15
+ <div className="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
16
+ <a
17
+ href="https://github.com/{{GITHUB_USER}}/{{PROJECT_NAME}}"
18
+ target="_blank"
19
+ rel="noopener noreferrer"
20
+ className="inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
21
+ >
22
+ View on GitHub
23
+ </a>
24
+ </div>
25
+
26
+ <div className="mt-16 grid gap-8 md:grid-cols-3">
27
+ <div className="rounded-lg border p-6">
28
+ <h3 className="text-lg font-semibold">Next.js 16</h3>
29
+ <p className="mt-2 text-sm text-muted-foreground">
30
+ Latest Next.js with App Router and React Server Components
31
+ </p>
32
+ </div>
33
+ <div className="rounded-lg border p-6">
34
+ <h3 className="text-lg font-semibold">Fastify + MongoDB</h3>
35
+ <p className="mt-2 text-sm text-muted-foreground">
36
+ High-performance API with MongoDB database
37
+ </p>
38
+ </div>
39
+ <div className="rounded-lg border p-6">
40
+ <h3 className="text-lg font-semibold">pnpm Monorepo</h3>
41
+ <p className="mt-2 text-sm text-muted-foreground">
42
+ Efficient workspace management with shared packages
43
+ </p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,8 @@
1
+ export default function NotFoundCatchAll() {
2
+ return (
3
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
4
+ <h1 className="text-4xl font-bold">404</h1>
5
+ <p className="mt-2 text-muted-foreground">Page not found</p>
6
+ </div>
7
+ );
8
+ }
@@ -0,0 +1,35 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { notFound } from 'next/navigation';
3
+ import { NextIntlClientProvider } from 'next-intl';
4
+ import { getMessages } from 'next-intl/server';
5
+ import { locales } from '@/i18n/config';
6
+
7
+ export function generateStaticParams() {
8
+ return locales.map((locale) => ({ locale }));
9
+ }
10
+
11
+ export default async function LocaleLayout({
12
+ children,
13
+ params,
14
+ }: {
15
+ children: ReactNode;
16
+ params: Promise<{ locale: string }>;
17
+ }) {
18
+ // Validate locale
19
+ const resolvedParams = await params;
20
+ if (!locales.includes(resolvedParams.locale as any)) {
21
+ notFound();
22
+ }
23
+
24
+ const messages = await getMessages();
25
+
26
+ return (
27
+ <html lang={resolvedParams.locale}>
28
+ <body>
29
+ <NextIntlClientProvider messages={messages}>
30
+ {children}
31
+ </NextIntlClientProvider>
32
+ </body>
33
+ </html>
34
+ );
35
+ }
@@ -0,0 +1,12 @@
1
+ import { useTranslations } from 'next-intl';
2
+
3
+ export default function NotFound() {
4
+ const t = useTranslations();
5
+
6
+ return (
7
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
8
+ <h1 className="text-4xl font-bold">404</h1>
9
+ <p className="mt-2 text-muted-foreground">Page not found</p>
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,30 @@
1
+ import { useTranslations } from 'next-intl';
2
+ import Link from 'next/link';
3
+
4
+ export function Footer() {
5
+ const t = useTranslations('footer');
6
+ const currentYear = new Date().getFullYear();
7
+
8
+ return (
9
+ <footer className="border-t bg-background">
10
+ <div className="container mx-auto px-4 py-8">
11
+ <div className="flex flex-col items-center justify-between gap-4 md:flex-row">
12
+ <p className="text-sm text-muted-foreground">
13
+ {t('copyright', { year: currentYear })}
14
+ </p>
15
+
16
+ <div className="flex gap-6">
17
+ <a
18
+ href="https://github.com/{{GITHUB_USER}}/{{PROJECT_NAME}}"
19
+ target="_blank"
20
+ rel="noopener noreferrer"
21
+ className="text-sm text-muted-foreground hover:text-foreground"
22
+ >
23
+ GitHub
24
+ </a>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </footer>
29
+ );
30
+ }
@@ -0,0 +1,34 @@
1
+ import { useTranslations } from 'next-intl';
2
+ import Link from 'next/link';
3
+
4
+ export function Nav() {
5
+ const t = useTranslations('nav');
6
+
7
+ return (
8
+ <nav className="sticky top-0 z-50 border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60">
9
+ <div className="container mx-auto flex h-16 items-center justify-between px-4">
10
+ <Link href="/" className="text-xl font-bold">
11
+ {{PROJECT_NAME}}
12
+ </Link>
13
+
14
+ <ul className="flex items-center gap-6">
15
+ <li>
16
+ <Link href="/" className="text-sm font-medium hover:text-primary">
17
+ {t('home')}
18
+ </Link>
19
+ </li>
20
+ <li>
21
+ <Link href="/about" className="text-sm font-medium hover:text-primary">
22
+ {t('about')}
23
+ </Link>
24
+ </li>
25
+ <li>
26
+ <Link href="/contact" className="text-sm font-medium hover:text-primary">
27
+ {t('contact')}
28
+ </Link>
29
+ </li>
30
+ </ul>
31
+ </div>
32
+ </nav>
33
+ );
34
+ }
@@ -0,0 +1,39 @@
1
+ # UI Components
2
+
3
+ This folder contains reusable UI components following the Atomic Design pattern.
4
+
5
+ ## Structure
6
+
7
+ - **atoms/** - Basic building blocks (Button, Input, Typography, etc.)
8
+ - **molecules/** - Combinations of atoms (Card, Modal, Form fields, etc.)
9
+
10
+ ## Guidelines
11
+
12
+ 1. All components should be in TypeScript
13
+ 2. Use Tailwind CSS for styling
14
+ 3. Interactive components need `'use client'` directive
15
+ 4. Add JSDoc comments for documentation
16
+ 5. Export components with `.component.tsx` suffix
17
+
18
+ ## Example Atom Component
19
+
20
+ ```tsx
21
+ // Button.component.tsx
22
+ interface ButtonProps {
23
+ variant?: 'primary' | 'secondary';
24
+ children: React.ReactNode;
25
+ }
26
+
27
+ export function Button({ variant = 'primary', children }: ButtonProps) {
28
+ const variants = {
29
+ primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
30
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
31
+ };
32
+
33
+ return (
34
+ <button className={`rounded-md px-4 py-2 font-medium transition-colors ${variants[variant]}`}>
35
+ {children}
36
+ </button>
37
+ );
38
+ }
39
+ ```
@@ -0,0 +1,55 @@
1
+ # UI Atoms
2
+
3
+ Basic, indivisible UI components (Atomic Design pattern).
4
+
5
+ ## Purpose
6
+
7
+ Atoms are the smallest building blocks of the UI. They cannot be broken down further without losing their meaning.
8
+
9
+ ## Examples
10
+
11
+ - Buttons
12
+ - Input fields
13
+ - Labels
14
+ - Icons
15
+ - Badges
16
+
17
+ ## Component Template
18
+
19
+ ```tsx
20
+ // Button.component.tsx
21
+ import { forwardRef } from 'react';
22
+ import { cn } from '@/lib/utils';
23
+
24
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
25
+ variant?: 'primary' | 'secondary' | 'ghost';
26
+ size?: 'sm' | 'md' | 'lg';
27
+ }
28
+
29
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
30
+ ({ variant = 'primary', size = 'md', className, children, ...props }, ref) => {
31
+ return (
32
+ <button
33
+ ref={ref}
34
+ className={cn(
35
+ 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
36
+ {
37
+ 'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'primary',
38
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/90': variant === 'secondary',
39
+ 'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
40
+ 'h-8 px-3 text-sm': size === 'sm',
41
+ 'h-10 px-4': size === 'md',
42
+ 'h-12 px-6 text-lg': size === 'lg',
43
+ },
44
+ className
45
+ )}
46
+ {...props}
47
+ >
48
+ {children}
49
+ </button>
50
+ );
51
+ }
52
+ );
53
+
54
+ Button.displayName = 'Button';
55
+ ```
@@ -0,0 +1,51 @@
1
+ # UI Molecules
2
+
3
+ Composite UI components made of atoms (Atomic Design pattern).
4
+
5
+ ## Purpose
6
+
7
+ Molecules are groups of atoms that work together as a unit.
8
+
9
+ ## Examples
10
+
11
+ - Form fields (label + input + error message)
12
+ - Search bar (input + button)
13
+ - Card header (title + description)
14
+ - Avatar with name
15
+
16
+ ## Component Template
17
+
18
+ ```tsx
19
+ // FormField.component.tsx
20
+ import { forwardRef } from 'react';
21
+ import { cn } from '@/lib/utils';
22
+
23
+ interface FormFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
24
+ label: string;
25
+ error?: string;
26
+ helperText?: string;
27
+ }
28
+
29
+ export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
30
+ ({ label, error, helperText, className, ...props }, ref) => {
31
+ return (
32
+ <div className="space-y-2">
33
+ <label className="text-sm font-medium">{label}</label>
34
+ <input
35
+ ref={ref}
36
+ className={cn(
37
+ 'w-full rounded-md border px-3 py-2',
38
+ error && 'border-destructive',
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ {error && <p className="text-sm text-destructive">{error}</p>}
44
+ {helperText && <p className="text-sm text-muted-foreground">{helperText}</p>}
45
+ </div>
46
+ );
47
+ }
48
+ );
49
+
50
+ FormField.displayName = 'FormField';
51
+ ```