aigroup-workflow 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/fix-build.md +10 -5
- package/.claude/commands/init-project.md +13 -8
- package/.claude/commands/plan.md +15 -8
- package/.claude/commands/review.md +12 -6
- package/.claude/commands/tdd.md +11 -5
- package/.claude/commands/workflow-start.md +20 -11
- package/.claude/settings.json +28 -0
- package/.codex/agents/architect.toml +207 -0
- package/.codex/agents/build-error-resolver.toml +110 -0
- package/.codex/agents/code-reviewer.toml +233 -0
- package/.codex/agents/doc-updater.toml +103 -0
- package/.codex/agents/e2e-runner.toml +103 -0
- package/.codex/agents/get-current-datetime.toml +23 -0
- package/.codex/agents/init-architect.toml +181 -0
- package/.codex/agents/planner.toml +208 -0
- package/.codex/agents/refactor-cleaner.toml +81 -0
- package/.codex/agents/rust-reviewer.toml +90 -0
- package/.codex/agents/security-reviewer.toml +104 -0
- package/.codex/agents/tdd-guide.toml +87 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +23 -1
- package/LICENSE +20 -20
- package/README.md +333 -333
- package/agents/a11y-architect.md +141 -141
- package/agents/architect.md +211 -211
- package/agents/build-error-resolver.md +114 -114
- package/agents/chief-of-staff.md +151 -151
- package/agents/code-architect.md +71 -71
- package/agents/code-explorer.md +69 -69
- package/agents/code-reviewer.md +237 -237
- package/agents/code-simplifier.md +47 -47
- package/agents/comment-analyzer.md +45 -45
- package/agents/conversation-analyzer.md +52 -52
- package/agents/cpp-build-resolver.md +90 -90
- package/agents/cpp-reviewer.md +72 -72
- package/agents/csharp-reviewer.md +101 -101
- package/agents/dart-build-resolver.md +201 -201
- package/agents/database-reviewer.md +91 -91
- package/agents/doc-updater.md +107 -107
- package/agents/docs-lookup.md +68 -68
- package/agents/e2e-runner.md +107 -107
- package/agents/flutter-reviewer.md +243 -243
- package/agents/gan-evaluator.md +209 -209
- package/agents/gan-generator.md +131 -131
- package/agents/gan-planner.md +99 -99
- package/agents/get-current-datetime.md +26 -26
- package/agents/go-build-resolver.md +94 -94
- package/agents/go-reviewer.md +76 -76
- package/agents/harness-optimizer.md +35 -35
- package/agents/healthcare-reviewer.md +83 -83
- package/agents/java-build-resolver.md +153 -153
- package/agents/java-reviewer.md +92 -92
- package/agents/kotlin-build-resolver.md +118 -118
- package/agents/kotlin-reviewer.md +159 -159
- package/agents/loop-operator.md +36 -36
- package/agents/opensource-forker.md +198 -198
- package/agents/opensource-packager.md +249 -249
- package/agents/opensource-sanitizer.md +188 -188
- package/agents/performance-optimizer.md +446 -446
- package/agents/planner.md +212 -212
- package/agents/pr-test-analyzer.md +45 -45
- package/agents/python-reviewer.md +98 -98
- package/agents/pytorch-build-resolver.md +120 -120
- package/agents/refactor-cleaner.md +85 -85
- package/agents/rust-build-resolver.md +148 -148
- package/agents/rust-reviewer.md +94 -94
- package/agents/security-reviewer.md +108 -108
- package/agents/seo-specialist.md +59 -59
- package/agents/silent-failure-hunter.md +50 -50
- package/agents/tdd-guide.md +91 -91
- package/agents/type-design-analyzer.md +41 -41
- package/agents/typescript-reviewer.md +112 -112
- package/cli/commands/update.mjs +1 -1
- package/cli/utils/scaffold.mjs +53 -0
- package/docs/rules/agents.md +166 -50
- package/docs/rules/cpp/coding-style.md +44 -44
- package/docs/rules/cpp/hooks.md +39 -39
- package/docs/rules/cpp/patterns.md +51 -51
- package/docs/rules/cpp/security.md +51 -51
- package/docs/rules/cpp/testing.md +44 -44
- package/docs/rules/csharp/coding-style.md +72 -72
- package/docs/rules/csharp/hooks.md +25 -25
- package/docs/rules/csharp/patterns.md +50 -50
- package/docs/rules/csharp/security.md +58 -58
- package/docs/rules/csharp/testing.md +46 -46
- package/docs/rules/dart/coding-style.md +159 -159
- package/docs/rules/dart/hooks.md +66 -66
- package/docs/rules/dart/patterns.md +261 -261
- package/docs/rules/dart/security.md +135 -135
- package/docs/rules/dart/testing.md +215 -215
- package/docs/rules/golang/coding-style.md +32 -32
- package/docs/rules/golang/hooks.md +17 -17
- package/docs/rules/golang/patterns.md +45 -45
- package/docs/rules/golang/security.md +34 -34
- package/docs/rules/golang/testing.md +31 -31
- package/docs/rules/java/coding-style.md +114 -114
- package/docs/rules/java/hooks.md +18 -18
- package/docs/rules/java/patterns.md +146 -146
- package/docs/rules/java/security.md +100 -100
- package/docs/rules/java/testing.md +131 -131
- package/docs/rules/kotlin/coding-style.md +86 -86
- package/docs/rules/kotlin/hooks.md +17 -17
- package/docs/rules/kotlin/patterns.md +146 -146
- package/docs/rules/kotlin/security.md +82 -82
- package/docs/rules/kotlin/testing.md +128 -128
- package/docs/rules/perl/coding-style.md +46 -46
- package/docs/rules/perl/hooks.md +22 -22
- package/docs/rules/perl/patterns.md +76 -76
- package/docs/rules/perl/security.md +69 -69
- package/docs/rules/perl/testing.md +54 -54
- package/docs/rules/php/coding-style.md +40 -40
- package/docs/rules/php/hooks.md +24 -24
- package/docs/rules/php/patterns.md +33 -33
- package/docs/rules/php/security.md +37 -37
- package/docs/rules/php/testing.md +39 -39
- package/docs/rules/python/coding-style.md +42 -42
- package/docs/rules/python/hooks.md +19 -19
- package/docs/rules/python/patterns.md +39 -39
- package/docs/rules/python/security.md +30 -30
- package/docs/rules/python/testing.md +38 -38
- package/docs/rules/rust/coding-style.md +151 -151
- package/docs/rules/rust/hooks.md +16 -16
- package/docs/rules/rust/patterns.md +168 -168
- package/docs/rules/rust/security.md +141 -141
- package/docs/rules/rust/testing.md +154 -154
- package/docs/rules/swift/coding-style.md +47 -47
- package/docs/rules/swift/hooks.md +20 -20
- package/docs/rules/swift/patterns.md +66 -66
- package/docs/rules/swift/security.md +33 -33
- package/docs/rules/swift/testing.md +45 -45
- package/docs/rules/typescript/coding-style.md +199 -199
- package/docs/rules/typescript/hooks.md +22 -22
- package/docs/rules/typescript/patterns.md +52 -52
- package/docs/rules/typescript/security.md +28 -28
- package/docs/rules/typescript/testing.md +18 -18
- package/docs/rules/web/coding-style.md +96 -96
- package/docs/rules/web/design-quality.md +62 -62
- package/docs/rules/web/hooks.md +120 -120
- package/docs/rules/web/patterns.md +79 -79
- package/docs/rules/web/performance.md +64 -64
- package/docs/rules/web/security.md +57 -57
- package/docs/rules/web/testing.md +55 -55
- package/docs/templates/README.md +36 -36
- package/docs/templates/ai-project-final.md +124 -124
- package/docs/templates/ai-project.md +105 -105
- package/docs/templates/api.md +157 -157
- package/docs/templates/bug.md +62 -62
- package/docs/templates/code-review.md +87 -87
- package/docs/templates/generic.md +116 -116
- package/docs/templates/implementation-plan.md +1 -1
- package/docs/templates/meeting.md +68 -68
- package/docs/templates/prd.md +98 -98
- package/docs/templates/ui.md +134 -134
- package/docs/workflow-pipeline.md +5 -5
- package/package.json +40 -39
- package/skills/SUPERPOWERS-LICENSE +21 -21
- package/skills/ai-ml/fine-tuning-expert/SKILL.md +162 -162
- package/skills/ai-ml/fine-tuning-expert/references/dataset-preparation.md +540 -540
- package/skills/ai-ml/fine-tuning-expert/references/deployment-optimization.md +673 -673
- package/skills/ai-ml/fine-tuning-expert/references/evaluation-metrics.md +597 -597
- package/skills/ai-ml/fine-tuning-expert/references/hyperparameter-tuning.md +565 -565
- package/skills/ai-ml/fine-tuning-expert/references/lora-peft.md +347 -347
- package/skills/ai-ml/ml-pipeline/SKILL.md +159 -159
- package/skills/ai-ml/ml-pipeline/references/experiment-tracking.md +833 -833
- package/skills/ai-ml/ml-pipeline/references/feature-engineering.md +631 -631
- package/skills/ai-ml/ml-pipeline/references/model-validation.md +978 -978
- package/skills/ai-ml/ml-pipeline/references/pipeline-orchestration.md +907 -907
- package/skills/ai-ml/ml-pipeline/references/training-pipelines.md +782 -782
- package/skills/ai-ml/rag-architect/SKILL.md +194 -194
- package/skills/ai-ml/rag-architect/references/chunking-strategies.md +878 -878
- package/skills/ai-ml/rag-architect/references/embedding-models.md +561 -561
- package/skills/ai-ml/rag-architect/references/rag-evaluation.md +833 -833
- package/skills/ai-ml/rag-architect/references/retrieval-optimization.md +795 -795
- package/skills/ai-ml/rag-architect/references/vector-databases.md +589 -589
- package/skills/ai-ml/spark-engineer/SKILL.md +148 -148
- package/skills/ai-ml/spark-engineer/references/partitioning-caching.md +543 -543
- package/skills/ai-ml/spark-engineer/references/performance-tuning.md +544 -544
- package/skills/ai-ml/spark-engineer/references/rdd-operations.md +599 -599
- package/skills/ai-ml/spark-engineer/references/spark-sql-dataframes.md +474 -474
- package/skills/ai-ml/spark-engineer/references/streaming-patterns.md +786 -786
- package/skills/backend/api-designer/SKILL.md +217 -217
- package/skills/backend/api-designer/references/error-handling.md +541 -541
- package/skills/backend/api-designer/references/openapi.md +824 -824
- package/skills/backend/api-designer/references/pagination.md +494 -494
- package/skills/backend/api-designer/references/rest-patterns.md +335 -335
- package/skills/backend/api-designer/references/versioning.md +391 -391
- package/skills/backend/architecture-designer/SKILL.md +117 -117
- package/skills/backend/architecture-designer/references/adr-template.md +116 -116
- package/skills/backend/architecture-designer/references/architecture-patterns.md +111 -111
- package/skills/backend/architecture-designer/references/database-selection.md +102 -102
- package/skills/backend/architecture-designer/references/nfr-checklist.md +112 -112
- package/skills/backend/architecture-designer/references/system-design.md +100 -100
- package/skills/backend/code-documenter/SKILL.md +147 -147
- package/skills/backend/code-documenter/references/api-docs-fastapi-django.md +166 -166
- package/skills/backend/code-documenter/references/api-docs-nestjs-express.md +220 -220
- package/skills/backend/code-documenter/references/coverage-reports.md +125 -125
- package/skills/backend/code-documenter/references/documentation-systems.md +333 -333
- package/skills/backend/code-documenter/references/interactive-api-docs.md +531 -531
- package/skills/backend/code-documenter/references/python-docstrings.md +121 -121
- package/skills/backend/code-documenter/references/typescript-jsdoc.md +145 -145
- package/skills/backend/code-documenter/references/user-guides-tutorials.md +530 -530
- package/skills/backend/debugging-wizard/SKILL.md +105 -105
- package/skills/backend/debugging-wizard/references/common-patterns.md +132 -132
- package/skills/backend/debugging-wizard/references/debugging-tools.md +140 -140
- package/skills/backend/debugging-wizard/references/quick-fixes.md +177 -177
- package/skills/backend/debugging-wizard/references/strategies.md +142 -142
- package/skills/backend/debugging-wizard/references/systematic-debugging.md +367 -367
- package/skills/backend/feature-forge/SKILL.md +98 -98
- package/skills/backend/feature-forge/references/acceptance-criteria.md +104 -104
- package/skills/backend/feature-forge/references/ears-syntax.md +99 -99
- package/skills/backend/feature-forge/references/interview-questions.md +150 -150
- package/skills/backend/feature-forge/references/pre-discovery-subagents.md +54 -54
- package/skills/backend/feature-forge/references/specification-template.md +103 -103
- package/skills/backend/fullstack-guardian/SKILL.md +105 -105
- package/skills/backend/fullstack-guardian/references/api-design-standards.md +307 -307
- package/skills/backend/fullstack-guardian/references/architecture-decisions.md +350 -350
- package/skills/backend/fullstack-guardian/references/backend-patterns.md +237 -237
- package/skills/backend/fullstack-guardian/references/common-patterns.md +134 -134
- package/skills/backend/fullstack-guardian/references/deliverables-checklist.md +354 -354
- package/skills/backend/fullstack-guardian/references/design-template.md +91 -91
- package/skills/backend/fullstack-guardian/references/error-handling.md +135 -135
- package/skills/backend/fullstack-guardian/references/frontend-patterns.md +340 -340
- package/skills/backend/fullstack-guardian/references/integration-patterns.md +333 -333
- package/skills/backend/fullstack-guardian/references/security-checklist.md +106 -106
- package/skills/backend/graphql-architect/SKILL.md +146 -146
- package/skills/backend/graphql-architect/references/federation.md +418 -418
- package/skills/backend/graphql-architect/references/migration-from-rest.md +1141 -1141
- package/skills/backend/graphql-architect/references/resolvers.md +425 -425
- package/skills/backend/graphql-architect/references/schema-design.md +393 -393
- package/skills/backend/graphql-architect/references/security.md +569 -569
- package/skills/backend/graphql-architect/references/subscriptions.md +510 -510
- package/skills/backend/legacy-modernizer/SKILL.md +137 -137
- package/skills/backend/legacy-modernizer/references/legacy-testing.md +381 -381
- package/skills/backend/legacy-modernizer/references/migration-strategies.md +423 -423
- package/skills/backend/legacy-modernizer/references/refactoring-patterns.md +395 -395
- package/skills/backend/legacy-modernizer/references/strangler-fig-pattern.md +281 -281
- package/skills/backend/legacy-modernizer/references/system-assessment.md +487 -487
- package/skills/backend/microservices-architect/SKILL.md +164 -164
- package/skills/backend/microservices-architect/references/communication.md +499 -499
- package/skills/backend/microservices-architect/references/data.md +721 -721
- package/skills/backend/microservices-architect/references/decomposition.md +344 -344
- package/skills/backend/microservices-architect/references/observability.md +805 -805
- package/skills/backend/microservices-architect/references/patterns.md +603 -603
- package/skills/database/database-optimizer/SKILL.md +147 -147
- package/skills/database/database-optimizer/references/index-strategies.md +331 -331
- package/skills/database/database-optimizer/references/monitoring-analysis.md +501 -501
- package/skills/database/database-optimizer/references/mysql-tuning.md +452 -452
- package/skills/database/database-optimizer/references/postgresql-tuning.md +413 -413
- package/skills/database/database-optimizer/references/query-optimization.md +251 -251
- package/skills/database/postgres-pro/SKILL.md +152 -152
- package/skills/database/postgres-pro/references/extensions.md +404 -404
- package/skills/database/postgres-pro/references/jsonb.md +321 -321
- package/skills/database/postgres-pro/references/maintenance.md +481 -481
- package/skills/database/postgres-pro/references/performance.md +265 -265
- package/skills/database/postgres-pro/references/replication.md +446 -446
- package/skills/database/sql-pro/SKILL.md +129 -129
- package/skills/database/sql-pro/references/database-design.md +402 -402
- package/skills/database/sql-pro/references/dialect-differences.md +419 -419
- package/skills/database/sql-pro/references/optimization.md +384 -384
- package/skills/database/sql-pro/references/query-patterns.md +285 -285
- package/skills/database/sql-pro/references/window-functions.md +328 -328
- package/skills/dotnet/csharp-developer/SKILL.md +125 -125
- package/skills/dotnet/csharp-developer/references/aspnet-core.md +394 -394
- package/skills/dotnet/csharp-developer/references/blazor.md +553 -553
- package/skills/dotnet/csharp-developer/references/entity-framework.md +409 -409
- package/skills/dotnet/csharp-developer/references/modern-csharp.md +248 -248
- package/skills/dotnet/csharp-developer/references/performance.md +498 -498
- package/skills/dotnet/dotnet-core-expert/SKILL.md +138 -138
- package/skills/dotnet/dotnet-core-expert/references/authentication.md +546 -546
- package/skills/dotnet/dotnet-core-expert/references/clean-architecture.md +455 -455
- package/skills/dotnet/dotnet-core-expert/references/cloud-native.md +548 -548
- package/skills/dotnet/dotnet-core-expert/references/entity-framework.md +440 -440
- package/skills/dotnet/dotnet-core-expert/references/minimal-apis.md +319 -319
- package/skills/frontend/angular-architect/SKILL.md +152 -152
- package/skills/frontend/angular-architect/references/components.md +297 -297
- package/skills/frontend/angular-architect/references/ngrx.md +401 -401
- package/skills/frontend/angular-architect/references/routing.md +361 -361
- package/skills/frontend/angular-architect/references/rxjs.md +319 -319
- package/skills/frontend/angular-architect/references/testing.md +405 -405
- package/skills/frontend/design-commands/design.md +91 -91
- package/skills/frontend/design-commands/handoff.md +97 -97
- package/skills/frontend/design-commands/prototype.md +120 -120
- package/skills/frontend/design-commands/spec.md +160 -160
- package/skills/frontend/design-commands/style.md +78 -78
- package/skills/frontend/flutter-expert/SKILL.md +138 -138
- package/skills/frontend/flutter-expert/references/bloc-state.md +259 -259
- package/skills/frontend/flutter-expert/references/gorouter-navigation.md +119 -119
- package/skills/frontend/flutter-expert/references/performance.md +99 -99
- package/skills/frontend/flutter-expert/references/project-structure.md +118 -118
- package/skills/frontend/flutter-expert/references/riverpod-state.md +130 -130
- package/skills/frontend/flutter-expert/references/widget-patterns.md +123 -123
- package/skills/frontend/nextjs-developer/SKILL.md +143 -143
- package/skills/frontend/nextjs-developer/references/app-router.md +311 -311
- package/skills/frontend/nextjs-developer/references/data-fetching.md +482 -482
- package/skills/frontend/nextjs-developer/references/deployment.md +545 -545
- package/skills/frontend/nextjs-developer/references/server-actions.md +462 -462
- package/skills/frontend/nextjs-developer/references/server-components.md +384 -384
- package/skills/frontend/react-expert/SKILL.md +149 -149
- package/skills/frontend/react-expert/references/hooks-patterns.md +162 -162
- package/skills/frontend/react-expert/references/migration-class-to-modern.md +1119 -1119
- package/skills/frontend/react-expert/references/performance.md +168 -168
- package/skills/frontend/react-expert/references/react-19-features.md +174 -174
- package/skills/frontend/react-expert/references/server-components.md +143 -143
- package/skills/frontend/react-expert/references/state-management.md +171 -171
- package/skills/frontend/react-expert/references/testing-react.md +174 -174
- package/skills/frontend/react-native-expert/SKILL.md +185 -185
- package/skills/frontend/react-native-expert/references/expo-router.md +187 -187
- package/skills/frontend/react-native-expert/references/list-optimization.md +204 -204
- package/skills/frontend/react-native-expert/references/platform-handling.md +188 -188
- package/skills/frontend/react-native-expert/references/project-structure.md +171 -171
- package/skills/frontend/react-native-expert/references/storage-hooks.md +173 -173
- package/skills/frontend/senior-frontend/SKILL.md +477 -477
- package/skills/frontend/senior-frontend/references/frontend_best_practices.md +806 -806
- package/skills/frontend/senior-frontend/references/nextjs_optimization_guide.md +724 -724
- package/skills/frontend/senior-frontend/references/react_patterns.md +746 -746
- package/skills/frontend/senior-frontend/scripts/bundle_analyzer.py +407 -407
- package/skills/frontend/senior-frontend/scripts/component_generator.py +329 -329
- package/skills/frontend/senior-frontend/scripts/frontend_scaffolder.py +1005 -1005
- package/skills/frontend/ui-ux-pro-max/SKILL.md +386 -386
- package/skills/frontend/ui-ux-pro-max/data/charts.csv +26 -26
- package/skills/frontend/ui-ux-pro-max/data/colors.csv +97 -97
- package/skills/frontend/ui-ux-pro-max/data/icons.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/landing.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/data/products.csv +96 -96
- package/skills/frontend/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/skills/frontend/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/skills/frontend/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/skills/frontend/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/skills/frontend/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/skills/frontend/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/skills/frontend/ui-ux-pro-max/data/styles.csv +68 -68
- package/skills/frontend/ui-ux-pro-max/data/typography.csv +57 -57
- package/skills/frontend/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/skills/frontend/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/scripts/core.py +253 -253
- package/skills/frontend/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/skills/frontend/ui-ux-pro-max/scripts/search.py +114 -114
- package/skills/frontend/vue-expert/SKILL.md +98 -98
- package/skills/frontend/vue-expert/references/build-tooling.md +480 -480
- package/skills/frontend/vue-expert/references/components.md +448 -448
- package/skills/frontend/vue-expert/references/composition-api.md +299 -299
- package/skills/frontend/vue-expert/references/mobile-hybrid.md +636 -636
- package/skills/frontend/vue-expert/references/nuxt.md +669 -669
- package/skills/frontend/vue-expert/references/state-management.md +449 -449
- package/skills/frontend/vue-expert/references/typescript.md +584 -584
- package/skills/frontend/vue-expert-js/SKILL.md +167 -167
- package/skills/frontend/vue-expert-js/references/component-architecture.md +219 -219
- package/skills/frontend/vue-expert-js/references/composables-patterns.md +183 -183
- package/skills/frontend/vue-expert-js/references/jsdoc-typing.md +535 -535
- package/skills/frontend/vue-expert-js/references/state-management.md +249 -249
- package/skills/frontend/vue-expert-js/references/testing-patterns.md +237 -237
- package/skills/go-rust-cpp/cpp-pro/SKILL.md +115 -115
- package/skills/go-rust-cpp/cpp-pro/references/build-tooling.md +440 -440
- package/skills/go-rust-cpp/cpp-pro/references/concurrency.md +437 -437
- package/skills/go-rust-cpp/cpp-pro/references/memory-performance.md +397 -397
- package/skills/go-rust-cpp/cpp-pro/references/modern-cpp.md +304 -304
- package/skills/go-rust-cpp/cpp-pro/references/templates.md +357 -357
- package/skills/go-rust-cpp/golang-pro/SKILL.md +122 -122
- package/skills/go-rust-cpp/golang-pro/references/concurrency.md +329 -329
- package/skills/go-rust-cpp/golang-pro/references/generics.md +442 -442
- package/skills/go-rust-cpp/golang-pro/references/interfaces.md +432 -432
- package/skills/go-rust-cpp/golang-pro/references/project-structure.md +477 -477
- package/skills/go-rust-cpp/golang-pro/references/testing.md +451 -451
- package/skills/go-rust-cpp/rust-engineer/SKILL.md +167 -167
- package/skills/go-rust-cpp/rust-engineer/references/async.md +458 -458
- package/skills/go-rust-cpp/rust-engineer/references/error-handling.md +334 -334
- package/skills/go-rust-cpp/rust-engineer/references/ownership.md +278 -278
- package/skills/go-rust-cpp/rust-engineer/references/testing.md +470 -470
- package/skills/go-rust-cpp/rust-engineer/references/traits.md +413 -413
- package/skills/infra/cli-developer/SKILL.md +113 -113
- package/skills/infra/cli-developer/references/design-patterns.md +221 -221
- package/skills/infra/cli-developer/references/go-cli.md +540 -540
- package/skills/infra/cli-developer/references/node-cli.md +383 -383
- package/skills/infra/cli-developer/references/python-cli.md +422 -422
- package/skills/infra/cli-developer/references/ux-patterns.md +448 -448
- package/skills/infra/cloud-architect/SKILL.md +216 -216
- package/skills/infra/cloud-architect/references/aws.md +394 -394
- package/skills/infra/cloud-architect/references/azure.md +562 -562
- package/skills/infra/cloud-architect/references/cost.md +582 -582
- package/skills/infra/cloud-architect/references/gcp.md +633 -633
- package/skills/infra/cloud-architect/references/multi-cloud.md +483 -483
- package/skills/infra/devops-engineer/SKILL.md +144 -144
- package/skills/infra/devops-engineer/references/deployment-strategies.md +241 -241
- package/skills/infra/devops-engineer/references/docker-patterns.md +113 -113
- package/skills/infra/devops-engineer/references/github-actions.md +139 -139
- package/skills/infra/devops-engineer/references/incident-response.md +331 -331
- package/skills/infra/devops-engineer/references/kubernetes.md +154 -154
- package/skills/infra/devops-engineer/references/platform-engineering.md +417 -417
- package/skills/infra/devops-engineer/references/release-automation.md +527 -527
- package/skills/infra/devops-engineer/references/terraform-iac.md +141 -141
- package/skills/infra/kubernetes-specialist/SKILL.md +241 -241
- package/skills/infra/kubernetes-specialist/references/configuration.md +452 -452
- package/skills/infra/kubernetes-specialist/references/cost-optimization.md +458 -458
- package/skills/infra/kubernetes-specialist/references/custom-operators.md +563 -563
- package/skills/infra/kubernetes-specialist/references/gitops.md +530 -530
- package/skills/infra/kubernetes-specialist/references/helm-charts.md +912 -912
- package/skills/infra/kubernetes-specialist/references/multi-cluster.md +507 -507
- package/skills/infra/kubernetes-specialist/references/networking.md +447 -447
- package/skills/infra/kubernetes-specialist/references/service-mesh.md +459 -459
- package/skills/infra/kubernetes-specialist/references/storage.md +535 -535
- package/skills/infra/kubernetes-specialist/references/troubleshooting.md +414 -414
- package/skills/infra/kubernetes-specialist/references/workloads.md +377 -377
- package/skills/infra/mcp-developer/SKILL.md +143 -143
- package/skills/infra/mcp-developer/references/protocol.md +244 -244
- package/skills/infra/mcp-developer/references/python-sdk.md +367 -367
- package/skills/infra/mcp-developer/references/resources.md +554 -554
- package/skills/infra/mcp-developer/references/tools.md +480 -480
- package/skills/infra/mcp-developer/references/typescript-sdk.md +350 -350
- package/skills/infra/monitoring-expert/SKILL.md +176 -176
- package/skills/infra/monitoring-expert/references/alerting-rules.md +141 -141
- package/skills/infra/monitoring-expert/references/application-profiling.md +331 -331
- package/skills/infra/monitoring-expert/references/capacity-planning.md +344 -344
- package/skills/infra/monitoring-expert/references/dashboards.md +126 -126
- package/skills/infra/monitoring-expert/references/opentelemetry.md +123 -123
- package/skills/infra/monitoring-expert/references/performance-testing.md +269 -269
- package/skills/infra/monitoring-expert/references/prometheus-metrics.md +136 -136
- package/skills/infra/monitoring-expert/references/structured-logging.md +142 -142
- package/skills/infra/sre-engineer/SKILL.md +181 -181
- package/skills/infra/sre-engineer/references/automation-toil.md +492 -492
- package/skills/infra/sre-engineer/references/error-budget-policy.md +334 -334
- package/skills/infra/sre-engineer/references/incident-chaos.md +576 -576
- package/skills/infra/sre-engineer/references/monitoring-alerting.md +424 -424
- package/skills/infra/sre-engineer/references/slo-sli-management.md +238 -238
- package/skills/infra/terraform-engineer/SKILL.md +143 -143
- package/skills/infra/terraform-engineer/references/best-practices.md +583 -583
- package/skills/infra/terraform-engineer/references/module-patterns.md +297 -297
- package/skills/infra/terraform-engineer/references/providers.md +452 -452
- package/skills/infra/terraform-engineer/references/state-management.md +371 -371
- package/skills/infra/terraform-engineer/references/testing.md +486 -486
- package/skills/infra/websocket-engineer/SKILL.md +168 -168
- package/skills/infra/websocket-engineer/references/alternatives.md +391 -391
- package/skills/infra/websocket-engineer/references/patterns.md +400 -400
- package/skills/infra/websocket-engineer/references/protocol.md +195 -195
- package/skills/infra/websocket-engineer/references/scaling.md +333 -333
- package/skills/infra/websocket-engineer/references/security.md +474 -474
- package/skills/java/java-architect/SKILL.md +132 -132
- package/skills/java/java-architect/references/jpa-optimization.md +393 -393
- package/skills/java/java-architect/references/reactive-webflux.md +356 -356
- package/skills/java/java-architect/references/spring-boot-setup.md +269 -269
- package/skills/java/java-architect/references/spring-security.md +445 -445
- package/skills/java/java-architect/references/testing-patterns.md +500 -500
- package/skills/java/kotlin-specialist/SKILL.md +147 -147
- package/skills/java/kotlin-specialist/references/android-compose.md +419 -419
- package/skills/java/kotlin-specialist/references/coroutines-flow.md +276 -276
- package/skills/java/kotlin-specialist/references/dsl-idioms.md +421 -421
- package/skills/java/kotlin-specialist/references/ktor-server.md +426 -426
- package/skills/java/kotlin-specialist/references/multiplatform-kmp.md +380 -380
- package/skills/java/spring-boot-engineer/SKILL.md +195 -195
- package/skills/java/spring-boot-engineer/references/cloud.md +498 -498
- package/skills/java/spring-boot-engineer/references/data.md +381 -381
- package/skills/java/spring-boot-engineer/references/security.md +459 -459
- package/skills/java/spring-boot-engineer/references/testing.md +545 -545
- package/skills/java/spring-boot-engineer/references/web.md +295 -295
- package/skills/javascript/javascript-pro/SKILL.md +132 -132
- package/skills/javascript/javascript-pro/references/async-patterns.md +334 -334
- package/skills/javascript/javascript-pro/references/browser-apis.md +398 -398
- package/skills/javascript/javascript-pro/references/modern-syntax.md +272 -272
- package/skills/javascript/javascript-pro/references/modules.md +357 -357
- package/skills/javascript/javascript-pro/references/node-essentials.md +471 -471
- package/skills/javascript/nestjs-expert/SKILL.md +206 -206
- package/skills/javascript/nestjs-expert/references/authentication.md +166 -166
- package/skills/javascript/nestjs-expert/references/controllers-routing.md +111 -111
- package/skills/javascript/nestjs-expert/references/dtos-validation.md +153 -153
- package/skills/javascript/nestjs-expert/references/migration-from-express.md +1237 -1237
- package/skills/javascript/nestjs-expert/references/services-di.md +140 -140
- package/skills/javascript/nestjs-expert/references/testing-patterns.md +186 -186
- package/skills/javascript/typescript-pro/SKILL.md +145 -145
- package/skills/javascript/typescript-pro/references/advanced-types.md +259 -259
- package/skills/javascript/typescript-pro/references/configuration.md +445 -445
- package/skills/javascript/typescript-pro/references/patterns.md +484 -484
- package/skills/javascript/typescript-pro/references/type-guards.md +352 -352
- package/skills/javascript/typescript-pro/references/utility-types.md +329 -329
- package/skills/php/laravel-specialist/SKILL.md +262 -262
- package/skills/php/laravel-specialist/references/eloquent.md +351 -351
- package/skills/php/laravel-specialist/references/livewire.md +512 -512
- package/skills/php/laravel-specialist/references/queues.md +423 -423
- package/skills/php/laravel-specialist/references/routing.md +362 -362
- package/skills/php/laravel-specialist/references/testing.md +522 -522
- package/skills/php/php-pro/SKILL.md +206 -206
- package/skills/php/php-pro/references/async-patterns.md +412 -412
- package/skills/php/php-pro/references/laravel-patterns.md +377 -377
- package/skills/php/php-pro/references/modern-php-features.md +323 -323
- package/skills/php/php-pro/references/symfony-patterns.md +466 -466
- package/skills/php/php-pro/references/testing-quality.md +466 -466
- package/skills/product/competitive-analysis/SKILL.md +257 -257
- package/skills/product/meeting-notes/SKILL.md +266 -266
- package/skills/product/prd-template/SKILL.md +150 -150
- package/skills/product/stakeholder-update/SKILL.md +225 -225
- package/skills/product/user-research-synthesis/SKILL.md +235 -235
- package/skills/python/django-expert/SKILL.md +162 -162
- package/skills/python/django-expert/references/authentication.md +145 -145
- package/skills/python/django-expert/references/drf-serializers.md +148 -148
- package/skills/python/django-expert/references/models-orm.md +151 -151
- package/skills/python/django-expert/references/testing-django.md +204 -204
- package/skills/python/django-expert/references/viewsets-views.md +153 -153
- package/skills/python/fastapi-expert/SKILL.md +185 -185
- package/skills/python/fastapi-expert/references/async-sqlalchemy.md +146 -146
- package/skills/python/fastapi-expert/references/authentication.md +159 -159
- package/skills/python/fastapi-expert/references/endpoints-routing.md +142 -142
- package/skills/python/fastapi-expert/references/migration-from-django.md +996 -996
- package/skills/python/fastapi-expert/references/pydantic-v2.md +135 -135
- package/skills/python/fastapi-expert/references/testing-async.md +159 -159
- package/skills/python/pandas-pro/SKILL.md +178 -178
- package/skills/python/pandas-pro/references/aggregation-groupby.md +545 -545
- package/skills/python/pandas-pro/references/data-cleaning.md +500 -500
- package/skills/python/pandas-pro/references/dataframe-operations.md +420 -420
- package/skills/python/pandas-pro/references/merging-joining.md +596 -596
- package/skills/python/pandas-pro/references/performance-optimization.md +597 -597
- package/skills/python/python-pro/SKILL.md +177 -177
- package/skills/python/python-pro/references/async-patterns.md +356 -356
- package/skills/python/python-pro/references/packaging.md +460 -460
- package/skills/python/python-pro/references/standard-library.md +378 -378
- package/skills/python/python-pro/references/testing.md +404 -404
- package/skills/python/python-pro/references/type-system.md +290 -290
- package/skills/quality/chaos-engineer/SKILL.md +182 -182
- package/skills/quality/chaos-engineer/references/chaos-tools.md +511 -511
- package/skills/quality/chaos-engineer/references/experiment-design.md +229 -229
- package/skills/quality/chaos-engineer/references/game-days.md +434 -434
- package/skills/quality/chaos-engineer/references/infrastructure-chaos.md +348 -348
- package/skills/quality/chaos-engineer/references/kubernetes-chaos.md +432 -432
- package/skills/quality/code-reviewer/SKILL.md +119 -119
- package/skills/quality/code-reviewer/references/common-issues.md +142 -142
- package/skills/quality/code-reviewer/references/feedback-examples.md +144 -144
- package/skills/quality/code-reviewer/references/receiving-feedback.md +238 -238
- package/skills/quality/code-reviewer/references/report-template.md +109 -109
- package/skills/quality/code-reviewer/references/review-checklist.md +88 -88
- package/skills/quality/code-reviewer/references/spec-compliance-review.md +258 -258
- package/skills/quality/playwright-expert/SKILL.md +169 -169
- package/skills/quality/playwright-expert/references/api-mocking.md +140 -140
- package/skills/quality/playwright-expert/references/configuration.md +155 -155
- package/skills/quality/playwright-expert/references/debugging-flaky.md +150 -150
- package/skills/quality/playwright-expert/references/page-object-model.md +152 -152
- package/skills/quality/playwright-expert/references/selectors-locators.md +119 -119
- package/skills/quality/secure-code-guardian/SKILL.md +191 -191
- package/skills/quality/secure-code-guardian/references/authentication.md +136 -136
- package/skills/quality/secure-code-guardian/references/input-validation.md +146 -146
- package/skills/quality/secure-code-guardian/references/owasp-prevention.md +135 -135
- package/skills/quality/secure-code-guardian/references/security-headers.md +133 -133
- package/skills/quality/secure-code-guardian/references/xss-csrf.md +157 -157
- package/skills/quality/security-reviewer/SKILL.md +103 -103
- package/skills/quality/security-reviewer/references/infrastructure-security.md +268 -268
- package/skills/quality/security-reviewer/references/penetration-testing.md +268 -268
- package/skills/quality/security-reviewer/references/report-template.md +170 -170
- package/skills/quality/security-reviewer/references/sast-tools.md +117 -117
- package/skills/quality/security-reviewer/references/secret-scanning.md +125 -125
- package/skills/quality/security-reviewer/references/vulnerability-patterns.md +152 -152
- package/skills/quality/senior-qa/README.md +196 -196
- package/skills/quality/senior-qa/SKILL.md +399 -399
- package/skills/quality/senior-qa/references/qa_best_practices.md +964 -964
- package/skills/quality/senior-qa/references/test_automation_patterns.md +1009 -1009
- package/skills/quality/senior-qa/references/testing_strategies.md +649 -649
- package/skills/quality/senior-qa/scripts/coverage_analyzer.py +836 -836
- package/skills/quality/senior-qa/scripts/e2e_test_scaffolder.py +820 -820
- package/skills/quality/senior-qa/scripts/test_suite_generator.py +605 -605
- package/skills/quality/tdd-guide/HOW_TO_USE.md +313 -313
- package/skills/quality/tdd-guide/README.md +680 -680
- package/skills/quality/tdd-guide/SKILL.md +122 -122
- package/skills/quality/tdd-guide/assets/expected_output.json +77 -77
- package/skills/quality/tdd-guide/assets/sample_input_python.json +39 -39
- package/skills/quality/tdd-guide/assets/sample_input_typescript.json +36 -36
- package/skills/quality/tdd-guide/references/ci-integration.md +195 -195
- package/skills/quality/tdd-guide/references/framework-guide.md +206 -206
- package/skills/quality/tdd-guide/references/tdd-best-practices.md +128 -128
- package/skills/quality/tdd-guide/scripts/coverage_analyzer.py +434 -434
- package/skills/quality/tdd-guide/scripts/fixture_generator.py +440 -440
- package/skills/quality/tdd-guide/scripts/format_detector.py +384 -384
- package/skills/quality/tdd-guide/scripts/framework_adapter.py +428 -428
- package/skills/quality/tdd-guide/scripts/metrics_calculator.py +456 -456
- package/skills/quality/tdd-guide/scripts/output_formatter.py +354 -354
- package/skills/quality/tdd-guide/scripts/tdd_workflow.py +474 -474
- package/skills/quality/tdd-guide/scripts/test_generator.py +438 -438
- package/skills/quality/test-master/SKILL.md +94 -94
- package/skills/quality/test-master/references/automation-frameworks.md +294 -294
- package/skills/quality/test-master/references/e2e-testing.md +128 -128
- package/skills/quality/test-master/references/integration-testing.md +120 -120
- package/skills/quality/test-master/references/performance-testing.md +118 -118
- package/skills/quality/test-master/references/qa-methodology.md +247 -247
- package/skills/quality/test-master/references/security-testing.md +127 -127
- package/skills/quality/test-master/references/tdd-iron-laws.md +174 -174
- package/skills/quality/test-master/references/test-reports.md +104 -104
- package/skills/quality/test-master/references/testing-anti-patterns.md +231 -231
- package/skills/quality/test-master/references/unit-testing.md +113 -113
- package/skills/ruby/rails-expert/SKILL.md +154 -154
- package/skills/ruby/rails-expert/references/active-record.md +244 -244
- package/skills/ruby/rails-expert/references/api-development.md +401 -401
- package/skills/ruby/rails-expert/references/background-jobs.md +272 -272
- package/skills/ruby/rails-expert/references/hotwire-turbo.md +228 -228
- package/skills/ruby/rails-expert/references/rspec-testing.md +367 -367
- package/skills/swift/swift-expert/SKILL.md +163 -163
- package/skills/swift/swift-expert/references/async-concurrency.md +360 -360
- package/skills/swift/swift-expert/references/memory-performance.md +377 -377
- package/skills/swift/swift-expert/references/protocol-oriented.md +354 -354
- package/skills/swift/swift-expert/references/swiftui-patterns.md +291 -291
- package/skills/swift/swift-expert/references/testing-patterns.md +399 -399
- package/skills/workflow/brainstorming/SKILL.md +164 -164
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -214
- package/skills/workflow/brainstorming/scripts/helper.js +88 -88
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -354
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -148
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -56
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -49
- package/skills/workflow/brainstorming/visual-companion.md +287 -287
- package/skills/workflow/documentation/SKILL.md +45 -45
- package/skills/workflow/entropy-management/SKILL.md +115 -115
- package/skills/workflow/executing-plans/SKILL.md +70 -70
- package/skills/workflow/finishing-a-development-branch/SKILL.md +200 -200
- package/skills/workflow/receiving-code-review/SKILL.md +213 -213
- package/skills/workflow/requesting-code-review/SKILL.md +105 -105
- package/skills/workflow/requesting-code-review/code-reviewer.md +146 -146
- package/skills/workflow/requirement-engineering/SKILL.md +111 -111
- package/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/workflow/systematic-debugging/SKILL.md +296 -296
- package/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/workflow/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/workflow/systematic-debugging/find-polluter.sh +63 -63
- package/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/workflow/systematic-debugging/test-academic.md +14 -14
- package/skills/workflow/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/workflow/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/workflow/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/workflow/using-git-worktrees/SKILL.md +218 -218
- package/skills/workflow/verification-before-completion/SKILL.md +139 -139
- package/skills/workflow/writing-plans/SKILL.md +151 -151
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -49
- package/skills/workflow/writing-skills/SKILL.md +655 -655
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -1150
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -187
- package/skills/workflow/writing-skills/render-graphs.js +168 -168
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -384
|
@@ -1,806 +1,806 @@
|
|
|
1
|
-
# Frontend Best Practices
|
|
2
|
-
|
|
3
|
-
Modern frontend development standards for accessibility, testing, TypeScript, and Tailwind CSS.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
- [Accessibility (a11y)](#accessibility-a11y)
|
|
10
|
-
- [Testing Strategies](#testing-strategies)
|
|
11
|
-
- [TypeScript Patterns](#typescript-patterns)
|
|
12
|
-
- [Tailwind CSS](#tailwind-css)
|
|
13
|
-
- [Project Structure](#project-structure)
|
|
14
|
-
- [Security](#security)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Accessibility (a11y)
|
|
19
|
-
|
|
20
|
-
### Semantic HTML
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
// BAD - Divs for everything
|
|
24
|
-
<div onClick={handleClick}>Click me</div>
|
|
25
|
-
<div class="header">...</div>
|
|
26
|
-
<div class="nav">...</div>
|
|
27
|
-
|
|
28
|
-
// GOOD - Semantic elements
|
|
29
|
-
<button onClick={handleClick}>Click me</button>
|
|
30
|
-
<header>...</header>
|
|
31
|
-
<nav>...</nav>
|
|
32
|
-
<main>...</main>
|
|
33
|
-
<article>...</article>
|
|
34
|
-
<aside>...</aside>
|
|
35
|
-
<footer>...</footer>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Keyboard Navigation
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
// Ensure all interactive elements are keyboard accessible
|
|
42
|
-
function Modal({ isOpen, onClose, children }: ModalProps) {
|
|
43
|
-
const modalRef = useRef<HTMLDivElement>(null);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (isOpen) {
|
|
47
|
-
// Focus first focusable element
|
|
48
|
-
const focusable = modalRef.current?.querySelectorAll(
|
|
49
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
50
|
-
);
|
|
51
|
-
(focusable?.[0] as HTMLElement)?.focus();
|
|
52
|
-
|
|
53
|
-
// Trap focus within modal
|
|
54
|
-
const handleTab = (e: KeyboardEvent) => {
|
|
55
|
-
if (e.key === 'Tab' && focusable) {
|
|
56
|
-
const first = focusable[0] as HTMLElement;
|
|
57
|
-
const last = focusable[focusable.length - 1] as HTMLElement;
|
|
58
|
-
|
|
59
|
-
if (e.shiftKey && document.activeElement === first) {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
last.focus();
|
|
62
|
-
} else if (!e.shiftKey && document.activeElement === last) {
|
|
63
|
-
e.preventDefault();
|
|
64
|
-
first.focus();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (e.key === 'Escape') {
|
|
69
|
-
onClose();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
document.addEventListener('keydown', handleTab);
|
|
74
|
-
return () => document.removeEventListener('keydown', handleTab);
|
|
75
|
-
}
|
|
76
|
-
}, [isOpen, onClose]);
|
|
77
|
-
|
|
78
|
-
if (!isOpen) return null;
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<div
|
|
82
|
-
ref={modalRef}
|
|
83
|
-
role="dialog"
|
|
84
|
-
aria-modal="true"
|
|
85
|
-
aria-labelledby="modal-title"
|
|
86
|
-
>
|
|
87
|
-
{children}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### ARIA Attributes
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
// Live regions for dynamic content
|
|
97
|
-
<div aria-live="polite" aria-atomic="true">
|
|
98
|
-
{status && <p>{status}</p>}
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
// Loading states
|
|
102
|
-
<button disabled={isLoading} aria-busy={isLoading}>
|
|
103
|
-
{isLoading ? 'Loading...' : 'Submit'}
|
|
104
|
-
</button>
|
|
105
|
-
|
|
106
|
-
// Form labels
|
|
107
|
-
<label htmlFor="email">Email address</label>
|
|
108
|
-
<input
|
|
109
|
-
id="email"
|
|
110
|
-
type="email"
|
|
111
|
-
aria-required="true"
|
|
112
|
-
aria-invalid={!!errors.email}
|
|
113
|
-
aria-describedby={errors.email ? 'email-error' : undefined}
|
|
114
|
-
/>
|
|
115
|
-
{errors.email && (
|
|
116
|
-
<p id="email-error" role="alert">
|
|
117
|
-
{errors.email}
|
|
118
|
-
</p>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
// Navigation
|
|
122
|
-
<nav aria-label="Main navigation">
|
|
123
|
-
<ul>
|
|
124
|
-
<li><a href="/" aria-current={isHome ? 'page' : undefined}>Home</a></li>
|
|
125
|
-
<li><a href="/about" aria-current={isAbout ? 'page' : undefined}>About</a></li>
|
|
126
|
-
</ul>
|
|
127
|
-
</nav>
|
|
128
|
-
|
|
129
|
-
// Toggle buttons
|
|
130
|
-
<button
|
|
131
|
-
aria-pressed={isEnabled}
|
|
132
|
-
onClick={() => setIsEnabled(!isEnabled)}
|
|
133
|
-
>
|
|
134
|
-
{isEnabled ? 'Enabled' : 'Disabled'}
|
|
135
|
-
</button>
|
|
136
|
-
|
|
137
|
-
// Expandable sections
|
|
138
|
-
<button
|
|
139
|
-
aria-expanded={isOpen}
|
|
140
|
-
aria-controls="content-panel"
|
|
141
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
142
|
-
>
|
|
143
|
-
Show details
|
|
144
|
-
</button>
|
|
145
|
-
<div id="content-panel" hidden={!isOpen}>
|
|
146
|
-
Content here
|
|
147
|
-
</div>
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Color Contrast
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
// Ensure 4.5:1 contrast ratio for text (WCAG AA)
|
|
154
|
-
// Use tools like @axe-core/react for testing
|
|
155
|
-
|
|
156
|
-
// tailwind.config.js - Define accessible colors
|
|
157
|
-
module.exports = {
|
|
158
|
-
theme: {
|
|
159
|
-
colors: {
|
|
160
|
-
// Primary with proper contrast
|
|
161
|
-
primary: {
|
|
162
|
-
DEFAULT: '#2563eb', // Blue 600
|
|
163
|
-
foreground: '#ffffff',
|
|
164
|
-
},
|
|
165
|
-
// Error state
|
|
166
|
-
error: {
|
|
167
|
-
DEFAULT: '#dc2626', // Red 600
|
|
168
|
-
foreground: '#ffffff',
|
|
169
|
-
},
|
|
170
|
-
// Text colors with proper contrast
|
|
171
|
-
foreground: '#0f172a', // Slate 900
|
|
172
|
-
muted: '#64748b', // Slate 500 - minimum 4.5:1 on white
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Never rely on color alone
|
|
178
|
-
<span className="text-red-600">
|
|
179
|
-
<ErrorIcon aria-hidden="true" />
|
|
180
|
-
<span>Error: Invalid input</span>
|
|
181
|
-
</span>
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Screen Reader Only Content
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
// Visually hidden but accessible to screen readers
|
|
188
|
-
const srOnly = 'absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0';
|
|
189
|
-
|
|
190
|
-
// Skip link for keyboard users
|
|
191
|
-
<a href="#main-content" className={srOnly + ' focus:not-sr-only focus:absolute focus:top-0'}>
|
|
192
|
-
Skip to main content
|
|
193
|
-
</a>
|
|
194
|
-
|
|
195
|
-
// Icon buttons need labels
|
|
196
|
-
<button aria-label="Close menu">
|
|
197
|
-
<XIcon aria-hidden="true" />
|
|
198
|
-
</button>
|
|
199
|
-
|
|
200
|
-
// Or use visually hidden text
|
|
201
|
-
<button>
|
|
202
|
-
<XIcon aria-hidden="true" />
|
|
203
|
-
<span className={srOnly}>Close menu</span>
|
|
204
|
-
</button>
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Testing Strategies
|
|
210
|
-
|
|
211
|
-
### Component Testing with Testing Library
|
|
212
|
-
|
|
213
|
-
```tsx
|
|
214
|
-
// Button.test.tsx
|
|
215
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
216
|
-
import userEvent from '@testing-library/user-event';
|
|
217
|
-
import { Button } from './Button';
|
|
218
|
-
|
|
219
|
-
describe('Button', () => {
|
|
220
|
-
it('renders with correct text', () => {
|
|
221
|
-
render(<Button>Click me</Button>);
|
|
222
|
-
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('calls onClick when clicked', async () => {
|
|
226
|
-
const user = userEvent.setup();
|
|
227
|
-
const handleClick = jest.fn();
|
|
228
|
-
|
|
229
|
-
render(<Button onClick={handleClick}>Click me</Button>);
|
|
230
|
-
await user.click(screen.getByRole('button'));
|
|
231
|
-
|
|
232
|
-
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('is disabled when loading', () => {
|
|
236
|
-
render(<Button isLoading>Submit</Button>);
|
|
237
|
-
expect(screen.getByRole('button')).toBeDisabled();
|
|
238
|
-
expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('shows loading text when loading', () => {
|
|
242
|
-
render(<Button isLoading loadingText="Submitting...">Submit</Button>);
|
|
243
|
-
expect(screen.getByText('Submitting...')).toBeInTheDocument();
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Hook Testing
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
// useCounter.test.ts
|
|
252
|
-
import { renderHook, act } from '@testing-library/react';
|
|
253
|
-
import { useCounter } from './useCounter';
|
|
254
|
-
|
|
255
|
-
describe('useCounter', () => {
|
|
256
|
-
it('initializes with default value', () => {
|
|
257
|
-
const { result } = renderHook(() => useCounter());
|
|
258
|
-
expect(result.current.count).toBe(0);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('initializes with custom value', () => {
|
|
262
|
-
const { result } = renderHook(() => useCounter(10));
|
|
263
|
-
expect(result.current.count).toBe(10);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('increments count', () => {
|
|
267
|
-
const { result } = renderHook(() => useCounter());
|
|
268
|
-
|
|
269
|
-
act(() => {
|
|
270
|
-
result.current.increment();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
expect(result.current.count).toBe(1);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('resets to initial value', () => {
|
|
277
|
-
const { result } = renderHook(() => useCounter(5));
|
|
278
|
-
|
|
279
|
-
act(() => {
|
|
280
|
-
result.current.increment();
|
|
281
|
-
result.current.increment();
|
|
282
|
-
result.current.reset();
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
expect(result.current.count).toBe(5);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### Integration Testing
|
|
291
|
-
|
|
292
|
-
```tsx
|
|
293
|
-
// LoginForm.test.tsx
|
|
294
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
295
|
-
import userEvent from '@testing-library/user-event';
|
|
296
|
-
import { LoginForm } from './LoginForm';
|
|
297
|
-
import { AuthProvider } from '@/contexts/AuthContext';
|
|
298
|
-
|
|
299
|
-
const mockLogin = jest.fn();
|
|
300
|
-
|
|
301
|
-
jest.mock('@/lib/auth', () => ({
|
|
302
|
-
login: (...args: unknown[]) => mockLogin(...args),
|
|
303
|
-
}));
|
|
304
|
-
|
|
305
|
-
describe('LoginForm', () => {
|
|
306
|
-
beforeEach(() => {
|
|
307
|
-
mockLogin.mockReset();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('submits form with valid credentials', async () => {
|
|
311
|
-
const user = userEvent.setup();
|
|
312
|
-
mockLogin.mockResolvedValueOnce({ user: { id: '1', name: 'Test' } });
|
|
313
|
-
|
|
314
|
-
render(
|
|
315
|
-
<AuthProvider>
|
|
316
|
-
<LoginForm />
|
|
317
|
-
</AuthProvider>
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
|
|
321
|
-
await user.type(screen.getByLabelText(/password/i), 'password123');
|
|
322
|
-
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
323
|
-
|
|
324
|
-
await waitFor(() => {
|
|
325
|
-
expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123');
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('shows validation errors for empty fields', async () => {
|
|
330
|
-
const user = userEvent.setup();
|
|
331
|
-
|
|
332
|
-
render(
|
|
333
|
-
<AuthProvider>
|
|
334
|
-
<LoginForm />
|
|
335
|
-
</AuthProvider>
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
339
|
-
|
|
340
|
-
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
|
|
341
|
-
expect(await screen.findByText(/password is required/i)).toBeInTheDocument();
|
|
342
|
-
expect(mockLogin).not.toHaveBeenCalled();
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### E2E Testing with Playwright
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
// e2e/checkout.spec.ts
|
|
351
|
-
import { test, expect } from '@playwright/test';
|
|
352
|
-
|
|
353
|
-
test.describe('Checkout flow', () => {
|
|
354
|
-
test.beforeEach(async ({ page }) => {
|
|
355
|
-
await page.goto('/');
|
|
356
|
-
await page.click('[data-testid="product-1"] button');
|
|
357
|
-
await page.click('[data-testid="cart-button"]');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test('completes checkout with valid payment', async ({ page }) => {
|
|
361
|
-
await page.click('text=Proceed to Checkout');
|
|
362
|
-
|
|
363
|
-
// Fill shipping info
|
|
364
|
-
await page.fill('[name="email"]', 'test@example.com');
|
|
365
|
-
await page.fill('[name="address"]', '123 Test St');
|
|
366
|
-
await page.fill('[name="city"]', 'Test City');
|
|
367
|
-
await page.selectOption('[name="state"]', 'CA');
|
|
368
|
-
await page.fill('[name="zip"]', '90210');
|
|
369
|
-
|
|
370
|
-
await page.click('text=Continue to Payment');
|
|
371
|
-
await page.click('text=Place Order');
|
|
372
|
-
|
|
373
|
-
// Verify success
|
|
374
|
-
await expect(page).toHaveURL(/\/order\/confirmation/);
|
|
375
|
-
await expect(page.locator('h1')).toHaveText('Order Confirmed!');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
## TypeScript Patterns
|
|
383
|
-
|
|
384
|
-
### Component Props
|
|
385
|
-
|
|
386
|
-
```tsx
|
|
387
|
-
// Use interface for component props
|
|
388
|
-
interface ButtonProps {
|
|
389
|
-
variant?: 'primary' | 'secondary' | 'ghost';
|
|
390
|
-
size?: 'sm' | 'md' | 'lg';
|
|
391
|
-
isLoading?: boolean;
|
|
392
|
-
children: React.ReactNode;
|
|
393
|
-
onClick?: () => void;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Extend HTML attributes
|
|
397
|
-
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
398
|
-
variant?: 'primary' | 'secondary';
|
|
399
|
-
isLoading?: boolean;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function Button({ variant = 'primary', isLoading, children, ...props }: ButtonProps) {
|
|
403
|
-
return (
|
|
404
|
-
<button
|
|
405
|
-
{...props}
|
|
406
|
-
disabled={props.disabled || isLoading}
|
|
407
|
-
className={cn(variants[variant], props.className)}
|
|
408
|
-
>
|
|
409
|
-
{isLoading ? <Spinner /> : children}
|
|
410
|
-
</button>
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Polymorphic components
|
|
415
|
-
type PolymorphicProps<E extends React.ElementType> = {
|
|
416
|
-
as?: E;
|
|
417
|
-
} & React.ComponentPropsWithoutRef<E>;
|
|
418
|
-
|
|
419
|
-
function Box<E extends React.ElementType = 'div'>({
|
|
420
|
-
as,
|
|
421
|
-
children,
|
|
422
|
-
...props
|
|
423
|
-
}: PolymorphicProps<E>) {
|
|
424
|
-
const Component = as || 'div';
|
|
425
|
-
return <Component {...props}>{children}</Component>;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Usage
|
|
429
|
-
<Box as="section" id="hero">Content</Box>
|
|
430
|
-
<Box as="article">Article content</Box>
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
### Discriminated Unions
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
// State machines with exhaustive type checking
|
|
437
|
-
type AsyncState<T> =
|
|
438
|
-
| { status: 'idle' }
|
|
439
|
-
| { status: 'loading' }
|
|
440
|
-
| { status: 'success'; data: T }
|
|
441
|
-
| { status: 'error'; error: Error };
|
|
442
|
-
|
|
443
|
-
function DataDisplay<T>({ state, render }: {
|
|
444
|
-
state: AsyncState<T>;
|
|
445
|
-
render: (data: T) => React.ReactNode;
|
|
446
|
-
}) {
|
|
447
|
-
switch (state.status) {
|
|
448
|
-
case 'idle':
|
|
449
|
-
return null;
|
|
450
|
-
case 'loading':
|
|
451
|
-
return <Spinner />;
|
|
452
|
-
case 'success':
|
|
453
|
-
return <>{render(state.data)}</>;
|
|
454
|
-
case 'error':
|
|
455
|
-
return <ErrorMessage error={state.error} />;
|
|
456
|
-
// TypeScript ensures all cases are handled
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### Generic Components
|
|
462
|
-
|
|
463
|
-
```tsx
|
|
464
|
-
// Generic list component
|
|
465
|
-
interface ListProps<T> {
|
|
466
|
-
items: T[];
|
|
467
|
-
renderItem: (item: T, index: number) => React.ReactNode;
|
|
468
|
-
keyExtractor: (item: T) => string;
|
|
469
|
-
emptyMessage?: string;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
|
|
473
|
-
if (items.length === 0) {
|
|
474
|
-
return <p className="text-muted">{emptyMessage || 'No items'}</p>;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return (
|
|
478
|
-
<ul>
|
|
479
|
-
{items.map((item, index) => (
|
|
480
|
-
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
|
|
481
|
-
))}
|
|
482
|
-
</ul>
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Usage
|
|
487
|
-
<List
|
|
488
|
-
items={users}
|
|
489
|
-
keyExtractor={(user) => user.id}
|
|
490
|
-
renderItem={(user) => <UserCard user={user} />}
|
|
491
|
-
/>
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Type Guards
|
|
495
|
-
|
|
496
|
-
```tsx
|
|
497
|
-
// User-defined type guards
|
|
498
|
-
interface User {
|
|
499
|
-
id: string;
|
|
500
|
-
name: string;
|
|
501
|
-
email: string;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
interface Admin extends User {
|
|
505
|
-
role: 'admin';
|
|
506
|
-
permissions: string[];
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function isAdmin(user: User): user is Admin {
|
|
510
|
-
return 'role' in user && user.role === 'admin';
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function UserBadge({ user }: { user: User }) {
|
|
514
|
-
if (isAdmin(user)) {
|
|
515
|
-
// TypeScript knows user is Admin here
|
|
516
|
-
return <Badge variant="admin">Admin ({user.permissions.length} perms)</Badge>;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return <Badge>User</Badge>;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// API response type guards
|
|
523
|
-
interface ApiSuccess<T> {
|
|
524
|
-
success: true;
|
|
525
|
-
data: T;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
interface ApiError {
|
|
529
|
-
success: false;
|
|
530
|
-
error: string;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
type ApiResponse<T> = ApiSuccess<T> | ApiError;
|
|
534
|
-
|
|
535
|
-
function isApiSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
|
|
536
|
-
return response.success === true;
|
|
537
|
-
}
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
## Tailwind CSS
|
|
543
|
-
|
|
544
|
-
### Component Variants with CVA
|
|
545
|
-
|
|
546
|
-
```tsx
|
|
547
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
548
|
-
import { cn } from '@/lib/utils';
|
|
549
|
-
|
|
550
|
-
const buttonVariants = cva(
|
|
551
|
-
// Base styles
|
|
552
|
-
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
553
|
-
{
|
|
554
|
-
variants: {
|
|
555
|
-
variant: {
|
|
556
|
-
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500',
|
|
557
|
-
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500',
|
|
558
|
-
ghost: 'hover:bg-gray-100 hover:text-gray-900',
|
|
559
|
-
destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
|
|
560
|
-
},
|
|
561
|
-
size: {
|
|
562
|
-
sm: 'h-8 px-3 text-sm',
|
|
563
|
-
md: 'h-10 px-4 text-sm',
|
|
564
|
-
lg: 'h-12 px-6 text-base',
|
|
565
|
-
icon: 'h-10 w-10',
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
defaultVariants: {
|
|
569
|
-
variant: 'primary',
|
|
570
|
-
size: 'md',
|
|
571
|
-
},
|
|
572
|
-
}
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
interface ButtonProps
|
|
576
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
577
|
-
VariantProps<typeof buttonVariants> {}
|
|
578
|
-
|
|
579
|
-
function Button({ className, variant, size, ...props }: ButtonProps) {
|
|
580
|
-
return (
|
|
581
|
-
<button
|
|
582
|
-
className={cn(buttonVariants({ variant, size }), className)}
|
|
583
|
-
{...props}
|
|
584
|
-
/>
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Usage
|
|
589
|
-
<Button variant="primary" size="lg">Large Primary</Button>
|
|
590
|
-
<Button variant="ghost" size="icon"><MenuIcon /></Button>
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### Responsive Design
|
|
594
|
-
|
|
595
|
-
```tsx
|
|
596
|
-
// Mobile-first responsive design
|
|
597
|
-
<div className="
|
|
598
|
-
grid
|
|
599
|
-
grid-cols-1 {/* Mobile: 1 column */}
|
|
600
|
-
sm:grid-cols-2 {/* 640px+: 2 columns */}
|
|
601
|
-
lg:grid-cols-3 {/* 1024px+: 3 columns */}
|
|
602
|
-
xl:grid-cols-4 {/* 1280px+: 4 columns */}
|
|
603
|
-
gap-4
|
|
604
|
-
sm:gap-6
|
|
605
|
-
lg:gap-8
|
|
606
|
-
">
|
|
607
|
-
{products.map(product => <ProductCard key={product.id} product={product} />)}
|
|
608
|
-
</div>
|
|
609
|
-
|
|
610
|
-
// Container with responsive padding
|
|
611
|
-
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
|
612
|
-
Content
|
|
613
|
-
</div>
|
|
614
|
-
|
|
615
|
-
// Hide/show based on breakpoint
|
|
616
|
-
<nav className="hidden md:flex">Desktop nav</nav>
|
|
617
|
-
<button className="md:hidden">Mobile menu</button>
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
### Animation Utilities
|
|
621
|
-
|
|
622
|
-
```tsx
|
|
623
|
-
// Skeleton loading
|
|
624
|
-
<div className="animate-pulse space-y-4">
|
|
625
|
-
<div className="h-4 bg-gray-200 rounded w-3/4" />
|
|
626
|
-
<div className="h-4 bg-gray-200 rounded w-1/2" />
|
|
627
|
-
</div>
|
|
628
|
-
|
|
629
|
-
// Transitions
|
|
630
|
-
<button className="
|
|
631
|
-
transition-all
|
|
632
|
-
duration-200
|
|
633
|
-
ease-in-out
|
|
634
|
-
hover:scale-105
|
|
635
|
-
active:scale-95
|
|
636
|
-
">
|
|
637
|
-
Hover me
|
|
638
|
-
</button>
|
|
639
|
-
|
|
640
|
-
// Custom animations in tailwind.config.js
|
|
641
|
-
module.exports = {
|
|
642
|
-
theme: {
|
|
643
|
-
extend: {
|
|
644
|
-
animation: {
|
|
645
|
-
'fade-in': 'fadeIn 0.3s ease-out',
|
|
646
|
-
'slide-up': 'slideUp 0.3s ease-out',
|
|
647
|
-
'spin-slow': 'spin 3s linear infinite',
|
|
648
|
-
},
|
|
649
|
-
keyframes: {
|
|
650
|
-
fadeIn: {
|
|
651
|
-
'0%': { opacity: '0' },
|
|
652
|
-
'100%': { opacity: '1' },
|
|
653
|
-
},
|
|
654
|
-
slideUp: {
|
|
655
|
-
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
|
656
|
-
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
},
|
|
660
|
-
},
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
// Usage
|
|
664
|
-
<div className="animate-fade-in">Fading in</div>
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
---
|
|
668
|
-
|
|
669
|
-
## Project Structure
|
|
670
|
-
|
|
671
|
-
### Feature-Based Structure
|
|
672
|
-
|
|
673
|
-
```
|
|
674
|
-
src/
|
|
675
|
-
├── app/ # Next.js App Router
|
|
676
|
-
│ ├── (auth)/ # Auth route group
|
|
677
|
-
│ │ ├── login/
|
|
678
|
-
│ │ └── register/
|
|
679
|
-
│ ├── dashboard/
|
|
680
|
-
│ │ ├── page.tsx
|
|
681
|
-
│ │ └── layout.tsx
|
|
682
|
-
│ └── layout.tsx
|
|
683
|
-
├── components/
|
|
684
|
-
│ ├── ui/ # Shared UI components
|
|
685
|
-
│ │ ├── Button.tsx
|
|
686
|
-
│ │ ├── Input.tsx
|
|
687
|
-
│ │ └── index.ts
|
|
688
|
-
│ └── features/ # Feature-specific components
|
|
689
|
-
│ ├── auth/
|
|
690
|
-
│ │ ├── LoginForm.tsx
|
|
691
|
-
│ │ └── RegisterForm.tsx
|
|
692
|
-
│ └── dashboard/
|
|
693
|
-
│ ├── StatsCard.tsx
|
|
694
|
-
│ └── RecentActivity.tsx
|
|
695
|
-
├── hooks/ # Custom React hooks
|
|
696
|
-
│ ├── useAuth.ts
|
|
697
|
-
│ ├── useDebounce.ts
|
|
698
|
-
│ └── useLocalStorage.ts
|
|
699
|
-
├── lib/ # Utilities and configs
|
|
700
|
-
│ ├── utils.ts
|
|
701
|
-
│ ├── api.ts
|
|
702
|
-
│ └── constants.ts
|
|
703
|
-
├── types/ # TypeScript types
|
|
704
|
-
│ ├── user.ts
|
|
705
|
-
│ └── api.ts
|
|
706
|
-
└── styles/
|
|
707
|
-
└── globals.css
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### Barrel Exports
|
|
711
|
-
|
|
712
|
-
```tsx
|
|
713
|
-
// components/ui/index.ts
|
|
714
|
-
export { Button } from './Button';
|
|
715
|
-
export { Input } from './Input';
|
|
716
|
-
export { Card, CardHeader, CardContent, CardFooter } from './Card';
|
|
717
|
-
export { Dialog, DialogTrigger, DialogContent } from './Dialog';
|
|
718
|
-
|
|
719
|
-
// Usage
|
|
720
|
-
import { Button, Input, Card } from '@/components/ui';
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
---
|
|
724
|
-
|
|
725
|
-
## Security
|
|
726
|
-
|
|
727
|
-
### XSS Prevention
|
|
728
|
-
|
|
729
|
-
React escapes content by default, which prevents most XSS attacks. When you need to render HTML content:
|
|
730
|
-
|
|
731
|
-
1. **Avoid rendering raw HTML** when possible
|
|
732
|
-
2. **Sanitize with DOMPurify** for trusted content sources
|
|
733
|
-
3. **Use allow-lists** for permitted tags and attributes
|
|
734
|
-
|
|
735
|
-
```tsx
|
|
736
|
-
// React escapes by default - this is safe
|
|
737
|
-
<div>{userInput}</div>
|
|
738
|
-
|
|
739
|
-
// When you must render HTML, sanitize first
|
|
740
|
-
import DOMPurify from 'dompurify';
|
|
741
|
-
|
|
742
|
-
function SafeHTML({ html }: { html: string }) {
|
|
743
|
-
const sanitized = DOMPurify.sanitize(html, {
|
|
744
|
-
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
|
|
745
|
-
ALLOWED_ATTR: ['href'],
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
749
|
-
}
|
|
750
|
-
```
|
|
751
|
-
|
|
752
|
-
### Input Validation
|
|
753
|
-
|
|
754
|
-
```tsx
|
|
755
|
-
import { z } from 'zod';
|
|
756
|
-
import { useForm } from 'react-hook-form';
|
|
757
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
758
|
-
|
|
759
|
-
const schema = z.object({
|
|
760
|
-
email: z.string().email('Invalid email address'),
|
|
761
|
-
password: z.string()
|
|
762
|
-
.min(8, 'Password must be at least 8 characters')
|
|
763
|
-
.regex(/[A-Z]/, 'Password must contain uppercase letter')
|
|
764
|
-
.regex(/[0-9]/, 'Password must contain number'),
|
|
765
|
-
confirmPassword: z.string(),
|
|
766
|
-
}).refine((data) => data.password === data.confirmPassword, {
|
|
767
|
-
message: 'Passwords do not match',
|
|
768
|
-
path: ['confirmPassword'],
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
type FormData = z.infer<typeof schema>;
|
|
772
|
-
|
|
773
|
-
function RegisterForm() {
|
|
774
|
-
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
|
|
775
|
-
resolver: zodResolver(schema),
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
return (
|
|
779
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
780
|
-
<Input {...register('email')} error={errors.email?.message} />
|
|
781
|
-
<Input type="password" {...register('password')} error={errors.password?.message} />
|
|
782
|
-
<Input type="password" {...register('confirmPassword')} error={errors.confirmPassword?.message} />
|
|
783
|
-
<Button type="submit">Register</Button>
|
|
784
|
-
</form>
|
|
785
|
-
);
|
|
786
|
-
}
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
### Secure API Calls
|
|
790
|
-
|
|
791
|
-
```tsx
|
|
792
|
-
// Use environment variables for API endpoints
|
|
793
|
-
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
794
|
-
|
|
795
|
-
// Never include secrets in client code - use server-side API routes
|
|
796
|
-
// app/api/data/route.ts
|
|
797
|
-
export async function GET() {
|
|
798
|
-
const response = await fetch('https://api.example.com/data', {
|
|
799
|
-
headers: {
|
|
800
|
-
'Authorization': `Bearer ${process.env.API_SECRET}`, // Server-side only
|
|
801
|
-
},
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
return Response.json(await response.json());
|
|
805
|
-
}
|
|
806
|
-
```
|
|
1
|
+
# Frontend Best Practices
|
|
2
|
+
|
|
3
|
+
Modern frontend development standards for accessibility, testing, TypeScript, and Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Accessibility (a11y)](#accessibility-a11y)
|
|
10
|
+
- [Testing Strategies](#testing-strategies)
|
|
11
|
+
- [TypeScript Patterns](#typescript-patterns)
|
|
12
|
+
- [Tailwind CSS](#tailwind-css)
|
|
13
|
+
- [Project Structure](#project-structure)
|
|
14
|
+
- [Security](#security)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Accessibility (a11y)
|
|
19
|
+
|
|
20
|
+
### Semantic HTML
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// BAD - Divs for everything
|
|
24
|
+
<div onClick={handleClick}>Click me</div>
|
|
25
|
+
<div class="header">...</div>
|
|
26
|
+
<div class="nav">...</div>
|
|
27
|
+
|
|
28
|
+
// GOOD - Semantic elements
|
|
29
|
+
<button onClick={handleClick}>Click me</button>
|
|
30
|
+
<header>...</header>
|
|
31
|
+
<nav>...</nav>
|
|
32
|
+
<main>...</main>
|
|
33
|
+
<article>...</article>
|
|
34
|
+
<aside>...</aside>
|
|
35
|
+
<footer>...</footer>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Keyboard Navigation
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Ensure all interactive elements are keyboard accessible
|
|
42
|
+
function Modal({ isOpen, onClose, children }: ModalProps) {
|
|
43
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (isOpen) {
|
|
47
|
+
// Focus first focusable element
|
|
48
|
+
const focusable = modalRef.current?.querySelectorAll(
|
|
49
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
50
|
+
);
|
|
51
|
+
(focusable?.[0] as HTMLElement)?.focus();
|
|
52
|
+
|
|
53
|
+
// Trap focus within modal
|
|
54
|
+
const handleTab = (e: KeyboardEvent) => {
|
|
55
|
+
if (e.key === 'Tab' && focusable) {
|
|
56
|
+
const first = focusable[0] as HTMLElement;
|
|
57
|
+
const last = focusable[focusable.length - 1] as HTMLElement;
|
|
58
|
+
|
|
59
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
last.focus();
|
|
62
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
first.focus();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (e.key === 'Escape') {
|
|
69
|
+
onClose();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
document.addEventListener('keydown', handleTab);
|
|
74
|
+
return () => document.removeEventListener('keydown', handleTab);
|
|
75
|
+
}
|
|
76
|
+
}, [isOpen, onClose]);
|
|
77
|
+
|
|
78
|
+
if (!isOpen) return null;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
ref={modalRef}
|
|
83
|
+
role="dialog"
|
|
84
|
+
aria-modal="true"
|
|
85
|
+
aria-labelledby="modal-title"
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### ARIA Attributes
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// Live regions for dynamic content
|
|
97
|
+
<div aria-live="polite" aria-atomic="true">
|
|
98
|
+
{status && <p>{status}</p>}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
// Loading states
|
|
102
|
+
<button disabled={isLoading} aria-busy={isLoading}>
|
|
103
|
+
{isLoading ? 'Loading...' : 'Submit'}
|
|
104
|
+
</button>
|
|
105
|
+
|
|
106
|
+
// Form labels
|
|
107
|
+
<label htmlFor="email">Email address</label>
|
|
108
|
+
<input
|
|
109
|
+
id="email"
|
|
110
|
+
type="email"
|
|
111
|
+
aria-required="true"
|
|
112
|
+
aria-invalid={!!errors.email}
|
|
113
|
+
aria-describedby={errors.email ? 'email-error' : undefined}
|
|
114
|
+
/>
|
|
115
|
+
{errors.email && (
|
|
116
|
+
<p id="email-error" role="alert">
|
|
117
|
+
{errors.email}
|
|
118
|
+
</p>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
// Navigation
|
|
122
|
+
<nav aria-label="Main navigation">
|
|
123
|
+
<ul>
|
|
124
|
+
<li><a href="/" aria-current={isHome ? 'page' : undefined}>Home</a></li>
|
|
125
|
+
<li><a href="/about" aria-current={isAbout ? 'page' : undefined}>About</a></li>
|
|
126
|
+
</ul>
|
|
127
|
+
</nav>
|
|
128
|
+
|
|
129
|
+
// Toggle buttons
|
|
130
|
+
<button
|
|
131
|
+
aria-pressed={isEnabled}
|
|
132
|
+
onClick={() => setIsEnabled(!isEnabled)}
|
|
133
|
+
>
|
|
134
|
+
{isEnabled ? 'Enabled' : 'Disabled'}
|
|
135
|
+
</button>
|
|
136
|
+
|
|
137
|
+
// Expandable sections
|
|
138
|
+
<button
|
|
139
|
+
aria-expanded={isOpen}
|
|
140
|
+
aria-controls="content-panel"
|
|
141
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
142
|
+
>
|
|
143
|
+
Show details
|
|
144
|
+
</button>
|
|
145
|
+
<div id="content-panel" hidden={!isOpen}>
|
|
146
|
+
Content here
|
|
147
|
+
</div>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Color Contrast
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// Ensure 4.5:1 contrast ratio for text (WCAG AA)
|
|
154
|
+
// Use tools like @axe-core/react for testing
|
|
155
|
+
|
|
156
|
+
// tailwind.config.js - Define accessible colors
|
|
157
|
+
module.exports = {
|
|
158
|
+
theme: {
|
|
159
|
+
colors: {
|
|
160
|
+
// Primary with proper contrast
|
|
161
|
+
primary: {
|
|
162
|
+
DEFAULT: '#2563eb', // Blue 600
|
|
163
|
+
foreground: '#ffffff',
|
|
164
|
+
},
|
|
165
|
+
// Error state
|
|
166
|
+
error: {
|
|
167
|
+
DEFAULT: '#dc2626', // Red 600
|
|
168
|
+
foreground: '#ffffff',
|
|
169
|
+
},
|
|
170
|
+
// Text colors with proper contrast
|
|
171
|
+
foreground: '#0f172a', // Slate 900
|
|
172
|
+
muted: '#64748b', // Slate 500 - minimum 4.5:1 on white
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Never rely on color alone
|
|
178
|
+
<span className="text-red-600">
|
|
179
|
+
<ErrorIcon aria-hidden="true" />
|
|
180
|
+
<span>Error: Invalid input</span>
|
|
181
|
+
</span>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Screen Reader Only Content
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
// Visually hidden but accessible to screen readers
|
|
188
|
+
const srOnly = 'absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0';
|
|
189
|
+
|
|
190
|
+
// Skip link for keyboard users
|
|
191
|
+
<a href="#main-content" className={srOnly + ' focus:not-sr-only focus:absolute focus:top-0'}>
|
|
192
|
+
Skip to main content
|
|
193
|
+
</a>
|
|
194
|
+
|
|
195
|
+
// Icon buttons need labels
|
|
196
|
+
<button aria-label="Close menu">
|
|
197
|
+
<XIcon aria-hidden="true" />
|
|
198
|
+
</button>
|
|
199
|
+
|
|
200
|
+
// Or use visually hidden text
|
|
201
|
+
<button>
|
|
202
|
+
<XIcon aria-hidden="true" />
|
|
203
|
+
<span className={srOnly}>Close menu</span>
|
|
204
|
+
</button>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Testing Strategies
|
|
210
|
+
|
|
211
|
+
### Component Testing with Testing Library
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// Button.test.tsx
|
|
215
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
216
|
+
import userEvent from '@testing-library/user-event';
|
|
217
|
+
import { Button } from './Button';
|
|
218
|
+
|
|
219
|
+
describe('Button', () => {
|
|
220
|
+
it('renders with correct text', () => {
|
|
221
|
+
render(<Button>Click me</Button>);
|
|
222
|
+
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('calls onClick when clicked', async () => {
|
|
226
|
+
const user = userEvent.setup();
|
|
227
|
+
const handleClick = jest.fn();
|
|
228
|
+
|
|
229
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
230
|
+
await user.click(screen.getByRole('button'));
|
|
231
|
+
|
|
232
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('is disabled when loading', () => {
|
|
236
|
+
render(<Button isLoading>Submit</Button>);
|
|
237
|
+
expect(screen.getByRole('button')).toBeDisabled();
|
|
238
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('shows loading text when loading', () => {
|
|
242
|
+
render(<Button isLoading loadingText="Submitting...">Submit</Button>);
|
|
243
|
+
expect(screen.getByText('Submitting...')).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Hook Testing
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// useCounter.test.ts
|
|
252
|
+
import { renderHook, act } from '@testing-library/react';
|
|
253
|
+
import { useCounter } from './useCounter';
|
|
254
|
+
|
|
255
|
+
describe('useCounter', () => {
|
|
256
|
+
it('initializes with default value', () => {
|
|
257
|
+
const { result } = renderHook(() => useCounter());
|
|
258
|
+
expect(result.current.count).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('initializes with custom value', () => {
|
|
262
|
+
const { result } = renderHook(() => useCounter(10));
|
|
263
|
+
expect(result.current.count).toBe(10);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('increments count', () => {
|
|
267
|
+
const { result } = renderHook(() => useCounter());
|
|
268
|
+
|
|
269
|
+
act(() => {
|
|
270
|
+
result.current.increment();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(result.current.count).toBe(1);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('resets to initial value', () => {
|
|
277
|
+
const { result } = renderHook(() => useCounter(5));
|
|
278
|
+
|
|
279
|
+
act(() => {
|
|
280
|
+
result.current.increment();
|
|
281
|
+
result.current.increment();
|
|
282
|
+
result.current.reset();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(result.current.count).toBe(5);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Integration Testing
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// LoginForm.test.tsx
|
|
294
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
295
|
+
import userEvent from '@testing-library/user-event';
|
|
296
|
+
import { LoginForm } from './LoginForm';
|
|
297
|
+
import { AuthProvider } from '@/contexts/AuthContext';
|
|
298
|
+
|
|
299
|
+
const mockLogin = jest.fn();
|
|
300
|
+
|
|
301
|
+
jest.mock('@/lib/auth', () => ({
|
|
302
|
+
login: (...args: unknown[]) => mockLogin(...args),
|
|
303
|
+
}));
|
|
304
|
+
|
|
305
|
+
describe('LoginForm', () => {
|
|
306
|
+
beforeEach(() => {
|
|
307
|
+
mockLogin.mockReset();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('submits form with valid credentials', async () => {
|
|
311
|
+
const user = userEvent.setup();
|
|
312
|
+
mockLogin.mockResolvedValueOnce({ user: { id: '1', name: 'Test' } });
|
|
313
|
+
|
|
314
|
+
render(
|
|
315
|
+
<AuthProvider>
|
|
316
|
+
<LoginForm />
|
|
317
|
+
</AuthProvider>
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
|
|
321
|
+
await user.type(screen.getByLabelText(/password/i), 'password123');
|
|
322
|
+
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
323
|
+
|
|
324
|
+
await waitFor(() => {
|
|
325
|
+
expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123');
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('shows validation errors for empty fields', async () => {
|
|
330
|
+
const user = userEvent.setup();
|
|
331
|
+
|
|
332
|
+
render(
|
|
333
|
+
<AuthProvider>
|
|
334
|
+
<LoginForm />
|
|
335
|
+
</AuthProvider>
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
339
|
+
|
|
340
|
+
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
|
|
341
|
+
expect(await screen.findByText(/password is required/i)).toBeInTheDocument();
|
|
342
|
+
expect(mockLogin).not.toHaveBeenCalled();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### E2E Testing with Playwright
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// e2e/checkout.spec.ts
|
|
351
|
+
import { test, expect } from '@playwright/test';
|
|
352
|
+
|
|
353
|
+
test.describe('Checkout flow', () => {
|
|
354
|
+
test.beforeEach(async ({ page }) => {
|
|
355
|
+
await page.goto('/');
|
|
356
|
+
await page.click('[data-testid="product-1"] button');
|
|
357
|
+
await page.click('[data-testid="cart-button"]');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('completes checkout with valid payment', async ({ page }) => {
|
|
361
|
+
await page.click('text=Proceed to Checkout');
|
|
362
|
+
|
|
363
|
+
// Fill shipping info
|
|
364
|
+
await page.fill('[name="email"]', 'test@example.com');
|
|
365
|
+
await page.fill('[name="address"]', '123 Test St');
|
|
366
|
+
await page.fill('[name="city"]', 'Test City');
|
|
367
|
+
await page.selectOption('[name="state"]', 'CA');
|
|
368
|
+
await page.fill('[name="zip"]', '90210');
|
|
369
|
+
|
|
370
|
+
await page.click('text=Continue to Payment');
|
|
371
|
+
await page.click('text=Place Order');
|
|
372
|
+
|
|
373
|
+
// Verify success
|
|
374
|
+
await expect(page).toHaveURL(/\/order\/confirmation/);
|
|
375
|
+
await expect(page.locator('h1')).toHaveText('Order Confirmed!');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## TypeScript Patterns
|
|
383
|
+
|
|
384
|
+
### Component Props
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
// Use interface for component props
|
|
388
|
+
interface ButtonProps {
|
|
389
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
390
|
+
size?: 'sm' | 'md' | 'lg';
|
|
391
|
+
isLoading?: boolean;
|
|
392
|
+
children: React.ReactNode;
|
|
393
|
+
onClick?: () => void;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Extend HTML attributes
|
|
397
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
398
|
+
variant?: 'primary' | 'secondary';
|
|
399
|
+
isLoading?: boolean;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function Button({ variant = 'primary', isLoading, children, ...props }: ButtonProps) {
|
|
403
|
+
return (
|
|
404
|
+
<button
|
|
405
|
+
{...props}
|
|
406
|
+
disabled={props.disabled || isLoading}
|
|
407
|
+
className={cn(variants[variant], props.className)}
|
|
408
|
+
>
|
|
409
|
+
{isLoading ? <Spinner /> : children}
|
|
410
|
+
</button>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Polymorphic components
|
|
415
|
+
type PolymorphicProps<E extends React.ElementType> = {
|
|
416
|
+
as?: E;
|
|
417
|
+
} & React.ComponentPropsWithoutRef<E>;
|
|
418
|
+
|
|
419
|
+
function Box<E extends React.ElementType = 'div'>({
|
|
420
|
+
as,
|
|
421
|
+
children,
|
|
422
|
+
...props
|
|
423
|
+
}: PolymorphicProps<E>) {
|
|
424
|
+
const Component = as || 'div';
|
|
425
|
+
return <Component {...props}>{children}</Component>;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Usage
|
|
429
|
+
<Box as="section" id="hero">Content</Box>
|
|
430
|
+
<Box as="article">Article content</Box>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Discriminated Unions
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
// State machines with exhaustive type checking
|
|
437
|
+
type AsyncState<T> =
|
|
438
|
+
| { status: 'idle' }
|
|
439
|
+
| { status: 'loading' }
|
|
440
|
+
| { status: 'success'; data: T }
|
|
441
|
+
| { status: 'error'; error: Error };
|
|
442
|
+
|
|
443
|
+
function DataDisplay<T>({ state, render }: {
|
|
444
|
+
state: AsyncState<T>;
|
|
445
|
+
render: (data: T) => React.ReactNode;
|
|
446
|
+
}) {
|
|
447
|
+
switch (state.status) {
|
|
448
|
+
case 'idle':
|
|
449
|
+
return null;
|
|
450
|
+
case 'loading':
|
|
451
|
+
return <Spinner />;
|
|
452
|
+
case 'success':
|
|
453
|
+
return <>{render(state.data)}</>;
|
|
454
|
+
case 'error':
|
|
455
|
+
return <ErrorMessage error={state.error} />;
|
|
456
|
+
// TypeScript ensures all cases are handled
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Generic Components
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
// Generic list component
|
|
465
|
+
interface ListProps<T> {
|
|
466
|
+
items: T[];
|
|
467
|
+
renderItem: (item: T, index: number) => React.ReactNode;
|
|
468
|
+
keyExtractor: (item: T) => string;
|
|
469
|
+
emptyMessage?: string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
|
|
473
|
+
if (items.length === 0) {
|
|
474
|
+
return <p className="text-muted">{emptyMessage || 'No items'}</p>;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<ul>
|
|
479
|
+
{items.map((item, index) => (
|
|
480
|
+
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
|
|
481
|
+
))}
|
|
482
|
+
</ul>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Usage
|
|
487
|
+
<List
|
|
488
|
+
items={users}
|
|
489
|
+
keyExtractor={(user) => user.id}
|
|
490
|
+
renderItem={(user) => <UserCard user={user} />}
|
|
491
|
+
/>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Type Guards
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
// User-defined type guards
|
|
498
|
+
interface User {
|
|
499
|
+
id: string;
|
|
500
|
+
name: string;
|
|
501
|
+
email: string;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
interface Admin extends User {
|
|
505
|
+
role: 'admin';
|
|
506
|
+
permissions: string[];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function isAdmin(user: User): user is Admin {
|
|
510
|
+
return 'role' in user && user.role === 'admin';
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function UserBadge({ user }: { user: User }) {
|
|
514
|
+
if (isAdmin(user)) {
|
|
515
|
+
// TypeScript knows user is Admin here
|
|
516
|
+
return <Badge variant="admin">Admin ({user.permissions.length} perms)</Badge>;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return <Badge>User</Badge>;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// API response type guards
|
|
523
|
+
interface ApiSuccess<T> {
|
|
524
|
+
success: true;
|
|
525
|
+
data: T;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
interface ApiError {
|
|
529
|
+
success: false;
|
|
530
|
+
error: string;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
type ApiResponse<T> = ApiSuccess<T> | ApiError;
|
|
534
|
+
|
|
535
|
+
function isApiSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
|
|
536
|
+
return response.success === true;
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Tailwind CSS
|
|
543
|
+
|
|
544
|
+
### Component Variants with CVA
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
548
|
+
import { cn } from '@/lib/utils';
|
|
549
|
+
|
|
550
|
+
const buttonVariants = cva(
|
|
551
|
+
// Base styles
|
|
552
|
+
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
553
|
+
{
|
|
554
|
+
variants: {
|
|
555
|
+
variant: {
|
|
556
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500',
|
|
557
|
+
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500',
|
|
558
|
+
ghost: 'hover:bg-gray-100 hover:text-gray-900',
|
|
559
|
+
destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
|
|
560
|
+
},
|
|
561
|
+
size: {
|
|
562
|
+
sm: 'h-8 px-3 text-sm',
|
|
563
|
+
md: 'h-10 px-4 text-sm',
|
|
564
|
+
lg: 'h-12 px-6 text-base',
|
|
565
|
+
icon: 'h-10 w-10',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
defaultVariants: {
|
|
569
|
+
variant: 'primary',
|
|
570
|
+
size: 'md',
|
|
571
|
+
},
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
interface ButtonProps
|
|
576
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
577
|
+
VariantProps<typeof buttonVariants> {}
|
|
578
|
+
|
|
579
|
+
function Button({ className, variant, size, ...props }: ButtonProps) {
|
|
580
|
+
return (
|
|
581
|
+
<button
|
|
582
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
583
|
+
{...props}
|
|
584
|
+
/>
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Usage
|
|
589
|
+
<Button variant="primary" size="lg">Large Primary</Button>
|
|
590
|
+
<Button variant="ghost" size="icon"><MenuIcon /></Button>
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Responsive Design
|
|
594
|
+
|
|
595
|
+
```tsx
|
|
596
|
+
// Mobile-first responsive design
|
|
597
|
+
<div className="
|
|
598
|
+
grid
|
|
599
|
+
grid-cols-1 {/* Mobile: 1 column */}
|
|
600
|
+
sm:grid-cols-2 {/* 640px+: 2 columns */}
|
|
601
|
+
lg:grid-cols-3 {/* 1024px+: 3 columns */}
|
|
602
|
+
xl:grid-cols-4 {/* 1280px+: 4 columns */}
|
|
603
|
+
gap-4
|
|
604
|
+
sm:gap-6
|
|
605
|
+
lg:gap-8
|
|
606
|
+
">
|
|
607
|
+
{products.map(product => <ProductCard key={product.id} product={product} />)}
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
// Container with responsive padding
|
|
611
|
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
|
612
|
+
Content
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
// Hide/show based on breakpoint
|
|
616
|
+
<nav className="hidden md:flex">Desktop nav</nav>
|
|
617
|
+
<button className="md:hidden">Mobile menu</button>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Animation Utilities
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
// Skeleton loading
|
|
624
|
+
<div className="animate-pulse space-y-4">
|
|
625
|
+
<div className="h-4 bg-gray-200 rounded w-3/4" />
|
|
626
|
+
<div className="h-4 bg-gray-200 rounded w-1/2" />
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
// Transitions
|
|
630
|
+
<button className="
|
|
631
|
+
transition-all
|
|
632
|
+
duration-200
|
|
633
|
+
ease-in-out
|
|
634
|
+
hover:scale-105
|
|
635
|
+
active:scale-95
|
|
636
|
+
">
|
|
637
|
+
Hover me
|
|
638
|
+
</button>
|
|
639
|
+
|
|
640
|
+
// Custom animations in tailwind.config.js
|
|
641
|
+
module.exports = {
|
|
642
|
+
theme: {
|
|
643
|
+
extend: {
|
|
644
|
+
animation: {
|
|
645
|
+
'fade-in': 'fadeIn 0.3s ease-out',
|
|
646
|
+
'slide-up': 'slideUp 0.3s ease-out',
|
|
647
|
+
'spin-slow': 'spin 3s linear infinite',
|
|
648
|
+
},
|
|
649
|
+
keyframes: {
|
|
650
|
+
fadeIn: {
|
|
651
|
+
'0%': { opacity: '0' },
|
|
652
|
+
'100%': { opacity: '1' },
|
|
653
|
+
},
|
|
654
|
+
slideUp: {
|
|
655
|
+
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
|
656
|
+
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// Usage
|
|
664
|
+
<div className="animate-fade-in">Fading in</div>
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## Project Structure
|
|
670
|
+
|
|
671
|
+
### Feature-Based Structure
|
|
672
|
+
|
|
673
|
+
```
|
|
674
|
+
src/
|
|
675
|
+
├── app/ # Next.js App Router
|
|
676
|
+
│ ├── (auth)/ # Auth route group
|
|
677
|
+
│ │ ├── login/
|
|
678
|
+
│ │ └── register/
|
|
679
|
+
│ ├── dashboard/
|
|
680
|
+
│ │ ├── page.tsx
|
|
681
|
+
│ │ └── layout.tsx
|
|
682
|
+
│ └── layout.tsx
|
|
683
|
+
├── components/
|
|
684
|
+
│ ├── ui/ # Shared UI components
|
|
685
|
+
│ │ ├── Button.tsx
|
|
686
|
+
│ │ ├── Input.tsx
|
|
687
|
+
│ │ └── index.ts
|
|
688
|
+
│ └── features/ # Feature-specific components
|
|
689
|
+
│ ├── auth/
|
|
690
|
+
│ │ ├── LoginForm.tsx
|
|
691
|
+
│ │ └── RegisterForm.tsx
|
|
692
|
+
│ └── dashboard/
|
|
693
|
+
│ ├── StatsCard.tsx
|
|
694
|
+
│ └── RecentActivity.tsx
|
|
695
|
+
├── hooks/ # Custom React hooks
|
|
696
|
+
│ ├── useAuth.ts
|
|
697
|
+
│ ├── useDebounce.ts
|
|
698
|
+
│ └── useLocalStorage.ts
|
|
699
|
+
├── lib/ # Utilities and configs
|
|
700
|
+
│ ├── utils.ts
|
|
701
|
+
│ ├── api.ts
|
|
702
|
+
│ └── constants.ts
|
|
703
|
+
├── types/ # TypeScript types
|
|
704
|
+
│ ├── user.ts
|
|
705
|
+
│ └── api.ts
|
|
706
|
+
└── styles/
|
|
707
|
+
└── globals.css
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Barrel Exports
|
|
711
|
+
|
|
712
|
+
```tsx
|
|
713
|
+
// components/ui/index.ts
|
|
714
|
+
export { Button } from './Button';
|
|
715
|
+
export { Input } from './Input';
|
|
716
|
+
export { Card, CardHeader, CardContent, CardFooter } from './Card';
|
|
717
|
+
export { Dialog, DialogTrigger, DialogContent } from './Dialog';
|
|
718
|
+
|
|
719
|
+
// Usage
|
|
720
|
+
import { Button, Input, Card } from '@/components/ui';
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## Security
|
|
726
|
+
|
|
727
|
+
### XSS Prevention
|
|
728
|
+
|
|
729
|
+
React escapes content by default, which prevents most XSS attacks. When you need to render HTML content:
|
|
730
|
+
|
|
731
|
+
1. **Avoid rendering raw HTML** when possible
|
|
732
|
+
2. **Sanitize with DOMPurify** for trusted content sources
|
|
733
|
+
3. **Use allow-lists** for permitted tags and attributes
|
|
734
|
+
|
|
735
|
+
```tsx
|
|
736
|
+
// React escapes by default - this is safe
|
|
737
|
+
<div>{userInput}</div>
|
|
738
|
+
|
|
739
|
+
// When you must render HTML, sanitize first
|
|
740
|
+
import DOMPurify from 'dompurify';
|
|
741
|
+
|
|
742
|
+
function SafeHTML({ html }: { html: string }) {
|
|
743
|
+
const sanitized = DOMPurify.sanitize(html, {
|
|
744
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
|
|
745
|
+
ALLOWED_ATTR: ['href'],
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### Input Validation
|
|
753
|
+
|
|
754
|
+
```tsx
|
|
755
|
+
import { z } from 'zod';
|
|
756
|
+
import { useForm } from 'react-hook-form';
|
|
757
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
758
|
+
|
|
759
|
+
const schema = z.object({
|
|
760
|
+
email: z.string().email('Invalid email address'),
|
|
761
|
+
password: z.string()
|
|
762
|
+
.min(8, 'Password must be at least 8 characters')
|
|
763
|
+
.regex(/[A-Z]/, 'Password must contain uppercase letter')
|
|
764
|
+
.regex(/[0-9]/, 'Password must contain number'),
|
|
765
|
+
confirmPassword: z.string(),
|
|
766
|
+
}).refine((data) => data.password === data.confirmPassword, {
|
|
767
|
+
message: 'Passwords do not match',
|
|
768
|
+
path: ['confirmPassword'],
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
type FormData = z.infer<typeof schema>;
|
|
772
|
+
|
|
773
|
+
function RegisterForm() {
|
|
774
|
+
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
|
|
775
|
+
resolver: zodResolver(schema),
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
return (
|
|
779
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
780
|
+
<Input {...register('email')} error={errors.email?.message} />
|
|
781
|
+
<Input type="password" {...register('password')} error={errors.password?.message} />
|
|
782
|
+
<Input type="password" {...register('confirmPassword')} error={errors.confirmPassword?.message} />
|
|
783
|
+
<Button type="submit">Register</Button>
|
|
784
|
+
</form>
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Secure API Calls
|
|
790
|
+
|
|
791
|
+
```tsx
|
|
792
|
+
// Use environment variables for API endpoints
|
|
793
|
+
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
794
|
+
|
|
795
|
+
// Never include secrets in client code - use server-side API routes
|
|
796
|
+
// app/api/data/route.ts
|
|
797
|
+
export async function GET() {
|
|
798
|
+
const response = await fetch('https://api.example.com/data', {
|
|
799
|
+
headers: {
|
|
800
|
+
'Authorization': `Bearer ${process.env.API_SECRET}`, // Server-side only
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
return Response.json(await response.json());
|
|
805
|
+
}
|
|
806
|
+
```
|