@activatus/code-standards 0.0.0-stage → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -2
- package/docs/Guides/_category_.json +4 -0
- package/docs/Guides/component-library-workflow.md +182 -0
- package/docs/Guides/migrating.md +161 -0
- package/docs/Guides/setup.md +158 -0
- package/docs/Standards/_category_.json +4 -0
- package/docs/Standards/agent-skills.md +125 -0
- package/docs/Standards/agent-specs.md +74 -0
- package/docs/Standards/direnv.mdx +249 -0
- package/docs/Standards/documentation.md +314 -0
- package/docs/Standards/editorconfig.md +74 -0
- package/docs/Standards/eslint.md +900 -0
- package/docs/Standards/human-readable-names.md +274 -0
- package/docs/Standards/lint-errors.md +171 -0
- package/docs/Standards/prettier.md +106 -0
- package/docs/Standards/readable-code.md +570 -0
- package/docs/Standards/stylelint.md +193 -0
- package/docs/Standards/styling.md +290 -0
- package/docs/Standards/tech-stack.md +158 -0
- package/docs/Structure/_category_.json +4 -0
- package/docs/Structure/api-routes.md +118 -0
- package/docs/Structure/component-organization.md +526 -0
- package/docs/Structure/folder-structure.md +366 -0
- package/docs/Structure/import-organization.md +158 -0
- package/docs/Structure/naming-conventions.md +334 -0
- package/docs/design-system-components.md +117 -0
- package/docs/intro.md +44 -0
- package/docs/ontology.md +127 -0
- package/docs/templates.mdx +396 -0
- package/docs/version-history.md +338 -0
- package/package.json +60 -3
- package/scripts/renameSymbol.mjs +188 -0
- package/static/templates/.editorconfig +25 -0
- package/static/templates/.envrc +25 -0
- package/static/templates/.nvmrc +1 -0
- package/static/templates/AGENTS.md +163 -0
- package/static/templates/decision-doc.md +19 -0
- package/static/templates/docs/ai/agent-harness.md +17 -0
- package/static/templates/docs/ai/agents.md +17 -0
- package/static/templates/docs/ai/architecture.md +17 -0
- package/static/templates/docs/ai/context.md +17 -0
- package/static/templates/docs/ai/model-routing.md +17 -0
- package/static/templates/docs/ai/models.md +17 -0
- package/static/templates/docs/ai/retrieval.md +17 -0
- package/static/templates/docs/ai/tools.md +17 -0
- package/static/templates/docs/architecture/authentication.md +17 -0
- package/static/templates/docs/architecture/backend.md +17 -0
- package/static/templates/docs/architecture/background-jobs.md +17 -0
- package/static/templates/docs/architecture/backups.md +17 -0
- package/static/templates/docs/architecture/caching.md +17 -0
- package/static/templates/docs/architecture/ci-cd.md +17 -0
- package/static/templates/docs/architecture/disaster-recovery.md +17 -0
- package/static/templates/docs/architecture/environments.md +17 -0
- package/static/templates/docs/architecture/frontend.md +17 -0
- package/static/templates/docs/architecture/hosting.md +17 -0
- package/static/templates/docs/architecture/observability.md +17 -0
- package/static/templates/docs/architecture/realtime.md +17 -0
- package/static/templates/docs/architecture/search.md +17 -0
- package/static/templates/docs/architecture/services.md +17 -0
- package/static/templates/docs/architecture/state-management.md +17 -0
- package/static/templates/docs/architecture/storage.md +17 -0
- package/static/templates/docs/architecture/system.md +17 -0
- package/static/templates/docs/business/buying-trigger.md +17 -0
- package/static/templates/docs/business/customer-value.md +17 -0
- package/static/templates/docs/business/customer.md +17 -0
- package/static/templates/docs/business/defensibility.md +17 -0
- package/static/templates/docs/business/growth.md +17 -0
- package/static/templates/docs/business/pricing.md +17 -0
- package/static/templates/docs/business/revenue-model.md +17 -0
- package/static/templates/docs/business/unit-economics.md +17 -0
- package/static/templates/docs/data/analytics.md +17 -0
- package/static/templates/docs/data/audit.md +17 -0
- package/static/templates/docs/data/database.md +17 -0
- package/static/templates/docs/data/indexing.md +17 -0
- package/static/templates/docs/data/migrations.md +17 -0
- package/static/templates/docs/data/model.md +17 -0
- package/static/templates/docs/data/retention.md +17 -0
- package/static/templates/docs/data/schema.md +17 -0
- package/static/templates/docs/deployment/approval.md +17 -0
- package/static/templates/docs/deployment/live-verification.md +17 -0
- package/static/templates/docs/deployment/process.md +17 -0
- package/static/templates/docs/deployment/rollback.md +17 -0
- package/static/templates/docs/design/design-system.md +17 -0
- package/static/templates/docs/design/errors.md +17 -0
- package/static/templates/docs/design/feedback.md +17 -0
- package/static/templates/docs/design/interactions.md +17 -0
- package/static/templates/docs/design/layout.md +17 -0
- package/static/templates/docs/design/navigation.md +17 -0
- package/static/templates/docs/design/principles.md +17 -0
- package/static/templates/docs/design/responsive.md +17 -0
- package/static/templates/docs/development/branching.md +17 -0
- package/static/templates/docs/development/code-quality.md +17 -0
- package/static/templates/docs/development/coding-standards.md +21 -0
- package/static/templates/docs/development/commits.md +17 -0
- package/static/templates/docs/development/merge-strategy.md +17 -0
- package/static/templates/docs/development/process.md +17 -0
- package/static/templates/docs/development/pull-requests.md +17 -0
- package/static/templates/docs/experience/accessibility.md +17 -0
- package/static/templates/docs/experience/aha-moment.md +17 -0
- package/static/templates/docs/experience/anti-experience.md +17 -0
- package/static/templates/docs/experience/friction.md +17 -0
- package/static/templates/docs/experience/outcomes.md +17 -0
- package/static/templates/docs/experience/principles.md +17 -0
- package/static/templates/docs/experience/retention.md +17 -0
- package/static/templates/docs/experience/sharing.md +17 -0
- package/static/templates/docs/overview/how-it-works.md +17 -0
- package/static/templates/docs/overview/what-is-it.md +17 -0
- package/static/templates/docs/overview/writing-docs.md +16 -0
- package/static/templates/docs/pitch/alternatives.md +17 -0
- package/static/templates/docs/pitch/assumptions.md +17 -0
- package/static/templates/docs/pitch/audience.md +17 -0
- package/static/templates/docs/pitch/concept.md +17 -0
- package/static/templates/docs/pitch/differentiators.md +17 -0
- package/static/templates/docs/pitch/outcomes.md +17 -0
- package/static/templates/docs/pitch/problem.md +17 -0
- package/static/templates/docs/pitch/product-market-fit.md +17 -0
- package/static/templates/docs/pitch/success-criteria.md +17 -0
- package/static/templates/docs/pitch/validation.md +17 -0
- package/static/templates/docs/pitch/value-proposition.md +17 -0
- package/static/templates/docs/pitch/vision.md +17 -0
- package/static/templates/docs/scope/core.md +17 -0
- package/static/templates/docs/scope/exclusions.md +17 -0
- package/static/templates/docs/scope/future.md +17 -0
- package/static/templates/docs/scope/launch.md +17 -0
- package/static/templates/docs/scope/post-launch.md +17 -0
- package/static/templates/docs/scope/priorities.md +17 -0
- package/static/templates/docs/scope/quality-bar.md +17 -0
- package/static/templates/docs/security/audit.md +17 -0
- package/static/templates/docs/security/data-access.md +17 -0
- package/static/templates/docs/security/logging.md +17 -0
- package/static/templates/docs/security/privacy.md +17 -0
- package/static/templates/docs/security/secrets.md +17 -0
- package/static/templates/docs/security/threat-model.md +17 -0
- package/static/templates/docs/testing/end-to-end.md +17 -0
- package/static/templates/docs/testing/integration.md +17 -0
- package/static/templates/docs/testing/regression.md +17 -0
- package/static/templates/docs/testing/security.md +17 -0
- package/static/templates/docs/testing/strategy.md +17 -0
- package/static/templates/docs/testing/test-data.md +17 -0
- package/static/templates/docs/testing/unit.md +17 -0
- package/static/templates/docs/uat/checks.md +17 -0
- package/static/templates/docs/uat/strategy.md +17 -0
- package/static/templates/docs/users/permissions.md +17 -0
- package/static/templates/docs/users/roles.md +17 -0
- package/static/templates/docs/users/user-types.md +17 -0
- package/static/templates/docs/workflows/assignment.md +17 -0
- package/static/templates/docs/workflows/bugfix.md +17 -0
- package/static/templates/docs/workflows/dependencies.md +17 -0
- package/static/templates/docs/workflows/deployment.md +17 -0
- package/static/templates/docs/workflows/development.md +17 -0
- package/static/templates/docs/workflows/planning.md +17 -0
- package/static/templates/docs/workflows/review.md +17 -0
- package/static/templates/docs/workflows/uat.md +17 -0
- package/static/templates/docsSite/docusaurus.config.mjs +144 -0
- package/static/templates/docsSite/gitignore.txt +3 -0
- package/static/templates/docsSite/package.json.txt +42 -0
- package/static/templates/docsSite/pnpm-workspace.yaml +2 -0
- package/static/templates/docsSite/sidebars.mjs +7 -0
- package/static/templates/docsSite/src/css/custom.css +7 -0
- package/static/templates/docsSite/src/pages/index.tsx +9 -0
- package/static/templates/docsSite/static/images/logo.svg +4 -0
- package/static/templates/docsSite/tsconfig.json +7 -0
- package/static/templates/eslint/base/eslint.config.mjs +104 -0
- package/static/templates/eslint/nextjs/eslint.config.mjs +181 -0
- package/static/templates/eslint/react/eslint.config.mjs +173 -0
- package/static/templates/eslint/typescript/eslint.config.mjs +154 -0
- package/static/templates/globals.css +16 -0
- package/static/templates/husky/pre-commit +1 -0
- package/static/templates/next.config.mjs +21 -0
- package/static/templates/prettier.config.mjs +26 -0
- package/static/templates/skills/codeReview.md +31 -0
- package/static/templates/skills/format.md +18 -0
- package/static/templates/skills/lint.md +21 -0
- package/static/templates/skills/lintFix.md +24 -0
- package/static/templates/skills/securityAudit.md +28 -0
- package/static/templates/skills/test.md +24 -0
- package/static/templates/skills/typescript.md +28 -0
- package/static/templates/skills/updateLayout.md +20 -0
- package/static/templates/stylelint/css/stylelint.config.mjs +40 -0
- package/static/templates/stylelint/scss/stylelint.config.mjs +13 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# API Route Standards
|
|
6
|
+
|
|
7
|
+
Standards for organizing and naming Next.js App Router API routes.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🌐 Next.js App Router API Routes
|
|
12
|
+
|
|
13
|
+
API routes in Next.js 16 use the App Router with a file-based routing system.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ✅ Folder Structure
|
|
18
|
+
|
|
19
|
+
**Use kebab-case for API route folders:**
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
app/api/
|
|
23
|
+
├── calendar-events/
|
|
24
|
+
│ └── route.ts # GET, POST, PUT, DELETE
|
|
25
|
+
├── webhooks/
|
|
26
|
+
│ ├── stripe/
|
|
27
|
+
│ │ └── route.ts
|
|
28
|
+
│ └── recall/
|
|
29
|
+
│ └── route.ts
|
|
30
|
+
└── oauth-callback/
|
|
31
|
+
└── google-calendar/
|
|
32
|
+
└── route.ts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Organization Principles
|
|
36
|
+
|
|
37
|
+
- **Group by domain** - Related endpoints in the same folder
|
|
38
|
+
- **Use kebab-case** - All lowercase with hyphens
|
|
39
|
+
- **Nest when needed** - Sub-folders for logical grouping
|
|
40
|
+
- **Avoid redundancy** - Don't repeat "api" or "route" in folder names
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✅ File Naming
|
|
45
|
+
|
|
46
|
+
**Always use `route.ts`:**
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// ✅ Correct
|
|
50
|
+
app / api / users / route.ts;
|
|
51
|
+
|
|
52
|
+
// ❌ Incorrect
|
|
53
|
+
app / api / users.ts;
|
|
54
|
+
app / api / Users / route.ts;
|
|
55
|
+
app / api / user - management / route.ts; // avoid redundant strings
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🗂️ Route Patterns
|
|
61
|
+
|
|
62
|
+
### Simple Routes
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
app/api/
|
|
66
|
+
├── users/
|
|
67
|
+
│ └── route.ts # /api/users
|
|
68
|
+
├── posts/
|
|
69
|
+
│ └── route.ts # /api/posts
|
|
70
|
+
└── comments/
|
|
71
|
+
└── route.ts # /api/comments
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Dynamic Routes
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
app/api/
|
|
78
|
+
├── users/
|
|
79
|
+
│ └── [id]/
|
|
80
|
+
│ └── route.ts # /api/users/123
|
|
81
|
+
└── posts/
|
|
82
|
+
└── [slug]/
|
|
83
|
+
└── route.ts # /api/posts/my-post
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Nested Routes
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
app/api/
|
|
90
|
+
├── webhooks/
|
|
91
|
+
│ ├── stripe/
|
|
92
|
+
│ │ └── route.ts # /api/webhooks/stripe
|
|
93
|
+
│ └── github/
|
|
94
|
+
│ └── route.ts # /api/webhooks/github
|
|
95
|
+
└── integrations/
|
|
96
|
+
└── calendar/
|
|
97
|
+
└── events/
|
|
98
|
+
└── route.ts # /api/integrations/calendar/events
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🔒 Security Best Practices
|
|
104
|
+
|
|
105
|
+
- **Validate input** - Use Zod schemas
|
|
106
|
+
- **Handle errors** - Try/catch with proper error responses
|
|
107
|
+
- **Rate limiting** - Implement rate limits for public endpoints
|
|
108
|
+
- **Authentication** - Check user permissions
|
|
109
|
+
- **CORS** - Configure properly for API routes
|
|
110
|
+
- **Environment variables** - Never log sensitive data
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 📚 Related Documentation
|
|
115
|
+
|
|
116
|
+
- [Folder Structure](/Structure/folder-structure) - Overall project structure
|
|
117
|
+
- [Naming Conventions](/Structure/naming-conventions) - File and folder naming
|
|
118
|
+
- [TypeScript](/Standards/tech-stack) - Type safety standards
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Component Organization
|
|
6
|
+
|
|
7
|
+
Each component should be self-contained with all related files in its own folder.
|
|
8
|
+
|
|
9
|
+
## ✅ Recommended Component Structure
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
components/
|
|
13
|
+
└── button/ # camelCase folder
|
|
14
|
+
├── Button.tsx # Component implementation
|
|
15
|
+
├── Button.test.tsx # Unit tests
|
|
16
|
+
├── Button.stories.tsx # Storybook stories
|
|
17
|
+
├── utils/ # Functions used only by this component
|
|
18
|
+
│ ├── getButtonLabel/
|
|
19
|
+
│ │ ├── getButtonLabel.ts
|
|
20
|
+
│ │ ├── getButtonLabel.test.ts
|
|
21
|
+
│ │ └── index.ts
|
|
22
|
+
│ └── index.ts # Exports every function
|
|
23
|
+
├── types.ts # Component types
|
|
24
|
+
└── index.ts # Public API export
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Benefits
|
|
28
|
+
|
|
29
|
+
- **Co-located related files** - Everything for one component in one place
|
|
30
|
+
- **Easy to move/delete** - Just drag the folder
|
|
31
|
+
- **Clear boundaries** - Know exactly where code lives
|
|
32
|
+
- **Simple to understand** - New developers can navigate quickly
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📄 `index.ts` Pattern
|
|
37
|
+
|
|
38
|
+
The `index.ts` file defines the component's public API and makes imports cleaner.
|
|
39
|
+
|
|
40
|
+
### Example
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// components/button/index.ts
|
|
44
|
+
export { Button } from "./Button";
|
|
45
|
+
export type { ButtonProps } from "./types";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Usage
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Clean import from component folder
|
|
52
|
+
import { Button } from "@/components/ui/button";
|
|
53
|
+
|
|
54
|
+
// Instead of:
|
|
55
|
+
import { Button } from "@/components/ui/button/Button";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Export Guidelines
|
|
59
|
+
|
|
60
|
+
- ✅ **Export public API only** - What users of the component need
|
|
61
|
+
- ✅ **Re-export types** - Convenience for TypeScript users
|
|
62
|
+
- ✅ **Keep imports clean** - Simple, predictable import paths
|
|
63
|
+
- ✅ **Document public exports** - Comments for exported items
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## File Breakdown
|
|
68
|
+
|
|
69
|
+
### 📝 `Component.tsx`
|
|
70
|
+
|
|
71
|
+
The main component implementation with JSX and logic.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Button.tsx
|
|
75
|
+
import type { ButtonProps } from './types';
|
|
76
|
+
|
|
77
|
+
export const Button = ({ children, variant, size, ...buttonProps }: ButtonProps) => {
|
|
78
|
+
return (
|
|
79
|
+
<button className={`button button-${variant}`} {...buttonProps}>
|
|
80
|
+
{children}
|
|
81
|
+
</button>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 🧪 `Component.test.tsx`
|
|
87
|
+
|
|
88
|
+
Unit tests using Vitest & Testing Library.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Button.test.tsx
|
|
92
|
+
import { render, screen } from '@testing-library/react';
|
|
93
|
+
import { describe, it, expect } from 'vitest';
|
|
94
|
+
import { Button } from './Button';
|
|
95
|
+
|
|
96
|
+
describe('Button', () => {
|
|
97
|
+
it('renders children', () => {
|
|
98
|
+
render(<Button>Click me</Button>);
|
|
99
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 📖 `Component.stories.tsx`
|
|
105
|
+
|
|
106
|
+
Storybook stories for documentation and visual testing.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Button.stories.tsx
|
|
110
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
111
|
+
import { Button } from "./Button";
|
|
112
|
+
|
|
113
|
+
const meta = {
|
|
114
|
+
title: "Components/Button",
|
|
115
|
+
component: Button,
|
|
116
|
+
} satisfies Meta<typeof Button>;
|
|
117
|
+
|
|
118
|
+
export default meta;
|
|
119
|
+
type StoryType = StoryObj<typeof Button>;
|
|
120
|
+
|
|
121
|
+
export const Primary: StoryType = {
|
|
122
|
+
args: {
|
|
123
|
+
children: "Primary Button",
|
|
124
|
+
variant: "primary",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 📋 `types.ts`
|
|
130
|
+
|
|
131
|
+
TypeScript type definitions.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// types.ts
|
|
135
|
+
export interface ButtonProps {
|
|
136
|
+
children: React.ReactNode;
|
|
137
|
+
variant?: "primary" | "secondary" | "danger";
|
|
138
|
+
size?: "small" | "medium" | "large";
|
|
139
|
+
onClick?: () => void;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 📦 `index.ts`
|
|
144
|
+
|
|
145
|
+
Public API exports - the barrel file.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// index.ts
|
|
149
|
+
export { Button } from "./Button";
|
|
150
|
+
export type { ButtonProps } from "./types";
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🧱 Writing a component
|
|
156
|
+
|
|
157
|
+
Every component file is laid out in the same order, so a reader always knows where to look.
|
|
158
|
+
|
|
159
|
+
| # | Section | Rule |
|
|
160
|
+
| --- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| 1 | Imports | Ordered per [Import Organization](/Structure/import-organization) |
|
|
162
|
+
| 2 | Component | The component is the first thing in the file after the imports. Always an [arrow function](/Standards/readable-code#arrow-functions-everywhere) — [never a class](/Standards/readable-code#functions-never-classes) |
|
|
163
|
+
| 3 | Variable declarations | Plain values worked out from props |
|
|
164
|
+
| 4 | State declarations | `useState`, `useReducer`, `useRef` |
|
|
165
|
+
| 5 | Calculated values | Wrapped in `useMemo` |
|
|
166
|
+
| 6 | Nested functions | Only functions that need the component's state or props, wrapped in `useCallback` |
|
|
167
|
+
| 7 | Effects | `useEffect` |
|
|
168
|
+
| 8 | Render logic | The `return`. No inline functions |
|
|
169
|
+
|
|
170
|
+
### Example
|
|
171
|
+
|
|
172
|
+
```tsx title="UserListClient.tsx"
|
|
173
|
+
"use client";
|
|
174
|
+
|
|
175
|
+
// 1. Imports
|
|
176
|
+
import type { ChangeEvent } from "react";
|
|
177
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
178
|
+
|
|
179
|
+
import type { UserListClientProps } from "./types";
|
|
180
|
+
import { UserListItem } from "./userListItem";
|
|
181
|
+
import { filterUsersByName } from "./utils";
|
|
182
|
+
|
|
183
|
+
// 2. The component is the first thing in the file
|
|
184
|
+
export const UserListClient = ({
|
|
185
|
+
users,
|
|
186
|
+
onUserSelect,
|
|
187
|
+
}: UserListClientProps) => {
|
|
188
|
+
// 3. Variable declarations
|
|
189
|
+
const totalUserCount = users.length;
|
|
190
|
+
|
|
191
|
+
// 4. State declarations
|
|
192
|
+
const [searchText, setSearchText] = useState("");
|
|
193
|
+
|
|
194
|
+
// 5. Calculated values, wrapped in useMemo
|
|
195
|
+
const visibleUsers = useMemo(
|
|
196
|
+
() => filterUsersByName({ users, searchText }),
|
|
197
|
+
[users, searchText],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// 6. Nested functions, wrapped in useCallback
|
|
201
|
+
const handleSearchTextChange = useCallback(
|
|
202
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
203
|
+
setSearchText(event.target.value);
|
|
204
|
+
},
|
|
205
|
+
[],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// 7. Effects
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
document.title = `${visibleUsers.length} of ${totalUserCount} users`;
|
|
211
|
+
}, [visibleUsers.length, totalUserCount]);
|
|
212
|
+
|
|
213
|
+
// 8. Render logic — no inline functions
|
|
214
|
+
return (
|
|
215
|
+
<section className="bg-surface text-text">
|
|
216
|
+
<input value={searchText} onChange={handleSearchTextChange} />
|
|
217
|
+
<ul>
|
|
218
|
+
{visibleUsers.map((user) => (
|
|
219
|
+
<UserListItem
|
|
220
|
+
key={user.id}
|
|
221
|
+
user={user}
|
|
222
|
+
onUserSelect={onUserSelect}
|
|
223
|
+
/>
|
|
224
|
+
))}
|
|
225
|
+
</ul>
|
|
226
|
+
</section>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Keep the UI file clean
|
|
232
|
+
|
|
233
|
+
**If a function can be written in another file, move it out of the UI file.** A function that does not need the component's state or props is not a nested function — it belongs in a [utils folder](/Structure/folder-structure#-utils).
|
|
234
|
+
|
|
235
|
+
`filterUsersByName` above only needs its arguments, so it lives in the component's `utils/` folder, in its own folder beside its test file:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
userList/
|
|
239
|
+
├── UserListClient.tsx
|
|
240
|
+
└── utils/
|
|
241
|
+
├── filterUsersByName/
|
|
242
|
+
│ ├── filterUsersByName.ts
|
|
243
|
+
│ ├── filterUsersByName.test.ts
|
|
244
|
+
│ └── index.ts
|
|
245
|
+
└── index.ts
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```ts title="utils/filterUsersByName/filterUsersByName.ts"
|
|
249
|
+
import type { UserType } from "../../types";
|
|
250
|
+
|
|
251
|
+
interface FilterUsersByNameProps {
|
|
252
|
+
users: UserType[];
|
|
253
|
+
searchText: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export const filterUsersByName = ({
|
|
257
|
+
users,
|
|
258
|
+
searchText,
|
|
259
|
+
}: FilterUsersByNameProps) => {
|
|
260
|
+
const lowerCaseSearchText = searchText.toLowerCase();
|
|
261
|
+
|
|
262
|
+
return users.filter((user) =>
|
|
263
|
+
user.name.toLowerCase().includes(lowerCaseSearchText),
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Each function folder has an `index.ts`, and so does the `utils/` folder — both are one line:
|
|
269
|
+
|
|
270
|
+
```ts title="utils/filterUsersByName/index.ts"
|
|
271
|
+
export { filterUsersByName } from "./filterUsersByName";
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ts title="utils/index.ts"
|
|
275
|
+
export { filterUsersByName } from "./filterUsersByName";
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The component imports it from the folder: `import { filterUsersByName } from "./utils";`
|
|
279
|
+
|
|
280
|
+
### No inline functions in render
|
|
281
|
+
|
|
282
|
+
Never define a function inside the JSX. Declare it above with `useCallback` and pass it by name:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
// ✅ Good
|
|
286
|
+
const handleSearchTextChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
287
|
+
setSearchText(event.target.value);
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
<input value={searchText} onChange={handleSearchTextChange} />
|
|
291
|
+
|
|
292
|
+
// ❌ Bad
|
|
293
|
+
<input value={searchText} onChange={(event) => setSearchText(event.target.value)} />
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
When each item in a list needs its own handler, give the item its own component rather than writing an inline function in the loop:
|
|
297
|
+
|
|
298
|
+
```tsx title="userListItem/UserListItem.tsx"
|
|
299
|
+
import { useCallback } from "react";
|
|
300
|
+
|
|
301
|
+
import type { UserType } from "../types";
|
|
302
|
+
|
|
303
|
+
interface UserListItemProps {
|
|
304
|
+
user: UserType;
|
|
305
|
+
onUserSelect?: (user: UserType) => void;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const UserListItem = ({
|
|
309
|
+
user,
|
|
310
|
+
onUserSelect,
|
|
311
|
+
}: UserListItemProps) => {
|
|
312
|
+
const handleClick = useCallback(() => {
|
|
313
|
+
onUserSelect?.(user);
|
|
314
|
+
}, [onUserSelect, user]);
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<li>
|
|
318
|
+
<button onClick={handleClick}>{user.name}</button>
|
|
319
|
+
</li>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Use `useMemo` and `useCallback`
|
|
325
|
+
|
|
326
|
+
- **`useMemo`** for any value calculated from props or state (filtering, sorting, totals)
|
|
327
|
+
- **`useCallback`** for every function passed to a child component or a JSX prop
|
|
328
|
+
|
|
329
|
+
ESLint enforces the function half of this with `react/jsx-no-bind` — see the [ESLint configuration](/Standards/eslint#reactjsx-no-bind-error).
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 🔀 Server and client components
|
|
334
|
+
|
|
335
|
+
**Server code and client code live in separate files.** The server component fetches the data and nests the client component inside it:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
components/
|
|
339
|
+
└── userList/
|
|
340
|
+
├── UserList.tsx # Server component: fetches data, renders the client component
|
|
341
|
+
├── UserListClient.tsx # Client component: state, effects, event handlers
|
|
342
|
+
├── utils/
|
|
343
|
+
├── types.ts
|
|
344
|
+
└── index.ts
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```tsx title="UserList.tsx"
|
|
348
|
+
import { getUsers } from "@/services/userService";
|
|
349
|
+
|
|
350
|
+
import { UserListClient } from "./UserListClient";
|
|
351
|
+
|
|
352
|
+
export const UserList = async () => {
|
|
353
|
+
const users = await getUsers();
|
|
354
|
+
|
|
355
|
+
return <UserListClient users={users} />;
|
|
356
|
+
};
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```tsx title="UserListClient.tsx"
|
|
360
|
+
"use client";
|
|
361
|
+
|
|
362
|
+
import { useState } from "react";
|
|
363
|
+
|
|
364
|
+
import type { UserListClientProps } from "./types";
|
|
365
|
+
|
|
366
|
+
export const UserListClient = ({ users }: UserListClientProps) => {
|
|
367
|
+
const [searchText, setSearchText] = useState("");
|
|
368
|
+
// ...
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Rules
|
|
373
|
+
|
|
374
|
+
- **Server file**: data fetching, database and service calls, secrets. No `"use client"`, no hooks, no event handlers.
|
|
375
|
+
- **Client file**: starts with `"use client"`. State, effects and event handlers only — it receives its data as props.
|
|
376
|
+
- **The server component nests the client component**, never the other way round.
|
|
377
|
+
- **Name the client file with a `Client` suffix** (`UserListClient.tsx`) so the pair is obvious.
|
|
378
|
+
- **Export the server component from `index.ts`** — it is the public entry point.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Reusable by design
|
|
383
|
+
|
|
384
|
+
Every component in `components/ui/` is built so it can be copied into another project unchanged: everything comes in through props, and it imports nothing from the project's features, services, stores or `app/` folder. Before building one, [look before you build](/Guides/component-library-workflow#1-look-before-you-build), and build it to the props in the [Design System Components](/design-system-components). See [Future-proofing](/Guides/component-library-workflow#future-proofing).
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Components from shadcn
|
|
389
|
+
|
|
390
|
+
A component added with the shadcn CLI is **our code** and follows everything on this page. Convert it when it is added: its own camelCase folder, one component per file, `types.ts`, `index.ts` and a test file. See [Adding a shadcn component](/Guides/component-library-workflow#adding-a-shadcn-component).
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Folder Naming
|
|
395
|
+
|
|
396
|
+
**Component folders use camelCase** to match the component name:
|
|
397
|
+
|
|
398
|
+
```text
|
|
399
|
+
✅ Good
|
|
400
|
+
components/
|
|
401
|
+
└── button/
|
|
402
|
+
├── Button.tsx
|
|
403
|
+
└── index.ts
|
|
404
|
+
|
|
405
|
+
❌ Bad
|
|
406
|
+
components/
|
|
407
|
+
└── Button/
|
|
408
|
+
├── Button.tsx
|
|
409
|
+
└── index.ts
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
This makes it easy to spot component folders and keeps them consistent with file naming.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Nested Components
|
|
417
|
+
|
|
418
|
+
For components with sub-components, give each sub-component its own folder:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
components/
|
|
422
|
+
└── card/
|
|
423
|
+
├── Card.tsx
|
|
424
|
+
├── Card.test.tsx
|
|
425
|
+
├── cardHeader/
|
|
426
|
+
│ ├── CardHeader.tsx
|
|
427
|
+
│ ├── CardHeader.test.tsx
|
|
428
|
+
│ ├── types.ts
|
|
429
|
+
│ └── index.ts
|
|
430
|
+
├── cardBody/
|
|
431
|
+
│ ├── CardBody.tsx
|
|
432
|
+
│ ├── CardBody.test.tsx
|
|
433
|
+
│ ├── types.ts
|
|
434
|
+
│ └── index.ts
|
|
435
|
+
├── cardFooter/
|
|
436
|
+
│ ├── CardFooter.tsx
|
|
437
|
+
│ ├── CardFooter.test.tsx
|
|
438
|
+
│ ├── types.ts
|
|
439
|
+
│ └── index.ts
|
|
440
|
+
├── types.ts
|
|
441
|
+
└── index.ts # Re-exports all sub-components
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### `index.ts` for Nested Components
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// Card/index.ts
|
|
448
|
+
export { Card } from "./Card";
|
|
449
|
+
export { CardHeader } from "./cardHeader";
|
|
450
|
+
export { CardBody } from "./cardBody";
|
|
451
|
+
export { CardFooter } from "./cardFooter";
|
|
452
|
+
export type { CardProps } from "./types";
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Importing Components
|
|
458
|
+
|
|
459
|
+
### Within the Same Folder
|
|
460
|
+
|
|
461
|
+
Inside a component's folder, import files directly. **Never import through the folder's own `index.ts`**, and never import the parent component from a sub-component — both create a [circular import](/Structure/import-organization#import-safety).
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
// card/Card.tsx — the parent imports its sub-components
|
|
465
|
+
import { CardBody } from "./cardBody";
|
|
466
|
+
import { CardHeader } from "./cardHeader";
|
|
467
|
+
import type { CardProps } from "./types";
|
|
468
|
+
|
|
469
|
+
// card/cardBody/CardBody.tsx — a sub-component imports types by file
|
|
470
|
+
import type { CardProps } from "../types";
|
|
471
|
+
|
|
472
|
+
// ❌ Bad — a sub-component importing its parent through the index
|
|
473
|
+
import { Card } from "..";
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### From Other Components
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
// UserProfile.tsx
|
|
480
|
+
import { Card, CardHeader, CardBody } from "@/components/ui/card";
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Best Practices
|
|
484
|
+
|
|
485
|
+
- ✅ From **outside** a folder, import through its `index.ts` — the folder, not the file
|
|
486
|
+
- ✅ From **inside** a folder, import the file directly — never the folder's own `index.ts`
|
|
487
|
+
- ❌ Never import a parent component from one of its sub-components
|
|
488
|
+
- ✅ Use path aliases (`@/`) for consistency
|
|
489
|
+
- ❌ Avoid deep relative imports (`../../../Button`)
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## When to Split Components
|
|
494
|
+
|
|
495
|
+
Consider creating separate component folders when:
|
|
496
|
+
|
|
497
|
+
1. **Used by more than one page** → Move to the section's `components/` folder, or `components/shared/`
|
|
498
|
+
2. **Complex logic** → Separate file for clarity
|
|
499
|
+
3. **Multiple variants** → Sub-components in same folder
|
|
500
|
+
4. **Shared styles** → Keep co-located
|
|
501
|
+
|
|
502
|
+
### Page Components
|
|
503
|
+
|
|
504
|
+
Keep components in the page's route folder if they're only used there:
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
app/
|
|
508
|
+
└── account-settings/
|
|
509
|
+
├── components/
|
|
510
|
+
│ └── accountForm/
|
|
511
|
+
│ ├── AccountForm.tsx
|
|
512
|
+
│ ├── AccountForm.test.tsx
|
|
513
|
+
│ └── index.ts
|
|
514
|
+
└── page.tsx
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
See [Where components live](/Structure/folder-structure#-where-components-live) for every location.
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Resources
|
|
522
|
+
|
|
523
|
+
- [Storybook Documentation](https://storybook.js.org/docs)
|
|
524
|
+
- [Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
|
525
|
+
- [Vitest](https://vitest.dev/)
|
|
526
|
+
- [Next.js Project Structure](https://nextjs.org/docs/app/building-your-application/routing/colocation)
|