From 6b08121f535256e5ca82b236d4c2d453e7015376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Ba=CC=88chler?= Date: Mon, 17 Nov 2025 22:46:38 +0100 Subject: [PATCH 1/7] add spec for separate example repo --- specs/example-project.md | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 specs/example-project.md diff --git a/specs/example-project.md b/specs/example-project.md new file mode 100644 index 0000000..69a19e2 --- /dev/null +++ b/specs/example-project.md @@ -0,0 +1,74 @@ +# Example Project Toggle Spec + +## Background +- The generator `create/main.js` copies the entire `template/` workspace into the target directory and immediately runs setup commands, producing a project pre-populated with demo data and demo UI flows. +- Removing the example today requires deep edits across `prototype`, `mock-backend`, and `mock-data`, plus updates to docs and build scripts. Developers struggle to strip the demo without breaking interconnected references to demo APIs, dataset builders, and MSW handlers. +- Goal: ship a clean, empty project by default while preserving the current demo as an opt-in bundle. + +## Current Template State +- **Prototype (`template/prototype`)** + - Routes are hard-wired to demo screens (`app/routes.ts` routing into `app/demo/*.tsx`, e.g. `dashboard.tsx`, `employees.tsx`) that assume MSW demo endpoints. + - Root loader/action logic (`app/root.tsx`) fetches demo user data (`DemoApi` from `app/demo/api.ts`) and renders demo-specific login buttons, navigation, and layout. + - Local MSW setup (`app/mocks/handlers.ts`) imports `@repo/mock-backend/medium` so the app only works with the demo dataset. +- **Mock backend (`template/mock-backend/src`)** + - Provides dataset-bound handlers (`handlers/demo.ts`) and dataset presets (`medium`, `full`) that generate demo responses by reading demo dataset indices. + - Auth utilities (`auth.ts`) expect JWT payloads populated by the demo dataset. +- **Mock data (`template/mock-data`)** + - Dataset generator (`src/generators/dataset.ts`) creates seeded data for companies, persons, roles, and auth tokens that power demo flows. + - CLI (`cli/generate-data.mjs`) and published JSON fixtures (`data-mocks/*.json`) serialize the demo dataset in small/medium/full sizes. +- **Generator behavior** + - `create/main.js` blindly copies `template/` and replaces tokens, so the default scaffold always contains demo code, fixtures, and run scripts that assume the dataset can build immediately. + - Automated tests (`tests/generator.test.mjs`) assert that the generated project can run `npm run build:data` and `npm run build`, implicitly relying on demo assets. + +## Desired Behavior & Requirements +1. **Empty project by default** + - Generated workspace should have the same package layout but with neutral starter files: prototype is a skeleton shell, mock backend/data contain minimal no-op implementations (or are omitted) so new teams can build from scratch. + - Default `npm run build`, `npm run typecheck`, and other scripts must succeed without demo data. +2. **Opt-in example bundle** + - Introduce a generator flag (e.g. `--with-example`, `withExample` option from `npm init`) that copies the existing demo content into a sibling `example/` directory _inside_ the generated project. + - The `example/` directory should mirror the current template structure (`prototype`, `mock-data`, `mock-backend`, etc.) so teams can browse or copy patterns without polluting the main workspace. + - Example assets should remain runnable in-place (document how to install & build the example). + - It must be possible to delete the example directory without breaking any dependencies. +3. **Flag plumbing** + - Update argument normalization in `create/index.js` so CLI flags / `npm init` options expose `withExample` (boolean). + - Modify `create/main.js` to: + - Copy the slim template into the project root. + - When `withExample` is true, additionally copy the rich demo tree into `targetDir/example`. + - Ensure environment token replacement, dependency installs, and format commands only touch the base project; example content can be left untouched or bootstrapped separately per spec decisions. +4. **Documentation & DX** + - Document the new flag and default-empty behavior in `README.md` (both repository root and generated template readmes as needed). + - Provide guidance inside `example/README.md` (new or repurposed) explaining how to run the demo and what parts can be copied into the main app. +5. **Testing & automation** + - Expand `tests/generator.test.mjs` (or add new cases) to cover: + - Default scaffold produces empty baseline and still passes install/typecheck/build (with updated expectations). + - `--with-example` scaffolds the extra directory and preserves demo assets (spot-check key files or run targeted commands if feasible). + - Ensure CI steps or scripts that assume demo data are updated or gated behind the flag. + +## Implementation Notes & Work Items +- Refactor template content: + - Extract current demo sources (`prototype/app/demo`, associated context providers, MSW handlers, dataset generation, mock backend handlers, `mock-data` presets) into a new source tree destined for `example/`. + - Introduce lightweight placeholders in the base template: e.g., a blank `prototype` with a single welcome page, stubbed mock-data scripts that return empty structures, and no prebuilt JSON fixtures. + - Decide whether base template should keep mock packages at all (empty package with placeholder README) vs. omit them; ensure workspaces remain valid. +- Update generator copy logic: + - Allow copying multiple source roots (base template and example bundle) while respecting existing token replacement (app name/title) without touching example content unless required. + - Consider deduplicating shared config (eslint/tsconfig) so both base and example can reuse it without duplication. + - Guard install/format steps when `withExample` is used (e.g., do not attempt to install dependencies inside `example/` unless explicitly desired). +- Data & auth considerations: + - Empty project should not ship demo JWT payloads or auth stubs; evaluate whether to include a minimal `auth` scaffold or leave instructions for developers to implement. + - When `withExample` is chosen, ensure dataset presets and JSON outputs remain accessible (copy existing `data-mocks/*.json`). +- Migration of docs & scripts: + - Audit references to demo flows in `template/docs` and update to reflect new optional example. + - Update `README` badges, quick-start instructions, and `docs/modules/ROOT/pages` to describe both baseline and example usage. +- Ensure `npm run build:data` and related scripts degrade gracefully when the dataset is empty (either no-op or produce placeholder files). + +## Open Questions +- Should the CLI prompt the user interactively about including the example when running without `--with-example`? +- Should the example directory be completely independent (own package.json/turbo config), or share the monorepo root and rely on workspaces? +- Do we need to ship prebuilt mock JSON for the empty template (e.g., empty arrays) to satisfy downstream tooling? +- How do we handle token replacement inside the example bundle—do we mirror replacements there or leave the original `planning-stack-template` strings? + +## Validation Plan +- Run generator default path with `SKIP_SETUP=1` to ensure scaffolding succeeds and key files contain the expected placeholders. +- Run generator with `--with-example`, install dependencies, and verify both the base project and the `example/` bundle can execute `npm run build:data`, `npm run build`, and `npm run dev` (document which commands should run where). +- Add automated test coverage for the new flag and update snapshots/expectations accordingly. +- Confirm lint/typecheck/build commands in the repository (root) remain green after restructuring template assets. From 26a4a1a617fba8187b227aac8c60830a72aed230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Ba=CC=88chler?= Date: Tue, 24 Mar 2026 08:32:06 +0100 Subject: [PATCH 2/7] split starter scaffold from example templates --- AGENTS.md | 8 +- README.md | 61 ++- create/index.js | 139 ++++++- create/main.js | 235 ++++++------ .../.cursor/rules/accessibility.mdc | 24 ++ .../react-router/.cursor/rules/react.mdc | 18 + .../react-router/.cursor/rules/tests.mdc | 11 + example-templates/react-router/.env.example | 2 + example-templates/react-router/.gitignore | 47 +++ example-templates/react-router/.gitlab-ci.yml | 188 ++++++++++ .../react-router/.licenses/isc.md | 17 + .../react-router/.lintstagedrc.js | 3 + example-templates/react-router/.nvmrc | 1 + .../react-router/.prettierignore | 6 + example-templates/react-router/.prettierrc.js | 30 ++ .../react-router/.vscode/settings.json | 3 + example-templates/react-router/LICENSE.md | 18 + example-templates/react-router/README.md | 33 ++ .../react-router/api/commons/enums.ts | 12 + .../react-router/api/commons/types.ts | 42 +++ .../react-router}/api/demo/types.ts | 0 .../react-router/api/eslint.config.js | 15 + .../react-router/api/package.json | 43 +++ .../react-router/api/tsconfig.json | 12 + .../react-router/config/eslint/README.md | 3 + .../react-router/config/eslint/base.js | 61 +++ .../react-router/config/eslint/package.json | 25 ++ .../react-router/config/eslint/react.js | 42 +++ .../react-router/config/typescript/base.json | 23 ++ .../config/typescript/node-library.json | 12 + .../config/typescript/package.json | 9 + .../config/typescript/react-app.json | 13 + .../config/typescript/react-library.json | 10 + .../react-router/docs/antora-playbook.yml | 22 ++ .../react-router/docs/antora.yml | 5 + .../ROOT/images/Prototyp-Repo.drawio.png | Bin 0 -> 24086 bytes .../react-router/docs/modules/ROOT/nav.adoc | 11 + .../docs/modules/ROOT/pages/architecture.adoc | 145 ++++++++ .../modules/ROOT/pages/design-system.adoc | 57 +++ .../modules/ROOT/pages/documentation.adoc | 93 +++++ .../modules/ROOT/pages/getting-started.adoc | 139 +++++++ .../docs/modules/ROOT/pages/index.adoc | 78 ++++ .../docs/modules/ROOT/pages/login.adoc | 62 ++++ .../docs/modules/ROOT/pages/mock-data.adoc | 46 +++ .../docs/modules/ROOT/pages/monorepo.adoc | 64 ++++ .../docs/modules/ROOT/pages/prototype.adoc | 105 ++++++ .../docs/modules/ROOT/pages/tailwind.adoc | 22 ++ .../docs/modules/ROOT/partials/monorepo.puml | 42 +++ .../react-router/docs/package.json | 22 ++ .../react-router/frontend/package.json | 12 + .../react-router/mocks/AGENTS.md | 128 +++++++ .../react-router/mocks/README.md | 74 ++++ .../react-router/mocks/cli/generate-data.mjs | 66 ++++ .../react-router/mocks/eslint.config.js | 12 + .../react-router/mocks/package.json | 63 ++++ .../mocks/src/generators/auth.ts | 0 .../mocks/src/generators/config.ts | 55 +++ .../mocks/src/generators/dataset.test.ts | 24 ++ .../mocks/src/generators/dataset.ts | 180 +++++++++ .../mocks/src/generators/demo/companies.ts | 0 .../mocks/src/generators/demo/persons.ts | 0 .../mocks/src/generators/demo/types.ts | 0 .../mocks/src/generators/utils.ts | 0 .../react-router/mocks/src/index.ts | 5 + .../react-router/mocks/src/static/README.md | 1 + .../react-router/mocks/tsconfig.json | 12 + .../react-router/mocks/vite.config.ts | 8 + example-templates/react-router/msw/AGENTS.md | 167 +++++++++ .../react-router/msw/eslint.config.js | 12 + .../react-router/msw/full/index.ts | 22 ++ .../react-router/msw/medium/index.ts | 18 + .../react-router/msw/package.json | 61 +++ .../react-router}/msw/src/auth.ts | 0 .../react-router}/msw/src/handlers/auth.ts | 0 .../react-router}/msw/src/handlers/demo.ts | 0 .../react-router/msw/src/handlers/index.ts | 2 + .../react-router/msw/src/index.ts | 2 + .../react-router/msw/src/utils.ts | 39 ++ .../react-router/msw/tsconfig.json | 12 + example-templates/react-router/package.json | 53 +++ .../react-router/prototype/.cursorrules.md | 18 + .../react-router/prototype/README.md | 22 ++ .../context-providers/apiContext.tsx | 0 .../context-providers/permissionContext.tsx | 0 .../prototype/app/components/ui/avatar.tsx | 45 +++ .../prototype/app/components/ui/button.tsx | 50 +++ .../prototype/app/components/ui/card.tsx | 55 +++ .../app/components/ui/empty-state.tsx | 1 + .../prototype/app/components/ui/table.tsx | 94 +++++ .../prototype/app/demo/admin.tsx | 0 .../react-router}/prototype/app/demo/api.ts | 0 .../prototype/app/demo/dashboard.tsx | 0 .../prototype/app/demo/employees.tsx | 0 .../prototype/app/entry.client.tsx | 17 + .../prototype/app/hooks/useBreakpoint.ts | 36 ++ .../react-router/prototype/app/lib/utils.ts | 35 ++ .../prototype/app/mocks/browser.ts | 9 + .../prototype/app/mocks/handlers.ts | 9 + .../react-router/prototype/app/root.tsx | 288 +++++++++++++++ .../react-router/prototype/app/routes.ts | 11 + .../prototype/app/routes/_index.tsx | 16 + .../prototype/app/styles/tailwind.css | 70 ++++ .../react-router/prototype/app/vite-env.d.ts | 11 + .../react-router/prototype/components.json | 21 ++ .../react-router/prototype/eslint.config.mjs | 38 ++ .../react-router/prototype/package.json | 58 +++ .../react-router/prototype/postcss.config.js | 6 + .../react-router/prototype/public/favicon.ico | Bin 0 -> 16958 bytes .../prototype/public/mockServiceWorker.js | 349 ++++++++++++++++++ .../react-router/prototype/public/robots.txt | 2 + .../prototype/react-router.config.ts | 6 + .../react-router/prototype/tailwind.config.js | 10 + .../react-router/prototype/tsconfig.json | 20 + .../react-router/prototype/vite.config.ts | 26 ++ example-templates/react-router/turbo.json | 59 +++ example-templates/react-router/ui/README.md | 6 + .../react-router/ui/eslint.config.mjs | 21 ++ .../react-router/ui/package.json | 53 +++ .../react-router/ui/src/lib/utils.ts | 20 + .../react-router/ui/src/ui/empty-state.tsx | 19 + .../react-router/ui/src/ui/scroll-area.tsx | 44 +++ .../react-router/ui/tailwind.config.js | 60 +++ .../react-router/ui/tsconfig.json | 8 + .../ui/turbo/generators/config.ts | 30 ++ .../turbo/generators/templates/component.hbs | 8 + package.json | 5 +- template/.prettierignore | 3 +- template/README.md | 81 ++-- template/api/package.json | 5 +- .../docs/modules/ROOT/pages/architecture.adoc | 117 ++---- .../modules/ROOT/pages/getting-started.adoc | 106 ++---- template/docs/modules/ROOT/pages/index.adoc | 79 ++-- .../docs/modules/ROOT/pages/mock-data.adoc | 49 +-- .../docs/modules/ROOT/pages/monorepo.adoc | 47 ++- .../docs/modules/ROOT/pages/prototype.adoc | 108 +----- template/mocks/README.md | 76 +--- template/mocks/cli/generate-data.mjs | 2 +- template/mocks/src/generators/config.ts | 60 +-- template/mocks/src/generators/dataset.test.ts | 26 +- template/mocks/src/generators/dataset.ts | 182 +-------- template/mocks/src/index.ts | 1 - template/msw/full/index.ts | 19 +- template/msw/medium/index.ts | 15 +- template/msw/src/handlers/index.ts | 5 +- template/msw/src/index.ts | 3 +- template/prototype/README.md | 21 +- template/prototype/app/mocks/handlers.ts | 8 +- template/prototype/app/root.tsx | 328 +++------------- template/prototype/app/routes.ts | 5 +- template/prototype/app/routes/_index.tsx | 46 ++- template/prototype/vite.config.ts | 10 +- tests/generator.test.mjs | 43 +++ 152 files changed, 4971 insertions(+), 1237 deletions(-) create mode 100644 example-templates/react-router/.cursor/rules/accessibility.mdc create mode 100644 example-templates/react-router/.cursor/rules/react.mdc create mode 100644 example-templates/react-router/.cursor/rules/tests.mdc create mode 100644 example-templates/react-router/.env.example create mode 100644 example-templates/react-router/.gitignore create mode 100644 example-templates/react-router/.gitlab-ci.yml create mode 100644 example-templates/react-router/.licenses/isc.md create mode 100644 example-templates/react-router/.lintstagedrc.js create mode 100644 example-templates/react-router/.nvmrc create mode 100644 example-templates/react-router/.prettierignore create mode 100644 example-templates/react-router/.prettierrc.js create mode 100644 example-templates/react-router/.vscode/settings.json create mode 100644 example-templates/react-router/LICENSE.md create mode 100644 example-templates/react-router/README.md create mode 100644 example-templates/react-router/api/commons/enums.ts create mode 100644 example-templates/react-router/api/commons/types.ts rename {template => example-templates/react-router}/api/demo/types.ts (100%) create mode 100644 example-templates/react-router/api/eslint.config.js create mode 100644 example-templates/react-router/api/package.json create mode 100644 example-templates/react-router/api/tsconfig.json create mode 100644 example-templates/react-router/config/eslint/README.md create mode 100644 example-templates/react-router/config/eslint/base.js create mode 100644 example-templates/react-router/config/eslint/package.json create mode 100644 example-templates/react-router/config/eslint/react.js create mode 100644 example-templates/react-router/config/typescript/base.json create mode 100644 example-templates/react-router/config/typescript/node-library.json create mode 100644 example-templates/react-router/config/typescript/package.json create mode 100644 example-templates/react-router/config/typescript/react-app.json create mode 100644 example-templates/react-router/config/typescript/react-library.json create mode 100644 example-templates/react-router/docs/antora-playbook.yml create mode 100644 example-templates/react-router/docs/antora.yml create mode 100644 example-templates/react-router/docs/modules/ROOT/images/Prototyp-Repo.drawio.png create mode 100644 example-templates/react-router/docs/modules/ROOT/nav.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/architecture.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/design-system.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/documentation.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/getting-started.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/index.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/login.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/mock-data.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/monorepo.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/prototype.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/pages/tailwind.adoc create mode 100644 example-templates/react-router/docs/modules/ROOT/partials/monorepo.puml create mode 100644 example-templates/react-router/docs/package.json create mode 100644 example-templates/react-router/frontend/package.json create mode 100644 example-templates/react-router/mocks/AGENTS.md create mode 100644 example-templates/react-router/mocks/README.md create mode 100755 example-templates/react-router/mocks/cli/generate-data.mjs create mode 100644 example-templates/react-router/mocks/eslint.config.js create mode 100644 example-templates/react-router/mocks/package.json rename {template => example-templates/react-router}/mocks/src/generators/auth.ts (100%) create mode 100644 example-templates/react-router/mocks/src/generators/config.ts create mode 100644 example-templates/react-router/mocks/src/generators/dataset.test.ts create mode 100644 example-templates/react-router/mocks/src/generators/dataset.ts rename {template => example-templates/react-router}/mocks/src/generators/demo/companies.ts (100%) rename {template => example-templates/react-router}/mocks/src/generators/demo/persons.ts (100%) rename {template => example-templates/react-router}/mocks/src/generators/demo/types.ts (100%) rename {template => example-templates/react-router}/mocks/src/generators/utils.ts (100%) create mode 100644 example-templates/react-router/mocks/src/index.ts create mode 100644 example-templates/react-router/mocks/src/static/README.md create mode 100644 example-templates/react-router/mocks/tsconfig.json create mode 100644 example-templates/react-router/mocks/vite.config.ts create mode 100644 example-templates/react-router/msw/AGENTS.md create mode 100644 example-templates/react-router/msw/eslint.config.js create mode 100644 example-templates/react-router/msw/full/index.ts create mode 100644 example-templates/react-router/msw/medium/index.ts create mode 100644 example-templates/react-router/msw/package.json rename {template => example-templates/react-router}/msw/src/auth.ts (100%) rename {template => example-templates/react-router}/msw/src/handlers/auth.ts (100%) rename {template => example-templates/react-router}/msw/src/handlers/demo.ts (100%) create mode 100644 example-templates/react-router/msw/src/handlers/index.ts create mode 100644 example-templates/react-router/msw/src/index.ts create mode 100644 example-templates/react-router/msw/src/utils.ts create mode 100644 example-templates/react-router/msw/tsconfig.json create mode 100644 example-templates/react-router/package.json create mode 100644 example-templates/react-router/prototype/.cursorrules.md create mode 100644 example-templates/react-router/prototype/README.md rename {template => example-templates/react-router}/prototype/app/components/context-providers/apiContext.tsx (100%) rename {template => example-templates/react-router}/prototype/app/components/context-providers/permissionContext.tsx (100%) create mode 100644 example-templates/react-router/prototype/app/components/ui/avatar.tsx create mode 100644 example-templates/react-router/prototype/app/components/ui/button.tsx create mode 100644 example-templates/react-router/prototype/app/components/ui/card.tsx create mode 100644 example-templates/react-router/prototype/app/components/ui/empty-state.tsx create mode 100644 example-templates/react-router/prototype/app/components/ui/table.tsx rename {template => example-templates/react-router}/prototype/app/demo/admin.tsx (100%) rename {template => example-templates/react-router}/prototype/app/demo/api.ts (100%) rename {template => example-templates/react-router}/prototype/app/demo/dashboard.tsx (100%) rename {template => example-templates/react-router}/prototype/app/demo/employees.tsx (100%) create mode 100644 example-templates/react-router/prototype/app/entry.client.tsx create mode 100644 example-templates/react-router/prototype/app/hooks/useBreakpoint.ts create mode 100644 example-templates/react-router/prototype/app/lib/utils.ts create mode 100644 example-templates/react-router/prototype/app/mocks/browser.ts create mode 100644 example-templates/react-router/prototype/app/mocks/handlers.ts create mode 100644 example-templates/react-router/prototype/app/root.tsx create mode 100644 example-templates/react-router/prototype/app/routes.ts create mode 100644 example-templates/react-router/prototype/app/routes/_index.tsx create mode 100644 example-templates/react-router/prototype/app/styles/tailwind.css create mode 100644 example-templates/react-router/prototype/app/vite-env.d.ts create mode 100644 example-templates/react-router/prototype/components.json create mode 100644 example-templates/react-router/prototype/eslint.config.mjs create mode 100644 example-templates/react-router/prototype/package.json create mode 100644 example-templates/react-router/prototype/postcss.config.js create mode 100644 example-templates/react-router/prototype/public/favicon.ico create mode 100644 example-templates/react-router/prototype/public/mockServiceWorker.js create mode 100644 example-templates/react-router/prototype/public/robots.txt create mode 100644 example-templates/react-router/prototype/react-router.config.ts create mode 100644 example-templates/react-router/prototype/tailwind.config.js create mode 100644 example-templates/react-router/prototype/tsconfig.json create mode 100644 example-templates/react-router/prototype/vite.config.ts create mode 100644 example-templates/react-router/turbo.json create mode 100644 example-templates/react-router/ui/README.md create mode 100644 example-templates/react-router/ui/eslint.config.mjs create mode 100644 example-templates/react-router/ui/package.json create mode 100644 example-templates/react-router/ui/src/lib/utils.ts create mode 100644 example-templates/react-router/ui/src/ui/empty-state.tsx create mode 100644 example-templates/react-router/ui/src/ui/scroll-area.tsx create mode 100644 example-templates/react-router/ui/tailwind.config.js create mode 100644 example-templates/react-router/ui/tsconfig.json create mode 100644 example-templates/react-router/ui/turbo/generators/config.ts create mode 100644 example-templates/react-router/ui/turbo/generators/templates/component.hbs diff --git a/AGENTS.md b/AGENTS.md index c7164eb..4284a0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,19 +3,19 @@ Refer to the README.md for information about this repository. ## Project Structure & Module Organization -`create/` contains the Node-based scaffold (`index.js` normalizes CLI options, `main.js` mutates the template). `template/` ships the starter monorepo with workspaces such as `api/` for backend endpoints, `mock-backend/` and `mock-data/` for fixtures, `prototype/` for the prototype app, `ui/` for shared components, and `docs/` for the Antora documentation site. Shared lint and TS configs live in `template/config/`, and bootstrap assets for generated apps sit under `template`. +`create/` contains the Node-based scaffold (`index.js` normalizes CLI options, `main.js` mutates the template). `template/` ships the default starter monorepo with workspaces such as `api/`, `mocks/`, `msw/`, `prototype/`, `ui/`, and `docs/`. `example-templates/` contains optional independent examples that can be copied into generated projects under `examples//`. Shared lint and TS configs live in `template/config/`. ## Build, Test & Development Commands Use Node 22+. Run `npm install` at the repo root before touching the CLI. Template work happens inside `template/`: `npm install` for dependencies, `npm run dev` to launch Turbo-powered development, `npm run build` for a production check, `npm run typecheck` for repository-wide TypeScript validation, `npm run test` to execute Vitest suites, and `npm run format` to apply Prettier across Markdown and TypeScript files. ## Coding Style & Naming Conventions -Prettier enforces two-space indentation, single quotes, and trailing commas (`npm run format`). Keep imports auto-organized by the Prettier organize-imports plugin. Use `camelCase` for variables and functions, `PascalCase` for React components and types, and kebab-case for file names (e.g., `generate-data.mjs`). ESLint rules from `template/config/eslint/` run in every workspace—resolve warnings or document exceptions in-code. +Prettier enforces two-space indentation, single quotes, and trailing commas (`npm run format`). Keep imports auto-organized by the Prettier organize-imports plugin. Use `camelCase` for variables and functions, `PascalCase` for React components and types, and kebab-case for file names (e.g. `generate-data.mjs`). ESLint rules from `template/config/eslint/` run in every workspace, so resolve warnings or document exceptions in-code. ## Testing Guidelines -Vitest handles unit and integration coverage; colocate specs as `*.test.ts` or `*.spec.ts`. Run `npm run test` for the full suite, or target a package with `npm run test -- --filter=@repo/ui`. Always follow tests with `npm run typecheck` before opening a PR, and extend coverage around generators (`mock-data/cli`) and UI behavior when adding features. +Vitest handles unit and integration coverage; colocate specs as `*.test.ts` or `*.spec.ts`. Run `npm run test` for the full suite, or target a package with `npm run test -- --filter=@repo/ui`. Always follow tests with `npm run typecheck` before opening a PR. When changing the scaffold, cover both the default starter and any affected example templates. ## Commit & Pull Request Guidelines Mirror the existing history by writing concise, imperative subjects (`add build verification for Github`). Group logically related changes per commit. Pull requests must include a summary, testing notes (`npm run build`, `npm run test`, etc.), linked issues when applicable, and screenshots for UI updates. ## Setup & Configuration Notes -The scaffold copies `.env.example` to `.env` and swaps `planning-stack-template` tokens; keep those references in sync when editing template assets. Honor the `SKIP_SETUP` and `SKIP_FORMAT` flags in `create/main.js` so automated flows remain consistent. Update every consumer (e.g., `template/gitlab-ci.yml`) when adjusting CI scaffolding. +The scaffold copies `.env.example` to `.env` and swaps `planning-stack-template` tokens in the base template. Honor the `SKIP_SETUP` and `SKIP_FORMAT` flags in `create/main.js` so automated flows remain consistent. Keep example templates independent from the base workspace so they can be deleted safely and are not built automatically by the root Turborepo configuration. diff --git a/README.md b/README.md index 573b7c5..3ef9bc6 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,35 @@ -# Frontend Monorepo for Rapid Prototyping +# Tinker Stack -This is a npm Template for a Frontend Monorepo. +Tinker Stack is a project scaffold for frontend monorepos focused on rapid prototyping. -It puts emphasis on rapid prototyping and a prototype-driven development -([Pixar Planning](https://www.youtube.com/watch?v=gbuWJ48T0bE&t=1294s)). +By default it generates a clean starter workspace with: -The prototypes are using a virtual and ephemeral backend, so changes can be made very quickly without any need for -data migrations on schema changes. +- a minimal React Router prototype shell +- shared `api`, `ui`, `msw`, and `mocks` packages +- Turborepo, TypeScript, ESLint, Prettier, and Antora wiring -Stakeholders can test a fully working UI before a single line of backend code has been written. +The scaffold no longer ships a demo application in the base workspace. -Test UI concepts on day 1, iterate on them with the designer on day 2 and present them to the customer on day 3. +## Create a project -The API to the backend has usually had several major iterations before a backend developer even gets involved. - -## What is included? - -This monorepo contains the following packages/apps: - -### Apps and Packages - -- A prototype for the application, based on React Router SPA. -- A package that provides the domain types and enums that can be shared with the backend. -- A documentation in [Antora](https://antora.org/) (AsciiDoc) format. -- A package that provides synthetic data for the applications using - [Faker.js](https://fakerjs.dev/). -- A package that provides a mock API via service workers using [MSW](https://mswjs.io/). -- A component library that is shared by the main application and the prototype (still empty). -- ESLint and TypeScript configurations that are shared throughout the monorepo. - -### Features +```bash +npm create tinker-stack@latest +``` -- [TypeScript](https://www.typescriptlang.org/) Monorepo based on ESM standards. -- Turborepo setup for building and running the monorepo. -- Code formatting with [Prettier](https://prettier.io/) and linting with - [ESLint](https://eslint.org/). +To include an independent example template: -## Documentation +```bash +npm create tinker-stack@latest -- --example react-router +``` -The project documentation is written in [AsciiDoc](https://asciidoctor.org/) and is generated using -[Antora](https://antora.org/). +The selected examples are copied into `examples//` inside the generated project. They are +self-contained and can be deleted without affecting the main workspace. -## Getting Started +## Example templates -To get started, run the following command: +The generator is designed for multiple named examples. Today the repository includes: -```bash -npm create tinker-stack@latest -``` +- `react-router` -This will create a new project based on the Tinker Stack. +Each example is installed and run separately from its own directory. The root project does not add +example folders to its npm workspaces, so Turborepo ignores them by default. diff --git a/create/index.js b/create/index.js index 5713a21..e261249 100755 --- a/create/index.js +++ b/create/index.js @@ -8,30 +8,120 @@ import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const packageRoot = path.dirname(path.dirname(__filename)); const templateDir = path.join(packageRoot, 'template'); +const exampleTemplatesDir = path.join(packageRoot, 'example-templates'); +const DEFAULT_EXAMPLE = 'react-router'; -function targetFromArgv() { - const argv = process.argv.slice(2); - // prefer a last non-flag positional argument as the target folder - for (let i = argv.length - 1; i >= 0; i--) { - if (!argv[i].startsWith('-')) return argv[i]; +function parseCliArgs(argv = process.argv.slice(2)) { + const options = { + cwd: undefined, + debug: undefined, + install: undefined, + examples: [], + withExample: false + }; + const positionals = []; + + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + + if (arg === '--example' || arg === '--examples') { + const value = argv[i + 1]; + if (value && !value.startsWith('-')) { + options.examples.push(...splitExampleNames(value)); + i += 1; + } + continue; + } + + if (arg.startsWith('--example=')) { + options.examples.push(...splitExampleNames(arg.slice('--example='.length))); + continue; + } + + if (arg.startsWith('--examples=')) { + options.examples.push(...splitExampleNames(arg.slice('--examples='.length))); + continue; + } + + if (arg === '--with-example') { + options.withExample = true; + continue; + } + + if (arg === '--cwd') { + const value = argv[i + 1]; + if (value && !value.startsWith('-')) { + options.cwd = value; + i += 1; + } + continue; + } + + if (arg.startsWith('--cwd=')) { + options.cwd = arg.slice('--cwd='.length); + continue; + } + + if (arg === '--debug') { + options.debug = true; + continue; + } + + if (arg === '--install') { + options.install = true; + continue; + } + + if (arg === '--no-install') { + options.install = false; + continue; + } + + if (!arg.startsWith('-')) { + positionals.push(arg); + } + } + + return { + ...options, + positionals + }; +} + +function splitExampleNames(value) { + return value + .split(',') + .map(example => example.trim()) + .filter(Boolean); +} + +function normalizeExampleNames(value) { + if (Array.isArray(value)) { + return value.flatMap(example => normalizeExampleNames(example)); } - return undefined; + + if (typeof value === 'string') { + return splitExampleNames(value); + } + + return []; } function normalizeOptions(input = {}) { // input may be an options object passed by npm init or undefined let opts = {}; if (typeof input === 'object' && input !== null) opts = input; + const cli = parseCliArgs(); const targetDir = opts.targetDirectory || opts.name || // common field name for npm init opts.project || - targetFromArgv(); + cli.positionals.at(-1); const resolvedCwd = - typeof opts.cwd === 'string' && opts.cwd.length > 0 - ? path.resolve(process.cwd(), opts.cwd) + typeof (opts.cwd ?? cli.cwd) === 'string' && (opts.cwd ?? cli.cwd).length > 0 + ? path.resolve(process.cwd(), opts.cwd ?? cli.cwd) : process.cwd(); const resolvedTargetDir = @@ -39,13 +129,27 @@ function normalizeOptions(input = {}) { ? path.resolve(resolvedCwd, targetDir) : undefined; + const examples = [ + ...normalizeExampleNames(opts.examples), + ...normalizeExampleNames(opts.example), + ...normalizeExampleNames(cli.examples) + ]; + + if (opts.withExample === true || cli.withExample) { + examples.push(DEFAULT_EXAMPLE); + } + + const normalizedExamples = [...new Set(examples)]; + return { ...opts, targetDir: resolvedTargetDir, - debug: !!opts.debug, - install: opts.install ?? true, + debug: Boolean(opts.debug ?? cli.debug), + install: opts.install ?? cli.install ?? true, cwd: resolvedCwd, - templateDir // always use the built-in template + templateDir, // always use the built-in template + exampleTemplatesDir, + examples: normalizedExamples }; } @@ -62,6 +166,17 @@ async function main(...args) { throw new Error(`Template directory not found: ${templateDir}\n${err.message}`); } + if (opts.examples.length > 0) { + try { + const stat = fs.statSync(exampleTemplatesDir); + if (!stat.isDirectory()) { + throw new Error(`Example templates path is not a directory: ${exampleTemplatesDir}`); + } + } catch (err) { + throw new Error(`Example templates directory not found: ${exampleTemplatesDir}\n${err.message}`); + } + } + // call the implementation with simple, normalized options await createMain(opts); } diff --git a/create/main.js b/create/main.js index 346acef..4c74160 100644 --- a/create/main.js +++ b/create/main.js @@ -1,13 +1,10 @@ import inquirer from 'inquirer'; import { execSync } from 'node:child_process'; -import crypto from 'node:crypto'; import fs from 'node:fs/promises'; import path from 'node:path'; -const getRandomString = length => crypto.randomBytes(length).toString('hex'); - -export async function main({ cwd, templateDir, targetDir }) { - const APP_TITLE = await getTitle(); +export async function main({ cwd, templateDir, exampleTemplatesDir, targetDir, examples = [], title }) { + const APP_TITLE = await getTitle(title); const APP_NAME = (APP_TITLE) // get rid of anything that's not allowed in an app name @@ -19,93 +16,102 @@ export async function main({ cwd, templateDir, targetDir }) { console.log({ cwd, templateDir, targetDir, APP_NAME, APP_TITLE }); - await fs.cp(templateDir, targetDir, { recursive: true, force: false, errorOnExist: true }, (err) => { - console.warn(err); + await fs.cp(templateDir, targetDir, { + recursive: true, + force: false, + errorOnExist: true, }); + if (examples.length > 0) { + const examplesDir = path.join(targetDir, 'examples'); + await fs.mkdir(examplesDir, { recursive: true }); + + for (const example of examples) { + const sourceDir = path.join(exampleTemplatesDir, example); + + try { + const stat = await fs.stat(sourceDir); + if (!stat.isDirectory()) { + throw new Error(`Example template path is not a directory: ${sourceDir}`); + } + } catch (error) { + throw new Error(`Unknown example template "${example}"`); + } + + await fs.cp(sourceDir, path.join(examplesDir, example), { + recursive: true, + force: false, + errorOnExist: true, + }); + } + } + const EXAMPLE_ENV_PATH = path.join(targetDir, '.env.example'); - const ENV_PATH = path.join(targetDir, '.env'); - const PKG_PATH = path.join(targetDir, 'package.json'); - - const appNameRegex = /planning-stack-template/g; - const appTitleRegex = /PLANNING STACK TEMPLATE/g; - - const [env, packageJsonString] = await Promise.all([ - fs.readFile(EXAMPLE_ENV_PATH, 'utf-8'), - fs.readFile(PKG_PATH, 'utf-8'), - ]); - - const filesWithAppName = await Promise.all([ - PKG_PATH, - path.join(targetDir, 'README.md'), - path.join(targetDir, 'docs', 'antora-playbook.yml'), - path.join(targetDir, 'docs', 'antora.yml'), - path.join(targetDir, 'docs', 'modules/ROOT/pages/architecture.adoc'), - path.join(targetDir, 'docs', 'modules/ROOT/pages/documentation.adoc'), - path.join(targetDir, 'docs', 'modules/ROOT/pages/getting-started.adoc'), - path.join(targetDir, 'mocks', 'cli', 'generate-data.mjs'), - path.join(targetDir, 'prototype', 'README.md'), - path.join(targetDir, 'ui', 'README.md'), - ]); - - const filesWithAppTitle = await Promise.all([ - path.join(targetDir, 'README.md'), - path.join(targetDir, 'docs', 'antora-playbook.yml'), - path.join(targetDir, 'docs', 'antora.yml'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'architecture.adoc'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'index.adoc'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'monorepo.adoc'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'prototype.adoc'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'partials', 'monorepo.puml'), - path.join(targetDir, 'docs', 'modules', 'ROOT', 'nav.adoc'), - path.join(targetDir, 'docs', 'antora-playbook.yml'), - path.join(targetDir, 'docs', 'package.json'), - path.join(targetDir, 'prototype', 'app', 'root.tsx'), - path.join(targetDir, 'prototype', 'app', 'routes', '_index.tsx'), - path.join(targetDir, 'mocks', 'cli', 'generate-data.mjs'), - ]); - - // Replace all instances of the app title - for (const file of filesWithAppTitle) { - const fileContent = await fs.readFile(file, 'utf-8'); - const newFile = fileContent.replaceAll(appTitleRegex, APP_TITLE); - await fs.writeFile(file, newFile); - } - // Replace all instances of the app name - for (const file of filesWithAppName) { - const fileContent = await fs.readFile(file, 'utf-8'); - const newFile = fileContent.replaceAll(appNameRegex, APP_NAME); - await fs.writeFile(file, newFile); - } - - const packageJson = JSON.parse(packageJsonString); - - packageJson.name = APP_NAME; - delete packageJson.author; - delete packageJson.license; - - const fileOperationPromises = [ - fs.copyFile(EXAMPLE_ENV_PATH, ENV_PATH), - fs.writeFile(PKG_PATH, JSON.stringify(packageJson, null, 2)), - ]; - - await Promise.all(fileOperationPromises); - - if (!process.env.SKIP_SETUP) { - execSync('npm install', { cwd: targetDir, stdio: 'inherit' }); - execSync('npm run typecheck', { cwd: targetDir, stdio: 'inherit' }); - execSync('npm run build:data', { cwd: targetDir, stdio: 'inherit' }); - } - - if (!process.env.SKIP_FORMAT) { - execSync('npm run format -- --log-level warn', { - cwd: targetDir, - stdio: 'inherit', - }); - } - - console.log( - ` + const ENV_PATH = path.join(targetDir, '.env'); + const PKG_PATH = path.join(targetDir, 'package.json'); + + const appNameRegex = /planning-stack-template/g; + const appTitleRegex = /PLANNING STACK TEMPLATE/g; + + const packageJsonString = await fs.readFile(PKG_PATH, 'utf-8'); + + const filesWithAppName = [ + PKG_PATH, + path.join(targetDir, 'README.md'), + path.join(targetDir, 'docs', 'antora-playbook.yml'), + path.join(targetDir, 'docs', 'antora.yml'), + path.join(targetDir, 'docs', 'modules/ROOT/pages/architecture.adoc'), + path.join(targetDir, 'docs', 'modules/ROOT/pages/documentation.adoc'), + path.join(targetDir, 'docs', 'modules/ROOT/pages/getting-started.adoc'), + path.join(targetDir, 'prototype', 'README.md'), + path.join(targetDir, 'ui', 'README.md'), + ]; + + const filesWithAppTitle = [ + path.join(targetDir, 'README.md'), + path.join(targetDir, 'docs', 'antora-playbook.yml'), + path.join(targetDir, 'docs', 'antora.yml'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'architecture.adoc'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'index.adoc'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'monorepo.adoc'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'pages', 'prototype.adoc'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'partials', 'monorepo.puml'), + path.join(targetDir, 'docs', 'modules', 'ROOT', 'nav.adoc'), + path.join(targetDir, 'docs', 'package.json'), + path.join(targetDir, 'prototype', 'app', 'root.tsx'), + path.join(targetDir, 'prototype', 'app', 'routes', '_index.tsx'), + path.join(targetDir, 'prototype', 'README.md'), + ]; + + await replaceTokensInFiles(filesWithAppTitle, appTitleRegex, APP_TITLE); + await replaceTokensInFiles(filesWithAppName, appNameRegex, APP_NAME); + + const packageJson = JSON.parse(packageJsonString); + + packageJson.name = APP_NAME; + delete packageJson.author; + delete packageJson.license; + + await Promise.all([ + fs.copyFile(EXAMPLE_ENV_PATH, ENV_PATH), + fs.writeFile(PKG_PATH, JSON.stringify(packageJson, null, 2)), + ]); + + if (!process.env.SKIP_SETUP) { + execSync('npm install', { cwd: targetDir, stdio: 'inherit' }); + execSync('npm run typecheck', { cwd: targetDir, stdio: 'inherit' }); + execSync('npm run build:data', { cwd: targetDir, stdio: 'inherit' }); + } + + if (!process.env.SKIP_FORMAT) { + execSync('npm run format -- --log-level warn', { + cwd: targetDir, + stdio: 'inherit', + }); + } + + console.log( + ` Setup is complete. What's next? @@ -114,23 +120,40 @@ What's next? - Build your mock API - Build your prototype - Iterate - `.trim(), - ); + `.trim(), + ); +} + +async function replaceTokensInFiles(files, pattern, replacement) { + for (const file of files) { + try { + const fileContent = await fs.readFile(file, 'utf-8'); + await fs.writeFile(file, fileContent.replaceAll(pattern, replacement)); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + } } -async function getTitle() { - // Check if we are in interactive mode - if (process.env.CI) { - return 'Demo Title'; - } - - const { title } = await inquirer.prompt([ - { - type: 'input', - name: 'title', - message: 'Enter the title of the app', - }, - ]); - - return title; +async function getTitle(providedTitle) { + if (typeof providedTitle === 'string' && providedTitle.trim().length > 0) { + return providedTitle.trim(); + } + + // Check if we are in interactive mode + if (process.env.CI) { + return 'Demo Title'; + } + + const { title } = await inquirer.prompt([ + { + type: 'input', + name: 'title', + message: 'Enter the title of the app', + }, + ]); + + return title; } diff --git a/example-templates/react-router/.cursor/rules/accessibility.mdc b/example-templates/react-router/.cursor/rules/accessibility.mdc new file mode 100644 index 0000000..9774ae0 --- /dev/null +++ b/example-templates/react-router/.cursor/rules/accessibility.mdc @@ -0,0 +1,24 @@ +--- +description: All tasks that are related to improving accessibility (a11y) +globs: +alwaysApply: false +--- + +# Accessibility Rules + +All UI components in must comply with the WCAG 2.1 AA standard. + +- For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies. +- Non-native user interactive components need their `role` attribute set. Pay attention to nested components and also take the role of parent components into account. +- Non-native user interactive components which can be in different UI states, need to be annotated with WAI-ARIA state and property attributes where possible. +- Interactive components need to be labelled either via label elements or through `aria-label` or `aria-labelledby`. +- Do not add noise and information that is not relevant to a user. +- Avoid redundancy. Verify that there are no duplicated annotations. Also check parent and child components. +- Keyboard navigation needs to work in all components. +- User focus needs to be properly guided. +- New titles need to be localized. +- Interactive elements cannot contain other interactive elements. + +Do not forget to also update the tests for a component. + +Use the class `sr-only` for elements that should only be visible to screenreaders. \ No newline at end of file diff --git a/example-templates/react-router/.cursor/rules/react.mdc b/example-templates/react-router/.cursor/rules/react.mdc new file mode 100644 index 0000000..eab9da8 --- /dev/null +++ b/example-templates/react-router/.cursor/rules/react.mdc @@ -0,0 +1,18 @@ +--- +description: +globs: *.tsx +alwaysApply: false +--- + +# Rules for authoring React components + +- React is not just a UI framework but also a concept of how a UI is composed. Information should only flow downwards (One-Way dataflow). Never break this concept. +- Only trigger updates through events / actions or handlers. +- Avoid Refs to a child component. Use Props / Context instead and have the child component react to a change in props. Never trigger changes on a different component explicitly. +- React uses functional concepts instead of OOP concepts. +- Components and Hooks must be pure. +- Prefer useReducer over multiple useStates in the same component. +- Document the code in a way that you still understand it a year later. + + + diff --git a/example-templates/react-router/.cursor/rules/tests.mdc b/example-templates/react-router/.cursor/rules/tests.mdc new file mode 100644 index 0000000..0cf4039 --- /dev/null +++ b/example-templates/react-router/.cursor/rules/tests.mdc @@ -0,0 +1,11 @@ +--- +description: +globs: *.spec.tsx +alwaysApply: false +--- + +# Tests + +- This project is using Testing-Library +- Use semantic selectors from testing-library over querySelector(). +- Prefer user-event over fireEvent. diff --git a/example-templates/react-router/.env.example b/example-templates/react-router/.env.example new file mode 100644 index 0000000..0827589 --- /dev/null +++ b/example-templates/react-router/.env.example @@ -0,0 +1,2 @@ +VITE_BUILD_NUMBER=Vite +VITE_VERSION=dev \ No newline at end of file diff --git a/example-templates/react-router/.gitignore b/example-templates/react-router/.gitignore new file mode 100644 index 0000000..afda6bb --- /dev/null +++ b/example-templates/react-router/.gitignore @@ -0,0 +1,47 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +/.cache +.react-router/ +/public/build +tsconfig.tsbuildinfo +data-mocks/ + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +vite.config.ts.timestamp-* + +# Misc +.DS_Store +*.pem +.idea/ + + +catalyst-ui-kit/ \ No newline at end of file diff --git a/example-templates/react-router/.gitlab-ci.yml b/example-templates/react-router/.gitlab-ci.yml new file mode 100644 index 0000000..ce76e08 --- /dev/null +++ b/example-templates/react-router/.gitlab-ci.yml @@ -0,0 +1,188 @@ +# Root CI File + +include: + - component: $CI_SERVER_HOST/$CI_COMPONENT_DIR/gitlab-environments/preview@1.0 + inputs: + build_job: 'build prototype' + stage: publish + build_folder: 'prototype/build/client' + +default: + image: node:22 + interruptible: true + # Usage of global_cache key to override the default push-pull policy + # https://docs.gitlab.com/ee/ci/caching/index.html + cache: &npm_cache + key: + files: + - package-lock.json + policy: pull + paths: + - 'node_modules/' + - './*/node_modules/' + - 'config/*/node_modules/' + timeout: 2 minutes + +stages: + - install + - verify + - build + - publish + +variables: + TURBO_TELEMETRY_DISABLED: 1 + VITE_BUILD_NUMBER: ${CI_PIPELINE_IID} + VITE_VERSION: ${CI_COMMIT_SHORT_SHA} + +workflow: + rules: + - if: $CI_MERGE_REQUEST_IID + - if: $CI_COMMIT_TAG + when: always + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_TAG == null + - if: $CI_PIPELINE_SOURCE == "schedule" + +############################################################ +# +# Install stage +# +############################################################ + +# Install dependencies +install dependencies: + stage: install + interruptible: false + cache: + - <<: *npm_cache + policy: pull-push + script: + - node -v + - echo "Installing Dependencies" + - 'find . -name "node_modules" -type d -prune -exec rm -rf "{}" +' + - time npm clean-install + # We need to install the linux version of rollup here. https://github.com/npm/cli/issues/4828 + - npm i -O @rollup/rollup-linux-x64-gnu + rules: + - if: $CI_COMMIT_REF_PROTECTED == "true" + - if: $CI_MERGE_REQUEST_IID + changes: + - 'package-lock.json' + allow_failure: false + - if: $CI_PIPELINE_SOURCE == "schedule" + +force install dependencies: + extends: install dependencies + rules: + - when: manual + allow_failure: true + script: + - node -v + - 'find . -name "node_modules" -type d -prune -exec rm -rf "{}" +' + - npm install --frozen-lockfile --no-progress --non-interactive + - npm i -O @rollup/rollup-linux-x64-gnu + +############################################################ +# +# Verify stage +# +############################################################ + +# Run Type Checks on all changed modules. +verify: + stage: verify + interruptible: true + variables: + ESLINT_CODE_QUALITY_REPORT: 'gl-code-quality-report.json' + cache: + - <<: *npm_cache + - key: verify-$CI_COMMIT_REF_SLUG + paths: + - '.turbo/' + - '**/.turbo/' + needs: + - job: install dependencies + optional: true + script: + - node --version + - npm --version + - npm ci + - ./node_modules/.bin/turbo run typecheck + - ./node_modules/.bin/turbo run lint + artifacts: + when: always + expire_in: 5 days + paths: + - gl-code-quality-report.json + reports: + codequality: 'gl-code-quality-report.json' + +test: + stage: verify + interruptible: true + needs: + - job: install dependencies + optional: true + script: + - ./node_modules/.bin/turbo run test + +############################################################ +# +# Build stage +# +############################################################ + +build prototype: + interruptible: true + stage: build + script: + - npm ci + - npm run build:prototype + artifacts: + paths: + - prototype/build + +############################################################ +# +# Publish stage +# +############################################################ + +pages: + stage: publish + needs: + - job: install dependencies + optional: true + allow_failure: true + script: + - npm ci + - npm run docs + artifacts: + paths: + - docs/build + publish: docs/build + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + changes: + - 'docs/**/*' + +publish prototype continuous: + extends: create preview + variables: + CI_MERGE_REQUEST_ID: p${CI_PROJECT_ID}-cont + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + environment: + name: 'prototype/${CI_PROJECT_PATH_SLUG}/continuous' + url: $CI_MERGE_REQUEST_REVIEW_URL + on_stop: 'stop prototype continuous' + +stop prototype continuous: + extends: stop preview + variables: + CI_MERGE_REQUEST_ID: p${CI_PROJECT_ID}-cont + environment: + name: 'prototype/${CI_PROJECT_PATH_SLUG}/continuous' + action: stop + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + when: manual diff --git a/example-templates/react-router/.licenses/isc.md b/example-templates/react-router/.licenses/isc.md new file mode 100644 index 0000000..31b673a --- /dev/null +++ b/example-templates/react-router/.licenses/isc.md @@ -0,0 +1,17 @@ +# ISC License + +- Lucide + +Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All +other copyright (c) for Lucide are held by Lucide Contributors 2022. + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee +is hereby granted, provided that the above copyright notice and this permission notice appear in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. diff --git a/example-templates/react-router/.lintstagedrc.js b/example-templates/react-router/.lintstagedrc.js new file mode 100644 index 0000000..90e682a --- /dev/null +++ b/example-templates/react-router/.lintstagedrc.js @@ -0,0 +1,3 @@ +export default { + '*.{js,jsx,ts,tsx,json,css,scss,md}': ['prettier --write'], +} diff --git a/example-templates/react-router/.nvmrc b/example-templates/react-router/.nvmrc new file mode 100644 index 0000000..e4f846d --- /dev/null +++ b/example-templates/react-router/.nvmrc @@ -0,0 +1 @@ +v22.10.0 diff --git a/example-templates/react-router/.prettierignore b/example-templates/react-router/.prettierignore new file mode 100644 index 0000000..c7e2c6b --- /dev/null +++ b/example-templates/react-router/.prettierignore @@ -0,0 +1,6 @@ +/node_modules +package-lock.json +.gitignore +.cursorrules.md +**/public +.htaccess \ No newline at end of file diff --git a/example-templates/react-router/.prettierrc.js b/example-templates/react-router/.prettierrc.js new file mode 100644 index 0000000..5af14a2 --- /dev/null +++ b/example-templates/react-router/.prettierrc.js @@ -0,0 +1,30 @@ +/** @type {import("prettier").Options} */ +export default { + arrowParens: 'avoid', + bracketSameLine: false, + bracketSpacing: true, + embeddedLanguageFormatting: 'auto', + endOfLine: 'lf', + htmlWhitespaceSensitivity: 'css', + insertPragma: false, + jsxSingleQuote: false, + printWidth: 100, + proseWrap: 'always', + quoteProps: 'consistent', + requirePragma: false, + semi: true, + singleAttributePerLine: false, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', + useTabs: true, + overrides: [ + { + files: ['**/*.json', '**/*.yaml', '**/*.yml', '/**/*.adoc', '**/*.md'], + options: { + useTabs: false, + }, + }, + ], + plugins: ['prettier-plugin-organize-imports'], +}; diff --git a/example-templates/react-router/.vscode/settings.json b/example-templates/react-router/.vscode/settings.json new file mode 100644 index 0000000..335f886 --- /dev/null +++ b/example-templates/react-router/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode" +} \ No newline at end of file diff --git a/example-templates/react-router/LICENSE.md b/example-templates/react-router/LICENSE.md new file mode 100644 index 0000000..55654ce --- /dev/null +++ b/example-templates/react-router/LICENSE.md @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2025 ti&m + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/example-templates/react-router/README.md b/example-templates/react-router/README.md new file mode 100644 index 0000000..cd950c2 --- /dev/null +++ b/example-templates/react-router/README.md @@ -0,0 +1,33 @@ +# React Router Example + +This is the self-contained React Router example for Tinker Stack. + +It preserves the original demo-oriented workspace, including: + +- a richer prototype flow +- generated mock data +- MSW handlers wired to that dataset +- docs that describe the demo setup + +## Run the example + +Work inside this directory, not from the generated project root: + +```bash +npm install +npm run build:data +npm run dev +``` + +## How to use it + +This example is meant to be mined for patterns, not coupled to the main workspace. + +Copy ideas selectively from: + +- `prototype/` for routing, layouts, and UI flows +- `mocks/` for seeded dataset generation +- `msw/` for handler composition +- `api/` for shared response types + +You can delete the entire example directory without affecting the generated root project. diff --git a/example-templates/react-router/api/commons/enums.ts b/example-templates/react-router/api/commons/enums.ts new file mode 100644 index 0000000..3984b3c --- /dev/null +++ b/example-templates/react-router/api/commons/enums.ts @@ -0,0 +1,12 @@ +/** + * Granular Permissions. + * The UI only uses these permissions which are derived from the roles in the JWT. + * The key is the permission name and the value is the permission description. + */ +export const permissions = { + 'reports-create': 'Create reports', + 'reports-approve': 'Approve reports', + 'admin': 'Administrative privileges', +}; + +export type Permission = keyof typeof permissions; diff --git a/example-templates/react-router/api/commons/types.ts b/example-templates/react-router/api/commons/types.ts new file mode 100644 index 0000000..3df1886 --- /dev/null +++ b/example-templates/react-router/api/commons/types.ts @@ -0,0 +1,42 @@ +/** + * Image Object + */ +export type Image = { + url: string; + alt?: string; + width?: number; + height?: number; +}; + +/** + * Calendar Date String (YYYY-MM-DD) + */ +export type IsoDateString = string; +/** + * ISO 8601 Timestamp String + */ +export type IsoTimestampString = string; + +/** + * Spring Boot Paginated Response + */ +export type PaginatedResponse = { + content: T[]; + pageable: { + pageNumber: number; + pageSize: number; + }; + totalPages: number; + totalElements: number; + first: boolean; + last: boolean; + size: number; + number: number; +}; + +export type File = { + readonly type: string; + readonly name: string; + readonly size: number; + readonly createdAt: IsoTimestampString; +}; diff --git a/template/api/demo/types.ts b/example-templates/react-router/api/demo/types.ts similarity index 100% rename from template/api/demo/types.ts rename to example-templates/react-router/api/demo/types.ts diff --git a/example-templates/react-router/api/eslint.config.js b/example-templates/react-router/api/eslint.config.js new file mode 100644 index 0000000..75d444b --- /dev/null +++ b/example-templates/react-router/api/eslint.config.js @@ -0,0 +1,15 @@ +import baseConfig from '@repo/eslint-config/base'; + +export default [ + { + ignores: ['**/node_modules/**', '**/dist/**', '**/.turbo/**'], + }, + ...baseConfig, + { + languageOptions: { + parserOptions: { + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +]; diff --git a/example-templates/react-router/api/package.json b/example-templates/react-router/api/package.json new file mode 100644 index 0000000..c297c84 --- /dev/null +++ b/example-templates/react-router/api/package.json @@ -0,0 +1,43 @@ +{ + "name": "@repo/api", + "description": "Domain Types und Enums", + "version": "0.1.0", + "sideEffects": false, + "type": "module", + "scripts": { + "build": "rimraf dist && tsc && tsc-alias", + "dev": "tsc && (concurrently \"tsc -w\" \"tsc-alias -w\")", + "clean": "rimraf dist && rimraf .turbo", + "format": "prettier --write .", + "typecheck": "tsc && tsc-alias", + "lint": "eslint .", + "lint-staged": "lint-staged" + }, + "devDependencies": { + "@repo/eslint-config": "*", + "@repo/typescript-config": "*" + }, + "engines": { + "node": ">=22.0.0" + }, + "exports": { + "./commons/types": { + "types": "./dist/commons/types.d.ts" + }, + "./commons/enums": { + "types": "./dist/commons/enums.d.ts", + "import": "./dist/commons/enums.js" + }, + "./demo/types": { + "types": "./dist/demo/types.d.ts" + } + }, + "lint-staged": { + "*.{js,ts,tsx}": [ + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} \ No newline at end of file diff --git a/example-templates/react-router/api/tsconfig.json b/example-templates/react-router/api/tsconfig.json new file mode 100644 index 0000000..1c4452b --- /dev/null +++ b/example-templates/react-router/api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@repo/typescript-config/node-library.json", + "include": ["./**/*.ts"], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#/*": ["./*"] + }, + "outDir": "dist" + }, + "exclude": ["node_modules", "dist", ".turbo"] +} diff --git a/example-templates/react-router/config/eslint/README.md b/example-templates/react-router/config/eslint/README.md new file mode 100644 index 0000000..8b42d90 --- /dev/null +++ b/example-templates/react-router/config/eslint/README.md @@ -0,0 +1,3 @@ +# `@turbo/eslint-config` + +Collection of internal eslint configurations. diff --git a/example-templates/react-router/config/eslint/base.js b/example-templates/react-router/config/eslint/base.js new file mode 100644 index 0000000..d14dd65 --- /dev/null +++ b/example-templates/react-router/config/eslint/base.js @@ -0,0 +1,61 @@ +import eslint from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import importPlugin from 'eslint-plugin-import'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +/** + * This is the basic rule set for all projects. + * Make sure to add the languageOptions.parserOptions.tsconfigRootDir to the project you want to lint. + * @returns { import('typescript-eslint').FlatConfig.ConfigArray } + */ +export default tseslint.config( + eslint.configs.recommended, + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + projectService: { + allowDefaultProject: ['*.js'], + defaultProject: 'tsconfig.json', + }, + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + // ...globals.browser, + ...globals.serviceworker, + }, + }, + settings: { + 'import/internal-regex': '^#/', + }, + rules: { + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-duplicate-type-constituents': [ + 'error', + { + ignoreUnions: true, + ignoreIntersections: true, + }, + ], + '@typescript-eslint/consistent-type-imports': [ + 'error', + { fixStyle: 'separate-type-imports', prefer: 'type-imports' }, + ], + 'import/no-unresolved': 'off', // does not properly work with nodenext yet. + 'import/no-relative-packages': 'error', + 'import/no-relative-parent-imports': 'error', + }, + }, + eslintConfigPrettier, +); diff --git a/example-templates/react-router/config/eslint/package.json b/example-templates/react-router/config/eslint/package.json new file mode 100644 index 0000000..7bf38de --- /dev/null +++ b/example-templates/react-router/config/eslint/package.json @@ -0,0 +1,25 @@ +{ + "name": "@repo/eslint-config", + "version": "0.0.0", + "private": true, + "files": [ + "base.js", + "react.js" + ], + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", + "eslint-config-prettier": "^10.1.8", + "eslint-config-turbo": "^2.6.3", + "eslint-plugin-only-warn": "^1.1.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.49.0" + }, + "type": "module", + "exports": { + "./base": "./base.js", + "./react": "./react.js" + } +} diff --git a/example-templates/react-router/config/eslint/react.js b/example-templates/react-router/config/eslint/react.js new file mode 100644 index 0000000..d163295 --- /dev/null +++ b/example-templates/react-router/config/eslint/react.js @@ -0,0 +1,42 @@ +import baseConfig from '@repo/eslint-config/base'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import tseslint from 'typescript-eslint'; + +/** + * This config adds additional rules for React and JSX. + * @returns { import('typescript-eslint').FlatConfig.ConfigArray } + */ +export default tseslint.config( + ...baseConfig, + { + settings: { + react: { + version: 'detect', + }, + }, + }, + reactPlugin.configs.flat.recommended, + reactPlugin.configs.flat['jsx-runtime'], + // enable a11y rules for the frontend and UI only + // jsxA11yPlugin.flatConfigs.recommended, + // React + { + files: ['**/*.{tsx}'], + settings: { + formComponents: ['Form'], + linkComponents: [ + { name: 'Link', linkAttribute: 'to' }, + { name: 'NavLink', linkAttribute: 'to' }, + ], + }, + plugins: { + 'react-hooks': reactHooksPlugin, + }, + rules: { + 'react/react-in-jsx-scope': 'off', + ...reactHooksPlugin.configs.recommended.rules, + 'react-hooks/rules-of-hooks': 'error', + }, + }, +); diff --git a/example-templates/react-router/config/typescript/base.json b/example-templates/react-router/config/typescript/base.json new file mode 100644 index 0000000..20d7e23 --- /dev/null +++ b/example-templates/react-router/config/typescript/base.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "composite": true, + "esModuleInterop": true, + "incremental": true, + "isolatedModules": true, + "lib": ["ESNext"], + "forceConsistentCasingInFileNames": true, + "module": "NodeNext", + "moduleDetection": "force", + "moduleResolution": "NodeNext", + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": false, + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "noEmit": true + } +} diff --git a/example-templates/react-router/config/typescript/node-library.json b/example-templates/react-router/config/typescript/node-library.json new file mode 100644 index 0000000..9fb6019 --- /dev/null +++ b/example-templates/react-router/config/typescript/node-library.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node Library", + "extends": "./base.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "target": "ESNext", + } + } \ No newline at end of file diff --git a/example-templates/react-router/config/typescript/package.json b/example-templates/react-router/config/typescript/package.json new file mode 100644 index 0000000..27c0e60 --- /dev/null +++ b/example-templates/react-router/config/typescript/package.json @@ -0,0 +1,9 @@ +{ + "name": "@repo/typescript-config", + "version": "0.0.0", + "private": true, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/example-templates/react-router/config/typescript/react-app.json b/example-templates/react-router/config/typescript/react-app.json new file mode 100644 index 0000000..37e1448 --- /dev/null +++ b/example-templates/react-router/config/typescript/react-app.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "React App", + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ESNext", "DOM", "DOM.Iterable", "WebWorker"], + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true + } +} diff --git a/example-templates/react-router/config/typescript/react-library.json b/example-templates/react-router/config/typescript/react-library.json new file mode 100644 index 0000000..2543a7f --- /dev/null +++ b/example-templates/react-router/config/typescript/react-library.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "React Library", + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "allowImportingTsExtensions": true, + } +} diff --git a/example-templates/react-router/docs/antora-playbook.yml b/example-templates/react-router/docs/antora-playbook.yml new file mode 100644 index 0000000..dfc9cb1 --- /dev/null +++ b/example-templates/react-router/docs/antora-playbook.yml @@ -0,0 +1,22 @@ +site: + title: PLANNING STACK TEMPLATE Dokumentation + start_page: planning-stack-template-frontend::index.adoc + url: https://frontend-6dd0b0.pages.ti8m.ch +content: + sources: + - url: ../ + branches: HEAD + start_path: docs +ui: + bundle: + url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable + snapshot: true +output: + dir: ./build +asciidoc: + attributes: + # makes the extension download and store diagrams in Antora's _images folder (in output dir) + kroki-fetch-diagram: true + product: PLANNING STACK TEMPLATE + extensions: + - asciidoctor-kroki diff --git a/example-templates/react-router/docs/antora.yml b/example-templates/react-router/docs/antora.yml new file mode 100644 index 0000000..18fdf82 --- /dev/null +++ b/example-templates/react-router/docs/antora.yml @@ -0,0 +1,5 @@ +name: 'planning-stack-template-frontend' +title: 'PLANNING STACK TEMPLATE Frontend' +version: true +nav: + - modules/ROOT/nav.adoc diff --git a/example-templates/react-router/docs/modules/ROOT/images/Prototyp-Repo.drawio.png b/example-templates/react-router/docs/modules/ROOT/images/Prototyp-Repo.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..3e73d5a9d44f9bb9bf3fc79ace5d15ea263d57d5 GIT binary patch literal 24086 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≧aj4 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 T8Sp=Phmn<3lqh*@81VlAoYLA% literal 0 HcmV?d00001 diff --git a/example-templates/react-router/docs/modules/ROOT/nav.adoc b/example-templates/react-router/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000..f8c4bf7 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/nav.adoc @@ -0,0 +1,11 @@ +:product: PLANNING STACK TEMPLATE + +* xref:index.adoc[Übersicht] +** xref:getting-started.adoc[Erste Schritte] +** xref:architecture.adoc[Architektur] +** xref:monorepo.adoc[Monorepo] +** xref:prototype.adoc[Prototyp] +** xref:mocks.adoc[Testdaten] +** xref:design-system.adoc[Design System] +** xref:login.adoc[Login Flow] +** xref:tailwind.adoc[Tailwind] \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/architecture.adoc b/example-templates/react-router/docs/modules/ROOT/pages/architecture.adoc new file mode 100644 index 0000000..a940995 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/architecture.adoc @@ -0,0 +1,145 @@ += Architektur +:experimental: +:plantuml-server-url: http://www.plantuml.com/plantuml +:source-highlighter: highlight.js + +== Überblick + +Das PLANNING STACK TEMPLATE Frontend ist als Monorepo mit mehreren Packages aufgebaut. Der Fokus liegt auf: + +* Schnelle Iteration durch Prototyping +* Wiederverwendbare Komponenten +* Typsicherheit durch TypeScript +* Realistische Testdaten + +== Architekturprinzipien + +=== Domain-Driven Design + + + + +=== Prototypen-getriebene Entwicklung + +Der Entwicklungsprozess folgt einem iterativen Ansatz: + +1. Features werden zuerst im Prototyp implementiert +2. Designer-, Stakeholder- und Benutzer-Feedback wird eingeholt +3. Validierte Features werden in die produktive Anwendung übernommen + +Dies ermöglicht: + +* Reduzierung von Entwicklungsrisiken +* Validierung von UX-Konzepten +* Schnelles Feedback von Benutzern + +=== Typsicherheit + +* Strikte TypeScript Konfiguration +* Zentrale Definition von Domain Types +* Shared Types zwischen Packages + +=== Komponenten-Bibliothek + +Die UI-Komponenten sind in einem separaten Package (`@repo/ui`) organisiert: + +* Basis auf shadcn/ui, Tailwind UI und Tailwind +* Wiederverwendbar zwischen Prototyp und Produktion +* Einheitliches Design-System basierend auf Tailwind UI und dem Figma Design System + +== Technische Architektur + +Siehe auch xref:monorepo.adoc#aufbau[Monorepo]. + +=== Frontend Stack + +[cols="1,2,2"] +|=== +|Technologie |Verwendung |Begründung + +|React +|UI Framework +|Etabliert, grosses Ökosystem + +|React Router +|Full-Stack Framework +|Gute DX, Server Components + +|Tailwind +|CSS Framework +|Utility-First, Wartbar + +|shadcn/ui +|UI-Komponenten +|Zugänglich, Anpassbar + +|TypeScript +|Programmiersprache +|Typsicherheit, DX +|=== + +=== Mock Backend + +[plantuml,msw,svg] +---- +@startuml +actor Browser +participant "React Router App" as App +participant "MSW" as MSW +database "Mock Data" as Data + +Browser -> App: Request +App -> MSW: API Call +MSW -> Data: Load Data +Data --> MSW: Data +MSW --> App: Response +App --> Browser: Render +@enduml +---- + +* MSW (Mock Service Worker) für API-Simulation +* Realistische HTTP-Requests +* Einfacher Wechsel zu echtem Backend + +== Entwicklungsprozess + +=== Feature Entwicklung + +1. Feature-Anforderung +2. Prototyp Implementation +3. User Testing +4. Feedback Integration +5. Production Implementation + +=== Code Organisation + +* Feature-basierte Ordnerstruktur +* Shared Logic in Utils +* Typed API Boundaries + +== Deployment + +=== Prototype + +* Automatisches Deployment bei Push auf `main` +** URL: +** Aktuelle Entwicklungsversion +* Deployment bei Tag-Commit +** URL: +** Stabile Version für User-Testing + +=== Documentation + +* Antora Site Generator +* Deployed zusammen mit Prototype +* Automatische Updates + +== Supporting Subdomain + + +== Zukünftige Entwicklung + +* Migration zu Production App +* API Integration +* E2E Tests +* Performance Monitoring \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/design-system.adoc b/example-templates/react-router/docs/modules/ROOT/pages/design-system.adoc new file mode 100644 index 0000000..ffa094c --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/design-system.adoc @@ -0,0 +1,57 @@ += Design System +:experimental: + +== Überblick + + +== Komponenten + + +=== Verwendung + +[source,tsx] +---- +import { Button } from '@repo/ui/components/button' +import { LucideIcon } from 'lucide-react' + +export function IconButton({ icon: Icon }) { + return ( + + ) +} +---- + +== Theming + +=== Figma Integration + +* Designs aus Figma Projekt +* Farben und Typography synchron halten +* Komponenten-Patterns übernehmen + +=== Tailwind Konfiguration + +* Custom Colors aus CI +* Spacing und Typography nach Design System +* Erweiterbare Struktur + +Siehe auch xref:tailwind.adoc[Tailwind Dokumentation] für Best Practices. + +== Komponenten entwickeln + +=== Workflow + +1. Figma Design reviewen +2. shadcn/ui Komponente als Basis nehmen +3. Mit Tailwind UI Patterns abgleichen +4. An Design System anpassen +5. Dokumentation erstellen + +=== Guidelines + +* Accessibility first +* Responsive Design +* Konsistente Props API +* Dokumentation in Storybook diff --git a/example-templates/react-router/docs/modules/ROOT/pages/documentation.adoc b/example-templates/react-router/docs/modules/ROOT/pages/documentation.adoc new file mode 100644 index 0000000..4478161 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/documentation.adoc @@ -0,0 +1,93 @@ += Dokumentation +:experimental: +:url-antora-docs: https://docs.antora.org/antora/latest/ +:url-kroki-docs: https://docs.kroki.io/kroki/ + +== Überblick + +Die Entwicklerdokumentation wird mit {url-antora-docs}[Antora] erstellt und über GitLab Pages bereitgestellt. + +* URL: +* Automatisches Deployment bei Push auf `main` +* Versionierung über Git + +== Struktur + +[source] +---- +docs/ +├── antora.yml # Antora Projekt-Konfiguration +├── antora-playbook.yml # Site Generator Konfiguration +└── modules/ + └── ROOT/ + ├── nav.adoc # Navigation + ├── pages/ # Dokumentationsseiten + └── partials/ # Wiederverwendbare Inhalte +---- + +== Diagramme + +Die Dokumentation unterstützt verschiedene Diagrammtypen über {url-kroki-docs}[Kroki]: + +=== PlantUML + +[source,asciidoc] +---- +[plantuml,format=svg] +---- +@startuml +package "Frontend" { + [Components] + [Routes] +} +[Components] --> [Routes] +@enduml +---- +---- + +=== Weitere Diagrammtypen + +* Mermaid +* C4 (mit PlantUML) +* GraphViz +* ... + +Siehe {url-kroki-docs}#diagram-types[Kroki Diagram Types] für eine vollständige Liste. + +== Lokale Entwicklung + +=== Voraussetzungen + +* Node.js >= 22.0.0 +* npm >= 10.0.0 + +=== Dokumentation lokal generieren + +[source,bash] +---- +# Im docs Verzeichnis +npm run docs +---- + +Die generierte Dokumentation ist dann unter `docs/build` verfügbar. + +== Best Practices + +* Neue Features parallel zur Implementierung dokumentieren +* Diagramme für komplexe Zusammenhänge nutzen +* Wiederverwendbare Inhalte in `partials` auslagern +* Interne Links mit `xref:` erstellen + +== Deployment + +Das Deployment erfolgt automatisch über die GitLab CI/CD Pipeline: + +* Trigger: Push auf `main` +* Output: Statische Seiten +* Hosting: GitLab Pages + +== Nützliche Links + +* {url-antora-docs}[Antora Dokumentation] +* {url-antora-docs}asciidoc/page/[AsciiDoc Syntax Guide] +* {url-kroki-docs}[Kroki Dokumentation] \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/getting-started.adoc b/example-templates/react-router/docs/modules/ROOT/pages/getting-started.adoc new file mode 100644 index 0000000..7229b2a --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/getting-started.adoc @@ -0,0 +1,139 @@ += Erste Schritte +:experimental: +:icons: font + + + +== Voraussetzungen + +Für die Entwicklung werden folgende Tools benötigt: + +* Node.js >= 22 +* npm >= 10 +* Git + +== Repository klonen + +[source,bash] +---- +git clone TODO +cd planning-stack-template +---- + +== Installation + +Das Projekt verwendet npm Workspaces. Alle Abhängigkeiten können mit einem Befehl installiert werden: + +[source,bash] +---- +npm install +---- + +== Entwicklungsumgebung einrichten + +=== Mock-Daten generieren + +Vor dem ersten Start müssen die Mock-Daten generiert werden: + +[source,bash] +---- +npm run build:data +---- + +=== Entwicklungsserver starten + +Der Entwicklungsserver kann mit folgendem Befehl im Root-Verzeichnis des Repos gestartet werden: + +[source,bash] +---- +npm run dev +---- + +Dies startet: + +* Den https://reactrouter.com/home[React Router] Entwicklungsserver (Prototyp) +* Einen Watcher für die Mock-Daten +* Einen Watcher für die UI-Komponenten + +Die Anwendung ist dann unter http://localhost:5173 erreichbar. + +== Projektstruktur + +[source] +---- +. +├── api/ # Domain Types & Enums +├── config/ +│ ├── eslint/ # ESLint Konfigurationen +│ ├── typescript/ # Typescript Konfigurationen +├── docs/ # Dokumentation +├── frontend/ # Produktives Frontend (noch nicht eingerichtet) +├── mocks/ # Testdaten Generator +├── mock-api/ # Mock Backend +├── prototype/ # Klickbarer UX-Prototyp +└── ui/ # Design-System Komponenten +---- + + +== IDE Konfiguration + +=== VS Code + +Empfohlene Extensions: + +* ESLint +* Prettier +* Tailwind CSS IntelliSense +* AsciiDoc + +Die Projekteinstellungen für VS Code sind bereits im Repository enthalten. + +=== WebStorm + +Für WebStorm müssen folgende Plugins installiert werden: + +* Tailwind CSS +* AsciiDoc + +== Typische Entwicklungsabläufe + +=== Neue Feature im Prototyp entwickeln + +1. Feature-Branch erstellen +2. Route in `prototype/app/routes` anlegen +3. UI mit shadcn/ui Komponenten aufbauen +4. Mock-Daten in `mocks` erweitern falls nötig +5. Feature testen und iterieren +6. Pull Request erstellen + +=== UI-Komponente hinzufügen + +1. Komponente in `packages/ui/src/components` erstellen +2. Storybook Story schreiben +3. Tests hinzufügen +4. Komponente exportieren und im Prototyp verwenden + +== Nächste Schritte + +* xref:architecture.adoc[Architektur] verstehen +* xref:prototype.adoc[Prototyp] kennenlernen +* xref:design-system.adoc[Design System] erkunden + +== Troubleshooting + +=== Bekannte Probleme + +[qanda] +Typescript Fehler nach npm install:: + Führen Sie `npm run build` aus, um alle Packages zu bauen. + +Mock-Daten werden nicht aktualisiert:: + Löschen Sie den `.cache` Ordner und führen Sie `npm run build:data` erneut aus. + +=== Support + +Bei Problemen: + +1. Prüfen Sie die bekannten Probleme +2. Suchen Sie in den GitHub Issues +3. Erstellen Sie ein neues Issue \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/index.adoc b/example-templates/react-router/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..4d0ccaa --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,78 @@ += PLANNING STACK TEMPLATE Frontend Dokumentation +:description: Entwicklerdokumentation für das PLANNING STACK TEMPLATE Frontend +:experimental: + +== Über dieses Projekt + +Beschreibung des Projekts + +== Module + +.Frontend-Struktur +image::Prototyp-Repo.drawio.png[Repository-Struktur] + +Das Projekt ist als Monorepo mit folgenden Hauptmodulen aufgebaut: + +[cols="1,4"] +|=== +|Modul |Beschreibung + +|@repo/api +|Domain-Typen und Enums + +|@repo/docs +|Diese Dokumentation + +|@repo/e2e +|End-to-End und Integrations-Tests (Browser-Tests) + +|@repo/frontend +|Frontend-Anwendung + +|@repo/msw +|Mock-Backend mit MSW + +|@repo/mocks +|Synthetische Testdaten-Generator + +|@repo/prototype +|Klickbarer UX-Prototyp für schnelles Feedback + +|@repo/ui +|Design-System und Komponenten-Bibliothek + +|=== + +Das Mock-API muss mit dem Backend synchron gehalten werden. Somit ist es möglich, die Frontend-Anwendung lokal zu entwickeln und gleichzeitig das Backend zu testen. + +Ausserdem ist es dadurch möglich, mit dem Prototypen gegen das echte Backend zu testen. + + +== Technologie-Stack + +Das Frontend basiert auf folgenden Haupttechnologien: + +* *React* mit TypeScript für die UI-Entwicklung +* *Tailwind CSS* mit shadcn/ui für das Design-System +* *Turborepo* für das Monorepo-Management +* *MSW* (Mock Service Worker) für das Mock-Backend +* *Antora* für die Dokumentation + +Die Technologie für das Frontend ist noch offen. Empfohlen wird +React Router v7 als Framework (Isomorph). + +Der Prototyp verwendet React Router SPA, damit er auf einem Shared-Host deployed werden kann. + +== Erste Schritte + +Siehe xref:getting-started.adoc[Erste Schritte] für Setup-Anweisungen und einen Überblick über die Entwicklungsumgebung. + +== Architektur + +Die Anwendung folgt einer prototypen-getriebenen Entwicklung: + +1. Schnelle Iteration über Features im Prototyp +2. Feedback von Benutzern einholen +3. Validierte Features in die produktive Anwendung übernehmen + +Mehr Details zur Architektur unter xref:architecture.adoc[Architektur]. \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/login.adoc b/example-templates/react-router/docs/modules/ROOT/pages/login.adoc new file mode 100644 index 0000000..627d3a5 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/login.adoc @@ -0,0 +1,62 @@ += Login Flow (Prototyp) +:experimental: + +== Überblick + +Im Prototyp wird ein vereinfachtes Login-System verwendet: + +* Cookie-basierte Authentifizierung +* Mock Implementation mit MSW +* Vordefinierte Test-Benutzer + +== Implementation + +=== Mock Service Worker + +[plantuml,mock-auth,svg] +---- +@startuml +participant "Browser" as Browser +participant "MSW Handler" as MSW +database "Mock Data" as Data + +Browser -> MSW: POST /auth/login +note right: Form Data mit Username +MSW -> Data: Token lookup +Data --> MSW: JWT Payload +MSW -> MSW: Permissions aus\nRollen ermitteln +MSW --> Browser: Set-Cookie: authToken +@enduml +---- + +=== Test-Benutzer + +[cols="1,2,1"] +|=== +|Persona |Organisation |Permissions + +|Administrator +|Primär-Organisation +|Alle Org-Berechtigungen + +|=== + +== Authentifizierung + +Die Auth-Prüfung erfolgt in den MSW Handlers: + +[source,typescript] +---- +// Beispiel eines geschützten msw Endpoints +http.post('/reports/approve', async ({ cookies }) => { + const { sub, orgId, permissions } = auth(cookies); + // ... Handler Implementation +}); +---- + +== Hinweise + +* Diese Implementation ist nur für den Prototyp, sollte aber ähnlich sein zu der späteren Produktiv-Implementation +* Keine echte Sicherheit +* Dient nur der Feature-Entwicklung +* Produktive Implementation erfolgt später mit IAM \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/mock-data.adoc b/example-templates/react-router/docs/modules/ROOT/pages/mock-data.adoc new file mode 100644 index 0000000..e871f62 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/mock-data.adoc @@ -0,0 +1,46 @@ += Testdaten +:experimental: + +== Überblick + +Das `@repo/mocks` Package generiert synthetische Testdaten für die Entwicklung. Die Daten sind: + +* Realistisch und konsistent +* Typsicher durch TypeScript +* Reproduzierbar durch feste Seeds + +Die Daten werden im Entwicklungs-Modus On-The-Fly beim Reload der Applikation generiert. + +== Generator + +=== Technologie + +* https://fakerjs.dev/[Faker.js] für realistische Daten +* TypeScript für Typsicherheit +* 3 vorgegebene Konfigurationen für unterschiedliche Datenmengen + ** `small`: Für manuelle Prüfung der erstellten Daten. + ** `medium`: Für automatisches Testen und Entwicklung. + ** `full`: Alle Organisationen, für vollständige Tests. + + +=== Verwendung + +Siehe `@repo/mocks/README.md` + +Der Ordner enthält Generator-Klassen für die einzelnen Entitäten. + +In der Datei `dataset.ts` werden die einzelnen Generatoren zu einem Datensatz zusammengefasst. + +== Daten erweitern + +1. Domain-Types in `@repo/api` definieren und falls nötig im lokalen Repository anpassen. +2. Generator in `@repo/mocks/src/generators` erstellen +3. In `dataset.ts` integrieren +4. Build mit `npm run build:data` + +== Best Practices + +* Realistische Wertebereiche verwenden +* Beziehungen zwischen Entitäten beachten +* Lokale Besonderheiten berücksichtigen (z.B. Schweizer Adressen) +* Reproduzierbarkeit durch Seeds sicherstellen \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/monorepo.adoc b/example-templates/react-router/docs/modules/ROOT/pages/monorepo.adoc new file mode 100644 index 0000000..f31d30c --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/monorepo.adoc @@ -0,0 +1,64 @@ += Monorepo + +Siehe auch README.md. + +Wir verwenden Turborepo mit der https://turbo.build/repo/docs/core-concepts/internal-packages#compiled-packages[Compiled Packages] Strategie. + +Alle Pakete sind ESM-Module und verwenden den `@repo` Scope. + +Dieses Monorepo enthält die folgenden Pakete/Apps: + +== Apps und Pakete + +- `@repo/frontend`: das Frontend für die PLANNING STACK TEMPLATE Anwendung (noch zu erstellen). +- `@repo/prototype`: ein Prototyp für die PLANNING STACK TEMPLATE Anwendung. +- `@repo/api`: ein Paket, das die Domain-Typen und -Enums bereitstellt. +- `@repo/docs`: Dokumentationen für die PLANNING STACK TEMPLATE Anwendung im Antora-Format. +- `@repo/mocks`: ein Paket, das synthetische Daten für die Anwendungen bereitstellt. +- `@repo/mock-api`: ein Paket, das eine Mock-API über Service-Worker bereitstellt. +- `@repo/ui`: eine Basis-React-Komponentenbibliothek, die von den Anwendungen `FAS` und `prototype` + gemeinsam genutzt wird +- `@repo/eslint-config`: `eslint`-Konfigurationen +- `@repo/typescript-config`: `tsconfig.json`s, die im gesamten Monorepo verwendet werden + +== Build + +Die Builds sind alle von https://turbo.build[Turborepo] gesteuert und sollten aus dem +Root-Verzeichnis heraus aufgerufen werden. werden. + +Um alle Apps und Pakete zu bauen, führe den folgenden Befehl aus: + +``` +npm run build +``` + +Es ist auch möglich, nur ein bestimmtes Paket oder eine bestimmte App zu bauen: + +``` +npm run build:data + +npm run build:msw + +npm run build:prototype +``` + +### Entwicklung + +Aktuell ist standardmässig der Prototyp aktiviert. Um die Entwicklung zu starten, führe den +folgenden Befehl aus. Dieser startet einen lokalen Server und watcher für die Abhängigen Pakete. + +``` +npm run dev +``` + + + +== Aufbau + +[plantuml,monorepo,svg,role=component] +---- +include::partial$monorepo.puml[] +---- + +Das Monorepo verwendet die export und import Direktiven von node ESM Modulen. +Alle exportierten Namespaces müssen in der package.json Datei aufgelistet werden. \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/prototype.adoc b/example-templates/react-router/docs/modules/ROOT/pages/prototype.adoc new file mode 100644 index 0000000..a0d1223 --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/prototype.adoc @@ -0,0 +1,105 @@ += Prototyp +:experimental: + +== Zweck + +Der PLANNING STACK TEMPLATE Prototyp dient als: + +* Testumgebung für neue Features und UX-Konzepte +* Basis für Benutzerfeedback und Iterationen +* Proof-of-Concept für technische Lösungen + +== Struktur + +Die Ordnerstruktur des Prototypen orientiert sich an den Modulen des Produktionssystems. +Zusätzlich gibt es den Ordner 'components' für Modulübergreifende UI-Komponenten und 'hooks' für wiederverwendbare Logik. +Der Ordner 'lib' enthält Hilfsfunktionen. Der Ordner 'mocks' die Logik für die Mock-Daten. + +Die Routen sind über eine Route-Konfiguration in app/routes.ts definiert. +Die Route-Komponenten sind in den entsprechenden Modul-Ordnern. + +== API + +Der Prototyp verwendet das `ky` Paket für HTTP-Anfragen. + + +=== Technischer Aufbau + +* React Router SPA mit TypeScript +* Mock-Backend via MSW +* Synthetische Testdaten +* shadcn/ui Komponenten + +== Entwicklungsprozess + +=== Feature-Entwicklung + +1. Feature-Anforderung erfassen +2. Schnelle Implementierung im Prototyp +3. Review mit Stakeholdern +4. Feedback einarbeiten +5. User-Stories updaten +6. Feature für Produktion freigeben + +=== Guidelines + +* Fokus auf Benutzerinteraktion, nicht auf Datenvalidierung +* TODOs für fehlende Produktionsanforderungen +* Schnelle Iteration über perfekte Implementation +* Realistische Testdaten verwenden + +== Deployment + +Das Deployment erfolgt automatisch über die CI/CD Pipeline: + +=== Umgebungen + +[cols="1,3"] +|=== +|Umgebung |Beschreibung + +|Continuous Integration +|* Deployment bei Push auf `main` +* URL: +* Aktuelle Entwicklungsversion + +|Acceptance Test +|* Deployment bei Tag-Commit +* URL: +* Stabile Version für User-Testing + + +|Preview +|* Preview-Umgebung, z.B. für Merge-Requests +* URL: +* Alternative Version für A/B Tests. +|=== + +Die nötigen Zugangsdaten müssen in den Umgebungsvariablen gesetzt werden. Siehe `.env.example`. + +Das Deployment auf Preview kann bei einem Merge-Request manuell ausgelöst werden. + +=== Zugriff + +* Die Hosts sind über einen IP-Filter geschützt. Die Konfiguration ist in der .htaccess Datei im Ordner `prototype/app/public`. + +* Der Host für die Dokumentation kann nur vom internen Netzwerk aus erreicht werden. + + +== Limitationen + +* Keine echte Backend-Anbindung +* Eingeschränkte Validierung +* Vereinfachte Berechtigungen +* Keine Performance-Optimierung + +== Nächste Schritte + +* Integration von Benutzer-Feedback +* Priorisierung der Features für Produktion +* Identifikation technischer Herausforderungen + +== Bekannte Probleme + +* msw in einer Version höher als 2.6.3 (getestet bis 2.6.6) funktioniert nicht mit dem dev server. Das CSS kann nicht geladen werden. +Aktuell ist die Version auf 2.6.3 gepinnt. Allenfalls später nochmals testen und ggf. einen Github-Issue erstellen. \ No newline at end of file diff --git a/example-templates/react-router/docs/modules/ROOT/pages/tailwind.adoc b/example-templates/react-router/docs/modules/ROOT/pages/tailwind.adoc new file mode 100644 index 0000000..2359bef --- /dev/null +++ b/example-templates/react-router/docs/modules/ROOT/pages/tailwind.adoc @@ -0,0 +1,22 @@ += Tailwind Tipps und Tricks + +Tailwind Docs: https://tailwindcss.com/docs/ + +== State +Die Seite soll barrierefrei sein, daher bietet es sich an, die States falls möglich +auf den jeweiligen Aria-States zu basieren. + +.Beispiel Button +```tsx +