From 230af7bde53c474c3b4a5080c2e1afd7fda227f4 Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Fri, 30 Jan 2026 09:34:41 +0530 Subject: [PATCH 1/6] jotai and styling components --- src/__tests__/conditional-prompts.test.ts | 101 ++++++ .../integration/template-loading.test.ts | 50 ++- src/__tests__/readme-generator.test.ts | 237 +++++++++++++ src/cli/prompts.ts | 27 +- src/config/defaults.ts | 1 + src/config/schema.ts | 2 +- src/docs/index.ts | 1 + src/docs/readme-generator.ts | 315 ++++++++++++++++++ src/generator/index.ts | 10 +- .../overlays/state/jotai/manifest.json | 16 + .../overlays/state/jotai/src/stores/atoms.ts | 52 +++ .../overlays/state/jotai/src/stores/index.ts | 30 ++ 12 files changed, 829 insertions(+), 13 deletions(-) create mode 100644 src/__tests__/conditional-prompts.test.ts create mode 100644 src/__tests__/readme-generator.test.ts create mode 100644 src/docs/readme-generator.ts create mode 100644 src/templates/overlays/state/jotai/manifest.json create mode 100644 src/templates/overlays/state/jotai/src/stores/atoms.ts create mode 100644 src/templates/overlays/state/jotai/src/stores/index.ts diff --git a/src/__tests__/conditional-prompts.test.ts b/src/__tests__/conditional-prompts.test.ts new file mode 100644 index 0000000..e2d71a3 --- /dev/null +++ b/src/__tests__/conditional-prompts.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; +import { STATE_DESCRIPTIONS, STYLING_DESCRIPTIONS } from '../config/defaults'; +import { StateManagementSchema, StylingSchema } from '../config/schema'; + +/** + * Tests for conditional prompts logic + * These tests verify the configuration and validation for runtime-dependent styling options + */ +describe('Conditional Prompts Configuration', () => { + describe('Styling options', () => { + it('should have all 4 styling options defined', () => { + const stylingValues = StylingSchema.options; + + expect(stylingValues).toContain('css'); + expect(stylingValues).toContain('tailwind'); + expect(stylingValues).toContain('styled-components'); + expect(stylingValues).toContain('css-modules'); + expect(stylingValues).toHaveLength(4); + }); + + it('should have descriptions for all styling options', () => { + expect(STYLING_DESCRIPTIONS.css).toBeDefined(); + expect(STYLING_DESCRIPTIONS.tailwind).toBeDefined(); + expect(STYLING_DESCRIPTIONS['styled-components']).toBeDefined(); + expect(STYLING_DESCRIPTIONS['css-modules']).toBeDefined(); + }); + + it('Vite should support all 4 styling options', () => { + // For Vite, all 4 options should be valid + const viteOptions = ['tailwind', 'styled-components', 'css-modules', 'css']; + + viteOptions.forEach((option) => { + const result = StylingSchema.safeParse(option); + expect(result.success).toBe(true); + }); + }); + + it('Next.js should use tailwind (auto-selected)', () => { + // For Next.js, tailwind is auto-selected + const nextjsDefault = 'tailwind'; + const result = StylingSchema.safeParse(nextjsDefault); + + expect(result.success).toBe(true); + }); + }); + + describe('State management options', () => { + it('should have all state management options including jotai', () => { + const stateValues = StateManagementSchema.options; + + expect(stateValues).toContain('none'); + expect(stateValues).toContain('redux'); + expect(stateValues).toContain('zustand'); + expect(stateValues).toContain('jotai'); + expect(stateValues).toHaveLength(4); + }); + + it('should have descriptions for all state management options', () => { + expect(STATE_DESCRIPTIONS.none).toBeDefined(); + expect(STATE_DESCRIPTIONS.redux).toBeDefined(); + expect(STATE_DESCRIPTIONS.zustand).toBeDefined(); + expect(STATE_DESCRIPTIONS.jotai).toBeDefined(); + }); + + it('should validate jotai as a valid state management option', () => { + const result = StateManagementSchema.safeParse('jotai'); + expect(result.success).toBe(true); + }); + }); + + describe('Styling choices logic', () => { + /** + * Helper to get styling choices based on runtime + */ + function getStylingChoicesForRuntime(runtime: 'vite' | 'nextjs'): string[] { + if (runtime === 'vite') { + return ['tailwind', 'styled-components', 'css-modules', 'css']; + } + // Next.js auto-selects tailwind + return ['tailwind']; + } + + it('should return 4 styling options for Vite', () => { + const choices = getStylingChoicesForRuntime('vite'); + + expect(choices).toHaveLength(4); + expect(choices).toContain('tailwind'); + expect(choices).toContain('styled-components'); + expect(choices).toContain('css-modules'); + expect(choices).toContain('css'); + }); + + it('should return only tailwind for Next.js', () => { + const choices = getStylingChoicesForRuntime('nextjs'); + + expect(choices).toHaveLength(1); + expect(choices).toContain('tailwind'); + }); + }); +}); + diff --git a/src/__tests__/integration/template-loading.test.ts b/src/__tests__/integration/template-loading.test.ts index 1c03ce0..b509719 100644 --- a/src/__tests__/integration/template-loading.test.ts +++ b/src/__tests__/integration/template-loading.test.ts @@ -47,7 +47,7 @@ describe('TemplateRegistry', () => { expect(template.manifest.devDependencies).toHaveProperty('tailwindcss'); }); - it.skip('should load css-modules styling template', () => { + it('should load css-modules styling template', () => { const template = registry.loadAndRegister('styling/css-modules'); expect(template).toBeDefined(); @@ -70,6 +70,22 @@ describe('TemplateRegistry', () => { expect(template.manifest.dependencies).toHaveProperty('@reduxjs/toolkit'); }); + it('should load jotai state template', () => { + const template = registry.loadAndRegister('state/jotai'); + + expect(template).toBeDefined(); + expect(template.manifest).toBeDefined(); + expect(template.manifest.dependencies).toHaveProperty('jotai'); + }); + + it('should load styled-components styling template', () => { + const template = registry.loadAndRegister('styling/styled-components'); + + expect(template).toBeDefined(); + expect(template.manifest).toBeDefined(); + expect(template.manifest.dependencies).toHaveProperty('styled-components'); + }); + it('should load vitest testing template', () => { const template = registry.loadAndRegister('testing/vitest'); @@ -151,6 +167,32 @@ describe('TemplateRegistry', () => { expect(templates.some(t => t.path === 'state/redux')).toBe(true); }); + it('should load templates for Vite + Jotai config', () => { + const templates = registry.loadTemplatesForConfig({ + runtime: 'vite', + styling: { solution: 'styled-components' }, + stateManagement: 'jotai', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + expect(templates.some(t => t.path === 'runtime/vite')).toBe(true); + expect(templates.some(t => t.path === 'state/jotai')).toBe(true); + expect(templates.some(t => t.path === 'styling/styled-components')).toBe(true); + }); + + it('should load templates for Vite + CSS Modules config', () => { + const templates = registry.loadTemplatesForConfig({ + runtime: 'vite', + styling: { solution: 'css-modules' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + expect(templates.some(t => t.path === 'styling/css-modules')).toBe(true); + }); + it('should load testing templates when testing is enabled', () => { const templates = registry.loadTemplatesForConfig({ runtime: 'vite', @@ -279,9 +321,11 @@ describe('TemplateRegistry', () => { 'runtime/vite', 'runtime/nextjs', 'styling/tailwind', - // 'styling/css-modules', // Skipped - not available in CLI + 'styling/css-modules', + 'styling/styled-components', 'state/zustand', 'state/redux', + 'state/jotai', 'testing/vitest', 'testing/jest', 'testing/playwright', @@ -301,6 +345,8 @@ describe('TemplateRegistry', () => { const templatePaths = [ 'state/zustand', 'state/redux', + 'state/jotai', + 'styling/styled-components', 'testing/vitest', 'testing/playwright', 'features/tanstack-query', diff --git a/src/__tests__/readme-generator.test.ts b/src/__tests__/readme-generator.test.ts new file mode 100644 index 0000000..9dfaee9 --- /dev/null +++ b/src/__tests__/readme-generator.test.ts @@ -0,0 +1,237 @@ +import { describe, expect, it } from 'vitest'; +import { generateReadme } from '../docs/readme-generator'; +import { DEFAULT_CONFIG, type ProjectConfig } from '../config/schema'; + +describe('generateReadme', () => { + const createConfig = (overrides: Partial = {}): ProjectConfig => ({ + ...DEFAULT_CONFIG, + name: 'test-app', + path: './test-app', + ...overrides, + }); + + describe('basic structure', () => { + it('should include project name as title', () => { + const config = createConfig({ name: 'my-awesome-app' }); + const readme = generateReadme(config); + + expect(readme).toContain('# my-awesome-app'); + }); + + it('should include getting started section', () => { + const readme = generateReadme(createConfig()); + + expect(readme).toContain('## Getting Started'); + expect(readme).toContain('## Prerequisites'); + expect(readme).toContain('## Available Scripts'); + }); + + it('should include project structure section', () => { + const readme = generateReadme(createConfig()); + + expect(readme).toContain('## Project Structure'); + expect(readme).toContain('src/'); + expect(readme).toContain('components/'); + }); + + it('should include tech stack section', () => { + const readme = generateReadme(createConfig()); + + expect(readme).toContain('## Tech Stack'); + }); + + it('should include documentation links', () => { + const readme = generateReadme(createConfig()); + + expect(readme).toContain('## Documentation'); + }); + }); + + describe('badges', () => { + it('should include Vite badge for Vite runtime', () => { + const config = createConfig({ runtime: 'vite' }); + const readme = generateReadme(config); + + expect(readme).toContain('Vite'); + expect(readme).toContain('img.shields.io/badge'); + }); + + it('should include Next.js badge for Next.js runtime', () => { + const config = createConfig({ runtime: 'nextjs' }); + const readme = generateReadme(config); + + expect(readme).toContain('Next.js'); + }); + + it('should include TypeScript badge when using TypeScript', () => { + const config = createConfig({ language: 'typescript' }); + const readme = generateReadme(config); + + expect(readme).toContain('TypeScript'); + }); + + it('should include styling badge', () => { + const config = createConfig({ styling: { solution: 'tailwind' } }); + const readme = generateReadme(config); + + expect(readme).toContain('Tailwind'); + }); + + it('should include state management badge when not none', () => { + const config = createConfig({ stateManagement: 'zustand' }); + const readme = generateReadme(config); + + expect(readme).toContain('Zustand'); + }); + + it('should include jotai badge when using jotai', () => { + const config = createConfig({ stateManagement: 'jotai' }); + const readme = generateReadme(config); + + expect(readme).toContain('Jotai'); + }); + + it('should include TanStack Query badge when data fetching enabled', () => { + const config = createConfig({ dataFetching: { enabled: true, library: 'tanstack-query' } }); + const readme = generateReadme(config); + + expect(readme).toContain('TanStack'); + }); + }); + + describe('package manager commands', () => { + it('should use npm commands for npm package manager', () => { + const config = createConfig({ packageManager: 'npm' }); + const readme = generateReadme(config); + + expect(readme).toContain('npm install'); + expect(readme).toContain('npm run dev'); + }); + + it('should use yarn commands for yarn package manager', () => { + const config = createConfig({ packageManager: 'yarn' }); + const readme = generateReadme(config); + + expect(readme).toContain('yarn'); + expect(readme).toContain('yarn dev'); + }); + + it('should use pnpm commands for pnpm package manager', () => { + const config = createConfig({ packageManager: 'pnpm' }); + const readme = generateReadme(config); + + expect(readme).toContain('pnpm install'); + expect(readme).toContain('pnpm dev'); + }); + }); + + describe('runtime-specific content', () => { + it('should show correct port for Vite (5173)', () => { + const config = createConfig({ runtime: 'vite' }); + const readme = generateReadme(config); + + expect(readme).toContain('localhost:5173'); + }); + + it('should show correct port for Next.js (3000)', () => { + const config = createConfig({ runtime: 'nextjs' }); + const readme = generateReadme(config); + + expect(readme).toContain('localhost:3000'); + }); + + it('should include Vite documentation link for Vite projects', () => { + const config = createConfig({ runtime: 'vite' }); + const readme = generateReadme(config); + + expect(readme).toContain('vitejs.dev'); + }); + + it('should include Next.js documentation link for Next.js projects', () => { + const config = createConfig({ runtime: 'nextjs' }); + const readme = generateReadme(config); + + expect(readme).toContain('nextjs.org'); + }); + }); + + describe('testing scripts', () => { + it('should include test scripts when testing is enabled', () => { + const config = createConfig({ + testing: { + enabled: true, + unit: { enabled: true, runner: 'vitest' }, + component: { enabled: true, library: 'testing-library' }, + e2e: { enabled: true, runner: 'playwright' }, + }, + }); + const readme = generateReadme(config); + + expect(readme).toContain('test'); + expect(readme).toContain('test:watch'); + expect(readme).toContain('test:e2e'); + }); + + it('should not include e2e scripts when e2e is disabled', () => { + const config = createConfig({ + testing: { + enabled: true, + unit: { enabled: true, runner: 'vitest' }, + component: { enabled: true, library: 'testing-library' }, + e2e: { enabled: false, runner: 'none' }, + }, + }); + const readme = generateReadme(config); + + expect(readme).toContain('test'); + expect(readme).not.toContain('test:e2e'); + }); + }); + + describe('styling options', () => { + it('should include styled-components documentation link', () => { + const config = createConfig({ styling: { solution: 'styled-components' } }); + const readme = generateReadme(config); + + expect(readme).toContain('styled-components.com'); + }); + + it('should include CSS Modules documentation link', () => { + const config = createConfig({ styling: { solution: 'css-modules' } }); + const readme = generateReadme(config); + + expect(readme).toContain('css-modules'); + }); + + it('should include Tailwind documentation link', () => { + const config = createConfig({ styling: { solution: 'tailwind' } }); + const readme = generateReadme(config); + + expect(readme).toContain('tailwindcss.com'); + }); + }); + + describe('state management documentation links', () => { + it('should include Redux Toolkit documentation link', () => { + const config = createConfig({ stateManagement: 'redux' }); + const readme = generateReadme(config); + + expect(readme).toContain('redux-toolkit.js.org'); + }); + + it('should include Zustand documentation link', () => { + const config = createConfig({ stateManagement: 'zustand' }); + const readme = generateReadme(config); + + expect(readme).toContain('zustand-demo.pmnd.rs'); + }); + + it('should include Jotai documentation link', () => { + const config = createConfig({ stateManagement: 'jotai' }); + const readme = generateReadme(config); + + expect(readme).toContain('jotai.org'); + }); + }); +}); + diff --git a/src/cli/prompts.ts b/src/cli/prompts.ts index 44ef82d..197deb7 100644 --- a/src/cli/prompts.ts +++ b/src/cli/prompts.ts @@ -59,19 +59,32 @@ export async function promptForProjectDetails( ], })) as 'javascript' | 'typescript'; - const styling = (await select({ - message: 'Styling solution:', - choices: [ - { name: STYLING_DESCRIPTIONS.tailwind, value: 'tailwind' }, - { name: 'None - Plain CSS', value: 'css' }, - ], - })) as string; + // Styling options are conditional based on runtime + // Vite: 4 options (tailwind, styled-components, css-modules, css) + // Next.js: auto-select tailwind (best practice for App Router) + let styling: string; + if (runtime === 'vite') { + styling = (await select({ + message: 'Styling solution:', + choices: [ + { name: STYLING_DESCRIPTIONS.tailwind, value: 'tailwind' }, + { name: STYLING_DESCRIPTIONS['styled-components'], value: 'styled-components' }, + { name: STYLING_DESCRIPTIONS['css-modules'], value: 'css-modules' }, + { name: STYLING_DESCRIPTIONS.css, value: 'css' }, + ], + })) as string; + } else { + // Next.js - auto-select tailwind + styling = 'tailwind'; + console.log(' ✓ Styling: Tailwind CSS (recommended for Next.js)'); + } const stateManagement = (await select({ message: 'State management:', choices: [ { name: STATE_DESCRIPTIONS.none, value: 'none' }, { name: STATE_DESCRIPTIONS.zustand, value: 'zustand' }, + { name: STATE_DESCRIPTIONS.jotai, value: 'jotai' }, { name: STATE_DESCRIPTIONS.redux, value: 'redux' }, ], })) as string; diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 2dc288d..eb8d5c2 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -18,6 +18,7 @@ export const STATE_DESCRIPTIONS: Record = { none: 'No state management', redux: 'Redux Toolkit - Predictable state container', zustand: 'Zustand - Lightweight state management', + jotai: 'Jotai - Primitive and flexible state management', }; export const TEST_RUNNER_DESCRIPTIONS: Record = { diff --git a/src/config/schema.ts b/src/config/schema.ts index ccd3485..a1d1199 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -14,7 +14,7 @@ export type Language = z.infer; export const StylingSchema = z.enum(['css', 'tailwind', 'styled-components', 'css-modules']); export type Styling = z.infer; -export const StateManagementSchema = z.enum(['none', 'redux', 'zustand']); +export const StateManagementSchema = z.enum(['none', 'redux', 'zustand', 'jotai']); export type StateManagement = z.infer; export const PackageManagerSchema = z.enum(['npm', 'yarn', 'pnpm']); diff --git a/src/docs/index.ts b/src/docs/index.ts index dc0f8c3..25fd1c2 100644 --- a/src/docs/index.ts +++ b/src/docs/index.ts @@ -1,2 +1,3 @@ export * from './architecture-generator.js'; +export * from './readme-generator.js'; diff --git a/src/docs/readme-generator.ts b/src/docs/readme-generator.ts new file mode 100644 index 0000000..79dc7b6 --- /dev/null +++ b/src/docs/readme-generator.ts @@ -0,0 +1,315 @@ +import type { ProjectConfig } from '../config/schema.js'; + +/** + * Generate badge URLs for tech stack + */ +function generateBadges(config: ProjectConfig): string { + const badges: string[] = []; + + // Runtime badge + if (config.runtime === 'vite') { + badges.push('![Vite](https://img.shields.io/badge/Vite-646CFF?style=flat&logo=vite&logoColor=white)'); + } else { + badges.push('![Next.js](https://img.shields.io/badge/Next.js-000000?style=flat&logo=nextdotjs&logoColor=white)'); + } + + // Language badge + if (config.language === 'typescript') { + badges.push('![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)'); + } else { + badges.push('![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=flat&logo=javascript&logoColor=black)'); + } + + // Styling badge + const stylingBadges: Record = { + tailwind: '![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-06B6D4?style=flat&logo=tailwindcss&logoColor=white)', + 'styled-components': '![styled-components](https://img.shields.io/badge/styled--components-DB7093?style=flat&logo=styled-components&logoColor=white)', + 'css-modules': '![CSS Modules](https://img.shields.io/badge/CSS_Modules-1572B6?style=flat&logo=css3&logoColor=white)', + css: '![CSS](https://img.shields.io/badge/CSS-1572B6?style=flat&logo=css3&logoColor=white)', + }; + if (stylingBadges[config.styling.solution]) { + badges.push(stylingBadges[config.styling.solution]); + } + + // State management badge + const stateBadges: Record = { + redux: '![Redux](https://img.shields.io/badge/Redux-764ABC?style=flat&logo=redux&logoColor=white)', + zustand: '![Zustand](https://img.shields.io/badge/Zustand-443E38?style=flat&logo=react&logoColor=white)', + jotai: '![Jotai](https://img.shields.io/badge/Jotai-000000?style=flat&logo=react&logoColor=white)', + }; + if (config.stateManagement !== 'none' && stateBadges[config.stateManagement]) { + badges.push(stateBadges[config.stateManagement]); + } + + // Data fetching badge + if (config.dataFetching.enabled) { + badges.push('![TanStack Query](https://img.shields.io/badge/TanStack_Query-FF4154?style=flat&logo=reactquery&logoColor=white)'); + } + + // Testing badges + if (config.testing.enabled) { + const testBadges: Record = { + vitest: '![Vitest](https://img.shields.io/badge/Vitest-6E9F18?style=flat&logo=vitest&logoColor=white)', + jest: '![Jest](https://img.shields.io/badge/Jest-C21325?style=flat&logo=jest&logoColor=white)', + }; + if (testBadges[config.testing.unit.runner]) { + badges.push(testBadges[config.testing.unit.runner]); + } + + if (config.testing.e2e.enabled) { + const e2eBadges: Record = { + playwright: '![Playwright](https://img.shields.io/badge/Playwright-2EAD33?style=flat&logo=playwright&logoColor=white)', + cypress: '![Cypress](https://img.shields.io/badge/Cypress-17202C?style=flat&logo=cypress&logoColor=white)', + }; + if (e2eBadges[config.testing.e2e.runner]) { + badges.push(e2eBadges[config.testing.e2e.runner]); + } + } + } + + return badges.join(' '); +} + +/** + * Get package manager commands + */ +function getPackageManagerCommands(pm: string): { install: string; dev: string; build: string; test: string } { + const commands: Record = { + npm: { + install: 'npm install', + dev: 'npm run dev', + build: 'npm run build', + test: 'npm run test', + }, + yarn: { + install: 'yarn', + dev: 'yarn dev', + build: 'yarn build', + test: 'yarn test', + }, + pnpm: { + install: 'pnpm install', + dev: 'pnpm dev', + build: 'pnpm build', + test: 'pnpm test', + }, + }; + return commands[pm] || commands.npm; +} + +/** + * Generate scripts table based on config + */ +function generateScriptsTable(config: ProjectConfig): string { + const pm = config.packageManager; + const runCmd = pm === 'npm' ? 'npm run' : pm; + + const scripts: Array<{ command: string; description: string }> = [ + { command: `${runCmd} dev`, description: 'Start development server' }, + { command: `${runCmd} build`, description: 'Build for production' }, + { command: `${runCmd} preview`, description: 'Preview production build' }, + ]; + + if (config.testing.enabled) { + scripts.push({ command: `${runCmd} test`, description: 'Run unit tests' }); + scripts.push({ command: `${runCmd} test:watch`, description: 'Run tests in watch mode' }); + scripts.push({ command: `${runCmd} test:coverage`, description: 'Run tests with coverage' }); + + if (config.testing.e2e.enabled && config.testing.e2e.runner !== 'none') { + scripts.push({ command: `${runCmd} test:e2e`, description: 'Run E2E tests' }); + } + } + + if (config.linting.prettier) { + scripts.push({ command: `${runCmd} format`, description: 'Format code with Prettier' }); + } + + scripts.push({ command: `${runCmd} lint`, description: 'Lint code' }); + + const tableRows = scripts + .map((s) => `| \`${s.command}\` | ${s.description} |`) + .join('\n'); + + return `| Command | Description | +|---------|-------------| +${tableRows}`; +} + +/** + * Generate project structure section + */ +function generateProjectStructure(config: ProjectConfig): string { + const isNextjs = config.runtime === 'nextjs'; + + let structure = `\`\`\` +${config.name}/ +├── src/ +│ ├── components/ # Reusable UI components +│ │ └── ui/ # Base UI primitives +│ ├── features/ # Feature-based modules +│ ├── hooks/ # Custom React hooks`; + + if (config.dataFetching.enabled) { + structure += ` +│ ├── lib/ # Third-party library configs`; + } + + if (config.stateManagement !== 'none') { + structure += ` +│ ├── stores/ # State management stores`; + } + + structure += ` +│ ├── styles/ # Global styles +│ └── types/ # TypeScript type definitions`; + + if (isNextjs) { + structure += ` +│ └── app/ # Next.js App Router pages`; + } + + if (config.testing.enabled && config.testing.e2e.enabled) { + structure += ` +├── tests/ # E2E tests`; + } + + structure += ` +├── public/ # Static assets +└── [config files] # Configuration files +\`\`\``; + + return structure; +} + +/** + * Generate documentation links based on config + */ +function generateDocLinks(config: ProjectConfig): string { + const links: string[] = []; + + if (config.runtime === 'vite') { + links.push('- [Vite Documentation](https://vitejs.dev/)'); + links.push('- [React Router](https://reactrouter.com/)'); + } else { + links.push('- [Next.js Documentation](https://nextjs.org/docs)'); + } + + links.push('- [React Documentation](https://react.dev/)'); + + if (config.language === 'typescript') { + links.push('- [TypeScript Documentation](https://www.typescriptlang.org/docs/)'); + } + + const stylingDocs: Record = { + tailwind: '- [Tailwind CSS Documentation](https://tailwindcss.com/docs)', + 'styled-components': '- [styled-components Documentation](https://styled-components.com/docs)', + 'css-modules': '- [CSS Modules](https://github.com/css-modules/css-modules)', + }; + if (stylingDocs[config.styling.solution]) { + links.push(stylingDocs[config.styling.solution]); + } + + const stateDocs: Record = { + redux: '- [Redux Toolkit Documentation](https://redux-toolkit.js.org/)', + zustand: '- [Zustand Documentation](https://zustand-demo.pmnd.rs/)', + jotai: '- [Jotai Documentation](https://jotai.org/)', + }; + if (config.stateManagement !== 'none' && stateDocs[config.stateManagement]) { + links.push(stateDocs[config.stateManagement]); + } + + if (config.dataFetching.enabled) { + links.push('- [TanStack Query Documentation](https://tanstack.com/query/latest)'); + } + + if (config.testing.enabled) { + const testDocs: Record = { + vitest: '- [Vitest Documentation](https://vitest.dev/)', + jest: '- [Jest Documentation](https://jestjs.io/)', + }; + if (testDocs[config.testing.unit.runner]) { + links.push(testDocs[config.testing.unit.runner]); + } + + links.push('- [Testing Library](https://testing-library.com/docs/react-testing-library/intro/)'); + + if (config.testing.e2e.enabled) { + const e2eDocs: Record = { + playwright: '- [Playwright Documentation](https://playwright.dev/)', + cypress: '- [Cypress Documentation](https://docs.cypress.io/)', + }; + if (e2eDocs[config.testing.e2e.runner]) { + links.push(e2eDocs[config.testing.e2e.runner]); + } + } + } + + return links.join('\n'); +} + +/** + * Generate a dynamic README.md for the project + */ +export function generateReadme(config: ProjectConfig): string { + const badges = generateBadges(config); + const commands = getPackageManagerCommands(config.packageManager); + const scriptsTable = generateScriptsTable(config); + const projectStructure = generateProjectStructure(config); + const docLinks = generateDocLinks(config); + + const runtimeName = config.runtime === 'vite' ? 'Vite' : 'Next.js'; + const description = `A production-ready React application built with ${runtimeName}, scaffolded by create-react-forge.`; + + return `# ${config.name} + +${badges} + +${description} + +## Prerequisites + +- Node.js 18.x or higher +- ${config.packageManager} + +## Getting Started + +1. Install dependencies: + +\`\`\`bash +${commands.install} +\`\`\` + +2. Start the development server: + +\`\`\`bash +${commands.dev} +\`\`\` + +3. Open [http://localhost:${config.runtime === 'nextjs' ? '3000' : '5173'}](http://localhost:${config.runtime === 'nextjs' ? '3000' : '5173'}) in your browser. + +## Available Scripts + +${scriptsTable} + +## Project Structure + +${projectStructure} + +## Tech Stack + +- **Runtime**: ${runtimeName} +- **Language**: ${config.language === 'typescript' ? 'TypeScript' : 'JavaScript'} +- **Styling**: ${config.styling.solution} +${config.stateManagement !== 'none' ? `- **State Management**: ${config.stateManagement}\n` : ''}\ +${config.dataFetching.enabled ? `- **Data Fetching**: TanStack Query\n` : ''}\ +${config.testing.enabled ? `- **Testing**: ${config.testing.unit.runner}${config.testing.e2e.enabled ? ` + ${config.testing.e2e.runner}` : ''}\n` : ''} +## Documentation + +${docLinks} + +## License + +MIT +`; +} + diff --git a/src/generator/index.ts b/src/generator/index.ts index 7249386..0a6c13a 100644 --- a/src/generator/index.ts +++ b/src/generator/index.ts @@ -1,11 +1,11 @@ +import chalk from 'chalk'; import { execSync } from 'child_process'; import { existsSync } from 'fs'; import ora from 'ora'; -import chalk from 'chalk'; -import type { ProjectConfig } from '../config/schema.js'; import { ProjectAssembler } from '../assembler/index.js'; +import type { ProjectConfig } from '../config/schema.js'; +import { generateArchitectureDoc, generateReadme } from '../docs/index.js'; import { TemplateRegistry } from '../templates/registry.js'; -import { generateArchitectureDoc } from '../docs/architecture-generator.js'; /** * Project generation result @@ -77,6 +77,10 @@ export class ProjectGenerator { const archDoc = generateArchitectureDoc(this.config); this.assembler.addFile('ARCHITECTURE.md', archDoc); + // Add README Documentation + const readmeDoc = generateReadme(this.config); + this.assembler.addFile('README.md', readmeDoc); + // Step 4: Merge dependencies const { dependencies, devDependencies, scripts } = this.registry.getMergedDependencies(); this.assembler.mergeTemplateDeps({ dependencies, devDependencies, scripts }); diff --git a/src/templates/overlays/state/jotai/manifest.json b/src/templates/overlays/state/jotai/manifest.json new file mode 100644 index 0000000..36712ab --- /dev/null +++ b/src/templates/overlays/state/jotai/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "state-jotai", + "version": "1.0.0", + "description": "Jotai state management with primitive and flexible atoms", + "compatibleWith": ["runtime-vite", "runtime-nextjs"], + "dependencies": { + "jotai": "^2.10.0" + }, + "devDependencies": {}, + "scripts": {}, + "filePatterns": { + "include": ["**/*"], + "exclude": ["manifest.json"] + } +} + diff --git a/src/templates/overlays/state/jotai/src/stores/atoms.ts b/src/templates/overlays/state/jotai/src/stores/atoms.ts new file mode 100644 index 0000000..106399a --- /dev/null +++ b/src/templates/overlays/state/jotai/src/stores/atoms.ts @@ -0,0 +1,52 @@ +import { atom } from 'jotai'; + +/** + * Auth atoms for user authentication state + */ +export interface User { + id: string; + email: string; + name: string; +} + +export const userAtom = atom(null); +export const isAuthenticatedAtom = atom((get) => get(userAtom) !== null); + +/** + * Notification atoms for app-wide notifications + */ +export interface Notification { + id: string; + type: 'success' | 'error' | 'warning' | 'info'; + message: string; + title?: string; +} + +export const notificationsAtom = atom([]); + +// Derived atom to add a notification +export const addNotificationAtom = atom( + null, + (get, set, notification: Omit) => { + const id = crypto.randomUUID(); + set(notificationsAtom, [...get(notificationsAtom), { ...notification, id }]); + } +); + +// Derived atom to remove a notification +export const removeNotificationAtom = atom( + null, + (get, set, id: string) => { + set( + notificationsAtom, + get(notificationsAtom).filter((n) => n.id !== id) + ); + } +); + +/** + * Theme atom for dark/light mode + */ +export type Theme = 'light' | 'dark' | 'system'; +export const themeAtom = atom('system'); + diff --git a/src/templates/overlays/state/jotai/src/stores/index.ts b/src/templates/overlays/state/jotai/src/stores/index.ts new file mode 100644 index 0000000..c7e3cdd --- /dev/null +++ b/src/templates/overlays/state/jotai/src/stores/index.ts @@ -0,0 +1,30 @@ +/** + * Jotai Store Exports + * + * This file exports all atoms for easy importing throughout the application. + * Import atoms from this file to use Jotai state management. + * + * @example + * import { userAtom, notificationsAtom } from '@/stores'; + * import { useAtom, useAtomValue, useSetAtom } from 'jotai'; + * + * // Read and write + * const [user, setUser] = useAtom(userAtom); + * + * // Read only + * const notifications = useAtomValue(notificationsAtom); + * + * // Write only + * const addNotification = useSetAtom(addNotificationAtom); + */ + +export { + addNotificationAtom, isAuthenticatedAtom, + // Notification atoms + notificationsAtom, removeNotificationAtom, + // Theme atom + themeAtom, + // Auth atoms + userAtom, type Notification, type Theme, type User +} from './atoms.js'; + From f2360e4c3b0cdf583e4a6e322a136da03cfb063a Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Fri, 30 Jan 2026 09:59:45 +0530 Subject: [PATCH 2/6] update docs --- ARCHITECTURE.md | 29 ++++++++++++++++++++++------- README.md | 9 +++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a009332..fba9373 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -47,7 +47,7 @@ create-react-forge is a modular, layered CLI tool for scaffolding production-rea │ ┌────────────────────▼────────────────────────────────┐ │ Docs Layer │ - │ (auto-generates ARCHITECTURE.md for projects) │ + │ (auto-generates README.md & ARCHITECTURE.md) │ │ Location: src/docs/ │ └─────────────────────────────────────────────────────┘ ``` @@ -111,7 +111,7 @@ interface ProjectConfig { runtime: 'vite' | 'nextjs'; language: 'javascript' | 'typescript'; styling: { solution: 'css' | 'tailwind' | 'styled-components' | 'css-modules' }; - stateManagement: 'none' | 'redux' | 'zustand'; + stateManagement: 'none' | 'redux' | 'zustand' | 'jotai'; testing: TestingConfig; dataFetching: DataFetchingConfig; linting: LintingConfig; @@ -144,7 +144,7 @@ interface ProjectConfig { 1. Check if directory exists → fail if yes 2. Load templates based on config 3. Merge all template files -4. Generate ARCHITECTURE.md for the project +4. Generate README.md and ARCHITECTURE.md for the project 5. Merge dependencies and scripts 6. Write files to disk 7. Initialize git (optional) @@ -184,7 +184,8 @@ src/templates/overlays/ │ └── styled-components/ # styled-components setup ├── state/ │ ├── redux/ # Redux Toolkit store structure -│ └── zustand/ # Zustand store setup +│ ├── zustand/ # Zustand store setup +│ └── jotai/ # Jotai atomic state management ├── features/ │ └── tanstack-query/ # TanStack Query + hooks pattern ├── testing/ @@ -221,7 +222,7 @@ interface TemplateManifest { 1. **Base** — Core React files (components, hooks, lib, types) 2. **Runtime** — Vite or Next.js specific configs 3. **Styling** — Tailwind/CSS/Styled Components setup -4. **State** — Redux/Zustand store setup +4. **State** — Redux/Zustand/Jotai store setup 5. **Features** — TanStack Query, etc. 6. **Testing** — Vitest/Jest + RTL + Playwright @@ -294,6 +295,7 @@ interface TemplateManifest { '@reduxjs/toolkit': '^2.5.0', 'react-redux': '^9.2.0', 'zustand': '^5.0.3', + 'jotai': '^2.10.0', // Data Fetching '@tanstack/react-query': '^5.62.10', @@ -371,6 +373,12 @@ interface PluginContext { #### Files: +- **readme-generator.ts** — Generates README.md + - Dynamic project-specific README with tech stack badges + - Package manager-specific commands + - Available scripts table + - Project structure overview + - Documentation links based on config - **architecture-generator.ts** — Generates ARCHITECTURE.md - Creates project-specific documentation - Documents selected configuration @@ -523,7 +531,7 @@ Load Template Overlays (TemplateRegistry) ↓ Merge All Template Files ↓ -Generate ARCHITECTURE.md +Generate README.md & ARCHITECTURE.md ↓ Aggregate Dependencies (from manifests) ↓ @@ -668,16 +676,23 @@ npm run test:coverage # Coverage report ### Styling +Options are conditional based on runtime: + +**Vite (4 options)**: - `tailwind` — Tailwind CSS v4 (recommended) -- `css` — Plain CSS - `styled-components` — CSS-in-JS - `css-modules` — Scoped CSS +- `css` — Plain CSS + +**Next.js**: +- `tailwind` — Auto-selected (recommended for App Router) ### State Management - `none` — No setup (default) - `redux` — Redux Toolkit - `zustand` — Lightweight alternative +- `jotai` — Primitive and flexible atomic state ### Testing diff --git a/README.md b/README.md index f88b29d..cb75daa 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ The CLI is **prompt-driven**. You'll choose: - **Project directory** - **Runtime**: Vite or Next.js - **Language**: TypeScript or JavaScript -- **Styling**: Tailwind, CSS, Styled Components, or CSS Modules -- **State**: none, Zustand, or Redux Toolkit +- **Styling**: Tailwind, Styled Components, CSS Modules, or Plain CSS (Vite offers all 4; Next.js auto-selects Tailwind) +- **State**: none, Zustand, Jotai, or Redux Toolkit - **Testing**: full (unit+component+E2E), unit+component only, or none - **Unit runner**: Vitest or Jest (if testing enabled) - **E2E runner**: Playwright or Cypress (if full testing) @@ -72,6 +72,7 @@ my-app/ │ ├── testing/ # Test utilities, mocks (if selected) │ └── types/ # Shared types ├── tests/ # E2E tests (if selected) +├── README.md # Auto-generated project README ├── ARCHITECTURE.md # Auto-generated architecture docs └── [config files] ``` @@ -82,8 +83,8 @@ my-app/ |---|---| | **Runtime** | `vite`, `nextjs` | | **Language** | `typescript`, `javascript` | -| **Styling** | `tailwind`, `css`, `styled-components`, `css-modules` | -| **State** | `none`, `zustand`, `redux` | +| **Styling** | `tailwind`, `styled-components`, `css-modules`, `css` (Vite: all 4, Next.js: tailwind only) | +| **State** | `none`, `zustand`, `jotai`, `redux` | | **Testing** | `full`, `unit-component`, `none` | | **Unit runner** | `vitest`, `jest` | | **E2E runner** | `playwright`, `cypress` | From 0ac304739a394e6723c5903bc205f0bda695c9ac Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Fri, 30 Jan 2026 18:22:12 +0530 Subject: [PATCH 3/6] Add styling alignment tests and enhance template registry for runtime-specific --- .../integration/styling-verification.test.ts | 98 +++++- src/__tests__/styling-alignment.test.ts | 305 ++++++++++++++++++ .../css-modules/src/components/ui/Button.tsx | 47 +++ .../src/components/ui/Input.module.css | 57 ++++ .../css-modules/src/components/ui/Input.tsx | 47 +++ .../css-modules/src/components/ui/index.ts | 3 + .../overlays/styling/css-modules/src/css.d.ts | 10 + .../_nextjs/src/app/layout.tsx | 22 ++ .../_nextjs/src/app/providers.tsx | 19 ++ .../src/lib/StyledComponentsRegistry.tsx | 9 +- .../_vite/src/app/provider.tsx | 24 ++ .../styled-components/_vite/src/main.tsx | 10 + .../styling/styled-components/manifest.json | 6 +- src/templates/registry.ts | 40 ++- 14 files changed, 679 insertions(+), 18 deletions(-) create mode 100644 src/__tests__/styling-alignment.test.ts create mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx create mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css create mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx create mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/index.ts create mode 100644 src/templates/overlays/styling/css-modules/src/css.d.ts create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx rename src/templates/overlays/styling/styled-components/{ => _nextjs}/src/lib/StyledComponentsRegistry.tsx (73%) create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/main.tsx diff --git a/src/__tests__/integration/styling-verification.test.ts b/src/__tests__/integration/styling-verification.test.ts index 0f69864..4eec0c8 100644 --- a/src/__tests__/integration/styling-verification.test.ts +++ b/src/__tests__/integration/styling-verification.test.ts @@ -86,9 +86,29 @@ describe('Styling Solutions Verification', () => { }, 300000); }); - describe.skip('CSS Modules', () => { - it('should generate and build with CSS Modules', async () => { - const config = createConfig('css-modules', { + describe('CSS Modules', () => { + it('should generate project with CSS Modules file structure', async () => { + const config = createConfig('css-modules-structure', { + runtime: 'vite', + styling: { solution: 'css-modules' }, + }); + projectPaths.push(config.path); + + const generator = new ProjectGenerator(config); + const result = await generator.generate(); + + expect(result.success).toBe(true); + + // Verify CSS Module files exist + expect(existsSync(join(config.path, 'src/components/ui/Button.module.css'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Input.module.css'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Button.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Input.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/styles/globals.css'))).toBe(true); + }, 60000); + + it('should generate and build with CSS Modules (Vite)', async () => { + const config = createConfig('css-modules-vite', { runtime: 'vite', styling: { solution: 'css-modules' }, }); @@ -118,9 +138,79 @@ describe('Styling Solutions Verification', () => { expect(existsSync(join(config.path, 'dist'))).toBe(true); }, 300000); + + it('should generate and build with CSS Modules (Next.js)', async () => { + const config = createConfig('css-modules-nextjs', { + runtime: 'nextjs', + styling: { solution: 'css-modules' }, + }); + projectPaths.push(config.path); + + const generator = new ProjectGenerator(config); + const result = await generator.generate(); + + expect(result.success).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Button.module.css'))).toBe(true); + + // Install dependencies + console.log('Installing dependencies for Next.js + CSS Modules...'); + const installResult = await execa('npm', ['install'], { + cwd: config.path, + timeout: 120000, + }); + expect(installResult.exitCode).toBe(0); + + // Build the project + console.log('Building Next.js + CSS Modules...'); + const buildResult = await execa('npm', ['run', 'build'], { + cwd: config.path, + timeout: 180000, + }); + expect(buildResult.exitCode).toBe(0); + + expect(existsSync(join(config.path, '.next'))).toBe(true); + }, 360000); }); - describe.skip('Styled Components', () => { + describe('Styled Components', () => { + it('should generate project with Styled Components file structure (Vite)', async () => { + const config = createConfig('styled-vite-structure', { + runtime: 'vite', + styling: { solution: 'styled-components' }, + }); + projectPaths.push(config.path); + + const generator = new ProjectGenerator(config); + const result = await generator.generate(); + + expect(result.success).toBe(true); + + // Verify Styled Components files exist + expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Button.styled.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/app/provider.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/main.tsx'))).toBe(true); + }, 60000); + + it('should generate project with Styled Components file structure (Next.js)', async () => { + const config = createConfig('styled-nextjs-structure', { + runtime: 'nextjs', + styling: { solution: 'styled-components' }, + }); + projectPaths.push(config.path); + + const generator = new ProjectGenerator(config); + const result = await generator.generate(); + + expect(result.success).toBe(true); + + // Verify Styled Components files exist for Next.js + expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/lib/StyledComponentsRegistry.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/app/providers.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/app/layout.tsx'))).toBe(true); + }, 60000); + it('should generate and build with Styled Components (Vite)', async () => { const config = createConfig('styled-vite', { runtime: 'vite', diff --git a/src/__tests__/styling-alignment.test.ts b/src/__tests__/styling-alignment.test.ts new file mode 100644 index 0000000..29e2171 --- /dev/null +++ b/src/__tests__/styling-alignment.test.ts @@ -0,0 +1,305 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { TemplateRegistry } from '../templates/registry.js'; + +/** + * Styling Alignment Tests + * Verifies that styling overlays properly integrate with the app structure + * by checking file contents for correct imports and usage patterns + */ + +describe('Styling Alignment', () => { + let registry: TemplateRegistry; + + beforeEach(() => { + registry = new TemplateRegistry(); + }); + + describe('CSS Modules Overlay', () => { + it('should have Button.tsx that imports Button.module.css', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const buttonTsx = template.files.get('src/components/ui/Button.tsx'); + + expect(buttonTsx).toBeDefined(); + expect(buttonTsx).toContain("import styles from './Button.module.css'"); + expect(buttonTsx).toContain('styles.button'); + }); + + it('should have Input.tsx that imports Input.module.css', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const inputTsx = template.files.get('src/components/ui/Input.tsx'); + + expect(inputTsx).toBeDefined(); + expect(inputTsx).toContain("import styles from './Input.module.css'"); + expect(inputTsx).toContain('styles.input'); + }); + + it('should have index.ts that exports both Button and Input', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const indexTs = template.files.get('src/components/ui/index.ts'); + + expect(indexTs).toBeDefined(); + expect(indexTs).toContain("export { Button"); + expect(indexTs).toContain("export { Input"); + }); + + it('should have Button.module.css with required classes', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const buttonCss = template.files.get('src/components/ui/Button.module.css'); + + expect(buttonCss).toBeDefined(); + expect(buttonCss).toContain('.button'); + expect(buttonCss).toContain('.primary'); + expect(buttonCss).toContain('.secondary'); + expect(buttonCss).toContain('.sm'); + expect(buttonCss).toContain('.md'); + expect(buttonCss).toContain('.lg'); + }); + + it('should have Input.module.css with required classes', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const inputCss = template.files.get('src/components/ui/Input.module.css'); + + expect(inputCss).toBeDefined(); + expect(inputCss).toContain('.input'); + expect(inputCss).toContain('.label'); + expect(inputCss).toContain('.error'); + }); + + it('should have globals.css with CSS custom properties', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const globalsCss = template.files.get('src/styles/globals.css'); + + expect(globalsCss).toBeDefined(); + expect(globalsCss).toContain(':root'); + expect(globalsCss).toContain('--color-primary'); + expect(globalsCss).toContain('--spacing-'); + expect(globalsCss).toContain('--radius-'); + }); + + it('should properly merge with Vite runtime', () => { + registry.loadTemplatesForConfig({ + runtime: 'vite', + styling: { solution: 'css-modules' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // CSS Modules Button should override base Button + const buttonTsx = files.get('src/components/ui/Button.tsx'); + expect(buttonTsx).toContain("import styles from './Button.module.css'"); + + // Should have CSS module files + expect(files.has('src/components/ui/Button.module.css')).toBe(true); + expect(files.has('src/components/ui/Input.module.css')).toBe(true); + }); + + it('should properly merge with Next.js runtime', () => { + registry.loadTemplatesForConfig({ + runtime: 'nextjs', + styling: { solution: 'css-modules' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // CSS Modules Button should override base Button + const buttonTsx = files.get('src/components/ui/Button.tsx'); + expect(buttonTsx).toContain("import styles from './Button.module.css'"); + + // Should have CSS module files + expect(files.has('src/components/ui/Button.module.css')).toBe(true); + }); + }); + + describe('Styled Components Overlay', () => { + it('should have provider.tsx for Vite that includes GlobalStyles', () => { + const template = registry.loadAndRegister('styling/styled-components', 'vite'); + const providerTsx = template.files.get('src/app/provider.tsx'); + + expect(providerTsx).toBeDefined(); + expect(providerTsx).toContain("import { GlobalStyles } from '@/styles/globals'"); + expect(providerTsx).toContain(''); + }); + + it('should have providers.tsx for Next.js with StyledComponentsRegistry', () => { + const template = registry.loadAndRegister('styling/styled-components', 'nextjs'); + const providersTsx = template.files.get('src/app/providers.tsx'); + + expect(providersTsx).toBeDefined(); + expect(providersTsx).toContain("import StyledComponentsRegistry from '@/lib/StyledComponentsRegistry'"); + expect(providersTsx).toContain("import { GlobalStyles } from '@/styles/globals'"); + expect(providersTsx).toContain(''); + expect(providersTsx).toContain(''); + }); + + it('should have layout.tsx for Next.js without CSS import', () => { + const template = registry.loadAndRegister('styling/styled-components', 'nextjs'); + const layoutTsx = template.files.get('src/app/layout.tsx'); + + expect(layoutTsx).toBeDefined(); + expect(layoutTsx).not.toContain("import '@/styles/globals.css'"); + expect(layoutTsx).toContain(''); + }); + + it('should have main.tsx for Vite without CSS import', () => { + const template = registry.loadAndRegister('styling/styled-components', 'vite'); + const mainTsx = template.files.get('src/main.tsx'); + + expect(mainTsx).toBeDefined(); + expect(mainTsx).not.toContain("import '@/styles/globals.css'"); + expect(mainTsx).toContain(''); + }); + + it('should have StyledComponentsRegistry.tsx for Next.js SSR', () => { + const template = registry.loadAndRegister('styling/styled-components', 'nextjs'); + const registryTsx = template.files.get('src/lib/StyledComponentsRegistry.tsx'); + + expect(registryTsx).toBeDefined(); + expect(registryTsx).toContain("'use client'"); + expect(registryTsx).toContain('ServerStyleSheet'); + expect(registryTsx).toContain('StyleSheetManager'); + expect(registryTsx).toContain('useServerInsertedHTML'); + }); + + it('should have globals.ts with GlobalStyles export', () => { + const template = registry.loadAndRegister('styling/styled-components'); + const globalsTs = template.files.get('src/styles/globals.ts'); + + expect(globalsTs).toBeDefined(); + expect(globalsTs).toContain('createGlobalStyle'); + expect(globalsTs).toContain('export const GlobalStyles'); + }); + + it('should have Button.styled.ts with styled component', () => { + const template = registry.loadAndRegister('styling/styled-components'); + const buttonStyled = template.files.get('src/components/ui/Button.styled.ts'); + + expect(buttonStyled).toBeDefined(); + expect(buttonStyled).toContain("import styled from 'styled-components'"); + expect(buttonStyled).toContain('export const StyledButton'); + }); + + it('should properly merge with Vite runtime', () => { + registry.loadTemplatesForConfig({ + runtime: 'vite', + styling: { solution: 'styled-components' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // provider.tsx should include GlobalStyles + const providerTsx = files.get('src/app/provider.tsx'); + expect(providerTsx).toContain(''); + + // main.tsx should not import CSS + const mainTsx = files.get('src/main.tsx'); + expect(mainTsx).not.toContain("globals.css"); + + // Should have styled-components specific files + expect(files.has('src/styles/globals.ts')).toBe(true); + expect(files.has('src/components/ui/Button.styled.ts')).toBe(true); + }); + + it('should properly merge with Next.js runtime', () => { + registry.loadTemplatesForConfig({ + runtime: 'nextjs', + styling: { solution: 'styled-components' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // providers.tsx should include StyledComponentsRegistry + const providersTsx = files.get('src/app/providers.tsx'); + expect(providersTsx).toContain(''); + expect(providersTsx).toContain(''); + + // layout.tsx should not import CSS + const layoutTsx = files.get('src/app/layout.tsx'); + expect(layoutTsx).not.toContain("globals.css"); + + // Should have Next.js specific registry + expect(files.has('src/lib/StyledComponentsRegistry.tsx')).toBe(true); + }); + }); + + describe('Tailwind Overlay', () => { + it('should have globals.css with Tailwind directives', () => { + const template = registry.loadAndRegister('styling/tailwind'); + const globalsCss = template.files.get('src/styles/globals.css'); + + expect(globalsCss).toBeDefined(); + expect(globalsCss).toContain('@import'); + }); + + it('should have tailwind.config.js', () => { + const template = registry.loadAndRegister('styling/tailwind'); + const tailwindConfig = template.files.get('tailwind.config.js'); + + expect(tailwindConfig).toBeDefined(); + expect(tailwindConfig).toContain('content'); + }); + + it('should have postcss.config.js', () => { + const template = registry.loadAndRegister('styling/tailwind'); + const postcssConfig = template.files.get('postcss.config.js'); + + expect(postcssConfig).toBeDefined(); + }); + + it('should properly merge with Vite runtime', () => { + registry.loadTemplatesForConfig({ + runtime: 'vite', + styling: { solution: 'tailwind' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // Should have Tailwind config files + expect(files.has('tailwind.config.js')).toBe(true); + expect(files.has('postcss.config.js')).toBe(true); + + // Base Button should still use cn/clsx utility (Tailwind compatible) + const buttonTsx = files.get('src/components/ui/Button.tsx'); + expect(buttonTsx).toContain('cn('); + }); + }); + + describe('Cross-Styling Compatibility', () => { + it('CSS Modules should not have Tailwind-specific utilities', () => { + const template = registry.loadAndRegister('styling/css-modules'); + const buttonTsx = template.files.get('src/components/ui/Button.tsx'); + + expect(buttonTsx).toBeDefined(); + expect(buttonTsx).not.toContain('cn('); + expect(buttonTsx).not.toContain('clsx'); + expect(buttonTsx).toContain('styles.'); + }); + + it('Styled Components should not have CSS file imports', () => { + // Test Vite files + const viteTemplate = registry.loadAndRegister('styling/styled-components', 'vite'); + const providerTsx = viteTemplate.files.get('src/app/provider.tsx'); + const mainTsx = viteTemplate.files.get('src/main.tsx'); + + expect(providerTsx).toBeDefined(); + expect(mainTsx).toBeDefined(); + expect(providerTsx).not.toContain('.css'); + expect(mainTsx).not.toContain('.css'); + }); + }); +}); + diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx b/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx new file mode 100644 index 0000000..8988c39 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx @@ -0,0 +1,47 @@ +import { forwardRef } from 'react'; +import styles from './Button.module.css'; + +export type ButtonProps = React.ButtonHTMLAttributes & { + variant?: 'primary' | 'secondary' | 'outline' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + isLoading?: boolean; +}; + +export const Button = forwardRef( + ( + { + className, + variant = 'primary', + size = 'md', + isLoading = false, + disabled, + children, + ...props + }, + ref + ) => { + const classNames = [ + styles.button, + styles[variant], + styles[size], + className, + ] + .filter(Boolean) + .join(' '); + + return ( + + ); + } +); + +Button.displayName = 'Button'; + diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css b/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css new file mode 100644 index 0000000..ead2ed0 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css @@ -0,0 +1,57 @@ +.container { + width: 100%; +} + +.label { + display: block; + margin-bottom: var(--spacing-1); + font-size: 0.875rem; + font-weight: 500; + color: var(--color-gray-700); +} + +.input { + display: block; + width: 100%; + padding: var(--spacing-2) var(--spacing-3); + font-size: 0.875rem; + line-height: 1.5; + color: var(--color-gray-900); + background-color: white; + border: 1px solid var(--color-gray-300); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.input::placeholder { + color: var(--color-gray-400); +} + +.input:focus { + outline: none; + border-color: var(--color-primary-500); + box-shadow: 0 0 0 2px var(--color-primary-100); +} + +.input:disabled { + cursor: not-allowed; + background-color: var(--color-gray-50); + color: var(--color-gray-500); +} + +.inputError { + border-color: var(--color-red-500); +} + +.inputError:focus { + border-color: var(--color-red-500); + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); +} + +.error { + margin-top: var(--spacing-1); + font-size: 0.875rem; + color: var(--color-red-600); +} + diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx b/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx new file mode 100644 index 0000000..fb88e26 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx @@ -0,0 +1,47 @@ +import { forwardRef } from 'react'; +import styles from './Input.module.css'; + +export type InputProps = React.InputHTMLAttributes & { + label?: string; + error?: string; +}; + +export const Input = forwardRef( + ({ className, label, error, id, ...props }, ref) => { + const inputId = id || props.name; + + const inputClassNames = [ + styles.input, + error && styles.inputError, + className, + ] + .filter(Boolean) + .join(' '); + + return ( +
+ {label && ( + + )} + + {error && ( +

+ {error} +

+ )} +
+ ); + } +); + +Input.displayName = 'Input'; + diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/index.ts b/src/templates/overlays/styling/css-modules/src/components/ui/index.ts new file mode 100644 index 0000000..143c361 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/src/components/ui/index.ts @@ -0,0 +1,3 @@ +export { Button, type ButtonProps } from './Button'; +export { Input, type InputProps } from './Input'; + diff --git a/src/templates/overlays/styling/css-modules/src/css.d.ts b/src/templates/overlays/styling/css-modules/src/css.d.ts new file mode 100644 index 0000000..93aa10d --- /dev/null +++ b/src/templates/overlays/styling/css-modules/src/css.d.ts @@ -0,0 +1,10 @@ +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} + +declare module '*.css' { + const content: string; + export default content; +} + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx new file mode 100644 index 0000000..ba08e45 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from 'next'; +import { Providers } from './providers'; + +export const metadata: Metadata = { + title: '{{PROJECT_NAME}}', + description: 'A production-ready Next.js application', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx new file mode 100644 index 0000000..2448497 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { ReactNode } from 'react'; +import StyledComponentsRegistry from '@/lib/StyledComponentsRegistry'; +import { GlobalStyles } from '@/styles/globals'; + +type ProvidersProps = { + children: ReactNode; +}; + +export function Providers({ children }: ProvidersProps) { + return ( + + + {children} + + ); +} + diff --git a/src/templates/overlays/styling/styled-components/src/lib/StyledComponentsRegistry.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx similarity index 73% rename from src/templates/overlays/styling/styled-components/src/lib/StyledComponentsRegistry.tsx rename to src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx index 9375230..b57b27b 100644 --- a/src/templates/overlays/styling/styled-components/src/lib/StyledComponentsRegistry.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState } from 'react'; import { useServerInsertedHTML } from 'next/navigation'; +import React, { useState } from 'react'; import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; export default function StyledComponentsRegistry({ @@ -10,6 +10,7 @@ export default function StyledComponentsRegistry({ children: React.ReactNode; }) { // Only create stylesheet once with lazy initial state + // useState with initializer ensures this only runs once const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()); useServerInsertedHTML(() => { @@ -18,7 +19,11 @@ export default function StyledComponentsRegistry({ return <>{styles}; }); - if (typeof window !== 'undefined') return <>{children}; + // On the client side, styled-components automatically injects styles + // We only need StyleSheetManager on the server for SSR collection + if (typeof window !== 'undefined') { + return <>{children}; + } return ( diff --git a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx new file mode 100644 index 0000000..2ef0e42 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx @@ -0,0 +1,24 @@ +import { ErrorFallback } from '@/components/errors/ErrorFallback'; +import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; +import { GlobalStyles } from '@/styles/globals'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { BrowserRouter } from 'react-router-dom'; + +type AppProviderProps = { + children: React.ReactNode; +}; + +export function AppProvider({ children }: AppProviderProps) { + return ( + }> + + + + {children} + + + + ); +} + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx new file mode 100644 index 0000000..94ecf8c --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx @@ -0,0 +1,10 @@ +import { App } from '@/app/App'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); + diff --git a/src/templates/overlays/styling/styled-components/manifest.json b/src/templates/overlays/styling/styled-components/manifest.json index 7def56c..0694af8 100644 --- a/src/templates/overlays/styling/styled-components/manifest.json +++ b/src/templates/overlays/styling/styled-components/manifest.json @@ -13,7 +13,11 @@ "scripts": {}, "filePatterns": { "include": ["**/*"], - "exclude": ["manifest.json"] + "exclude": ["manifest.json", "_vite", "_nextjs"] + }, + "runtimeOverrides": { + "vite": "_vite", + "nextjs": "_nextjs" } } diff --git a/src/templates/registry.ts b/src/templates/registry.ts index a62fbe9..d472f16 100644 --- a/src/templates/registry.ts +++ b/src/templates/registry.ts @@ -17,6 +17,10 @@ export interface TemplateManifest { include?: string[]; exclude?: string[]; }; + runtimeOverrides?: { + vite?: string; + nextjs?: string; + }; } /** @@ -141,7 +145,7 @@ export class TemplateRegistry { /** * Load a template overlay from a directory */ - loadTemplate(templatePath: string): TemplateOverlay { + loadTemplate(templatePath: string, runtime?: 'vite' | 'nextjs'): TemplateOverlay { const fullPath = join(this.templatesDir, templatePath); const manifestPath = join(fullPath, 'manifest.json'); @@ -153,6 +157,20 @@ export class TemplateRegistry { const exclude = ['manifest.json', ...(manifest.filePatterns?.exclude || [])]; const files = readDirectoryRecursively(fullPath, fullPath, exclude); + // Load runtime-specific files if runtime overrides exist + if (runtime && manifest.runtimeOverrides) { + const runtimeDir = manifest.runtimeOverrides[runtime]; + if (runtimeDir) { + const runtimePath = join(fullPath, runtimeDir); + if (existsSync(runtimePath)) { + const runtimeFiles = readDirectoryRecursively(runtimePath, runtimePath, []); + runtimeFiles.forEach((content, path) => { + files.set(path, content); + }); + } + } + } + const overlay: TemplateOverlay = { name: manifest.name, path: templatePath, @@ -166,8 +184,8 @@ export class TemplateRegistry { /** * Load and register a template */ - loadAndRegister(templatePath: string): TemplateOverlay { - const overlay = this.loadTemplate(templatePath); + loadAndRegister(templatePath: string, runtime?: 'vite' | 'nextjs'): TemplateOverlay { + const overlay = this.loadTemplate(templatePath, runtime); this.loadedTemplates.set(templatePath, overlay); return overlay; } @@ -220,33 +238,33 @@ export class TemplateRegistry { // Load runtime template templates.push(this.loadAndRegister(`runtime/${config.runtime}`)); - // Load styling template + // Load styling template (pass runtime for runtime-specific overlays) if (config.styling.solution === 'tailwind') { - templates.push(this.loadAndRegister('styling/tailwind')); + templates.push(this.loadAndRegister('styling/tailwind', config.runtime)); } else if (config.styling.solution === 'css-modules') { - templates.push(this.loadAndRegister('styling/css-modules')); + templates.push(this.loadAndRegister('styling/css-modules', config.runtime)); } else if (config.styling.solution === 'styled-components') { - templates.push(this.loadAndRegister('styling/styled-components')); + templates.push(this.loadAndRegister('styling/styled-components', config.runtime)); } // Load state management template if (config.stateManagement && config.stateManagement !== 'none') { - templates.push(this.loadAndRegister(`state/${config.stateManagement}`)); + templates.push(this.loadAndRegister(`state/${config.stateManagement}`, config.runtime)); } // Load testing templates if (config.testing.enabled) { if (config.testing.unit?.runner) { - templates.push(this.loadAndRegister(`testing/${config.testing.unit.runner}`)); + templates.push(this.loadAndRegister(`testing/${config.testing.unit.runner}`, config.runtime)); } if (config.testing.e2e?.enabled && config.testing.e2e.runner && config.testing.e2e.runner !== 'none') { - templates.push(this.loadAndRegister(`testing/${config.testing.e2e.runner}`)); + templates.push(this.loadAndRegister(`testing/${config.testing.e2e.runner}`, config.runtime)); } } // Load data fetching template if (config.dataFetching.enabled) { - templates.push(this.loadAndRegister('features/tanstack-query')); + templates.push(this.loadAndRegister('features/tanstack-query', config.runtime)); } return templates; From 8b5643c6310cc6b727c02f59b99e27c64050abf3 Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Fri, 30 Jan 2026 22:19:43 +0530 Subject: [PATCH 4/6] update libs --- .gitignore | 1 + README.md | 6 +- src/__tests__/conditional-prompts.test.ts | 54 +++-- .../integration/build-verification.test.ts | 6 +- src/__tests__/integration/generator.test.ts | 6 +- .../integration/package-json.test.ts | 7 +- src/__tests__/integration/scenarios.test.ts | 8 +- .../integration/styling-verification.test.ts | 203 ++++-------------- .../integration/template-loading.test.ts | 71 +++--- src/__tests__/readme-generator.test.ts | 6 +- src/__tests__/styling-alignment.test.ts | 153 +++---------- src/cli/parser.ts | 3 +- src/cli/prompts.ts | 17 +- src/config/defaults.ts | 3 +- src/config/schema.ts | 6 +- src/docs/readme-generator.ts | 4 +- src/index.ts | 1 + src/plugins/index.ts | 1 + src/templates/overlays/base/manifest.json | 7 +- .../base/src/components/ui/Button.tsx | 134 +++++++++--- .../overlays/base/src/components/ui/Input.tsx | 84 +++++--- src/templates/overlays/base/src/lib/utils.ts | 10 - .../overlays/runtime/nextjs/src/app/error.tsx | 49 ++++- .../runtime/nextjs/src/app/loading.tsx | 32 ++- .../runtime/nextjs/src/app/not-found.tsx | 67 ++++-- .../overlays/runtime/nextjs/src/app/page.tsx | 68 ++++-- .../runtime/nextjs/src/styles/globals.css | 2 +- .../src/components/errors/ErrorFallback.tsx | 64 ++++-- .../vite/src/components/ui/LoadingSpinner.tsx | 46 ++-- .../vite/src/features/misc/routes/Landing.tsx | 99 +++++++-- .../src/features/misc/routes/NotFound.tsx | 96 +++++++-- .../overlays/runtime/vite/src/main.tsx | 2 - .../runtime/vite/src/styles/globals.css | 55 ----- .../css-modules/_nextjs/src/app/loading.tsx | 11 + .../_nextjs/src/app/page.module.css | 71 ++++++ .../css-modules/_nextjs/src/app/page.tsx | 30 +++ .../errors/ErrorFallback.module.css | 43 ++++ .../src/components/errors/ErrorFallback.tsx | 20 ++ .../components/ui/LoadingSpinner.module.css | 38 ++++ .../src/components/ui/LoadingSpinner.tsx | 21 ++ .../features/misc/routes/Landing.module.css | 71 ++++++ .../src/features/misc/routes/Landing.tsx | 30 +++ .../features/misc/routes/NotFound.module.css | 65 ++++++ .../src/features/misc/routes/NotFound.tsx | 23 ++ .../styling/css-modules/manifest.json | 14 -- .../src/components/ui/Button.module.css | 87 -------- .../css-modules/src/components/ui/Button.tsx | 47 ---- .../src/components/ui/Input.module.css | 57 ----- .../css-modules/src/components/ui/Input.tsx | 47 ---- .../css-modules/src/components/ui/index.ts | 3 - .../overlays/styling/css-modules/src/css.d.ts | 1 + .../css-modules/src/styles/globals.css | 91 -------- .../styling/css/_nextjs/src/app/error.css | 47 ++++ .../styling/css/_nextjs/src/app/error.tsx | 32 +++ .../styling/css/_nextjs/src/app/loading.css | 26 +++ .../styling/css/_nextjs/src/app/loading.tsx | 11 + .../styling/css/_nextjs/src/app/not-found.css | 68 ++++++ .../styling/css/_nextjs/src/app/not-found.tsx | 23 ++ .../styling/css/_nextjs/src/app/page.css | 71 ++++++ .../styling/css/_nextjs/src/app/page.tsx | 30 +++ .../src/components/errors/ErrorFallback.css | 47 ++++ .../src/components/errors/ErrorFallback.tsx | 20 ++ .../src/components/ui/LoadingSpinner.css | 38 ++++ .../src/components/ui/LoadingSpinner.tsx | 19 ++ .../src/features/misc/routes/Landing.css | 71 ++++++ .../src/features/misc/routes/Landing.tsx | 30 +++ .../src/features/misc/routes/NotFound.css | 68 ++++++ .../src/features/misc/routes/NotFound.tsx | 23 ++ .../overlays/styling/css/manifest.json | 17 ++ .../styling/css/src/styles/globals.css | 105 +++++++++ .../_nextjs/src/app/error.tsx | 70 ++++++ .../_nextjs/src/app/layout.tsx | 1 + .../_nextjs/src/app/loading.tsx | 38 ++++ .../_nextjs/src/app/not-found.tsx | 89 ++++++++ .../_nextjs/src/app/page.tsx | 100 +++++++++ .../_nextjs/src/app/providers.tsx | 3 +- .../src/lib/StyledComponentsRegistry.tsx | 1 + .../_vite/src/app/provider.tsx | 1 + .../src/components/errors/ErrorFallback.tsx | 57 +++++ .../src/components/ui/LoadingSpinner.tsx | 45 ++++ .../src/features/misc/routes/Landing.tsx | 98 +++++++++ .../src/features/misc/routes/NotFound.tsx | 87 ++++++++ .../styled-components/_vite/src/main.tsx | 1 + .../tailwind/_nextjs/src/app/error.tsx | 33 +++ .../tailwind/_nextjs/src/app/loading.tsx | 12 ++ .../tailwind/_nextjs/src/app/not-found.tsx | 26 +++ .../styling/tailwind/_nextjs/src/app/page.tsx | 33 +++ .../overlays/styling/tailwind/manifest.json | 8 +- src/templates/registry.ts | 5 +- src/templates/utils.ts | 6 +- 90 files changed, 2615 insertions(+), 986 deletions(-) delete mode 100644 src/templates/overlays/runtime/vite/src/styles/globals.css create mode 100644 src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx create mode 100644 src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css create mode 100644 src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css create mode 100644 src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx delete mode 100644 src/templates/overlays/styling/css-modules/manifest.json delete mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css delete mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx delete mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css delete mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx delete mode 100644 src/templates/overlays/styling/css-modules/src/components/ui/index.ts delete mode 100644 src/templates/overlays/styling/css-modules/src/styles/globals.css create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/error.css create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/error.tsx create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/loading.css create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/not-found.css create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/page.css create mode 100644 src/templates/overlays/styling/css/_nextjs/src/app/page.tsx create mode 100644 src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css create mode 100644 src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx create mode 100644 src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css create mode 100644 src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx create mode 100644 src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css create mode 100644 src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx create mode 100644 src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css create mode 100644 src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx create mode 100644 src/templates/overlays/styling/css/manifest.json create mode 100644 src/templates/overlays/styling/css/src/styles/globals.css create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx create mode 100644 src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx create mode 100644 src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx create mode 100644 src/templates/overlays/styling/tailwind/_nextjs/src/app/error.tsx create mode 100644 src/templates/overlays/styling/tailwind/_nextjs/src/app/loading.tsx create mode 100644 src/templates/overlays/styling/tailwind/_nextjs/src/app/not-found.tsx create mode 100644 src/templates/overlays/styling/tailwind/_nextjs/src/app/page.tsx diff --git a/.gitignore b/.gitignore index 994a50d..5a4d6ec 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ coverage/ + diff --git a/README.md b/README.md index cb75daa..7dfa3a9 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The CLI is **prompt-driven**. You'll choose: - **Project directory** - **Runtime**: Vite or Next.js - **Language**: TypeScript or JavaScript -- **Styling**: Tailwind, Styled Components, CSS Modules, or Plain CSS (Vite offers all 4; Next.js auto-selects Tailwind) +- **Styling**: Vite uses Styled Components (auto-selected); Next.js offers Tailwind CSS or None (plain CSS) - **State**: none, Zustand, Jotai, or Redux Toolkit - **Testing**: full (unit+component+E2E), unit+component only, or none - **Unit runner**: Vitest or Jest (if testing enabled) @@ -83,7 +83,7 @@ my-app/ |---|---| | **Runtime** | `vite`, `nextjs` | | **Language** | `typescript`, `javascript` | -| **Styling** | `tailwind`, `styled-components`, `css-modules`, `css` (Vite: all 4, Next.js: tailwind only) | +| **Styling** | Vite: `styled-components` (auto), Next.js: `tailwind` or `none` | | **State** | `none`, `zustand`, `jotai`, `redux` | | **Testing** | `full`, `unit-component`, `none` | | **Unit runner** | `vitest`, `jest` | @@ -139,7 +139,7 @@ import { TemplateRegistry } from 'create-react-forge/templates'; const registry = new TemplateRegistry(); const templates = registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'tailwind' }, + styling: { solution: 'styled-components' }, stateManagement: 'zustand', testing: { enabled: true, unit: { runner: 'vitest' }, e2e: { enabled: true, runner: 'playwright' } }, dataFetching: { enabled: true }, diff --git a/src/__tests__/conditional-prompts.test.ts b/src/__tests__/conditional-prompts.test.ts index e2d71a3..8b91fd4 100644 --- a/src/__tests__/conditional-prompts.test.ts +++ b/src/__tests__/conditional-prompts.test.ts @@ -8,39 +8,37 @@ import { StateManagementSchema, StylingSchema } from '../config/schema'; */ describe('Conditional Prompts Configuration', () => { describe('Styling options', () => { - it('should have all 4 styling options defined', () => { + it('should have all 3 styling options defined', () => { const stylingValues = StylingSchema.options; - expect(stylingValues).toContain('css'); + expect(stylingValues).toContain('none'); expect(stylingValues).toContain('tailwind'); expect(stylingValues).toContain('styled-components'); - expect(stylingValues).toContain('css-modules'); - expect(stylingValues).toHaveLength(4); + expect(stylingValues).toHaveLength(3); }); it('should have descriptions for all styling options', () => { - expect(STYLING_DESCRIPTIONS.css).toBeDefined(); + expect(STYLING_DESCRIPTIONS.none).toBeDefined(); expect(STYLING_DESCRIPTIONS.tailwind).toBeDefined(); expect(STYLING_DESCRIPTIONS['styled-components']).toBeDefined(); - expect(STYLING_DESCRIPTIONS['css-modules']).toBeDefined(); }); - it('Vite should support all 4 styling options', () => { - // For Vite, all 4 options should be valid - const viteOptions = ['tailwind', 'styled-components', 'css-modules', 'css']; + it('Vite should use styled-components (auto-selected)', () => { + // For Vite, styled-components is auto-selected + const viteDefault = 'styled-components'; + const result = StylingSchema.safeParse(viteDefault); - viteOptions.forEach((option) => { - const result = StylingSchema.safeParse(option); - expect(result.success).toBe(true); - }); + expect(result.success).toBe(true); }); - it('Next.js should use tailwind (auto-selected)', () => { - // For Next.js, tailwind is auto-selected - const nextjsDefault = 'tailwind'; - const result = StylingSchema.safeParse(nextjsDefault); + it('Next.js should support tailwind and none options', () => { + // For Next.js, user can choose tailwind or none + const nextjsOptions = ['tailwind', 'none']; - expect(result.success).toBe(true); + nextjsOptions.forEach((option) => { + const result = StylingSchema.safeParse(option); + expect(result.success).toBe(true); + }); }); }); @@ -74,28 +72,26 @@ describe('Conditional Prompts Configuration', () => { */ function getStylingChoicesForRuntime(runtime: 'vite' | 'nextjs'): string[] { if (runtime === 'vite') { - return ['tailwind', 'styled-components', 'css-modules', 'css']; + // Vite auto-selects styled-components + return ['styled-components']; } - // Next.js auto-selects tailwind - return ['tailwind']; + // Next.js offers tailwind or none + return ['tailwind', 'none']; } - it('should return 4 styling options for Vite', () => { + it('should return styled-components for Vite (auto-selected)', () => { const choices = getStylingChoicesForRuntime('vite'); - expect(choices).toHaveLength(4); - expect(choices).toContain('tailwind'); + expect(choices).toHaveLength(1); expect(choices).toContain('styled-components'); - expect(choices).toContain('css-modules'); - expect(choices).toContain('css'); }); - it('should return only tailwind for Next.js', () => { + it('should return tailwind and none for Next.js', () => { const choices = getStylingChoicesForRuntime('nextjs'); - expect(choices).toHaveLength(1); + expect(choices).toHaveLength(2); expect(choices).toContain('tailwind'); + expect(choices).toContain('none'); }); }); }); - diff --git a/src/__tests__/integration/build-verification.test.ts b/src/__tests__/integration/build-verification.test.ts index 95b370a..a26027a 100644 --- a/src/__tests__/integration/build-verification.test.ts +++ b/src/__tests__/integration/build-verification.test.ts @@ -58,7 +58,7 @@ describe('Build Verification Tests', () => { it('should generate and build a minimal Next.js project', async () => { const config = createConfig('nextjs-build-minimal', { runtime: 'nextjs', - styling: { solution: 'css' }, + styling: { solution: 'none' }, }); projectPaths.push(config.path); @@ -122,7 +122,7 @@ describe('Build Verification Tests', () => { it('should generate and build Next.js with state management', async () => { const config = createConfig('nextjs-build-zustand', { runtime: 'nextjs', - styling: { solution: 'css' }, + styling: { solution: 'none' }, stateManagement: 'zustand', }); projectPaths.push(config.path); @@ -156,7 +156,7 @@ describe('Build Verification Tests', () => { it('should generate and build a minimal Vite project', async () => { const config = createConfig('vite-build-minimal', { runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, }); projectPaths.push(config.path); diff --git a/src/__tests__/integration/generator.test.ts b/src/__tests__/integration/generator.test.ts index 2f1b067..6a8f099 100644 --- a/src/__tests__/integration/generator.test.ts +++ b/src/__tests__/integration/generator.test.ts @@ -192,11 +192,11 @@ describe('ProjectGenerator Integration', () => { expect(devDeps).toHaveProperty('@playwright/test'); }); - it.skip('should generate Next.js + CSS Modules + Redux', async () => { + it.skip('should generate Next.js + None styling + Redux', async () => { const config = createBaseConfig({ name: 'nextjs-redux', runtime: 'nextjs', - styling: { solution: 'css-modules' }, + styling: { solution: 'none' }, stateManagement: 'redux', testing: { enabled: true, @@ -231,7 +231,7 @@ describe('ProjectGenerator Integration', () => { const config = createBaseConfig({ name: 'vite-jest-pw', runtime: 'vite', - styling: { solution: 'css-modules' }, + styling: { solution: 'styled-components' }, testing: { enabled: true, unit: { enabled: true, runner: 'jest' }, diff --git a/src/__tests__/integration/package-json.test.ts b/src/__tests__/integration/package-json.test.ts index 640bf3b..44e29a5 100644 --- a/src/__tests__/integration/package-json.test.ts +++ b/src/__tests__/integration/package-json.test.ts @@ -165,9 +165,10 @@ describe('Package.json Generation', () => { expect(devDeps).toHaveProperty('autoprefixer'); }); - it.skip('should not include tailwind when css-modules selected', async () => { - const config = createConfig('css-modules-deps', { - styling: { solution: 'css-modules' }, + it.skip('should not include tailwind when none styling selected', async () => { + const config = createConfig('none-styling-deps', { + runtime: 'nextjs', + styling: { solution: 'none' }, }); projectPaths.push(config.path); diff --git a/src/__tests__/integration/scenarios.test.ts b/src/__tests__/integration/scenarios.test.ts index 4a992c6..935ea2a 100644 --- a/src/__tests__/integration/scenarios.test.ts +++ b/src/__tests__/integration/scenarios.test.ts @@ -63,10 +63,10 @@ describe('Real-World Scenarios', () => { }); describe('Scenario: Minimal SPA (Learning/Prototyping)', () => { - it('should generate minimal Vite + CSS project', async () => { + it('should generate minimal Vite + styled-components project', async () => { const config = createConfig('minimal-spa', { runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', dataFetching: { enabled: false, library: 'tanstack-query' }, testing: { @@ -230,10 +230,10 @@ describe('Real-World Scenarios', () => { }); describe.skip('Scenario: Next.js with Redux', () => { - it('should generate Next.js + CSS Modules + Redux', async () => { + it('should generate Next.js + None styling + Redux', async () => { const config = createConfig('nextjs-redux', { runtime: 'nextjs', - styling: { solution: 'css-modules' }, + styling: { solution: 'none' }, stateManagement: 'redux', dataFetching: { enabled: false, library: 'tanstack-query' }, testing: { diff --git a/src/__tests__/integration/styling-verification.test.ts b/src/__tests__/integration/styling-verification.test.ts index 4eec0c8..8eda968 100644 --- a/src/__tests__/integration/styling-verification.test.ts +++ b/src/__tests__/integration/styling-verification.test.ts @@ -1,8 +1,8 @@ +import { execa } from 'execa'; import { existsSync, rmSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { afterEach, describe, expect, it } from 'vitest'; -import { execa } from 'execa'; import { ProjectConfig } from '../../config/schema.js'; import { ProjectGenerator } from '../../generator/index.js'; @@ -28,7 +28,7 @@ function createConfig(name: string, overrides: Partial): ProjectC path: basePath, runtime: 'vite', language: 'typescript', - styling: { solution: 'tailwind' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', dataFetching: { enabled: false, library: 'tanstack-query' }, testing: { @@ -53,44 +53,11 @@ describe('Styling Solutions Verification', () => { projectPaths.length = 0; }); - describe('Plain CSS', () => { - it('should generate and build with plain CSS', async () => { - const config = createConfig('css-plain', { - runtime: 'vite', - styling: { solution: 'css' }, - }); - projectPaths.push(config.path); - - const generator = new ProjectGenerator(config); - const result = await generator.generate(); - - expect(result.success).toBe(true); - - // Install dependencies - console.log('Installing dependencies for plain CSS project...'); - const installResult = await execa('npm', ['install'], { - cwd: config.path, - timeout: 120000, - }); - expect(installResult.exitCode).toBe(0); - - // Build the project - console.log('Building plain CSS project...'); - const buildResult = await execa('npm', ['run', 'build'], { - cwd: config.path, - timeout: 120000, - }); - expect(buildResult.exitCode).toBe(0); - - expect(existsSync(join(config.path, 'dist'))).toBe(true); - }, 300000); - }); - - describe('CSS Modules', () => { - it('should generate project with CSS Modules file structure', async () => { - const config = createConfig('css-modules-structure', { + describe('Styled Components (Vite)', () => { + it('should generate project with Styled Components file structure', async () => { + const config = createConfig('styled-vite-structure', { runtime: 'vite', - styling: { solution: 'css-modules' }, + styling: { solution: 'styled-components' }, }); projectPaths.push(config.path); @@ -99,18 +66,17 @@ describe('Styling Solutions Verification', () => { expect(result.success).toBe(true); - // Verify CSS Module files exist - expect(existsSync(join(config.path, 'src/components/ui/Button.module.css'))).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Input.module.css'))).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Button.tsx'))).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Input.tsx'))).toBe(true); - expect(existsSync(join(config.path, 'src/styles/globals.css'))).toBe(true); + // Verify Styled Components files exist + expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Button.styled.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/app/provider.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/main.tsx'))).toBe(true); }, 60000); - it('should generate and build with CSS Modules (Vite)', async () => { - const config = createConfig('css-modules-vite', { + it('should generate and build with Styled Components', async () => { + const config = createConfig('styled-vite', { runtime: 'vite', - styling: { solution: 'css-modules' }, + styling: { solution: 'styled-components' }, }); projectPaths.push(config.path); @@ -118,10 +84,11 @@ describe('Styling Solutions Verification', () => { const result = await generator.generate(); expect(result.success).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Button.module.css'))).toBe(true); + expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); + expect(existsSync(join(config.path, 'src/components/ui/Button.styled.ts'))).toBe(true); // Install dependencies - console.log('Installing dependencies for CSS Modules project...'); + console.log('Installing dependencies for Styled Components project...'); const installResult = await execa('npm', ['install'], { cwd: config.path, timeout: 120000, @@ -129,7 +96,7 @@ describe('Styling Solutions Verification', () => { expect(installResult.exitCode).toBe(0); // Build the project - console.log('Building CSS Modules project...'); + console.log('Building Styled Components project...'); const buildResult = await execa('npm', ['run', 'build'], { cwd: config.path, timeout: 120000, @@ -138,11 +105,13 @@ describe('Styling Solutions Verification', () => { expect(existsSync(join(config.path, 'dist'))).toBe(true); }, 300000); + }); - it('should generate and build with CSS Modules (Next.js)', async () => { - const config = createConfig('css-modules-nextjs', { + describe('Tailwind CSS (Next.js)', () => { + it('should generate and build with Tailwind', async () => { + const config = createConfig('tailwind-nextjs', { runtime: 'nextjs', - styling: { solution: 'css-modules' }, + styling: { solution: 'tailwind' }, }); projectPaths.push(config.path); @@ -150,10 +119,11 @@ describe('Styling Solutions Verification', () => { const result = await generator.generate(); expect(result.success).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Button.module.css'))).toBe(true); + expect(existsSync(join(config.path, 'tailwind.config.js'))).toBe(true); + expect(existsSync(join(config.path, 'postcss.config.js'))).toBe(true); // Install dependencies - console.log('Installing dependencies for Next.js + CSS Modules...'); + console.log('Installing dependencies for Tailwind project...'); const installResult = await execa('npm', ['install'], { cwd: config.path, timeout: 120000, @@ -161,7 +131,7 @@ describe('Styling Solutions Verification', () => { expect(installResult.exitCode).toBe(0); // Build the project - console.log('Building Next.js + CSS Modules...'); + console.log('Building Tailwind project...'); const buildResult = await execa('npm', ['run', 'build'], { cwd: config.path, timeout: 180000, @@ -172,49 +142,11 @@ describe('Styling Solutions Verification', () => { }, 360000); }); - describe('Styled Components', () => { - it('should generate project with Styled Components file structure (Vite)', async () => { - const config = createConfig('styled-vite-structure', { - runtime: 'vite', - styling: { solution: 'styled-components' }, - }); - projectPaths.push(config.path); - - const generator = new ProjectGenerator(config); - const result = await generator.generate(); - - expect(result.success).toBe(true); - - // Verify Styled Components files exist - expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Button.styled.ts'))).toBe(true); - expect(existsSync(join(config.path, 'src/app/provider.tsx'))).toBe(true); - expect(existsSync(join(config.path, 'src/main.tsx'))).toBe(true); - }, 60000); - - it('should generate project with Styled Components file structure (Next.js)', async () => { - const config = createConfig('styled-nextjs-structure', { + describe('None Styling (Next.js)', () => { + it('should generate project with no styling framework', async () => { + const config = createConfig('none-nextjs-structure', { runtime: 'nextjs', - styling: { solution: 'styled-components' }, - }); - projectPaths.push(config.path); - - const generator = new ProjectGenerator(config); - const result = await generator.generate(); - - expect(result.success).toBe(true); - - // Verify Styled Components files exist for Next.js - expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); - expect(existsSync(join(config.path, 'src/lib/StyledComponentsRegistry.tsx'))).toBe(true); - expect(existsSync(join(config.path, 'src/app/providers.tsx'))).toBe(true); - expect(existsSync(join(config.path, 'src/app/layout.tsx'))).toBe(true); - }, 60000); - - it('should generate and build with Styled Components (Vite)', async () => { - const config = createConfig('styled-vite', { - runtime: 'vite', - styling: { solution: 'styled-components' }, + styling: { solution: 'none' }, }); projectPaths.push(config.path); @@ -222,32 +154,20 @@ describe('Styling Solutions Verification', () => { const result = await generator.generate(); expect(result.success).toBe(true); - expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); - expect(existsSync(join(config.path, 'src/components/ui/Button.styled.ts'))).toBe(true); - // Install dependencies - console.log('Installing dependencies for Styled Components project...'); - const installResult = await execa('npm', ['install'], { - cwd: config.path, - timeout: 120000, - }); - expect(installResult.exitCode).toBe(0); + // Verify no Tailwind config files + expect(existsSync(join(config.path, 'tailwind.config.js'))).toBe(false); + expect(existsSync(join(config.path, 'postcss.config.js'))).toBe(false); - // Build the project - console.log('Building Styled Components project...'); - const buildResult = await execa('npm', ['run', 'build'], { - cwd: config.path, - timeout: 120000, - }); - expect(buildResult.exitCode).toBe(0); - - expect(existsSync(join(config.path, 'dist'))).toBe(true); - }, 300000); + // Verify base files exist + expect(existsSync(join(config.path, 'src/app/page.tsx'))).toBe(true); + expect(existsSync(join(config.path, 'src/styles/globals.css'))).toBe(true); + }, 60000); - it('should generate and build with Styled Components (Next.js)', async () => { - const config = createConfig('styled-nextjs', { + it('should generate and build with no styling framework', async () => { + const config = createConfig('none-nextjs', { runtime: 'nextjs', - styling: { solution: 'styled-components' }, + styling: { solution: 'none' }, }); projectPaths.push(config.path); @@ -255,10 +175,9 @@ describe('Styling Solutions Verification', () => { const result = await generator.generate(); expect(result.success).toBe(true); - expect(existsSync(join(config.path, 'src/styles/globals.ts'))).toBe(true); // Install dependencies - console.log('Installing dependencies for Next.js + Styled Components...'); + console.log('Installing dependencies for None styling project...'); const installResult = await execa('npm', ['install'], { cwd: config.path, timeout: 120000, @@ -266,7 +185,7 @@ describe('Styling Solutions Verification', () => { expect(installResult.exitCode).toBe(0); // Build the project - console.log('Building Next.js + Styled Components...'); + console.log('Building None styling project...'); const buildResult = await execa('npm', ['run', 'build'], { cwd: config.path, timeout: 180000, @@ -276,40 +195,4 @@ describe('Styling Solutions Verification', () => { expect(existsSync(join(config.path, '.next'))).toBe(true); }, 360000); }); - - describe('Tailwind CSS', () => { - it('should generate and build with Tailwind', async () => { - const config = createConfig('tailwind', { - runtime: 'vite', - styling: { solution: 'tailwind' }, - }); - projectPaths.push(config.path); - - const generator = new ProjectGenerator(config); - const result = await generator.generate(); - - expect(result.success).toBe(true); - expect(existsSync(join(config.path, 'tailwind.config.js'))).toBe(true); - expect(existsSync(join(config.path, 'postcss.config.js'))).toBe(true); - - // Install dependencies - console.log('Installing dependencies for Tailwind project...'); - const installResult = await execa('npm', ['install'], { - cwd: config.path, - timeout: 120000, - }); - expect(installResult.exitCode).toBe(0); - - // Build the project - console.log('Building Tailwind project...'); - const buildResult = await execa('npm', ['run', 'build'], { - cwd: config.path, - timeout: 120000, - }); - expect(buildResult.exitCode).toBe(0); - - expect(existsSync(join(config.path, 'dist'))).toBe(true); - }, 300000); - }); }); - diff --git a/src/__tests__/integration/template-loading.test.ts b/src/__tests__/integration/template-loading.test.ts index b509719..d68a254 100644 --- a/src/__tests__/integration/template-loading.test.ts +++ b/src/__tests__/integration/template-loading.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { TemplateRegistry } from '../../templates/registry.js'; /** @@ -47,13 +47,6 @@ describe('TemplateRegistry', () => { expect(template.manifest.devDependencies).toHaveProperty('tailwindcss'); }); - it('should load css-modules styling template', () => { - const template = registry.loadAndRegister('styling/css-modules'); - - expect(template).toBeDefined(); - expect(template.manifest).toBeDefined(); - }); - it('should load zustand state template', () => { const template = registry.loadAndRegister('state/zustand'); @@ -124,10 +117,10 @@ describe('TemplateRegistry', () => { }); describe('loadTemplatesForConfig', () => { - it('should load templates for minimal Vite config', () => { + it('should load templates for minimal Vite config with styled-components', () => { const templates = registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', testing: { enabled: false, @@ -137,14 +130,15 @@ describe('TemplateRegistry', () => { dataFetching: { enabled: false }, }); - // Should have base and runtime - expect(templates.length).toBeGreaterThanOrEqual(2); + // Should have base, runtime, and styled-components + expect(templates.length).toBeGreaterThanOrEqual(3); expect(templates.some(t => t.name === 'base')).toBe(true); + expect(templates.some(t => t.path === 'styling/styled-components')).toBe(true); }); - it('should load templates for Vite + Tailwind config', () => { + it('should load templates for Next.js + Tailwind config', () => { const templates = registry.loadTemplatesForConfig({ - runtime: 'vite', + runtime: 'nextjs', styling: { solution: 'tailwind' }, stateManagement: 'none', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, @@ -154,10 +148,24 @@ describe('TemplateRegistry', () => { expect(templates.some(t => t.path === 'styling/tailwind')).toBe(true); }); + it('should load templates for Next.js + None styling config', () => { + const templates = registry.loadTemplatesForConfig({ + runtime: 'nextjs', + styling: { solution: 'none' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + // Should NOT load any styling template for 'none' + expect(templates.some(t => t.path === 'styling/tailwind')).toBe(false); + expect(templates.some(t => t.path === 'styling/styled-components')).toBe(false); + }); + it('should load templates for Next.js + Redux config', () => { const templates = registry.loadTemplatesForConfig({ runtime: 'nextjs', - styling: { solution: 'css' }, + styling: { solution: 'none' }, stateManagement: 'redux', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, dataFetching: { enabled: false }, @@ -181,22 +189,10 @@ describe('TemplateRegistry', () => { expect(templates.some(t => t.path === 'styling/styled-components')).toBe(true); }); - it('should load templates for Vite + CSS Modules config', () => { - const templates = registry.loadTemplatesForConfig({ - runtime: 'vite', - styling: { solution: 'css-modules' }, - stateManagement: 'none', - testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, - dataFetching: { enabled: false }, - }); - - expect(templates.some(t => t.path === 'styling/css-modules')).toBe(true); - }); - it('should load testing templates when testing is enabled', () => { const templates = registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', testing: { enabled: true, @@ -213,7 +209,7 @@ describe('TemplateRegistry', () => { it('should load tanstack-query template when dataFetching enabled', () => { const templates = registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, dataFetching: { enabled: true }, @@ -226,7 +222,7 @@ describe('TemplateRegistry', () => { describe('Merged Dependencies', () => { it('should merge dependencies from multiple templates', () => { registry.loadTemplatesForConfig({ - runtime: 'vite', + runtime: 'nextjs', styling: { solution: 'tailwind' }, stateManagement: 'zustand', testing: { @@ -250,7 +246,7 @@ describe('TemplateRegistry', () => { it('should include scripts from templates', () => { registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', testing: { enabled: true, @@ -269,7 +265,7 @@ describe('TemplateRegistry', () => { describe('Merged Files', () => { it('should merge files from multiple templates', () => { registry.loadTemplatesForConfig({ - runtime: 'vite', + runtime: 'nextjs', styling: { solution: 'tailwind' }, stateManagement: 'zustand', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, @@ -280,13 +276,6 @@ describe('TemplateRegistry', () => { expect(files.size).toBeGreaterThan(0); - // Should have files from different templates merged - // Check for vite config file - const hasViteConfig = Array.from(files.keys()).some( - path => path.includes('vite.config') - ); - expect(hasViteConfig).toBe(true); - // Check for store files from zustand const hasStoreFiles = Array.from(files.keys()).some( path => path.includes('stores') @@ -298,7 +287,7 @@ describe('TemplateRegistry', () => { // Load base first, then runtime registry.loadTemplatesForConfig({ runtime: 'vite', - styling: { solution: 'css' }, + styling: { solution: 'styled-components' }, stateManagement: 'none', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, dataFetching: { enabled: false }, @@ -321,7 +310,6 @@ describe('TemplateRegistry', () => { 'runtime/vite', 'runtime/nextjs', 'styling/tailwind', - 'styling/css-modules', 'styling/styled-components', 'state/zustand', 'state/redux', @@ -371,4 +359,3 @@ describe('TemplateRegistry', () => { }); }); }); - diff --git a/src/__tests__/readme-generator.test.ts b/src/__tests__/readme-generator.test.ts index 9dfaee9..8093a60 100644 --- a/src/__tests__/readme-generator.test.ts +++ b/src/__tests__/readme-generator.test.ts @@ -196,11 +196,11 @@ describe('generateReadme', () => { expect(readme).toContain('styled-components.com'); }); - it('should include CSS Modules documentation link', () => { - const config = createConfig({ styling: { solution: 'css-modules' } }); + it('should handle none styling option', () => { + const config = createConfig({ styling: { solution: 'none' } }); const readme = generateReadme(config); - expect(readme).toContain('css-modules'); + expect(readme).toContain('none'); }); it('should include Tailwind documentation link', () => { diff --git a/src/__tests__/styling-alignment.test.ts b/src/__tests__/styling-alignment.test.ts index 29e2171..0d1139e 100644 --- a/src/__tests__/styling-alignment.test.ts +++ b/src/__tests__/styling-alignment.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { TemplateRegistry } from '../templates/registry.js'; /** @@ -14,108 +14,6 @@ describe('Styling Alignment', () => { registry = new TemplateRegistry(); }); - describe('CSS Modules Overlay', () => { - it('should have Button.tsx that imports Button.module.css', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const buttonTsx = template.files.get('src/components/ui/Button.tsx'); - - expect(buttonTsx).toBeDefined(); - expect(buttonTsx).toContain("import styles from './Button.module.css'"); - expect(buttonTsx).toContain('styles.button'); - }); - - it('should have Input.tsx that imports Input.module.css', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const inputTsx = template.files.get('src/components/ui/Input.tsx'); - - expect(inputTsx).toBeDefined(); - expect(inputTsx).toContain("import styles from './Input.module.css'"); - expect(inputTsx).toContain('styles.input'); - }); - - it('should have index.ts that exports both Button and Input', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const indexTs = template.files.get('src/components/ui/index.ts'); - - expect(indexTs).toBeDefined(); - expect(indexTs).toContain("export { Button"); - expect(indexTs).toContain("export { Input"); - }); - - it('should have Button.module.css with required classes', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const buttonCss = template.files.get('src/components/ui/Button.module.css'); - - expect(buttonCss).toBeDefined(); - expect(buttonCss).toContain('.button'); - expect(buttonCss).toContain('.primary'); - expect(buttonCss).toContain('.secondary'); - expect(buttonCss).toContain('.sm'); - expect(buttonCss).toContain('.md'); - expect(buttonCss).toContain('.lg'); - }); - - it('should have Input.module.css with required classes', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const inputCss = template.files.get('src/components/ui/Input.module.css'); - - expect(inputCss).toBeDefined(); - expect(inputCss).toContain('.input'); - expect(inputCss).toContain('.label'); - expect(inputCss).toContain('.error'); - }); - - it('should have globals.css with CSS custom properties', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const globalsCss = template.files.get('src/styles/globals.css'); - - expect(globalsCss).toBeDefined(); - expect(globalsCss).toContain(':root'); - expect(globalsCss).toContain('--color-primary'); - expect(globalsCss).toContain('--spacing-'); - expect(globalsCss).toContain('--radius-'); - }); - - it('should properly merge with Vite runtime', () => { - registry.loadTemplatesForConfig({ - runtime: 'vite', - styling: { solution: 'css-modules' }, - stateManagement: 'none', - testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, - dataFetching: { enabled: false }, - }); - - const files = registry.getMergedFiles(); - - // CSS Modules Button should override base Button - const buttonTsx = files.get('src/components/ui/Button.tsx'); - expect(buttonTsx).toContain("import styles from './Button.module.css'"); - - // Should have CSS module files - expect(files.has('src/components/ui/Button.module.css')).toBe(true); - expect(files.has('src/components/ui/Input.module.css')).toBe(true); - }); - - it('should properly merge with Next.js runtime', () => { - registry.loadTemplatesForConfig({ - runtime: 'nextjs', - styling: { solution: 'css-modules' }, - stateManagement: 'none', - testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, - dataFetching: { enabled: false }, - }); - - const files = registry.getMergedFiles(); - - // CSS Modules Button should override base Button - const buttonTsx = files.get('src/components/ui/Button.tsx'); - expect(buttonTsx).toContain("import styles from './Button.module.css'"); - - // Should have CSS module files - expect(files.has('src/components/ui/Button.module.css')).toBe(true); - }); - }); - describe('Styled Components Overlay', () => { it('should have provider.tsx for Vite that includes GlobalStyles', () => { const template = registry.loadAndRegister('styling/styled-components', 'vite'); @@ -257,9 +155,9 @@ describe('Styling Alignment', () => { expect(postcssConfig).toBeDefined(); }); - it('should properly merge with Vite runtime', () => { + it('should properly merge with Next.js runtime', () => { registry.loadTemplatesForConfig({ - runtime: 'vite', + runtime: 'nextjs', styling: { solution: 'tailwind' }, stateManagement: 'none', testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, @@ -272,25 +170,41 @@ describe('Styling Alignment', () => { expect(files.has('tailwind.config.js')).toBe(true); expect(files.has('postcss.config.js')).toBe(true); - // Base Button should still use cn/clsx utility (Tailwind compatible) - const buttonTsx = files.get('src/components/ui/Button.tsx'); - expect(buttonTsx).toContain('cn('); + // Should have Tailwind-specific Next.js pages + const pageTsx = files.get('src/app/page.tsx'); + expect(pageTsx).toContain('className='); }); }); - describe('Cross-Styling Compatibility', () => { - it('CSS Modules should not have Tailwind-specific utilities', () => { - const template = registry.loadAndRegister('styling/css-modules'); - const buttonTsx = template.files.get('src/components/ui/Button.tsx'); - - expect(buttonTsx).toBeDefined(); - expect(buttonTsx).not.toContain('cn('); - expect(buttonTsx).not.toContain('clsx'); - expect(buttonTsx).toContain('styles.'); + describe('None Styling Option (Next.js)', () => { + it('should work without any styling overlay', () => { + registry.loadTemplatesForConfig({ + runtime: 'nextjs', + styling: { solution: 'none' }, + stateManagement: 'none', + testing: { enabled: false, e2e: { enabled: false, runner: 'none' } }, + dataFetching: { enabled: false }, + }); + + const files = registry.getMergedFiles(); + + // Should have base runtime files + expect(files.has('src/app/page.tsx')).toBe(true); + expect(files.has('src/app/layout.tsx')).toBe(true); + expect(files.has('src/styles/globals.css')).toBe(true); + + // Should NOT have Tailwind config files + expect(files.has('tailwind.config.js')).toBe(false); + expect(files.has('postcss.config.js')).toBe(false); + + // Page should use inline styles (not Tailwind classes) + const pageTsx = files.get('src/app/page.tsx'); + expect(pageTsx).toContain('style='); }); + }); - it('Styled Components should not have CSS file imports', () => { - // Test Vite files + describe('Cross-Styling Compatibility', () => { + it('Styled Components should not have CSS file imports in Vite files', () => { const viteTemplate = registry.loadAndRegister('styling/styled-components', 'vite'); const providerTsx = viteTemplate.files.get('src/app/provider.tsx'); const mainTsx = viteTemplate.files.get('src/main.tsx'); @@ -302,4 +216,3 @@ describe('Styling Alignment', () => { }); }); }); - diff --git a/src/cli/parser.ts b/src/cli/parser.ts index b6bd22b..c73dbca 100644 --- a/src/cli/parser.ts +++ b/src/cli/parser.ts @@ -36,10 +36,9 @@ export function createCommand(): Command { ) .addOption( new Option('--styling ', 'Styling solution').choices([ - 'css', + 'none', 'tailwind', 'styled-components', - 'css-modules', ]) ) .addOption( diff --git a/src/cli/prompts.ts b/src/cli/prompts.ts index 197deb7..98237ec 100644 --- a/src/cli/prompts.ts +++ b/src/cli/prompts.ts @@ -60,23 +60,22 @@ export async function promptForProjectDetails( })) as 'javascript' | 'typescript'; // Styling options are conditional based on runtime - // Vite: 4 options (tailwind, styled-components, css-modules, css) - // Next.js: auto-select tailwind (best practice for App Router) + // Vite: auto-select styled-components + // Next.js: prompt between tailwind and none let styling: string; if (runtime === 'vite') { + // Auto-select styled-components for Vite + styling = 'styled-components'; + console.log(' ✓ Styling: Styled Components (default for Vite)'); + } else { + // Next.js - prompt between Tailwind and None styling = (await select({ message: 'Styling solution:', choices: [ { name: STYLING_DESCRIPTIONS.tailwind, value: 'tailwind' }, - { name: STYLING_DESCRIPTIONS['styled-components'], value: 'styled-components' }, - { name: STYLING_DESCRIPTIONS['css-modules'], value: 'css-modules' }, - { name: STYLING_DESCRIPTIONS.css, value: 'css' }, + { name: STYLING_DESCRIPTIONS.none, value: 'none' }, ], })) as string; - } else { - // Next.js - auto-select tailwind - styling = 'tailwind'; - console.log(' ✓ Styling: Tailwind CSS (recommended for Next.js)'); } const stateManagement = (await select({ diff --git a/src/config/defaults.ts b/src/config/defaults.ts index eb8d5c2..aa1fade 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -8,10 +8,9 @@ export const RUNTIME_DESCRIPTIONS: Record = { }; export const STYLING_DESCRIPTIONS: Record = { - css: 'Plain CSS with CSS modules', + none: 'None - Plain CSS classes', tailwind: 'Tailwind CSS - Utility-first framework', 'styled-components': 'Styled Components - CSS-in-JS', - 'css-modules': 'CSS Modules - Scoped styling', }; export const STATE_DESCRIPTIONS: Record = { diff --git a/src/config/schema.ts b/src/config/schema.ts index a1d1199..b078c72 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -11,7 +11,7 @@ export type Runtime = z.infer; export const LanguageSchema = z.enum(['javascript', 'typescript']); export type Language = z.infer; -export const StylingSchema = z.enum(['css', 'tailwind', 'styled-components', 'css-modules']); +export const StylingSchema = z.enum(['none', 'tailwind', 'styled-components']); export type Styling = z.infer; export const StateManagementSchema = z.enum(['none', 'redux', 'zustand', 'jotai']); @@ -61,7 +61,7 @@ export const GitConfigSchema = z.object({ export type GitConfig = z.infer; export const StylingConfigSchema = z.object({ - solution: StylingSchema.default('tailwind'), + solution: StylingSchema.default('styled-components'), }); export type StylingConfigType = z.infer; @@ -94,7 +94,7 @@ export const DEFAULT_CONFIG: ProjectConfig = { runtime: 'vite', language: 'typescript', styling: { - solution: 'tailwind', + solution: 'styled-components', }, stateManagement: 'none', dataFetching: { diff --git a/src/docs/readme-generator.ts b/src/docs/readme-generator.ts index 79dc7b6..19d47b7 100644 --- a/src/docs/readme-generator.ts +++ b/src/docs/readme-generator.ts @@ -24,8 +24,7 @@ function generateBadges(config: ProjectConfig): string { const stylingBadges: Record = { tailwind: '![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-06B6D4?style=flat&logo=tailwindcss&logoColor=white)', 'styled-components': '![styled-components](https://img.shields.io/badge/styled--components-DB7093?style=flat&logo=styled-components&logoColor=white)', - 'css-modules': '![CSS Modules](https://img.shields.io/badge/CSS_Modules-1572B6?style=flat&logo=css3&logoColor=white)', - css: '![CSS](https://img.shields.io/badge/CSS-1572B6?style=flat&logo=css3&logoColor=white)', + none: '![CSS](https://img.shields.io/badge/CSS-1572B6?style=flat&logo=css3&logoColor=white)', }; if (stylingBadges[config.styling.solution]) { badges.push(stylingBadges[config.styling.solution]); @@ -203,7 +202,6 @@ function generateDocLinks(config: ProjectConfig): string { const stylingDocs: Record = { tailwind: '- [Tailwind CSS Documentation](https://tailwindcss.com/docs)', 'styled-components': '- [styled-components Documentation](https://styled-components.com/docs)', - 'css-modules': '- [CSS Modules](https://github.com/css-modules/css-modules)', }; if (stylingDocs[config.styling.solution]) { links.push(stylingDocs[config.styling.solution]); diff --git a/src/index.ts b/src/index.ts index bb1cea6..5192fbb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,3 +10,4 @@ main().catch((error) => { + diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 1020e59..6452453 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -2,3 +2,4 @@ export * from './types.js'; export * from './loader.js'; export * from './manager.js'; + diff --git a/src/templates/overlays/base/manifest.json b/src/templates/overlays/base/manifest.json index e77a94a..6b6b720 100644 --- a/src/templates/overlays/base/manifest.json +++ b/src/templates/overlays/base/manifest.json @@ -4,13 +4,14 @@ "description": "Base shared utilities, types, and hooks following bulletproof-react patterns", "compatibleWith": ["runtime-vite", "runtime-nextjs"], "dependencies": { - "clsx": "^2.1.0" + "styled-components": "^6.1.14" + }, + "devDependencies": { + "@types/styled-components": "^5.1.34" }, - "devDependencies": {}, "scripts": {}, "filePatterns": { "include": ["**/*"], "exclude": ["manifest.json"] } } - diff --git a/src/templates/overlays/base/src/components/ui/Button.tsx b/src/templates/overlays/base/src/components/ui/Button.tsx index 12e754d..f39f7b4 100644 --- a/src/templates/overlays/base/src/components/ui/Button.tsx +++ b/src/templates/overlays/base/src/components/ui/Button.tsx @@ -1,5 +1,5 @@ import { forwardRef } from 'react'; -import { cn } from '@/lib/utils'; +import styled, { css } from 'styled-components'; export type ButtonProps = React.ButtonHTMLAttributes & { variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'; @@ -7,29 +7,110 @@ export type ButtonProps = React.ButtonHTMLAttributes & { isLoading?: boolean; }; -const variants = { - primary: - 'bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:outline-indigo-600', - secondary: - 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:outline-gray-600', - outline: - 'border border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 focus-visible:outline-gray-600', - ghost: - 'bg-transparent text-gray-700 hover:bg-gray-100 focus-visible:outline-gray-600', - danger: - 'bg-red-600 text-white hover:bg-red-500 focus-visible:outline-red-600', +const variantStyles = { + primary: css` + background-color: #4f46e5; + color: white; + &:hover:not(:disabled) { + background-color: #6366f1; + } + `, + secondary: css` + background-color: #f3f4f6; + color: #111827; + &:hover:not(:disabled) { + background-color: #e5e7eb; + } + `, + outline: css` + background-color: transparent; + color: #374151; + border: 1px solid #d1d5db; + &:hover:not(:disabled) { + background-color: #f9fafb; + } + `, + ghost: css` + background-color: transparent; + color: #374151; + &:hover:not(:disabled) { + background-color: #f3f4f6; + } + `, + danger: css` + background-color: #dc2626; + color: white; + &:hover:not(:disabled) { + background-color: #ef4444; + } + `, }; -const sizes = { - sm: 'px-2.5 py-1.5 text-xs', - md: 'px-3.5 py-2.5 text-sm', - lg: 'px-4 py-3 text-base', +const sizeStyles = { + sm: css` + padding: 0.375rem 0.625rem; + font-size: 0.75rem; + `, + md: css` + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + `, + lg: css` + padding: 0.75rem 1rem; + font-size: 1rem; + `, }; +const StyledButton = styled.button<{ + $variant: ButtonProps['variant']; + $size: ButtonProps['size']; +}>` + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + font-weight: 600; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s, color 0.2s; + border: none; + cursor: pointer; + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } + + ${(props) => variantStyles[props.$variant || 'primary']} + ${(props) => sizeStyles[props.$size || 'md']} +`; + +const LoadingSpinner = styled.span` + margin-right: 0.5rem; + width: 1rem; + height: 1rem; + border: 2px solid currentColor; + border-top-color: transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } +`; + export const Button = forwardRef( ( { - className, variant = 'primary', size = 'md', isLoading = false, @@ -40,27 +121,18 @@ export const Button = forwardRef( ref ) => { return ( - + ); } ); Button.displayName = 'Button'; - diff --git a/src/templates/overlays/base/src/components/ui/Input.tsx b/src/templates/overlays/base/src/components/ui/Input.tsx index 75af930..2d8dbc3 100644 --- a/src/templates/overlays/base/src/components/ui/Input.tsx +++ b/src/templates/overlays/base/src/components/ui/Input.tsx @@ -1,51 +1,77 @@ import { forwardRef } from 'react'; -import { cn } from '@/lib/utils'; +import styled from 'styled-components'; export type InputProps = React.InputHTMLAttributes & { label?: string; error?: string; }; +const InputWrapper = styled.div` + width: 100%; +`; + +const Label = styled.label` + display: block; + margin-bottom: 0.25rem; + font-size: 0.875rem; + font-weight: 500; + color: #374151; +`; + +const StyledInput = styled.input<{ $hasError?: boolean }>` + display: block; + width: 100%; + padding: 0.5rem 0.75rem; + font-size: 1rem; + color: #111827; + background-color: white; + border: 1px solid ${(props) => (props.$hasError ? '#fca5a5' : '#d1d5db')}; + border-radius: 0.375rem; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: border-color 0.2s, box-shadow 0.2s; + + &::placeholder { + color: #9ca3af; + } + + &:focus { + outline: none; + border-color: ${(props) => (props.$hasError ? '#ef4444' : '#4f46e5')}; + box-shadow: 0 0 0 2px ${(props) => (props.$hasError ? '#fecaca' : '#c7d2fe')}; + } + + &:disabled { + cursor: not-allowed; + background-color: #f9fafb; + color: #6b7280; + } +`; + +const ErrorMessage = styled.p` + margin-top: 0.25rem; + font-size: 0.875rem; + color: #dc2626; +`; + export const Input = forwardRef( - ({ className, label, error, id, ...props }, ref) => { + ({ label, error, id, ...props }, ref) => { const inputId = id || props.name; return ( -
- {label && ( - - )} - + {label && } + - {error && ( -

- {error} -

- )} -
+ {error && {error}} + ); } ); Input.displayName = 'Input'; - diff --git a/src/templates/overlays/base/src/lib/utils.ts b/src/templates/overlays/base/src/lib/utils.ts index 5496507..2f549df 100644 --- a/src/templates/overlays/base/src/lib/utils.ts +++ b/src/templates/overlays/base/src/lib/utils.ts @@ -1,12 +1,3 @@ -import { clsx, type ClassValue } from 'clsx'; - -/** - * Utility function for constructing className strings conditionally - */ -export function cn(...inputs: ClassValue[]) { - return clsx(inputs); -} - /** * Format a date to a human-readable string */ @@ -31,4 +22,3 @@ export function sleep(ms: number): Promise { export function generateId(): string { return Math.random().toString(36).substring(2, 9); } - diff --git a/src/templates/overlays/runtime/nextjs/src/app/error.tsx b/src/templates/overlays/runtime/nextjs/src/app/error.tsx index c676d5a..2106e5e 100644 --- a/src/templates/overlays/runtime/nextjs/src/app/error.tsx +++ b/src/templates/overlays/runtime/nextjs/src/app/error.tsx @@ -2,6 +2,40 @@ import { useEffect } from 'react'; +const styles = { + container: { + display: 'flex', + minHeight: '100vh', + flexDirection: 'column' as const, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + textAlign: 'center' as const, + }, + title: { + fontSize: '1.5rem', + fontWeight: 700, + color: '#dc2626', + }, + message: { + marginTop: '1rem', + color: '#4b5563', + }, + retryButton: { + marginTop: '1.5rem', + padding: '0.625rem 0.875rem', + fontSize: '0.875rem', + fontWeight: 600, + color: 'white', + backgroundColor: '#4f46e5', + border: 'none', + borderRadius: '0.375rem', + boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)', + cursor: 'pointer', + }, +}; + export default function Error({ error, reset, @@ -10,25 +44,20 @@ export default function Error({ reset: () => void; }) { useEffect(() => { - // Log the error to an error reporting service console.error(error); }, [error]); return ( -
-
-

Something went wrong

-

+

+
+

Something went wrong

+

{error.message || 'An unexpected error occurred'}

-
); } - diff --git a/src/templates/overlays/runtime/nextjs/src/app/loading.tsx b/src/templates/overlays/runtime/nextjs/src/app/loading.tsx index 643816f..a6e60c2 100644 --- a/src/templates/overlays/runtime/nextjs/src/app/loading.tsx +++ b/src/templates/overlays/runtime/nextjs/src/app/loading.tsx @@ -1,12 +1,30 @@ +const styles = { + container: { + display: 'flex', + minHeight: '100vh', + alignItems: 'center', + justifyContent: 'center', + }, + spinner: { + width: '2rem', + height: '2rem', + border: '2px solid #d1d5db', + borderTopColor: '#4f46e5', + borderRadius: '50%', + animation: 'spin 1s linear infinite', + }, +}; + export default function Loading() { return ( -
-
+
+ +
); } - diff --git a/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx b/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx index 3f877e7..a64fea7 100644 --- a/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx +++ b/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx @@ -1,21 +1,63 @@ import Link from 'next/link'; +const styles = { + container: { + display: 'flex', + minHeight: '100vh', + flexDirection: 'column' as const, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + textAlign: 'center' as const, + }, + errorCode: { + fontSize: '1rem', + fontWeight: 600, + color: '#4f46e5', + }, + title: { + marginTop: '1rem', + fontSize: '1.875rem', + fontWeight: 700, + letterSpacing: '-0.025em', + }, + description: { + marginTop: '1.5rem', + fontSize: '1rem', + lineHeight: '1.75rem', + color: '#4b5563', + }, + actions: { + marginTop: '2.5rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '1.5rem', + }, + homeLink: { + padding: '0.625rem 0.875rem', + fontSize: '0.875rem', + fontWeight: 600, + color: 'white', + backgroundColor: '#4f46e5', + borderRadius: '0.375rem', + textDecoration: 'none', + boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)', + }, +}; + export default function NotFound() { return ( -
-
-

404

-

- Page not found -

-

+

+
+

404

+

Page not found

+

Sorry, we couldn't find the page you're looking for.

-
- +
+ Go back home
@@ -23,4 +65,3 @@ export default function NotFound() {
); } - diff --git a/src/templates/overlays/runtime/nextjs/src/app/page.tsx b/src/templates/overlays/runtime/nextjs/src/app/page.tsx index 846753b..a6514c6 100644 --- a/src/templates/overlays/runtime/nextjs/src/app/page.tsx +++ b/src/templates/overlays/runtime/nextjs/src/app/page.tsx @@ -1,25 +1,68 @@ import Link from 'next/link'; +const styles = { + container: { + display: 'flex', + minHeight: '100vh', + flexDirection: 'column' as const, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + textAlign: 'center' as const, + }, + title: { + fontSize: '2.25rem', + fontWeight: 700, + letterSpacing: '-0.025em', + }, + description: { + marginTop: '1.5rem', + fontSize: '1.125rem', + lineHeight: '2rem', + color: '#4b5563', + }, + actions: { + marginTop: '2.5rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '1.5rem', + }, + primaryLink: { + padding: '0.625rem 0.875rem', + fontSize: '0.875rem', + fontWeight: 600, + color: 'white', + backgroundColor: '#4f46e5', + borderRadius: '0.375rem', + textDecoration: 'none', + boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)', + }, + secondaryLink: { + fontSize: '0.875rem', + fontWeight: 600, + lineHeight: '1.5rem', + color: 'inherit', + textDecoration: 'none', + }, +}; + export default function HomePage() { return ( -
-
-

- Welcome to Your App -

-

+

+
+

Welcome to Your App

+

A production-ready Next.js application scaffolded with create-react-forge.

-
- + ); } - diff --git a/src/templates/overlays/runtime/nextjs/src/styles/globals.css b/src/templates/overlays/runtime/nextjs/src/styles/globals.css index d9eb156..96d0ab3 100644 --- a/src/templates/overlays/runtime/nextjs/src/styles/globals.css +++ b/src/templates/overlays/runtime/nextjs/src/styles/globals.css @@ -1,4 +1,4 @@ -/* Base styles - will be enhanced by styling overlay (tailwind/css-modules) */ +/* Base styles - will be enhanced by styling overlay (tailwind) if selected */ *, *::before, diff --git a/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx index 571d760..ff7bb5f 100644 --- a/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx @@ -1,21 +1,55 @@ import { FallbackProps } from 'react-error-boundary'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 1.5rem; + font-weight: 700; + color: #dc2626; +`; + +const Message = styled.p` + margin-top: 1rem; + color: #4b5563; +`; + +const RetryButton = styled.button` + margin-top: 1.5rem; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + background-color: #4f46e5; + border: none; + border-radius: 0.375rem; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } +`; export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { return ( -
-
-

Something went wrong

-

- {error.message || 'An unexpected error occurred'} -

- -
-
+ + + Something went wrong + {error.message || 'An unexpected error occurred'} + Try again + + ); } - diff --git a/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx index 6f3db8a..5a5f74e 100644 --- a/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx @@ -1,23 +1,43 @@ +import styled, { keyframes } from 'styled-components'; + type LoadingSpinnerProps = { size?: 'sm' | 'md' | 'lg'; - className?: string; }; +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + const sizes = { - sm: 'h-4 w-4', - md: 'h-8 w-8', - lg: 'h-16 w-16', + sm: '1rem', + md: '2rem', + lg: '4rem', }; -export function LoadingSpinner({ size = 'md', className = '' }: LoadingSpinnerProps) { +const SpinnerContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; +`; + +const Spinner = styled.div<{ $size: 'sm' | 'md' | 'lg' }>` + width: ${(props) => sizes[props.$size]}; + height: ${(props) => sizes[props.$size]}; + border: 2px solid #d1d5db; + border-top-color: #4f46e5; + border-radius: 50%; + animation: ${spin} 1s linear infinite; +`; + +export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { return ( -
-
-
+ + + ); } - diff --git a/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx index f192b91..e4340fa 100644 --- a/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx @@ -1,33 +1,90 @@ import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + + @media (min-width: 640px) { + font-size: 3.75rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: #4b5563; +`; + +const Actions = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryLink = styled(Link)` + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + background-color: #4f46e5; + border-radius: 0.375rem; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + text-decoration: none; + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } +`; + +const SecondaryLink = styled.a` + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: inherit; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +`; export function Landing() { return ( -
+ + + + ); } - diff --git a/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx index b7c5d4c..7839f91 100644 --- a/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx @@ -1,26 +1,84 @@ import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const ErrorCode = styled.p` + font-size: 1rem; + font-weight: 600; + color: #4f46e5; +`; + +const Title = styled.h1` + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + + @media (min-width: 640px) { + font-size: 3rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: #4b5563; +`; + +const Actions = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const HomeLink = styled(Link)` + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + background-color: #4f46e5; + border-radius: 0.375rem; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + text-decoration: none; + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; export function NotFound() { return ( -
-
-

404

-

- Page not found -

-

+ + + 404 + Page not found + Sorry, we couldn't find the page you're looking for. -

-
- - Go back home - -
-
-
+ + + Go back home + + + ); } - diff --git a/src/templates/overlays/runtime/vite/src/main.tsx b/src/templates/overlays/runtime/vite/src/main.tsx index 807ad1b..fc10c03 100644 --- a/src/templates/overlays/runtime/vite/src/main.tsx +++ b/src/templates/overlays/runtime/vite/src/main.tsx @@ -1,5 +1,4 @@ import { App } from '@/app/App'; -import '@/styles/globals.css'; import React from 'react'; import ReactDOM from 'react-dom/client'; @@ -8,4 +7,3 @@ ReactDOM.createRoot(document.getElementById('root')!).render( ); - diff --git a/src/templates/overlays/runtime/vite/src/styles/globals.css b/src/templates/overlays/runtime/vite/src/styles/globals.css deleted file mode 100644 index 7ef60d4..0000000 --- a/src/templates/overlays/runtime/vite/src/styles/globals.css +++ /dev/null @@ -1,55 +0,0 @@ -/* Base styles - will be enhanced by styling overlay (tailwind/css-modules) */ - -*, -*::before, -*::after { - box-sizing: border-box; -} - -* { - margin: 0; -} - -html, -body { - height: 100%; -} - -body { - line-height: 1.5; - -webkit-font-smoothing: antialiased; - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - 'Helvetica Neue', Arial, sans-serif; -} - -img, -picture, -video, -canvas, -svg { - display: block; - max-width: 100%; -} - -input, -button, -textarea, -select { - font: inherit; -} - -p, -h1, -h2, -h3, -h4, -h5, -h6 { - overflow-wrap: break-word; -} - -#root { - isolation: isolate; - height: 100%; -} - diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx new file mode 100644 index 0000000..00df953 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx @@ -0,0 +1,11 @@ +import styles from './loading.module.css'; + +export default function Loading() { + return ( +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css new file mode 100644 index 0000000..d74fd9f --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css @@ -0,0 +1,71 @@ +.container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.content { + text-align: center; +} + +.title { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .title { + font-size: 3.75rem; + } +} + +.description { + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: var(--color-text-muted); +} + +.buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.primaryBtn { + display: inline-block; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; +} + +.primaryBtn:hover { + background-color: var(--color-primary-hover); +} + +.linkBtn { + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: var(--color-text); + text-decoration: none; + transition: color 0.2s; +} + +.linkBtn:hover { + color: var(--color-primary); +} + + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx new file mode 100644 index 0000000..4c473db --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx @@ -0,0 +1,30 @@ +import Link from 'next/link'; +import styles from './page.module.css'; + +export default function HomePage() { + return ( +
+
+

Welcome to Your App

+

+ A production-ready Next.js application scaffolded with create-react-forge. +

+
+ + Get started + + + Learn more + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css new file mode 100644 index 0000000..e581112 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css @@ -0,0 +1,43 @@ +.container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.content { + text-align: center; +} + +.title { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-danger); +} + +.message { + margin-top: 1rem; + color: var(--color-text-muted); +} + +.button { + margin-top: 1.5rem; + display: inline-block; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + border: none; + cursor: pointer; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; +} + +.button:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx new file mode 100644 index 0000000..ff8d3d7 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx @@ -0,0 +1,20 @@ +import { FallbackProps } from 'react-error-boundary'; +import styles from './ErrorFallback.module.css'; + +export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + return ( +
+
+

Something went wrong

+

+ {error.message || 'An unexpected error occurred'} +

+ +
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css new file mode 100644 index 0000000..73be275 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css @@ -0,0 +1,38 @@ +.container { + display: flex; + align-items: center; + justify-content: center; +} + +.spinner { + border-radius: 50%; + border: 2px solid var(--color-border); + border-top-color: var(--color-primary); + animation: spin 1s linear infinite; +} + +.sm { + width: 1rem; + height: 1rem; +} + +.md { + width: 2rem; + height: 2rem; +} + +.lg { + width: 4rem; + height: 4rem; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..4333ee9 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx @@ -0,0 +1,21 @@ +import styles from './LoadingSpinner.module.css'; + +type LoadingSpinnerProps = { + size?: 'sm' | 'md' | 'lg'; +}; + +export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + const sizeClass = styles[size] || styles.md; + + return ( +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css new file mode 100644 index 0000000..d74fd9f --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css @@ -0,0 +1,71 @@ +.container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.content { + text-align: center; +} + +.title { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .title { + font-size: 3.75rem; + } +} + +.description { + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: var(--color-text-muted); +} + +.buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.primaryBtn { + display: inline-block; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; +} + +.primaryBtn:hover { + background-color: var(--color-primary-hover); +} + +.linkBtn { + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: var(--color-text); + text-decoration: none; + transition: color 0.2s; +} + +.linkBtn:hover { + color: var(--color-primary); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx new file mode 100644 index 0000000..53894d4 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx @@ -0,0 +1,30 @@ +import { Link } from 'react-router-dom'; +import styles from './Landing.module.css'; + +export function Landing() { + return ( +
+
+

Welcome to Your App

+

+ A production-ready React application scaffolded with create-react-forge. +

+
+ + Get started + + + Learn more + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css new file mode 100644 index 0000000..76cd189 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css @@ -0,0 +1,65 @@ +.container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.content { + text-align: center; +} + +.code { + font-size: 1rem; + font-weight: 600; + color: var(--color-primary); +} + +.title { + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .title { + font-size: 3rem; + } +} + +.description { + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: var(--color-text-muted); +} + +.buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.primaryBtn { + display: inline-block; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; +} + +.primaryBtn:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx new file mode 100644 index 0000000..0cb3541 --- /dev/null +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx @@ -0,0 +1,23 @@ +import { Link } from 'react-router-dom'; +import styles from './NotFound.module.css'; + +export function NotFound() { + return ( +
+
+

404

+

Page not found

+

+ Sorry, we couldn't find the page you're looking for. +

+
+ + Go back home + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css-modules/manifest.json b/src/templates/overlays/styling/css-modules/manifest.json deleted file mode 100644 index 22cedee..0000000 --- a/src/templates/overlays/styling/css-modules/manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "styling-css-modules", - "version": "1.0.0", - "description": "CSS Modules styling with scoped CSS", - "compatibleWith": ["runtime-vite", "runtime-nextjs"], - "dependencies": {}, - "devDependencies": {}, - "scripts": {}, - "filePatterns": { - "include": ["**/*"], - "exclude": ["manifest.json"] - } -} - diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css b/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css deleted file mode 100644 index 5b4c8ef..0000000 --- a/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css +++ /dev/null @@ -1,87 +0,0 @@ -.button { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-md); - font-weight: 600; - transition: all 0.15s ease; - cursor: pointer; - border: none; -} - -.button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* Variants */ -.primary { - background-color: var(--color-primary-600); - color: white; -} - -.primary:hover:not(:disabled) { - background-color: var(--color-primary-500); -} - -.secondary { - background-color: var(--color-gray-100); - color: var(--color-gray-900); -} - -.secondary:hover:not(:disabled) { - background-color: var(--color-gray-200); -} - -.outline { - background-color: transparent; - border: 1px solid var(--color-gray-300); - color: var(--color-gray-700); -} - -.outline:hover:not(:disabled) { - background-color: var(--color-gray-50); -} - -.danger { - background-color: var(--color-red-600); - color: white; -} - -.danger:hover:not(:disabled) { - background-color: var(--color-red-500); -} - -/* Sizes */ -.sm { - padding: var(--spacing-1) var(--spacing-2); - font-size: 0.75rem; -} - -.md { - padding: var(--spacing-2) var(--spacing-4); - font-size: 0.875rem; -} - -.lg { - padding: var(--spacing-3) var(--spacing-4); - font-size: 1rem; -} - -/* Loading spinner */ -.spinner { - width: 1rem; - height: 1rem; - margin-right: var(--spacing-2); - border: 2px solid currentColor; - border-top-color: transparent; - border-radius: var(--radius-full); - animation: spin 0.6s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx b/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx deleted file mode 100644 index 8988c39..0000000 --- a/src/templates/overlays/styling/css-modules/src/components/ui/Button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { forwardRef } from 'react'; -import styles from './Button.module.css'; - -export type ButtonProps = React.ButtonHTMLAttributes & { - variant?: 'primary' | 'secondary' | 'outline' | 'danger'; - size?: 'sm' | 'md' | 'lg'; - isLoading?: boolean; -}; - -export const Button = forwardRef( - ( - { - className, - variant = 'primary', - size = 'md', - isLoading = false, - disabled, - children, - ...props - }, - ref - ) => { - const classNames = [ - styles.button, - styles[variant], - styles[size], - className, - ] - .filter(Boolean) - .join(' '); - - return ( - - ); - } -); - -Button.displayName = 'Button'; - diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css b/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css deleted file mode 100644 index ead2ed0..0000000 --- a/src/templates/overlays/styling/css-modules/src/components/ui/Input.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.container { - width: 100%; -} - -.label { - display: block; - margin-bottom: var(--spacing-1); - font-size: 0.875rem; - font-weight: 500; - color: var(--color-gray-700); -} - -.input { - display: block; - width: 100%; - padding: var(--spacing-2) var(--spacing-3); - font-size: 0.875rem; - line-height: 1.5; - color: var(--color-gray-900); - background-color: white; - border: 1px solid var(--color-gray-300); - border-radius: var(--radius-md); - box-shadow: var(--shadow-sm); - transition: border-color 0.15s ease, box-shadow 0.15s ease; -} - -.input::placeholder { - color: var(--color-gray-400); -} - -.input:focus { - outline: none; - border-color: var(--color-primary-500); - box-shadow: 0 0 0 2px var(--color-primary-100); -} - -.input:disabled { - cursor: not-allowed; - background-color: var(--color-gray-50); - color: var(--color-gray-500); -} - -.inputError { - border-color: var(--color-red-500); -} - -.inputError:focus { - border-color: var(--color-red-500); - box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); -} - -.error { - margin-top: var(--spacing-1); - font-size: 0.875rem; - color: var(--color-red-600); -} - diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx b/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx deleted file mode 100644 index fb88e26..0000000 --- a/src/templates/overlays/styling/css-modules/src/components/ui/Input.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { forwardRef } from 'react'; -import styles from './Input.module.css'; - -export type InputProps = React.InputHTMLAttributes & { - label?: string; - error?: string; -}; - -export const Input = forwardRef( - ({ className, label, error, id, ...props }, ref) => { - const inputId = id || props.name; - - const inputClassNames = [ - styles.input, - error && styles.inputError, - className, - ] - .filter(Boolean) - .join(' '); - - return ( -
- {label && ( - - )} - - {error && ( -

- {error} -

- )} -
- ); - } -); - -Input.displayName = 'Input'; - diff --git a/src/templates/overlays/styling/css-modules/src/components/ui/index.ts b/src/templates/overlays/styling/css-modules/src/components/ui/index.ts deleted file mode 100644 index 143c361..0000000 --- a/src/templates/overlays/styling/css-modules/src/components/ui/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Button, type ButtonProps } from './Button'; -export { Input, type InputProps } from './Input'; - diff --git a/src/templates/overlays/styling/css-modules/src/css.d.ts b/src/templates/overlays/styling/css-modules/src/css.d.ts index 93aa10d..8ce4b74 100644 --- a/src/templates/overlays/styling/css-modules/src/css.d.ts +++ b/src/templates/overlays/styling/css-modules/src/css.d.ts @@ -8,3 +8,4 @@ declare module '*.css' { export default content; } + diff --git a/src/templates/overlays/styling/css-modules/src/styles/globals.css b/src/templates/overlays/styling/css-modules/src/styles/globals.css deleted file mode 100644 index eedf94b..0000000 --- a/src/templates/overlays/styling/css-modules/src/styles/globals.css +++ /dev/null @@ -1,91 +0,0 @@ -/* CSS Custom Properties (Design Tokens) */ -:root { - /* Colors */ - --color-primary-50: #eff6ff; - --color-primary-100: #dbeafe; - --color-primary-500: #3b82f6; - --color-primary-600: #2563eb; - --color-primary-700: #1d4ed8; - - --color-gray-50: #f9fafb; - --color-gray-100: #f3f4f6; - --color-gray-200: #e5e7eb; - --color-gray-300: #d1d5db; - --color-gray-400: #9ca3af; - --color-gray-500: #6b7280; - --color-gray-600: #4b5563; - --color-gray-700: #374151; - --color-gray-800: #1f2937; - --color-gray-900: #111827; - - --color-red-500: #ef4444; - --color-red-600: #dc2626; - - /* Typography */ - --font-family-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', - Roboto, 'Helvetica Neue', Arial, sans-serif; - --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - monospace; - - /* Spacing */ - --spacing-1: 0.25rem; - --spacing-2: 0.5rem; - --spacing-3: 0.75rem; - --spacing-4: 1rem; - --spacing-6: 1.5rem; - --spacing-8: 2rem; - - /* Border Radius */ - --radius-sm: 0.25rem; - --radius-md: 0.375rem; - --radius-lg: 0.5rem; - --radius-full: 9999px; - - /* Shadows */ - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); -} - -/* Reset */ -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body { - font-family: var(--font-family-sans); - line-height: 1.5; - color: var(--color-gray-900); - background-color: white; -} - -img, -picture, -video, -canvas, -svg { - display: block; - max-width: 100%; -} - -input, -button, -textarea, -select { - font: inherit; -} - -/* Focus styles */ -:focus-visible { - outline: 2px solid var(--color-primary-500); - outline-offset: 2px; -} - diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.css b/src/templates/overlays/styling/css/_nextjs/src/app/error.css new file mode 100644 index 0000000..d4a2173 --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.css @@ -0,0 +1,47 @@ +.error-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.error-content { + text-align: center; +} + +.error-title { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-danger); +} + +.error-message { + margin-top: 1rem; + color: var(--color-text-muted); +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; + border: none; + cursor: pointer; +} + +.btn-primary { + margin-top: 1.5rem; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx new file mode 100644 index 0000000..3971a09 --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { useEffect } from 'react'; +import './error.css'; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+
+

Something went wrong

+

+ {error.message || 'An unexpected error occurred'} +

+ +
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.css b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css new file mode 100644 index 0000000..b430c7c --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css @@ -0,0 +1,26 @@ +.spinner-container { + display: flex; + min-height: 100vh; + align-items: center; + justify-content: center; +} + +.spinner { + width: 2rem; + height: 2rem; + border-radius: 50%; + border: 2px solid var(--color-border); + border-top-color: var(--color-primary); + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx new file mode 100644 index 0000000..cdda92d --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx @@ -0,0 +1,11 @@ +import './loading.css'; + +export default function Loading() { + return ( +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css new file mode 100644 index 0000000..5f8d4c7 --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css @@ -0,0 +1,68 @@ +.not-found-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.not-found-content { + text-align: center; +} + +.not-found-code { + font-size: 1rem; + font-weight: 600; + color: var(--color-primary); +} + +.not-found-title { + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .not-found-title { + font-size: 3rem; + } +} + +.not-found-description { + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: var(--color-text-muted); +} + +.not-found-buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; +} + +.btn-primary { + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx new file mode 100644 index 0000000..a826547 --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx @@ -0,0 +1,23 @@ +import Link from 'next/link'; +import './not-found.css'; + +export default function NotFound() { + return ( +
+
+

404

+

Page not found

+

+ Sorry, we couldn't find the page you're looking for. +

+
+ + Go back home + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.css b/src/templates/overlays/styling/css/_nextjs/src/app/page.css new file mode 100644 index 0000000..c242f3a --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.css @@ -0,0 +1,71 @@ +.landing-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.landing-content { + text-align: center; +} + +.landing-title { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .landing-title { + font-size: 3.75rem; + } +} + +.landing-description { + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: var(--color-text-muted); +} + +.landing-buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; +} + +.btn-primary { + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + +.btn-link { + font-size: 0.875rem; + line-height: 1.5rem; + color: var(--color-text); +} + +.btn-link:hover { + color: var(--color-primary); +} + + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx new file mode 100644 index 0000000..4357d30 --- /dev/null +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx @@ -0,0 +1,30 @@ +import Link from 'next/link'; +import './page.css'; + +export default function HomePage() { + return ( +
+
+

Welcome to Your App

+

+ A production-ready Next.js application scaffolded with create-react-forge. +

+
+ + Get started + + + Learn more + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css new file mode 100644 index 0000000..d4a2173 --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css @@ -0,0 +1,47 @@ +.error-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.error-content { + text-align: center; +} + +.error-title { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-danger); +} + +.error-message { + margin-top: 1rem; + color: var(--color-text-muted); +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; + border: none; + cursor: pointer; +} + +.btn-primary { + margin-top: 1.5rem; + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx new file mode 100644 index 0000000..9d5ad26 --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx @@ -0,0 +1,20 @@ +import { FallbackProps } from 'react-error-boundary'; +import './ErrorFallback.css'; + +export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + return ( +
+
+

Something went wrong

+

+ {error.message || 'An unexpected error occurred'} +

+ +
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css new file mode 100644 index 0000000..d8c20db --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css @@ -0,0 +1,38 @@ +.spinner-container { + display: flex; + align-items: center; + justify-content: center; +} + +.spinner { + border-radius: 50%; + border: 2px solid var(--color-border); + border-top-color: var(--color-primary); + animation: spin 1s linear infinite; +} + +.spinner-sm { + width: 1rem; + height: 1rem; +} + +.spinner-md { + width: 2rem; + height: 2rem; +} + +.spinner-lg { + width: 4rem; + height: 4rem; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..1a75019 --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx @@ -0,0 +1,19 @@ +import './LoadingSpinner.css'; + +type LoadingSpinnerProps = { + size?: 'sm' | 'md' | 'lg'; +}; + +export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + return ( +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css new file mode 100644 index 0000000..c242f3a --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css @@ -0,0 +1,71 @@ +.landing-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.landing-content { + text-align: center; +} + +.landing-title { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .landing-title { + font-size: 3.75rem; + } +} + +.landing-description { + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: var(--color-text-muted); +} + +.landing-buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; +} + +.btn-primary { + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + +.btn-link { + font-size: 0.875rem; + line-height: 1.5rem; + color: var(--color-text); +} + +.btn-link:hover { + color: var(--color-primary); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx new file mode 100644 index 0000000..38d8001 --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx @@ -0,0 +1,30 @@ +import { Link } from 'react-router-dom'; +import './Landing.css'; + +export function Landing() { + return ( +
+
+

Welcome to Your App

+

+ A production-ready React application scaffolded with create-react-forge. +

+
+ + Get started + + + Learn more + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css new file mode 100644 index 0000000..5f8d4c7 --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css @@ -0,0 +1,68 @@ +.not-found-container { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.not-found-content { + text-align: center; +} + +.not-found-code { + font-size: 1rem; + font-weight: 600; + color: var(--color-primary); +} + +.not-found-title { + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text); +} + +@media (min-width: 640px) { + .not-found-title { + font-size: 3rem; + } +} + +.not-found-description { + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: var(--color-text-muted); +} + +.not-found-buttons { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.btn { + display: inline-block; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; +} + +.btn-primary { + border-radius: var(--radius-md); + background-color: var(--color-primary); + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + color: white; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); +} + +.btn-primary:hover { + background-color: var(--color-primary-hover); +} + + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx new file mode 100644 index 0000000..968492b --- /dev/null +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx @@ -0,0 +1,23 @@ +import { Link } from 'react-router-dom'; +import './NotFound.css'; + +export function NotFound() { + return ( +
+
+

404

+

Page not found

+

+ Sorry, we couldn't find the page you're looking for. +

+
+ + Go back home + +
+
+
+ ); +} + + diff --git a/src/templates/overlays/styling/css/manifest.json b/src/templates/overlays/styling/css/manifest.json new file mode 100644 index 0000000..06c5b1a --- /dev/null +++ b/src/templates/overlays/styling/css/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "styling-css", + "version": "1.0.0", + "description": "Plain CSS styling solution", + "compatibleWith": ["runtime-vite"], + "dependencies": {}, + "devDependencies": {}, + "scripts": {}, + "filePatterns": { + "include": ["**/*"], + "exclude": ["manifest.json", "_vite"] + }, + "runtimeOverrides": { + "vite": "_vite" + } +} + diff --git a/src/templates/overlays/styling/css/src/styles/globals.css b/src/templates/overlays/styling/css/src/styles/globals.css new file mode 100644 index 0000000..a8719a6 --- /dev/null +++ b/src/templates/overlays/styling/css/src/styles/globals.css @@ -0,0 +1,105 @@ +/* CSS Reset and Base Styles */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* Colors */ + --color-primary: #4f46e5; + --color-primary-hover: #6366f1; + --color-secondary: #64748b; + --color-success: #22c55e; + --color-warning: #f59e0b; + --color-danger: #ef4444; + --color-text: #111827; + --color-text-muted: #4b5563; + --color-background: #ffffff; + --color-border: #e5e7eb; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 2.5rem; + + /* Border Radius */ + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-full: 9999px; + + /* Font */ + --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace; +} + +html { + font-family: var(--font-sans); + line-height: 1.5; + -webkit-text-size-adjust: 100%; +} + +body { + color: var(--color-text); + background-color: var(--color-background); +} + +a { + color: inherit; + text-decoration: none; +} + +button { + font-family: inherit; + font-size: inherit; + cursor: pointer; +} + +/* Utility Classes */ +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.text-center { + text-align: center; +} + +.min-h-screen { + min-height: 100vh; +} + +.gap-6 { + gap: var(--spacing-lg); +} + +.mt-4 { + margin-top: var(--spacing-md); +} + +.mt-6 { + margin-top: var(--spacing-lg); +} + +.mt-10 { + margin-top: var(--spacing-2xl); +} + + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx new file mode 100644 index 0000000..8f916fe --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx @@ -0,0 +1,70 @@ +'use client'; + +import { useEffect } from 'react'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 1.5rem; + font-weight: 700; + color: #dc2626; +`; + +const Message = styled.p` + margin-top: 1rem; + color: #4b5563; +`; + +const RetryButton = styled.button` + margin-top: 1.5rem; + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + border: none; + cursor: pointer; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } +`; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error(error); + }, [error]); + + return ( + + + Something went wrong + {error.message || 'An unexpected error occurred'} + Try again + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx index ba08e45..8490b19 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx @@ -20,3 +20,4 @@ export default function RootLayout({ ); } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx new file mode 100644 index 0000000..43623f7 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx @@ -0,0 +1,38 @@ +'use client'; + +import styled, { keyframes } from 'styled-components'; + +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +const Container = styled.div` + display: flex; + min-height: 100vh; + align-items: center; + justify-content: center; +`; + +const Spinner = styled.div` + width: 2rem; + height: 2rem; + border-radius: 50%; + border: 2px solid #d1d5db; + border-top-color: #4f46e5; + animation: ${spin} 1s linear infinite; +`; + +export default function Loading() { + return ( + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx new file mode 100644 index 0000000..1419c03 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx @@ -0,0 +1,89 @@ +'use client'; + +import Link from 'next/link'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const ErrorCode = styled.p` + font-size: 1rem; + font-weight: 600; + color: #4f46e5; +`; + +const Title = styled.h1` + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +export default function NotFound() { + return ( + + + 404 + Page not found + + Sorry, we couldn't find the page you're looking for. + + + Go back home + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx new file mode 100644 index 0000000..1208d7a --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx @@ -0,0 +1,100 @@ +'use client'; + +import Link from 'next/link'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3.75rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +const SecondaryLink = styled.a` + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: #111827; + text-decoration: none; + + &:hover { + color: #4f46e5; + } +`; + +export default function HomePage() { + return ( + + + Welcome to Your App + + A production-ready Next.js application scaffolded with create-react-forge. + + + Get started + + Learn more + + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx index 2448497..c962505 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx @@ -1,8 +1,8 @@ 'use client'; -import { ReactNode } from 'react'; import StyledComponentsRegistry from '@/lib/StyledComponentsRegistry'; import { GlobalStyles } from '@/styles/globals'; +import { ReactNode } from 'react'; type ProvidersProps = { children: ReactNode; @@ -17,3 +17,4 @@ export function Providers({ children }: ProvidersProps) { ); } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx index b57b27b..a170c04 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx @@ -32,3 +32,4 @@ export default function StyledComponentsRegistry({ ); } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx index 2ef0e42..dc53055 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx @@ -22,3 +22,4 @@ export function AppProvider({ children }: AppProviderProps) { ); } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx new file mode 100644 index 0000000..033f220 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx @@ -0,0 +1,57 @@ +import { FallbackProps } from 'react-error-boundary'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 1.5rem; + font-weight: 700; + color: #dc2626; +`; + +const Message = styled.p` + margin-top: 1rem; + color: #4b5563; +`; + +const RetryButton = styled.button` + margin-top: 1.5rem; + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + border: none; + cursor: pointer; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } +`; + +export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + return ( + + + Something went wrong + {error.message || 'An unexpected error occurred'} + Try again + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..0cfc8c9 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx @@ -0,0 +1,45 @@ +import styled, { keyframes } from 'styled-components'; + +type LoadingSpinnerProps = { + size?: 'sm' | 'md' | 'lg'; +}; + +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +const sizes = { + sm: '1rem', + md: '2rem', + lg: '4rem', +}; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: center; +`; + +const Spinner = styled.div<{ $size: 'sm' | 'md' | 'lg' }>` + width: ${(props) => sizes[props.$size]}; + height: ${(props) => sizes[props.$size]}; + border-radius: 50%; + border: 2px solid #d1d5db; + border-top-color: #4f46e5; + animation: ${spin} 1s linear infinite; +`; + +export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + return ( + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx new file mode 100644 index 0000000..3266d49 --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx @@ -0,0 +1,98 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3.75rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +const SecondaryLink = styled.a` + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: #111827; + text-decoration: none; + + &:hover { + color: #4f46e5; + } +`; + +export function Landing() { + return ( + + + Welcome to Your App + + A production-ready React application scaffolded with create-react-forge. + + + Get started + + Learn more + + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx new file mode 100644 index 0000000..17a5efc --- /dev/null +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx @@ -0,0 +1,87 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const ErrorCode = styled.p` + font-size: 1rem; + font-weight: 600; + color: #4f46e5; +`; + +const Title = styled.h1` + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +export function NotFound() { + return ( + + + 404 + Page not found + + Sorry, we couldn't find the page you're looking for. + + + Go back home + + + + ); +} + + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx index 94ecf8c..8fb6db2 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx @@ -8,3 +8,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render( ); + diff --git a/src/templates/overlays/styling/tailwind/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/tailwind/_nextjs/src/app/error.tsx new file mode 100644 index 0000000..635128c --- /dev/null +++ b/src/templates/overlays/styling/tailwind/_nextjs/src/app/error.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useEffect } from 'react'; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+
+

Something went wrong

+

+ {error.message || 'An unexpected error occurred'} +

+ +
+
+ ); +} + diff --git a/src/templates/overlays/styling/tailwind/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/tailwind/_nextjs/src/app/loading.tsx new file mode 100644 index 0000000..643816f --- /dev/null +++ b/src/templates/overlays/styling/tailwind/_nextjs/src/app/loading.tsx @@ -0,0 +1,12 @@ +export default function Loading() { + return ( +
+
+
+ ); +} + diff --git a/src/templates/overlays/styling/tailwind/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/tailwind/_nextjs/src/app/not-found.tsx new file mode 100644 index 0000000..3f877e7 --- /dev/null +++ b/src/templates/overlays/styling/tailwind/_nextjs/src/app/not-found.tsx @@ -0,0 +1,26 @@ +import Link from 'next/link'; + +export default function NotFound() { + return ( +
+
+

404

+

+ Page not found +

+

+ Sorry, we couldn't find the page you're looking for. +

+
+ + Go back home + +
+
+
+ ); +} + diff --git a/src/templates/overlays/styling/tailwind/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/tailwind/_nextjs/src/app/page.tsx new file mode 100644 index 0000000..846753b --- /dev/null +++ b/src/templates/overlays/styling/tailwind/_nextjs/src/app/page.tsx @@ -0,0 +1,33 @@ +import Link from 'next/link'; + +export default function HomePage() { + return ( +
+
+

+ Welcome to Your App +

+

+ A production-ready Next.js application scaffolded with create-react-forge. +

+
+ + Get started + + + Learn more + +
+
+
+ ); +} + diff --git a/src/templates/overlays/styling/tailwind/manifest.json b/src/templates/overlays/styling/tailwind/manifest.json index e0f484e..5fce7e7 100644 --- a/src/templates/overlays/styling/tailwind/manifest.json +++ b/src/templates/overlays/styling/tailwind/manifest.json @@ -2,7 +2,7 @@ "name": "styling-tailwind", "version": "1.0.0", "description": "Tailwind CSS styling setup with modern configuration", - "compatibleWith": ["runtime-vite", "runtime-nextjs"], + "compatibleWith": ["runtime-nextjs"], "dependencies": {}, "devDependencies": { "tailwindcss": "^4.0.0", @@ -13,7 +13,9 @@ "scripts": {}, "filePatterns": { "include": ["**/*"], - "exclude": ["manifest.json"] + "exclude": ["manifest.json", "_nextjs"] + }, + "runtimeOverrides": { + "nextjs": "_nextjs" } } - diff --git a/src/templates/registry.ts b/src/templates/registry.ts index d472f16..037d80c 100644 --- a/src/templates/registry.ts +++ b/src/templates/registry.ts @@ -239,13 +239,14 @@ export class TemplateRegistry { templates.push(this.loadAndRegister(`runtime/${config.runtime}`)); // Load styling template (pass runtime for runtime-specific overlays) + // Vite: always styled-components + // Next.js: tailwind or none (none = no styling overlay, use runtime defaults) if (config.styling.solution === 'tailwind') { templates.push(this.loadAndRegister('styling/tailwind', config.runtime)); - } else if (config.styling.solution === 'css-modules') { - templates.push(this.loadAndRegister('styling/css-modules', config.runtime)); } else if (config.styling.solution === 'styled-components') { templates.push(this.loadAndRegister('styling/styled-components', config.runtime)); } + // 'none' - don't load any styling overlay, use runtime defaults // Load state management template if (config.stateManagement && config.stateManagement !== 'none') { diff --git a/src/templates/utils.ts b/src/templates/utils.ts index f7ea1cb..aef2f27 100644 --- a/src/templates/utils.ts +++ b/src/templates/utils.ts @@ -15,9 +15,9 @@ export function getTemplatePathForRuntime(runtime: 'vite' | 'nextjs'): string { * Get template path for styling solution */ export function getTemplatePathForStyling(styling: string): string { - if (styling === 'css') return ''; + if (styling === 'none') return ''; if (styling === 'tailwind') return 'styling/tailwind'; - if (styling === 'css-modules') return 'styling/css-modules'; + if (styling === 'styled-components') return 'styling/styled-components'; return `styling/${styling}`; } @@ -102,7 +102,7 @@ export function getApplicableTemplates(config: { }): string[] { const templates = ['base', getTemplatePathForRuntime(config.runtime as 'vite' | 'nextjs')]; - if (config.styling && config.styling !== 'css') { + if (config.styling && config.styling !== 'none') { const stylingPath = getTemplatePathForStyling(config.styling); if (stylingPath) templates.push(stylingPath); } From 1f50446e8942311ed531809f00d6a9624d107558 Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Sat, 31 Jan 2026 19:10:57 +0530 Subject: [PATCH 5/6] update file --- .gitignore | 1 + src/index.ts | 1 + src/plugins/index.ts | 1 + .../css-modules/_nextjs/src/app/loading.tsx | 1 + .../_nextjs/src/app/page.module.css | 1 + .../css-modules/_nextjs/src/app/page.tsx | 1 + .../errors/ErrorFallback.module.css | 1 + .../src/components/errors/ErrorFallback.tsx | 1 + .../components/ui/LoadingSpinner.module.css | 1 + .../src/components/ui/LoadingSpinner.tsx | 1 + .../features/misc/routes/Landing.module.css | 1 + .../src/features/misc/routes/Landing.tsx | 1 + .../features/misc/routes/NotFound.module.css | 1 + .../src/features/misc/routes/NotFound.tsx | 1 + .../overlays/styling/css-modules/src/css.d.ts | 1 + .../styling/css/_nextjs/src/app/error.css | 1 + .../styling/css/_nextjs/src/app/error.tsx | 1 + .../styling/css/_nextjs/src/app/loading.css | 1 + .../styling/css/_nextjs/src/app/loading.tsx | 1 + .../styling/css/_nextjs/src/app/not-found.css | 1 + .../styling/css/_nextjs/src/app/not-found.tsx | 1 + .../styling/css/_nextjs/src/app/page.css | 1 + .../styling/css/_nextjs/src/app/page.tsx | 1 + .../src/components/errors/ErrorFallback.css | 1 + .../src/components/errors/ErrorFallback.tsx | 1 + .../src/components/ui/LoadingSpinner.css | 1 + .../src/components/ui/LoadingSpinner.tsx | 1 + .../src/features/misc/routes/Landing.css | 1 + .../src/features/misc/routes/Landing.tsx | 1 + .../src/features/misc/routes/NotFound.css | 1 + .../src/features/misc/routes/NotFound.tsx | 1 + .../styling/css/src/styles/globals.css | 1 + .../_nextjs/src/app/error.tsx | 1 + .../_nextjs/src/app/layout.tsx | 1 + .../_nextjs/src/app/loading.tsx | 1 + .../_nextjs/src/app/not-found.tsx | 1 + .../_nextjs/src/app/page.tsx | 1 + .../_nextjs/src/app/providers.tsx | 1 + .../src/lib/StyledComponentsRegistry.tsx | 1 + .../_vite/src/app/provider.tsx | 1 + .../src/components/errors/ErrorFallback.tsx | 1 + .../src/components/ui/LoadingSpinner.tsx | 1 + .../src/features/misc/routes/Landing.tsx | 1 + .../src/features/misc/routes/NotFound.tsx | 1 + .../styled-components/_vite/src/main.tsx | 1 + test/.babelrc | 5 + test/ARCHITECTURE.md | 55 + test/README.md | 72 + test/index.html | 14 + test/package-lock.json | 2206 +++++++++++++++++ test/package.json | 29 + test/public/vite.svg | 2 + test/src/app/App.tsx | 11 + test/src/app/provider.tsx | 25 + test/src/app/router.tsx | 19 + test/src/components/errors/ErrorFallback.tsx | 57 + test/src/components/ui/Button.styled.ts | 42 + test/src/components/ui/Button.tsx | 138 ++ test/src/components/ui/Input.tsx | 77 + test/src/components/ui/LoadingSpinner.tsx | 45 + test/src/components/ui/index.ts | 3 + test/src/features/misc/routes/Landing.tsx | 98 + test/src/features/misc/routes/NotFound.tsx | 87 + test/src/hooks/use-disclosure.ts | 21 + test/src/hooks/use-local-storage.ts | 80 + test/src/lib/api-client.ts | 115 + test/src/lib/utils.ts | 24 + test/src/main.tsx | 11 + test/src/stores/auth.ts | 48 + test/src/stores/index.ts | 3 + test/src/stores/notifications.ts | 54 + test/src/styles/globals.ts | 28 + test/src/types/api.ts | 47 + test/tsconfig.json | 32 + test/tsconfig.node.json | 22 + test/vite.config.ts | 22 + 76 files changed, 3537 insertions(+) create mode 100644 test/.babelrc create mode 100644 test/ARCHITECTURE.md create mode 100644 test/README.md create mode 100644 test/index.html create mode 100644 test/package-lock.json create mode 100644 test/package.json create mode 100644 test/public/vite.svg create mode 100644 test/src/app/App.tsx create mode 100644 test/src/app/provider.tsx create mode 100644 test/src/app/router.tsx create mode 100644 test/src/components/errors/ErrorFallback.tsx create mode 100644 test/src/components/ui/Button.styled.ts create mode 100644 test/src/components/ui/Button.tsx create mode 100644 test/src/components/ui/Input.tsx create mode 100644 test/src/components/ui/LoadingSpinner.tsx create mode 100644 test/src/components/ui/index.ts create mode 100644 test/src/features/misc/routes/Landing.tsx create mode 100644 test/src/features/misc/routes/NotFound.tsx create mode 100644 test/src/hooks/use-disclosure.ts create mode 100644 test/src/hooks/use-local-storage.ts create mode 100644 test/src/lib/api-client.ts create mode 100644 test/src/lib/utils.ts create mode 100644 test/src/main.tsx create mode 100644 test/src/stores/auth.ts create mode 100644 test/src/stores/index.ts create mode 100644 test/src/stores/notifications.ts create mode 100644 test/src/styles/globals.ts create mode 100644 test/src/types/api.ts create mode 100644 test/tsconfig.json create mode 100644 test/tsconfig.node.json create mode 100644 test/vite.config.ts diff --git a/.gitignore b/.gitignore index 5a4d6ec..5bd64a0 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ coverage/ + diff --git a/src/index.ts b/src/index.ts index 5192fbb..b0c447f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,3 +11,4 @@ main().catch((error) => { + diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 6452453..c71fc0d 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -3,3 +3,4 @@ export * from './loader.js'; export * from './manager.js'; + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx index 00df953..a12c1a9 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx @@ -9,3 +9,4 @@ export default function Loading() { } + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css index d74fd9f..b301576 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css @@ -69,3 +69,4 @@ } + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx index 4c473db..08055a0 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx @@ -28,3 +28,4 @@ export default function HomePage() { } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css index e581112..2e6e262 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css @@ -41,3 +41,4 @@ } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx index ff8d3d7..dec8d21 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx @@ -18,3 +18,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css index 73be275..c8ccfb4 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css @@ -36,3 +36,4 @@ } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx index 4333ee9..8f10d82 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx @@ -19,3 +19,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css index d74fd9f..b301576 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css @@ -69,3 +69,4 @@ } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx index 53894d4..8048980 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx @@ -28,3 +28,4 @@ export function Landing() { } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css index 76cd189..1675489 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css @@ -63,3 +63,4 @@ } + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx index 0cb3541..24934ca 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx @@ -21,3 +21,4 @@ export function NotFound() { } + diff --git a/src/templates/overlays/styling/css-modules/src/css.d.ts b/src/templates/overlays/styling/css-modules/src/css.d.ts index 8ce4b74..de856dd 100644 --- a/src/templates/overlays/styling/css-modules/src/css.d.ts +++ b/src/templates/overlays/styling/css-modules/src/css.d.ts @@ -9,3 +9,4 @@ declare module '*.css' { } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.css b/src/templates/overlays/styling/css/_nextjs/src/app/error.css index d4a2173..949d5d8 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/error.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.css @@ -45,3 +45,4 @@ } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx index 3971a09..c5f7410 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx @@ -30,3 +30,4 @@ export default function Error({ } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.css b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css index b430c7c..2f6c342 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/loading.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css @@ -24,3 +24,4 @@ } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx index cdda92d..623039a 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx @@ -9,3 +9,4 @@ export default function Loading() { } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css index 5f8d4c7..e70b51b 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css @@ -66,3 +66,4 @@ } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx index a826547..39c0eef 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx @@ -21,3 +21,4 @@ export default function NotFound() { } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.css b/src/templates/overlays/styling/css/_nextjs/src/app/page.css index c242f3a..25aa966 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/page.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.css @@ -69,3 +69,4 @@ } + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx index 4357d30..2f52ee7 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx @@ -28,3 +28,4 @@ export default function HomePage() { } + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css index d4a2173..949d5d8 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css @@ -45,3 +45,4 @@ } + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx index 9d5ad26..83abbf7 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx @@ -18,3 +18,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { } + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css index d8c20db..6c05912 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css @@ -36,3 +36,4 @@ } + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx index 1a75019..8cda9a3 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx @@ -17,3 +17,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { } + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css index c242f3a..25aa966 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css @@ -69,3 +69,4 @@ } + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx index 38d8001..b96b2f9 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx @@ -28,3 +28,4 @@ export function Landing() { } + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css index 5f8d4c7..e70b51b 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css @@ -66,3 +66,4 @@ } + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx index 968492b..dd69794 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx @@ -21,3 +21,4 @@ export function NotFound() { } + diff --git a/src/templates/overlays/styling/css/src/styles/globals.css b/src/templates/overlays/styling/css/src/styles/globals.css index a8719a6..d225e36 100644 --- a/src/templates/overlays/styling/css/src/styles/globals.css +++ b/src/templates/overlays/styling/css/src/styles/globals.css @@ -103,3 +103,4 @@ button { } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx index 8f916fe..fa5bb58 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx @@ -68,3 +68,4 @@ export default function Error({ } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx index 8490b19..158c160 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx @@ -21,3 +21,4 @@ export default function RootLayout({ } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx index 43623f7..c17e45c 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx @@ -36,3 +36,4 @@ export default function Loading() { } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx index 1419c03..1d08c30 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx @@ -87,3 +87,4 @@ export default function NotFound() { } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx index 1208d7a..a62433d 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx @@ -98,3 +98,4 @@ export default function HomePage() { } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx index c962505..17d6e26 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx @@ -18,3 +18,4 @@ export function Providers({ children }: ProvidersProps) { } + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx index a170c04..e5e947f 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx @@ -33,3 +33,4 @@ export default function StyledComponentsRegistry({ } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx index dc53055..21939b2 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx @@ -23,3 +23,4 @@ export function AppProvider({ children }: AppProviderProps) { } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx index 033f220..0c268f0 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx @@ -55,3 +55,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx index 0cfc8c9..0f61f94 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx @@ -43,3 +43,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx index 3266d49..292889d 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx @@ -96,3 +96,4 @@ export function Landing() { } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx index 17a5efc..fcc661d 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx @@ -85,3 +85,4 @@ export function NotFound() { } + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx index 8fb6db2..11b57f7 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx @@ -9,3 +9,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render( ); + diff --git a/test/.babelrc b/test/.babelrc new file mode 100644 index 0000000..495e2f9 --- /dev/null +++ b/test/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": ["next/babel"], + "plugins": [["styled-components", { "ssr": true }]] +} + diff --git a/test/ARCHITECTURE.md b/test/ARCHITECTURE.md new file mode 100644 index 0000000..3220870 --- /dev/null +++ b/test/ARCHITECTURE.md @@ -0,0 +1,55 @@ +# Project Architecture + +## Overview + +This project was bootstrapped with `create-react-forge` using: + +- **Runtime**: Vite (SPA) +- **Language**: TypeScript +- **Styling**: styled-components +- **State Management**: zustand +- **Data Fetching**: None +- **Testing**: Disabled + +## Directory Structure + +``` +src/ +├── components/ # Reusable UI components +│ └── ui/ # Base UI primitives +├── features/ # Feature-based modules +├── hooks/ # Custom React hooks +├── providers/ # React context providers +├── stores/ # State management stores +├── types/ # TypeScript type definitions +└── utils/ # Utility functions +``` + +## Naming Conventions + +### Files +- **Components**: PascalCase (e.g., UserProfile.tsx) +- **Hooks**: camelCase with "use" prefix (e.g., useAuth.ts) +- **Utilities**: camelCase (e.g., formatDate.ts) +- **Constants**: SCREAMING_SNAKE_CASE (e.g., constants.ts) + +### Code +- **Components**: PascalCase +- **Functions**: camelCase +- **Variables**: camelCase +- **Constants**: SCREAMING_SNAKE_CASE +- **Types/Interfaces**: PascalCase + +## Testing Strategy + +Testing is currently disabled. + +## Data Fetching + +Standard `fetch` or `axios` is used for data fetching. + +## State Management + + +We use **zustand** for global client state. + diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..7ea2452 --- /dev/null +++ b/test/README.md @@ -0,0 +1,72 @@ +# test + +![Vite](https://img.shields.io/badge/Vite-646CFF?style=flat&logo=vite&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white) ![styled-components](https://img.shields.io/badge/styled--components-DB7093?style=flat&logo=styled-components&logoColor=white) ![Zustand](https://img.shields.io/badge/Zustand-443E38?style=flat&logo=react&logoColor=white) + +A production-ready React application built with Vite, scaffolded by create-react-forge. + +## Prerequisites + +- Node.js 18.x or higher +- npm + +## Getting Started + +1. Install dependencies: + +```bash +npm install +``` + +2. Start the development server: + +```bash +npm run dev +``` + +3. Open [http://localhost:5173](http://localhost:5173) in your browser. + +## Available Scripts + +| Command | Description | +|---------|-------------| +| `npm run dev` | Start development server | +| `npm run build` | Build for production | +| `npm run preview` | Preview production build | +| `npm run format` | Format code with Prettier | +| `npm run lint` | Lint code | + +## Project Structure + +``` +test/ +├── src/ +│ ├── components/ # Reusable UI components +│ │ └── ui/ # Base UI primitives +│ ├── features/ # Feature-based modules +│ ├── hooks/ # Custom React hooks +│ ├── stores/ # State management stores +│ ├── styles/ # Global styles +│ └── types/ # TypeScript type definitions +├── public/ # Static assets +└── [config files] # Configuration files +``` + +## Tech Stack + +- **Runtime**: Vite +- **Language**: TypeScript +- **Styling**: styled-components +- **State Management**: zustand + +## Documentation + +- [Vite Documentation](https://vitejs.dev/) +- [React Router](https://reactrouter.com/) +- [React Documentation](https://react.dev/) +- [TypeScript Documentation](https://www.typescriptlang.org/docs/) +- [styled-components Documentation](https://styled-components.com/docs) +- [Zustand Documentation](https://zustand-demo.pmnd.rs/) + +## License + +MIT diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..cb2cb35 --- /dev/null +++ b/test/index.html @@ -0,0 +1,14 @@ + + + + + + + test + + +
+ + + + diff --git a/test/package-lock.json b/test/package-lock.json new file mode 100644 index 0000000..6864f32 --- /dev/null +++ b/test/package-lock.json @@ -0,0 +1,2206 @@ +{ + "name": "test", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test", + "version": "0.1.0", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-error-boundary": "^4.1.2", + "react-router-dom": "^7.1.1", + "styled-components": "^6.1.14", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "@types/react": "^19.0.6", + "@types/react-dom": "^19.0.3", + "@types/styled-components": "^5.1.34", + "@vitejs/plugin-react": "^4.3.4", + "babel-plugin-styled-components": "^2.1.4", + "typescript": "^5.3.0", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/styled-components": { + "version": "5.1.36", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.36.tgz", + "integrity": "sha512-pGMRNY5G2rNDKEv2DOiFYa7Ft1r0jrhmgBwHhOMzPTgCjO76bCot0/4uEfqj7K0Jf1KdQmDtAuaDk9EAs9foSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-error-boundary": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", + "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-components": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.8.tgz", + "integrity": "sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz", + "integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/test/package.json b/test/package.json new file mode 100644 index 0000000..5d74bf1 --- /dev/null +++ b/test/package.json @@ -0,0 +1,29 @@ +{ + "name": "test", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-error-boundary": "^4.1.2", + "react-router-dom": "^7.1.1", + "styled-components": "^6.1.14", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "@types/react": "^19.0.6", + "@types/react-dom": "^19.0.3", + "@types/styled-components": "^5.1.34", + "@vitejs/plugin-react": "^4.3.4", + "babel-plugin-styled-components": "^2.1.4", + "typescript": "^5.3.0", + "vite": "^6.0.7" + } +} diff --git a/test/public/vite.svg b/test/public/vite.svg new file mode 100644 index 0000000..b3e75a1 --- /dev/null +++ b/test/public/vite.svg @@ -0,0 +1,2 @@ + + diff --git a/test/src/app/App.tsx b/test/src/app/App.tsx new file mode 100644 index 0000000..aaf2bfc --- /dev/null +++ b/test/src/app/App.tsx @@ -0,0 +1,11 @@ +import { AppProvider } from '@/app/provider'; +import { AppRouter } from '@/app/router'; + +export function App() { + return ( + + + + ); +} + diff --git a/test/src/app/provider.tsx b/test/src/app/provider.tsx new file mode 100644 index 0000000..dc53055 --- /dev/null +++ b/test/src/app/provider.tsx @@ -0,0 +1,25 @@ +import { ErrorFallback } from '@/components/errors/ErrorFallback'; +import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; +import { GlobalStyles } from '@/styles/globals'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { BrowserRouter } from 'react-router-dom'; + +type AppProviderProps = { + children: React.ReactNode; +}; + +export function AppProvider({ children }: AppProviderProps) { + return ( + }> + + + + {children} + + + + ); +} + + diff --git a/test/src/app/router.tsx b/test/src/app/router.tsx new file mode 100644 index 0000000..b5fd33a --- /dev/null +++ b/test/src/app/router.tsx @@ -0,0 +1,19 @@ +import { Landing } from '@/features/misc/routes/Landing'; +import { NotFound } from '@/features/misc/routes/NotFound'; +import { useRoutes } from 'react-router-dom'; + +export function AppRouter() { + const routes = useRoutes([ + { + path: '/', + element: , + }, + { + path: '*', + element: , + }, + ]); + + return routes; +} + diff --git a/test/src/components/errors/ErrorFallback.tsx b/test/src/components/errors/ErrorFallback.tsx new file mode 100644 index 0000000..033f220 --- /dev/null +++ b/test/src/components/errors/ErrorFallback.tsx @@ -0,0 +1,57 @@ +import { FallbackProps } from 'react-error-boundary'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 1.5rem; + font-weight: 700; + color: #dc2626; +`; + +const Message = styled.p` + margin-top: 1rem; + color: #4b5563; +`; + +const RetryButton = styled.button` + margin-top: 1.5rem; + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + border: none; + cursor: pointer; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } +`; + +export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + return ( + + + Something went wrong + {error.message || 'An unexpected error occurred'} + Try again + + + ); +} + + diff --git a/test/src/components/ui/Button.styled.ts b/test/src/components/ui/Button.styled.ts new file mode 100644 index 0000000..9ea9cb3 --- /dev/null +++ b/test/src/components/ui/Button.styled.ts @@ -0,0 +1,42 @@ +import styled from 'styled-components'; + +export const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>` + padding: 0.5rem 1rem; + font-size: 1rem; + font-weight: 500; + border-radius: 0.375rem; + border: none; + cursor: pointer; + transition: all 0.2s ease-in-out; + + ${({ variant = 'primary' }) => + variant === 'primary' + ? ` + background-color: #3b82f6; + color: white; + + &:hover { + background-color: #2563eb; + } + + &:disabled { + background-color: #9ca3af; + cursor: not-allowed; + } + ` + : ` + background-color: #e5e7eb; + color: #1f2937; + + &:hover { + background-color: #d1d5db; + } + + &:disabled { + background-color: #f3f4f6; + color: #9ca3af; + cursor: not-allowed; + } + `} +`; + diff --git a/test/src/components/ui/Button.tsx b/test/src/components/ui/Button.tsx new file mode 100644 index 0000000..f39f7b4 --- /dev/null +++ b/test/src/components/ui/Button.tsx @@ -0,0 +1,138 @@ +import { forwardRef } from 'react'; +import styled, { css } from 'styled-components'; + +export type ButtonProps = React.ButtonHTMLAttributes & { + variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + isLoading?: boolean; +}; + +const variantStyles = { + primary: css` + background-color: #4f46e5; + color: white; + &:hover:not(:disabled) { + background-color: #6366f1; + } + `, + secondary: css` + background-color: #f3f4f6; + color: #111827; + &:hover:not(:disabled) { + background-color: #e5e7eb; + } + `, + outline: css` + background-color: transparent; + color: #374151; + border: 1px solid #d1d5db; + &:hover:not(:disabled) { + background-color: #f9fafb; + } + `, + ghost: css` + background-color: transparent; + color: #374151; + &:hover:not(:disabled) { + background-color: #f3f4f6; + } + `, + danger: css` + background-color: #dc2626; + color: white; + &:hover:not(:disabled) { + background-color: #ef4444; + } + `, +}; + +const sizeStyles = { + sm: css` + padding: 0.375rem 0.625rem; + font-size: 0.75rem; + `, + md: css` + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + `, + lg: css` + padding: 0.75rem 1rem; + font-size: 1rem; + `, +}; + +const StyledButton = styled.button<{ + $variant: ButtonProps['variant']; + $size: ButtonProps['size']; +}>` + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + font-weight: 600; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s, color 0.2s; + border: none; + cursor: pointer; + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } + + ${(props) => variantStyles[props.$variant || 'primary']} + ${(props) => sizeStyles[props.$size || 'md']} +`; + +const LoadingSpinner = styled.span` + margin-right: 0.5rem; + width: 1rem; + height: 1rem; + border: 2px solid currentColor; + border-top-color: transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } +`; + +export const Button = forwardRef( + ( + { + variant = 'primary', + size = 'md', + isLoading = false, + disabled, + children, + ...props + }, + ref + ) => { + return ( + + {isLoading && } + {children} + + ); + } +); + +Button.displayName = 'Button'; diff --git a/test/src/components/ui/Input.tsx b/test/src/components/ui/Input.tsx new file mode 100644 index 0000000..2d8dbc3 --- /dev/null +++ b/test/src/components/ui/Input.tsx @@ -0,0 +1,77 @@ +import { forwardRef } from 'react'; +import styled from 'styled-components'; + +export type InputProps = React.InputHTMLAttributes & { + label?: string; + error?: string; +}; + +const InputWrapper = styled.div` + width: 100%; +`; + +const Label = styled.label` + display: block; + margin-bottom: 0.25rem; + font-size: 0.875rem; + font-weight: 500; + color: #374151; +`; + +const StyledInput = styled.input<{ $hasError?: boolean }>` + display: block; + width: 100%; + padding: 0.5rem 0.75rem; + font-size: 1rem; + color: #111827; + background-color: white; + border: 1px solid ${(props) => (props.$hasError ? '#fca5a5' : '#d1d5db')}; + border-radius: 0.375rem; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: border-color 0.2s, box-shadow 0.2s; + + &::placeholder { + color: #9ca3af; + } + + &:focus { + outline: none; + border-color: ${(props) => (props.$hasError ? '#ef4444' : '#4f46e5')}; + box-shadow: 0 0 0 2px ${(props) => (props.$hasError ? '#fecaca' : '#c7d2fe')}; + } + + &:disabled { + cursor: not-allowed; + background-color: #f9fafb; + color: #6b7280; + } +`; + +const ErrorMessage = styled.p` + margin-top: 0.25rem; + font-size: 0.875rem; + color: #dc2626; +`; + +export const Input = forwardRef( + ({ label, error, id, ...props }, ref) => { + const inputId = id || props.name; + + return ( + + {label && } + + {error && {error}} + + ); + } +); + +Input.displayName = 'Input'; diff --git a/test/src/components/ui/LoadingSpinner.tsx b/test/src/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..0cfc8c9 --- /dev/null +++ b/test/src/components/ui/LoadingSpinner.tsx @@ -0,0 +1,45 @@ +import styled, { keyframes } from 'styled-components'; + +type LoadingSpinnerProps = { + size?: 'sm' | 'md' | 'lg'; +}; + +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +const sizes = { + sm: '1rem', + md: '2rem', + lg: '4rem', +}; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: center; +`; + +const Spinner = styled.div<{ $size: 'sm' | 'md' | 'lg' }>` + width: ${(props) => sizes[props.$size]}; + height: ${(props) => sizes[props.$size]}; + border-radius: 50%; + border: 2px solid #d1d5db; + border-top-color: #4f46e5; + animation: ${spin} 1s linear infinite; +`; + +export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + return ( + + + + ); +} + + diff --git a/test/src/components/ui/index.ts b/test/src/components/ui/index.ts new file mode 100644 index 0000000..143c361 --- /dev/null +++ b/test/src/components/ui/index.ts @@ -0,0 +1,3 @@ +export { Button, type ButtonProps } from './Button'; +export { Input, type InputProps } from './Input'; + diff --git a/test/src/features/misc/routes/Landing.tsx b/test/src/features/misc/routes/Landing.tsx new file mode 100644 index 0000000..3266d49 --- /dev/null +++ b/test/src/features/misc/routes/Landing.tsx @@ -0,0 +1,98 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const Title = styled.h1` + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3.75rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1.125rem; + line-height: 2rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +const SecondaryLink = styled.a` + font-size: 0.875rem; + font-weight: 600; + line-height: 1.5rem; + color: #111827; + text-decoration: none; + + &:hover { + color: #4f46e5; + } +`; + +export function Landing() { + return ( + + + Welcome to Your App + + A production-ready React application scaffolded with create-react-forge. + + + Get started + + Learn more + + + + + ); +} + + diff --git a/test/src/features/misc/routes/NotFound.tsx b/test/src/features/misc/routes/NotFound.tsx new file mode 100644 index 0000000..17a5efc --- /dev/null +++ b/test/src/features/misc/routes/NotFound.tsx @@ -0,0 +1,87 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Content = styled.div` + text-align: center; +`; + +const ErrorCode = styled.p` + font-size: 1rem; + font-weight: 600; + color: #4f46e5; +`; + +const Title = styled.h1` + margin-top: 1rem; + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + + @media (min-width: 640px) { + font-size: 3rem; + } +`; + +const Description = styled.p` + margin-top: 1.5rem; + font-size: 1rem; + line-height: 1.75rem; + color: #4b5563; +`; + +const ButtonGroup = styled.div` + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; +`; + +const PrimaryButton = styled(Link)` + border-radius: 0.375rem; + background-color: #4f46e5; + padding: 0.625rem 0.875rem; + font-size: 0.875rem; + font-weight: 600; + color: white; + text-decoration: none; + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + transition: background-color 0.2s; + + &:hover { + background-color: #6366f1; + } + + &:focus-visible { + outline: 2px solid #4f46e5; + outline-offset: 2px; + } +`; + +export function NotFound() { + return ( + + + 404 + Page not found + + Sorry, we couldn't find the page you're looking for. + + + Go back home + + + + ); +} + + diff --git a/test/src/hooks/use-disclosure.ts b/test/src/hooks/use-disclosure.ts new file mode 100644 index 0000000..7bc8678 --- /dev/null +++ b/test/src/hooks/use-disclosure.ts @@ -0,0 +1,21 @@ +import { useCallback, useState } from 'react'; + +/** + * Hook for managing disclosure state (modals, dropdowns, etc.) + * Following bulletproof-react patterns + */ +export function useDisclosure(initialState = false) { + const [isOpen, setIsOpen] = useState(initialState); + + const open = useCallback(() => setIsOpen(true), []); + const close = useCallback(() => setIsOpen(false), []); + const toggle = useCallback(() => setIsOpen((prev) => !prev), []); + + return { + isOpen, + open, + close, + toggle, + }; +} + diff --git a/test/src/hooks/use-local-storage.ts b/test/src/hooks/use-local-storage.ts new file mode 100644 index 0000000..c83d12b --- /dev/null +++ b/test/src/hooks/use-local-storage.ts @@ -0,0 +1,80 @@ +import { useCallback, useEffect, useState } from 'react'; + +/** + * Hook for syncing state with localStorage + */ +export function useLocalStorage( + key: string, + initialValue: T +): [T, (value: T | ((prev: T) => T)) => void, () => void] { + // Get stored value or use initial value + const readValue = useCallback((): T => { + if (typeof window === 'undefined') { + return initialValue; + } + + try { + const item = window.localStorage.getItem(key); + return item ? (JSON.parse(item) as T) : initialValue; + } catch (error) { + console.warn(`Error reading localStorage key "${key}":`, error); + return initialValue; + } + }, [initialValue, key]); + + const [storedValue, setStoredValue] = useState(readValue); + + // Set value to localStorage + const setValue = useCallback( + (value: T | ((prev: T) => T)) => { + if (typeof window === 'undefined') { + console.warn(`Cannot set localStorage key "${key}" during SSR`); + return; + } + + try { + const valueToStore = value instanceof Function ? value(storedValue) : value; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + setStoredValue(valueToStore); + + // Dispatch storage event for other tabs/windows + window.dispatchEvent(new Event('local-storage')); + } catch (error) { + console.warn(`Error setting localStorage key "${key}":`, error); + } + }, + [key, storedValue] + ); + + // Remove value from localStorage + const removeValue = useCallback(() => { + if (typeof window === 'undefined') { + return; + } + + try { + window.localStorage.removeItem(key); + setStoredValue(initialValue); + } catch (error) { + console.warn(`Error removing localStorage key "${key}":`, error); + } + }, [initialValue, key]); + + // Listen for changes in other tabs/windows + useEffect(() => { + const handleStorageChange = () => { + setStoredValue(readValue()); + }; + + window.addEventListener('storage', handleStorageChange); + window.addEventListener('local-storage', handleStorageChange); + + return () => { + window.removeEventListener('storage', handleStorageChange); + window.removeEventListener('local-storage', handleStorageChange); + }; + }, [readValue]); + + return [storedValue, setValue, removeValue]; +} + diff --git a/test/src/lib/api-client.ts b/test/src/lib/api-client.ts new file mode 100644 index 0000000..7c9dea9 --- /dev/null +++ b/test/src/lib/api-client.ts @@ -0,0 +1,115 @@ +/** + * API Client - Centralized HTTP client following bulletproof-react patterns + * Replace with your actual API configuration + */ + +export type RequestConfig = { + method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + headers?: Record; + body?: unknown; + params?: Record; +}; + +export type ApiResponse = { + data: T; + status: number; +}; + +// Get API URL from environment - works for both Vite and Next.js +const getApiUrl = (): string => { + // Check for Vite environment variable (import.meta.env) + if (typeof import.meta !== 'undefined' && (import.meta as any).env) { + return (import.meta as any).env.VITE_API_URL || 'http://localhost:3001'; + } + // Check for Next.js environment variable (process.env) + if (typeof process !== 'undefined' && process.env) { + return process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; + } + // Fallback + return 'http://localhost:3001'; +}; + +const API_URL = getApiUrl(); + +/** + * Build URL with query parameters + */ +function buildUrl(endpoint: string, params?: Record): string { + const url = new URL(endpoint, API_URL); + + if (params) { + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, String(value)); + }); + } + + return url.toString(); +} + +/** + * Get authorization headers (customize based on your auth strategy) + */ +function getAuthHeaders(): Record { + const token = localStorage.getItem('token'); + + if (token) { + return { Authorization: `Bearer ${token}` }; + } + + return {}; +} + +/** + * Main API client function + */ +export async function apiClient( + endpoint: string, + config: RequestConfig = {} +): Promise> { + const { method = 'GET', headers = {}, body, params } = config; + + const url = buildUrl(endpoint, params); + + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + ...getAuthHeaders(), + ...headers, + }, + body: body ? JSON.stringify(body) : undefined, + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({})); + throw new Error(error.message || `HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + data, + status: response.status, + }; +} + +/** + * Convenience methods + */ +export const api = { + get: (endpoint: string, params?: Record) => + apiClient(endpoint, { method: 'GET', params }), + + post: (endpoint: string, body: unknown) => + apiClient(endpoint, { method: 'POST', body }), + + put: (endpoint: string, body: unknown) => + apiClient(endpoint, { method: 'PUT', body }), + + patch: (endpoint: string, body: unknown) => + apiClient(endpoint, { method: 'PATCH', body }), + + delete: (endpoint: string) => + apiClient(endpoint, { method: 'DELETE' }), +}; + diff --git a/test/src/lib/utils.ts b/test/src/lib/utils.ts new file mode 100644 index 0000000..2f549df --- /dev/null +++ b/test/src/lib/utils.ts @@ -0,0 +1,24 @@ +/** + * Format a date to a human-readable string + */ +export function formatDate(date: Date | string): string { + return new Intl.DateTimeFormat('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + }).format(new Date(date)); +} + +/** + * Delay execution for a specified number of milliseconds + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Generate a unique ID + */ +export function generateId(): string { + return Math.random().toString(36).substring(2, 9); +} diff --git a/test/src/main.tsx b/test/src/main.tsx new file mode 100644 index 0000000..8fb6db2 --- /dev/null +++ b/test/src/main.tsx @@ -0,0 +1,11 @@ +import { App } from '@/app/App'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); + + diff --git a/test/src/stores/auth.ts b/test/src/stores/auth.ts new file mode 100644 index 0000000..eaa71bc --- /dev/null +++ b/test/src/stores/auth.ts @@ -0,0 +1,48 @@ +import type { User } from '@/types/api'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +/** + * Auth store with persistence following bulletproof-react patterns + */ + +type AuthState = { + user: User | null; + token: string | null; + isAuthenticated: boolean; + setAuth: (user: User, token: string) => void; + logout: () => void; +}; + +export const useAuth = create()( + persist( + (set) => ({ + user: null, + token: null, + isAuthenticated: false, + + setAuth: (user, token) => + set({ + user, + token, + isAuthenticated: true, + }), + + logout: () => + set({ + user: null, + token: null, + isAuthenticated: false, + }), + }), + { + name: 'auth-storage', + partialize: (state) => ({ + user: state.user, + token: state.token, + isAuthenticated: state.isAuthenticated, + }), + } + ) +); + diff --git a/test/src/stores/index.ts b/test/src/stores/index.ts new file mode 100644 index 0000000..2a250da --- /dev/null +++ b/test/src/stores/index.ts @@ -0,0 +1,3 @@ +export { useAuth } from './auth'; +export { useNotifications, type Notification, type NotificationType } from './notifications'; + diff --git a/test/src/stores/notifications.ts b/test/src/stores/notifications.ts new file mode 100644 index 0000000..08381e2 --- /dev/null +++ b/test/src/stores/notifications.ts @@ -0,0 +1,54 @@ +import { generateId } from '@/lib/utils'; +import { create } from 'zustand'; + +/** + * Notification store following bulletproof-react patterns + */ + +export type NotificationType = 'info' | 'success' | 'warning' | 'error'; + +export type Notification = { + id: string; + type: NotificationType; + title: string; + message?: string; + duration?: number; +}; + +type NotificationsState = { + notifications: Notification[]; + addNotification: (notification: Omit) => void; + removeNotification: (id: string) => void; + clearNotifications: () => void; +}; + +export const useNotifications = create((set) => ({ + notifications: [], + + addNotification: (notification) => { + const id = generateId(); + const newNotification = { ...notification, id }; + + set((state) => ({ + notifications: [...state.notifications, newNotification], + })); + + // Auto-remove after duration (default 5 seconds) + const duration = notification.duration ?? 5000; + if (duration > 0) { + setTimeout(() => { + set((state) => ({ + notifications: state.notifications.filter((n) => n.id !== id), + })); + }, duration); + } + }, + + removeNotification: (id) => + set((state) => ({ + notifications: state.notifications.filter((n) => n.id !== id), + })), + + clearNotifications: () => set({ notifications: [] }), +})); + diff --git a/test/src/styles/globals.ts b/test/src/styles/globals.ts new file mode 100644 index 0000000..2285054 --- /dev/null +++ b/test/src/styles/globals.ts @@ -0,0 +1,28 @@ +import { createGlobalStyle } from 'styled-components'; + +export const GlobalStyles = createGlobalStyle` + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + html, + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + a { + color: inherit; + text-decoration: none; + } + + code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; + } +`; + diff --git a/test/src/types/api.ts b/test/src/types/api.ts new file mode 100644 index 0000000..bcda646 --- /dev/null +++ b/test/src/types/api.ts @@ -0,0 +1,47 @@ +/** + * Common API types following bulletproof-react patterns + */ + +export type BaseEntity = { + id: string; + createdAt: string; + updatedAt: string; +}; + +export type PaginatedResponse = { + data: T[]; + meta: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +}; + +export type ApiError = { + message: string; + statusCode: number; + errors?: Record; +}; + +export type User = BaseEntity & { + email: string; + name: string; + avatar?: string; + role: 'admin' | 'user'; +}; + +export type AuthResponse = { + user: User; + token: string; +}; + +export type LoginCredentials = { + email: string; + password: string; +}; + +export type RegisterCredentials = LoginCredentials & { + name: string; +}; + diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..cfdd5f9 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path aliases */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/test/tsconfig.node.json b/test/tsconfig.node.json new file mode 100644 index 0000000..58113cd --- /dev/null +++ b/test/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "composite": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} + diff --git a/test/vite.config.ts b/test/vite.config.ts new file mode 100644 index 0000000..b5a2b44 --- /dev/null +++ b/test/vite.config.ts @@ -0,0 +1,22 @@ +import react from '@vitejs/plugin-react'; +import path from 'path'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 3000, + open: true, + }, + build: { + outDir: 'dist', + sourcemap: true, + }, +}); + From 38198f2a902472bf223f3db99365290052538c7b Mon Sep 17 00:00:00 2001 From: chiragmak10 Date: Tue, 3 Feb 2026 15:50:59 +0530 Subject: [PATCH 6/6] lint --- .gitignore | 1 + src/plugins/index.ts | 1 + .../overlays/styling/css-modules/_nextjs/src/app/loading.tsx | 1 + .../overlays/styling/css-modules/_nextjs/src/app/page.module.css | 1 + .../overlays/styling/css-modules/_nextjs/src/app/page.tsx | 1 + .../_vite/src/components/errors/ErrorFallback.module.css | 1 + .../css-modules/_vite/src/components/errors/ErrorFallback.tsx | 1 + .../_vite/src/components/ui/LoadingSpinner.module.css | 1 + .../css-modules/_vite/src/components/ui/LoadingSpinner.tsx | 1 + .../_vite/src/features/misc/routes/Landing.module.css | 1 + .../css-modules/_vite/src/features/misc/routes/Landing.tsx | 1 + .../_vite/src/features/misc/routes/NotFound.module.css | 1 + .../css-modules/_vite/src/features/misc/routes/NotFound.tsx | 1 + src/templates/overlays/styling/css-modules/src/css.d.ts | 1 + src/templates/overlays/styling/css/_nextjs/src/app/error.css | 1 + src/templates/overlays/styling/css/_nextjs/src/app/error.tsx | 1 + src/templates/overlays/styling/css/_nextjs/src/app/loading.css | 1 + src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx | 1 + src/templates/overlays/styling/css/_nextjs/src/app/not-found.css | 1 + src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx | 1 + src/templates/overlays/styling/css/_nextjs/src/app/page.css | 1 + src/templates/overlays/styling/css/_nextjs/src/app/page.tsx | 1 + .../styling/css/_vite/src/components/errors/ErrorFallback.css | 1 + .../styling/css/_vite/src/components/errors/ErrorFallback.tsx | 1 + .../styling/css/_vite/src/components/ui/LoadingSpinner.css | 1 + .../styling/css/_vite/src/components/ui/LoadingSpinner.tsx | 1 + .../styling/css/_vite/src/features/misc/routes/Landing.css | 1 + .../styling/css/_vite/src/features/misc/routes/Landing.tsx | 1 + .../styling/css/_vite/src/features/misc/routes/NotFound.css | 1 + .../styling/css/_vite/src/features/misc/routes/NotFound.tsx | 1 + src/templates/overlays/styling/css/src/styles/globals.css | 1 + .../overlays/styling/styled-components/_nextjs/src/app/error.tsx | 1 + .../styling/styled-components/_nextjs/src/app/layout.tsx | 1 + .../styling/styled-components/_nextjs/src/app/loading.tsx | 1 + .../styling/styled-components/_nextjs/src/app/not-found.tsx | 1 + .../overlays/styling/styled-components/_nextjs/src/app/page.tsx | 1 + .../styling/styled-components/_nextjs/src/app/providers.tsx | 1 + .../_nextjs/src/lib/StyledComponentsRegistry.tsx | 1 + .../styling/styled-components/_vite/src/app/provider.tsx | 1 + .../_vite/src/components/errors/ErrorFallback.tsx | 1 + .../styled-components/_vite/src/components/ui/LoadingSpinner.tsx | 1 + .../styled-components/_vite/src/features/misc/routes/Landing.tsx | 1 + .../_vite/src/features/misc/routes/NotFound.tsx | 1 + .../overlays/styling/styled-components/_vite/src/main.tsx | 1 + 44 files changed, 44 insertions(+) diff --git a/.gitignore b/.gitignore index 5bd64a0..98cef2b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ coverage/ + diff --git a/src/plugins/index.ts b/src/plugins/index.ts index c71fc0d..3b21808 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -4,3 +4,4 @@ export * from './manager.js'; + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx index a12c1a9..7c4de41 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx @@ -10,3 +10,4 @@ export default function Loading() { + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css index b301576..bcf43e5 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css @@ -70,3 +70,4 @@ + diff --git a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx index 08055a0..b396a16 100644 --- a/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx @@ -29,3 +29,4 @@ export default function HomePage() { + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css index 2e6e262..5512dde 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css @@ -42,3 +42,4 @@ + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx index dec8d21..f5f284f 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx @@ -19,3 +19,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css index c8ccfb4..2210160 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css @@ -37,3 +37,4 @@ + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx index 8f10d82..c6c4f9d 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx @@ -20,3 +20,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css index b301576..bcf43e5 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css @@ -70,3 +70,4 @@ + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx index 8048980..c81d768 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx @@ -29,3 +29,4 @@ export function Landing() { + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css index 1675489..ac2508b 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css @@ -64,3 +64,4 @@ + diff --git a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx index 24934ca..586c422 100644 --- a/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx @@ -22,3 +22,4 @@ export function NotFound() { + diff --git a/src/templates/overlays/styling/css-modules/src/css.d.ts b/src/templates/overlays/styling/css-modules/src/css.d.ts index de856dd..22881a7 100644 --- a/src/templates/overlays/styling/css-modules/src/css.d.ts +++ b/src/templates/overlays/styling/css-modules/src/css.d.ts @@ -10,3 +10,4 @@ declare module '*.css' { + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.css b/src/templates/overlays/styling/css/_nextjs/src/app/error.css index 949d5d8..fd4c5fa 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/error.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.css @@ -46,3 +46,4 @@ + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx index c5f7410..1daab6d 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx @@ -31,3 +31,4 @@ export default function Error({ + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.css b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css index 2f6c342..a517d54 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/loading.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.css @@ -25,3 +25,4 @@ + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx index 623039a..0325e66 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx @@ -10,3 +10,4 @@ export default function Loading() { + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css index e70b51b..c70ceaf 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css @@ -67,3 +67,4 @@ + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx index 39c0eef..885b05f 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx @@ -22,3 +22,4 @@ export default function NotFound() { + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.css b/src/templates/overlays/styling/css/_nextjs/src/app/page.css index 25aa966..54a3a1d 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/page.css +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.css @@ -70,3 +70,4 @@ + diff --git a/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx index 2f52ee7..6d6e756 100644 --- a/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx @@ -29,3 +29,4 @@ export default function HomePage() { + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css index 949d5d8..fd4c5fa 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css @@ -46,3 +46,4 @@ + diff --git a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx index 83abbf7..c1de42f 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx @@ -19,3 +19,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css index 6c05912..0f8f763 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css @@ -37,3 +37,4 @@ + diff --git a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx index 8cda9a3..3ae98f2 100644 --- a/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx @@ -18,3 +18,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css index 25aa966..54a3a1d 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css @@ -70,3 +70,4 @@ + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx index b96b2f9..f92b7f4 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx @@ -29,3 +29,4 @@ export function Landing() { + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css index e70b51b..c70ceaf 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css @@ -67,3 +67,4 @@ + diff --git a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx index dd69794..0ffc4f5 100644 --- a/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx @@ -22,3 +22,4 @@ export function NotFound() { + diff --git a/src/templates/overlays/styling/css/src/styles/globals.css b/src/templates/overlays/styling/css/src/styles/globals.css index d225e36..25ebc08 100644 --- a/src/templates/overlays/styling/css/src/styles/globals.css +++ b/src/templates/overlays/styling/css/src/styles/globals.css @@ -104,3 +104,4 @@ button { + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx index fa5bb58..d0dcdd5 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx @@ -69,3 +69,4 @@ export default function Error({ + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx index 158c160..83fd0fd 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx @@ -22,3 +22,4 @@ export default function RootLayout({ + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx index c17e45c..87888b8 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx @@ -37,3 +37,4 @@ export default function Loading() { + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx index 1d08c30..b2616f7 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx @@ -88,3 +88,4 @@ export default function NotFound() { + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx index a62433d..11d174c 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx @@ -99,3 +99,4 @@ export default function HomePage() { + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx index 17d6e26..44b276d 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx @@ -19,3 +19,4 @@ export function Providers({ children }: ProvidersProps) { + diff --git a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx index e5e947f..b15e88c 100644 --- a/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx +++ b/src/templates/overlays/styling/styled-components/_nextjs/src/lib/StyledComponentsRegistry.tsx @@ -34,3 +34,4 @@ export default function StyledComponentsRegistry({ + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx index 21939b2..f4058d6 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx @@ -24,3 +24,4 @@ export function AppProvider({ children }: AppProviderProps) { + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx index 0c268f0..2c0eeb9 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx @@ -56,3 +56,4 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx index 0f61f94..fbe30cc 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx @@ -44,3 +44,4 @@ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) { + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx index 292889d..6ef2741 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx @@ -97,3 +97,4 @@ export function Landing() { + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx index fcc661d..fff510e 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx @@ -86,3 +86,4 @@ export function NotFound() { + diff --git a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx index 11b57f7..58d193d 100644 --- a/src/templates/overlays/styling/styled-components/_vite/src/main.tsx +++ b/src/templates/overlays/styling/styled-components/_vite/src/main.tsx @@ -10,3 +10,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render( +