@canonical/code-standards 0.1.0 → 0.1.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/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/workflows/ci.yml +40 -0
- package/biome.json +6 -0
- package/bun.lock +200 -0
- package/data/code.ttl +208 -167
- package/data/css.ttl +110 -91
- package/data/icons.ttl +186 -150
- package/data/packaging.ttl +428 -170
- package/data/react.ttl +306 -244
- package/data/rust.ttl +563 -467
- package/data/storybook.ttl +108 -90
- package/data/styling.ttl +40 -40
- package/data/tsdoc.ttl +111 -86
- package/data/turtle.ttl +89 -68
- package/definitions/CodeStandard.ttl +28 -20
- package/docs/code.md +37 -327
- package/docs/css.md +24 -20
- package/docs/icons.md +41 -42
- package/docs/index.md +2 -1
- package/docs/packaging.md +643 -0
- package/docs/react.md +58 -59
- package/docs/rust.md +92 -158
- package/docs/storybook.md +18 -20
- package/docs/styling.md +8 -8
- package/docs/tsdoc.md +16 -16
- package/docs/turtle.md +15 -15
- package/package.json +16 -2
- package/skills/add-standard/SKILL.md +83 -47
- package/src/scripts/generate-docs.ts +95 -13
- package/src/scripts/index.ts +4 -2
- package/tsconfig.json +8 -0
package/data/react.ttl
CHANGED
|
@@ -15,9 +15,10 @@ cs:ComponentTSDoc a cs:CodeStandard ;
|
|
|
15
15
|
cs:name "react/component/tsdoc" ;
|
|
16
16
|
cs:hasCategory cs:ReactCategory ;
|
|
17
17
|
cs:description """Component TSDoc documentation must use the description from the design system ontology (DSL). The TSDoc should NOT include @example blocks since stories serve as the examples. The description should be copied verbatim from the DSL, maintaining the original wording and meaning.""" ;
|
|
18
|
-
cs:
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
cs:do [
|
|
19
|
+
cs:description "Use the description from the DSL ontology verbatim." ;
|
|
20
|
+
cs:language "typescript" ;
|
|
21
|
+
cs:code """
|
|
21
22
|
/**
|
|
22
23
|
* The label component is a compact, non-interactive visual element used to
|
|
23
24
|
* categorize content or indicate a status. Its primary role is metadata
|
|
@@ -31,41 +32,44 @@ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
|
|
|
31
32
|
{children}
|
|
32
33
|
</span>
|
|
33
34
|
);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
cs:
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
"""
|
|
36
|
+
] ;
|
|
37
|
+
cs:dont [
|
|
38
|
+
cs:description "Write custom descriptions that deviate from the DSL." ;
|
|
39
|
+
cs:language "typescript" ;
|
|
40
|
+
cs:code """
|
|
39
41
|
// Bad: Custom title and paraphrased description
|
|
40
42
|
/**
|
|
41
43
|
* Label component
|
|
42
44
|
*
|
|
43
45
|
* A compact visual element for status indication.
|
|
44
46
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
"""
|
|
48
|
+
] ;
|
|
49
|
+
cs:dont [
|
|
50
|
+
cs:description "Include @example blocks - stories fulfill this role." ;
|
|
51
|
+
cs:language "typescript" ;
|
|
52
|
+
cs:code """
|
|
49
53
|
// Bad: Examples belong in stories, not TSDoc
|
|
50
54
|
/**
|
|
51
55
|
* The label component is a compact...
|
|
52
56
|
*
|
|
53
57
|
* @example
|
|
54
|
-
* ```tsx
|
|
55
58
|
* <Label>Default</Label>
|
|
56
59
|
* <Label criticality="warning">Warning</Label>
|
|
57
|
-
* ```
|
|
58
60
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
"""
|
|
62
|
+
] ;
|
|
63
|
+
cs:dont [
|
|
64
|
+
cs:description "Omit the @implements tag that links to the DSL." ;
|
|
65
|
+
cs:language "typescript" ;
|
|
66
|
+
cs:code """
|
|
63
67
|
// Bad: Missing @implements tag
|
|
64
68
|
/**
|
|
65
69
|
* The label component is a compact...
|
|
66
70
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
"""
|
|
72
|
+
] .
|
|
69
73
|
|
|
70
74
|
# Component Folder Structure Standard
|
|
71
75
|
cs:ComponentFolderStructure a cs:CodeStandard ;
|
|
@@ -73,9 +77,10 @@ cs:ComponentFolderStructure a cs:CodeStandard ;
|
|
|
73
77
|
cs:extends cs:ComponentFolderStructure ;
|
|
74
78
|
cs:hasCategory cs:ReactCategory ;
|
|
75
79
|
cs:description "Each component must reside in its own folder, which contains all related files: component implementation, tests, type definitions, and optionally stories and styles. Some components may be styleless or may not have stories; in these cases, styles.css and [MyComponent].stories.tsx are optional. Context providers follow a different structure (see react/component/structure/context)." ;
|
|
76
|
-
cs:
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
cs:do [
|
|
81
|
+
cs:description "Place all component-related files within a single folder named after the component." ;
|
|
82
|
+
cs:language "bash" ;
|
|
83
|
+
cs:code """
|
|
79
84
|
[MyComponent]/
|
|
80
85
|
├── [MyComponent].tsx
|
|
81
86
|
├── [MyComponent].stories.tsx
|
|
@@ -83,11 +88,12 @@ cs:ComponentFolderStructure a cs:CodeStandard ;
|
|
|
83
88
|
├── index.ts
|
|
84
89
|
├── styles.css
|
|
85
90
|
└── types.ts
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
cs:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
"""
|
|
92
|
+
] ;
|
|
93
|
+
cs:dont [
|
|
94
|
+
cs:description "Scatter component files across different parts of the application." ;
|
|
95
|
+
cs:language "bash" ;
|
|
96
|
+
cs:code """
|
|
91
97
|
# Bad: Files are not co-located
|
|
92
98
|
components/
|
|
93
99
|
├── [MyComponent].tsx
|
|
@@ -95,46 +101,50 @@ stories/
|
|
|
95
101
|
└── [MyComponent].stories.tsx
|
|
96
102
|
styles/
|
|
97
103
|
└── [MyComponent].css
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
"""
|
|
105
|
+
] .
|
|
100
106
|
|
|
101
107
|
# Barrel Exports Standard
|
|
102
108
|
cs:BarrelExports a cs:CodeStandard ;
|
|
103
109
|
cs:name "react/component/barrel-exports" ;
|
|
104
110
|
cs:hasCategory cs:ReactCategory ;
|
|
105
111
|
cs:description "The index.ts file must be a complete barrel export for the component folder, re-exporting all public APIs." ;
|
|
106
|
-
cs:
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
cs:do [
|
|
113
|
+
cs:description "Create an index.ts file that re-exports all public APIs from the component folder." ;
|
|
114
|
+
cs:language "typescript" ;
|
|
115
|
+
cs:code """
|
|
109
116
|
// index.ts
|
|
110
117
|
export { default as [MyComponent] } from './[MyComponent].js';
|
|
111
118
|
export type * from './types.js';
|
|
112
119
|
// If you have multiple components:
|
|
113
120
|
export { default as SubComponent } from './SubComponent.js';
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
cs:
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
"""
|
|
122
|
+
] ;
|
|
123
|
+
cs:dont [
|
|
124
|
+
cs:description "Omit the index.ts file or fail to re-export all public APIs." ;
|
|
125
|
+
cs:language "typescript" ;
|
|
126
|
+
cs:code """
|
|
119
127
|
// Bad: index.ts only exports default, omits types and named exports
|
|
120
128
|
export { default } from './[MyComponent].js';
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
"""
|
|
130
|
+
] ;
|
|
131
|
+
cs:dont [
|
|
132
|
+
cs:description "Use `export * from './types.js'` as it allows value exports, which is not expected for types files." ;
|
|
133
|
+
cs:language "typescript" ;
|
|
134
|
+
cs:code """
|
|
125
135
|
// Bad: Using export * from './types.js' allows value exports, which is not allowed
|
|
126
136
|
export * from './types.js';
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
"""
|
|
138
|
+
] .
|
|
129
139
|
|
|
130
140
|
# Component File Naming Standard
|
|
131
141
|
cs:ComponentFileNaming a cs:CodeStandard ;
|
|
132
142
|
cs:name "react/component/file-naming" ;
|
|
133
143
|
cs:hasCategory cs:ReactCategory ;
|
|
134
144
|
cs:description "Component folder and file naming is based on scope. Component-specific files (implementation, stories, tests) must be prefixed with the component's name (e.g., `MyComponent.tsx`, `MyComponent.stories.tsx`). Domain-level files that serve the entire folder (e.g., `Context.tsx`, `styles.css`, `types.ts`) should use generic, descriptive names, as the folder already provides the domain context." ;
|
|
135
|
-
cs:
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
cs:do [
|
|
146
|
+
cs:description "Prefix component-specific files and use generic names for domain-level files." ;
|
|
147
|
+
cs:code """
|
|
138
148
|
[MyComponent]/
|
|
139
149
|
├── [MyComponent].tsx # Component-specific
|
|
140
150
|
├── [MyComponent].stories.tsx # Component-specific
|
|
@@ -142,17 +152,17 @@ cs:ComponentFileNaming a cs:CodeStandard ;
|
|
|
142
152
|
├── Context.tsx # Domain-level
|
|
143
153
|
├── types.ts # Domain-level
|
|
144
154
|
└── styles.css # Domain-level
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
cs:
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
"""
|
|
156
|
+
] ;
|
|
157
|
+
cs:dont [
|
|
158
|
+
cs:description "Add redundant prefixes to domain-level files." ;
|
|
159
|
+
cs:code """
|
|
150
160
|
[MyComponent]/
|
|
151
161
|
├── [MyComponent].Context.tsx # Bad: Redundant prefix
|
|
152
162
|
├── [MyComponent].types.ts # Bad: Redundant prefix
|
|
153
163
|
└── [MyComponent].styles.css # Bad: Redundant prefix
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
"""
|
|
165
|
+
] .
|
|
156
166
|
|
|
157
167
|
# Component Props Standard
|
|
158
168
|
cs:ComponentProps a cs:CodeStandard ;
|
|
@@ -163,9 +173,10 @@ cs:ComponentProps a cs:CodeStandard ;
|
|
|
163
173
|
- Be destructured when used in markup
|
|
164
174
|
- Be spread to the root element when unused
|
|
165
175
|
- Follow type-specific patterns based on what the component renders""" ;
|
|
166
|
-
cs:
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
cs:do [
|
|
177
|
+
cs:description "Document props with TSDoc comments and use proper destructuring and spreading." ;
|
|
178
|
+
cs:language "typescript" ;
|
|
179
|
+
cs:code """
|
|
169
180
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
170
181
|
/** The button's text content */
|
|
171
182
|
label: string;
|
|
@@ -190,11 +201,12 @@ const Button = ({
|
|
|
190
201
|
<span>{label}</span>
|
|
191
202
|
</button>
|
|
192
203
|
);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
cs:
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
"""
|
|
205
|
+
] ;
|
|
206
|
+
cs:dont [
|
|
207
|
+
cs:description "Mix explicit and spread props or destructure props unnecessarily." ;
|
|
208
|
+
cs:language "typescript" ;
|
|
209
|
+
cs:code """
|
|
198
210
|
// Bad: Mixing explicit props with spread
|
|
199
211
|
const Button = (props: ButtonProps) => (
|
|
200
212
|
<button
|
|
@@ -225,8 +237,8 @@ const Button = ({
|
|
|
225
237
|
{label}
|
|
226
238
|
</button>
|
|
227
239
|
);
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
"""
|
|
241
|
+
] .
|
|
230
242
|
|
|
231
243
|
# HTML Rendering Components Props Standard
|
|
232
244
|
cs:HTMLRenderingProps a cs:CodeStandard ;
|
|
@@ -234,66 +246,70 @@ cs:HTMLRenderingProps a cs:CodeStandard ;
|
|
|
234
246
|
cs:hasCategory cs:ReactCategory ;
|
|
235
247
|
cs:extends cs:ComponentProps ;
|
|
236
248
|
cs:description "Components that render HTML markup must extend the base HTML element props interface to enable passing native properties through spreading." ;
|
|
237
|
-
cs:
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
cs:do [
|
|
250
|
+
cs:description "Extend the appropriate React HTML props interface and add component-specific props." ;
|
|
251
|
+
cs:language "typescript" ;
|
|
252
|
+
cs:code """
|
|
240
253
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
241
254
|
/** The button label */
|
|
242
255
|
label: string;
|
|
243
256
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
cs:
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
"""
|
|
258
|
+
] ;
|
|
259
|
+
cs:dont [
|
|
260
|
+
cs:description "Manually redefine standard HTML attributes that are already available through the base interface." ;
|
|
261
|
+
cs:language "typescript" ;
|
|
262
|
+
cs:code """
|
|
249
263
|
export interface ButtonProps {
|
|
250
264
|
/** The button label */
|
|
251
265
|
label: string;
|
|
252
266
|
onClick?: () => void; // Bad: Duplicates HTML button props
|
|
253
267
|
disabled?: boolean; // Bad: Duplicates HTML button props
|
|
254
268
|
}
|
|
255
|
-
|
|
256
|
-
|
|
269
|
+
"""
|
|
270
|
+
] .
|
|
257
271
|
|
|
258
272
|
# Component Naming Standard
|
|
259
273
|
cs:ComponentNaming a cs:CodeStandard ;
|
|
260
274
|
cs:name "react/component/naming" ;
|
|
261
275
|
cs:hasCategory cs:ReactCategory ;
|
|
262
276
|
cs:description "Components must use PascalCase naming and be descriptive of their purpose." ;
|
|
263
|
-
cs:
|
|
264
|
-
|
|
277
|
+
cs:do [
|
|
278
|
+
cs:description """Use PascalCase and descriptive names for components:
|
|
265
279
|
UserProfile
|
|
266
280
|
NavigationBar
|
|
267
|
-
SearchResultList
|
|
268
|
-
|
|
269
|
-
cs:
|
|
270
|
-
|
|
281
|
+
SearchResultList"""
|
|
282
|
+
] ;
|
|
283
|
+
cs:dont [
|
|
284
|
+
cs:description """Use non-PascalCase or unclear names:
|
|
271
285
|
userProfile
|
|
272
286
|
navigation_bar
|
|
273
|
-
searchresultlist
|
|
274
|
-
|
|
287
|
+
searchresultlist"""
|
|
288
|
+
] .
|
|
275
289
|
|
|
276
290
|
# Hook Naming Standard
|
|
277
291
|
cs:HookNaming a cs:CodeStandard ;
|
|
278
292
|
cs:name "react/hooks/naming" ;
|
|
279
293
|
cs:hasCategory cs:ReactCategory ;
|
|
280
294
|
cs:description "The hook name must start with 'use' and clearly describe its purpose." ;
|
|
281
|
-
cs:
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
cs:do [
|
|
296
|
+
cs:description "Name custom hooks so the hook name starts with 'use' and is descriptive" ;
|
|
297
|
+
cs:language "typescript" ;
|
|
298
|
+
cs:code """
|
|
284
299
|
useWindowSize()
|
|
285
300
|
useAuthentication()
|
|
286
301
|
useFormValidation()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
cs:
|
|
290
|
-
|
|
291
|
-
|
|
302
|
+
"""
|
|
303
|
+
] ;
|
|
304
|
+
cs:dont [
|
|
305
|
+
cs:description "Name hooks without the 'use' prefix at the start of the hook name" ;
|
|
306
|
+
cs:language "typescript" ;
|
|
307
|
+
cs:code """
|
|
292
308
|
windowSize()
|
|
293
309
|
getAuth()
|
|
294
310
|
formValidation()
|
|
295
|
-
|
|
296
|
-
|
|
311
|
+
"""
|
|
312
|
+
] .
|
|
297
313
|
|
|
298
314
|
# Component Dependencies Standard
|
|
299
315
|
cs:ComponentDependencies a cs:CodeStandard ;
|
|
@@ -303,9 +319,9 @@ cs:ComponentDependencies a cs:CodeStandard ;
|
|
|
303
319
|
- Subcomponents must be in a `common/` folder within the parent component's directory
|
|
304
320
|
- Dependencies can flow downwards (parent to subcomponent) or sideways (between siblings)
|
|
305
321
|
- Dependencies must not flow upwards (subcomponent to parent)""" ;
|
|
306
|
-
cs:
|
|
307
|
-
|
|
308
|
-
|
|
322
|
+
cs:do [
|
|
323
|
+
cs:description "Place subcomponents in a `common/` folder inside the parent component directory." ;
|
|
324
|
+
cs:code """
|
|
309
325
|
Card/
|
|
310
326
|
├── Card.tsx
|
|
311
327
|
├── common/
|
|
@@ -316,30 +332,35 @@ Card/
|
|
|
316
332
|
│ └── utils/
|
|
317
333
|
│ └── helpers.ts
|
|
318
334
|
└── index.ts
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
335
|
+
"""
|
|
336
|
+
] ;
|
|
337
|
+
cs:do [
|
|
338
|
+
cs:description "Allow subcomponents to depend on siblings or shared utilities within the same component scope." ;
|
|
339
|
+
cs:language "typescript" ;
|
|
340
|
+
cs:code """
|
|
323
341
|
// Header.tsx can import from utils/
|
|
324
342
|
import { helper } from '../utils/helpers.js';
|
|
325
343
|
|
|
326
344
|
// Footer.tsx can import from Header.tsx
|
|
327
345
|
import Header from '../Header.js';
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
cs:
|
|
331
|
-
|
|
332
|
-
|
|
346
|
+
"""
|
|
347
|
+
] ;
|
|
348
|
+
cs:dont [
|
|
349
|
+
cs:description "Create dependencies that flow upwards from a subcomponent to its parent." ;
|
|
350
|
+
cs:language "typescript" ;
|
|
351
|
+
cs:code """
|
|
333
352
|
// Bad: Header.tsx in Card/common/ should not import from Card.tsx
|
|
334
353
|
import Card from '../../Card.js';
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
"""
|
|
355
|
+
] ;
|
|
356
|
+
cs:dont [
|
|
357
|
+
cs:description "Allow external components to depend on the internal structure of another component." ;
|
|
358
|
+
cs:language "typescript" ;
|
|
359
|
+
cs:code """
|
|
339
360
|
// Bad: AnotherComponent should not import from Card's internal common folder
|
|
340
361
|
import Header from '../Card/common/Header.js';
|
|
341
|
-
|
|
342
|
-
|
|
362
|
+
"""
|
|
363
|
+
] .
|
|
343
364
|
|
|
344
365
|
# Subcomponents Export and Consumption API Standard
|
|
345
366
|
cs:SubcomponentsExportAPI a cs:CodeStandard ;
|
|
@@ -355,65 +376,77 @@ Public subcomponents must be:
|
|
|
355
376
|
- Kept to a single level of nesting.
|
|
356
377
|
|
|
357
378
|
Private subcomponents must remain internal to the component's implementation and not be exported by any file.""" ;
|
|
358
|
-
cs:
|
|
359
|
-
|
|
360
|
-
|
|
379
|
+
cs:do [
|
|
380
|
+
cs:description "Export public subcomponents by attaching them to the parent component using dot notation" ;
|
|
381
|
+
cs:language "typescript" ;
|
|
382
|
+
cs:code """
|
|
361
383
|
const Item = (props: ItemProps) => { /* ... */ };
|
|
362
384
|
const Accordion = (props: AccordionProps) => { /* ... */ };
|
|
363
385
|
Accordion.Item = Item;
|
|
364
386
|
export default Accordion;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
387
|
+
"""
|
|
388
|
+
] ;
|
|
389
|
+
cs:do [
|
|
390
|
+
cs:description "Use semantic, self-descriptive names for subcomponents" ;
|
|
391
|
+
cs:language "typescript" ;
|
|
392
|
+
cs:code """
|
|
369
393
|
Accordion.Item
|
|
370
394
|
Card.Header
|
|
371
395
|
Card.Footer
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
396
|
+
"""
|
|
397
|
+
] ;
|
|
398
|
+
cs:do [
|
|
399
|
+
cs:description "Keep subcomponent nesting to a single level" ;
|
|
400
|
+
cs:language "typescript" ;
|
|
401
|
+
cs:code """
|
|
376
402
|
<Card>
|
|
377
403
|
<Card.Header />
|
|
378
404
|
<Card.Footer />
|
|
379
405
|
</Card>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
406
|
+
"""
|
|
407
|
+
] ;
|
|
408
|
+
cs:dont [
|
|
409
|
+
cs:description "Repeat the parent component name in subcomponent names" ;
|
|
410
|
+
cs:language "typescript" ;
|
|
411
|
+
cs:code """
|
|
386
412
|
Card.CardHeader = Header; // Bad: Redundant 'Card' prefix
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
413
|
+
"""
|
|
414
|
+
] ;
|
|
415
|
+
cs:dont [
|
|
416
|
+
cs:description "Map a subcomponent to a different name (renaming)" ;
|
|
417
|
+
cs:language "typescript" ;
|
|
418
|
+
cs:code """
|
|
391
419
|
Card.Top = Header; // Bad: Mapping 'Header' to 'Top' is not allowed
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
420
|
+
"""
|
|
421
|
+
] ;
|
|
422
|
+
cs:dont [
|
|
423
|
+
cs:description "Use non-semantic or unclear subcomponent names" ;
|
|
424
|
+
cs:code """
|
|
396
425
|
Card/
|
|
397
426
|
└── common/
|
|
398
427
|
├── Part/ # Bad: Too vague, not semantic - what part?
|
|
399
428
|
├── Element/ # Bad: Too vague, not semantic - what element?
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
429
|
+
"""
|
|
430
|
+
] ;
|
|
431
|
+
cs:dont [
|
|
432
|
+
cs:description "Nest subcomponents more than one level deep" ;
|
|
433
|
+
cs:language "typescript" ;
|
|
434
|
+
cs:code """
|
|
404
435
|
<Card>
|
|
405
436
|
<Card.Header>
|
|
406
437
|
<Card.Header.Title />
|
|
407
438
|
</Card.Header>
|
|
408
439
|
</Card>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
440
|
+
"""
|
|
441
|
+
] ;
|
|
442
|
+
cs:dont [
|
|
443
|
+
cs:description "Export private subcomponents that are not intended for public use." ;
|
|
444
|
+
cs:language "typescript" ;
|
|
445
|
+
cs:code """
|
|
413
446
|
// Bad: Exporting internal-only subcomponents
|
|
414
447
|
export { InternalHelper };
|
|
415
|
-
|
|
416
|
-
|
|
448
|
+
"""
|
|
449
|
+
] .
|
|
417
450
|
|
|
418
451
|
# CSS ClassName Construction Standard
|
|
419
452
|
cs:ClassNameConstruction a cs:CodeStandard ;
|
|
@@ -428,9 +461,10 @@ cs:ClassNameConstruction a cs:CodeStandard ;
|
|
|
428
461
|
b. Modifier Classes: Classes derived from component props (e.g., `emphasis`, `severity`).
|
|
429
462
|
c. Consumer Classes: The `className` prop passed by the consumer.
|
|
430
463
|
4. Filtering and Joining: The array must be processed with `.filter(Boolean).join(" ")` to remove any falsy values (e.g., undefined props, expressions that evaluate to false) and create the final space-delimited string.""" ;
|
|
431
|
-
cs:
|
|
432
|
-
|
|
433
|
-
|
|
464
|
+
cs:do [
|
|
465
|
+
cs:description "Follow the complete pattern for class name construction." ;
|
|
466
|
+
cs:language "tsx" ;
|
|
467
|
+
cs:code """
|
|
434
468
|
const componentCssClassName = "ds badge";
|
|
435
469
|
|
|
436
470
|
const Badge = ({
|
|
@@ -450,27 +484,32 @@ const Badge = ({
|
|
|
450
484
|
</span>
|
|
451
485
|
);
|
|
452
486
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
cs:
|
|
456
|
-
|
|
457
|
-
|
|
487
|
+
"""
|
|
488
|
+
] ;
|
|
489
|
+
cs:dont [
|
|
490
|
+
cs:description "Hardcode the base class name inside the JSX." ;
|
|
491
|
+
cs:language "tsx" ;
|
|
492
|
+
cs:code """
|
|
458
493
|
// Bad: Base class "ds badge" is hardcoded.
|
|
459
494
|
<span className={["ds badge", severity, className].filter(Boolean).join(" ")}>
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
495
|
+
"""
|
|
496
|
+
] ;
|
|
497
|
+
cs:dont [
|
|
498
|
+
cs:description "Place the consumer `className` prop before other classes." ;
|
|
499
|
+
cs:language "tsx" ;
|
|
500
|
+
cs:code """
|
|
464
501
|
// Bad: Consumer class is first
|
|
465
502
|
<span className={[className, componentCssClassName, severity].filter(Boolean).join(" ")}>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
503
|
+
"""
|
|
504
|
+
] ;
|
|
505
|
+
cs:dont [
|
|
506
|
+
cs:description "Use string concatenation or template literals to add class names." ;
|
|
507
|
+
cs:language "tsx" ;
|
|
508
|
+
cs:code """
|
|
470
509
|
// Bad: Harder to read and maintain, vulnerable to inconsistent formatting
|
|
471
510
|
<span className={`${componentCssClassName} ${severity} ${className}`}>
|
|
472
|
-
|
|
473
|
-
|
|
511
|
+
"""
|
|
512
|
+
] .
|
|
474
513
|
|
|
475
514
|
# Wrapper Component Props Standard
|
|
476
515
|
cs:WrapperComponentProps a cs:CodeStandard ;
|
|
@@ -478,9 +517,10 @@ cs:WrapperComponentProps a cs:CodeStandard ;
|
|
|
478
517
|
cs:hasCategory cs:ReactCategory ;
|
|
479
518
|
cs:extends cs:ComponentProps ;
|
|
480
519
|
cs:description "Wrapper components must use namespaced props for inner components and accept unscoped props for the wrapper element." ;
|
|
481
|
-
cs:
|
|
482
|
-
|
|
483
|
-
|
|
520
|
+
cs:do [
|
|
521
|
+
cs:description "Use namespaced props for inner components and unscoped props for the wrapper element." ;
|
|
522
|
+
cs:language "tsx" ;
|
|
523
|
+
cs:code """
|
|
484
524
|
interface ThumbnailSectionProps extends SectionProps {
|
|
485
525
|
/** Props for the thumbnail image */
|
|
486
526
|
imageProps: Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt"> & {
|
|
@@ -497,11 +537,12 @@ const ThumbnailSection = ({
|
|
|
497
537
|
<img {...imageProps} />
|
|
498
538
|
</Section>
|
|
499
539
|
);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
cs:
|
|
503
|
-
|
|
504
|
-
|
|
540
|
+
"""
|
|
541
|
+
] ;
|
|
542
|
+
cs:dont [
|
|
543
|
+
cs:description "Mix prop scopes between wrapper and inner components." ;
|
|
544
|
+
cs:language "tsx" ;
|
|
545
|
+
cs:code """
|
|
505
546
|
interface ThumbnailSectionProps {
|
|
506
547
|
src: string; // Bad: Unscoped image props
|
|
507
548
|
alt: string; // Bad: Unscoped image props
|
|
@@ -513,8 +554,8 @@ const ThumbnailSection = ({ src, alt, width, ...props }: ThumbnailSectionProps)
|
|
|
513
554
|
<img src={src} alt={alt} width={width} />
|
|
514
555
|
</Section>
|
|
515
556
|
);
|
|
516
|
-
|
|
517
|
-
|
|
557
|
+
"""
|
|
558
|
+
] .
|
|
518
559
|
|
|
519
560
|
# Custom Hooks Standard
|
|
520
561
|
cs:CustomHooks a cs:CodeStandard ;
|
|
@@ -528,17 +569,21 @@ cs:CustomHooks a cs:CodeStandard ;
|
|
|
528
569
|
|
|
529
570
|
All of the types for a domain level's hooks must be defined in the `hooks/types.ts` file of that folder.
|
|
530
571
|
Each hook must define a [HookName]Props and [HookName]Result type in `hooks/types.ts`.""" ;
|
|
531
|
-
cs:
|
|
532
|
-
|
|
533
|
-
|
|
572
|
+
cs:do [
|
|
573
|
+
cs:description "Create a custom hook that focuses on a single concern within the domain." ;
|
|
574
|
+
cs:language "typescript" ;
|
|
575
|
+
cs:code """
|
|
534
576
|
// [MyComponent]/hooks/useWindowFitment.ts
|
|
535
577
|
const useWindowFitment = ({
|
|
536
578
|
onBestPositionChange,
|
|
537
579
|
autoFit = false,
|
|
538
580
|
}: UseWindowFitmentProps): UseWindowFitmentResult => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
581
|
+
"""
|
|
582
|
+
] ;
|
|
583
|
+
cs:do [
|
|
584
|
+
cs:description "Create hook types in `hooks/types.ts`." ;
|
|
585
|
+
cs:language "typescript" ;
|
|
586
|
+
cs:code """
|
|
542
587
|
// [MyComponent]/hooks/types.ts
|
|
543
588
|
export interface UseWindowFitmentProps {
|
|
544
589
|
/**
|
|
@@ -571,21 +616,23 @@ export interface UseWindowFitmentResult {
|
|
|
571
616
|
*/
|
|
572
617
|
popupPositionStyle: CSSProperties;
|
|
573
618
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
619
|
+
"""
|
|
620
|
+
] ;
|
|
621
|
+
cs:dont [
|
|
622
|
+
cs:description "Create a custom hook for simple, non-reusable state." ;
|
|
623
|
+
cs:language "tsx" ;
|
|
624
|
+
cs:code """
|
|
580
625
|
// Bad: Unnecessary abstraction for a simple counter
|
|
581
626
|
const useCounter = () => {
|
|
582
627
|
const [count, setCount] = useState(0);
|
|
583
628
|
return { count, setCount };
|
|
584
629
|
};
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
630
|
+
"""
|
|
631
|
+
] ;
|
|
632
|
+
cs:dont [
|
|
633
|
+
cs:description "Mix multiple concerns in a single hook" ;
|
|
634
|
+
cs:language "typescript" ;
|
|
635
|
+
cs:code """
|
|
589
636
|
// Bad: Multiple concerns in one hook
|
|
590
637
|
const useUserData = () => {
|
|
591
638
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
@@ -594,8 +641,8 @@ const useUserData = () => {
|
|
|
594
641
|
const [notifications, setNotifications] = useState([]);
|
|
595
642
|
return { isAuthenticated, profile, settings, notifications };
|
|
596
643
|
};
|
|
597
|
-
|
|
598
|
-
|
|
644
|
+
"""
|
|
645
|
+
] .
|
|
599
646
|
|
|
600
647
|
# Context Provider Structure Standard
|
|
601
648
|
cs:ContextProviderStructure a cs:CodeStandard ;
|
|
@@ -603,9 +650,9 @@ cs:ContextProviderStructure a cs:CodeStandard ;
|
|
|
603
650
|
cs:extends cs:ComponentFolderStructure ;
|
|
604
651
|
cs:hasCategory cs:ReactCategory ;
|
|
605
652
|
cs:description "Context providers must use `Provider.tsx` as the main component file instead of the standard component naming pattern `[MyComponent].tsx`. Provider prop and return types must be defined in the hook-level types file `hooks/types.ts` with `UseProviderStateProps` (containing all `ProviderProps` minus `children`) and `UseProviderStateResult` (matching the context options)." ;
|
|
606
|
-
cs:
|
|
607
|
-
|
|
608
|
-
|
|
653
|
+
cs:do [
|
|
654
|
+
cs:description "Organize provider-related files by separating concerns into distinct files: place the context definition in `Context.tsx`, the provider implementation in `Provider.tsx`, and provider-specific hooks in a `hooks/` directory. Create a `hooks/useProviderState.ts` file to centrally manage the state of the provider.`" ;
|
|
655
|
+
cs:code """
|
|
609
656
|
[MyComponent]/
|
|
610
657
|
├── Context.tsx
|
|
611
658
|
├── Provider.tsx
|
|
@@ -620,10 +667,12 @@ cs:ContextProviderStructure a cs:CodeStandard ;
|
|
|
620
667
|
├── index.ts
|
|
621
668
|
├── types.ts
|
|
622
669
|
└── useProviderState.ts
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
670
|
+
"""
|
|
671
|
+
] ;
|
|
672
|
+
cs:do [
|
|
673
|
+
cs:description "Create the context type within `types.ts`." ;
|
|
674
|
+
cs:language "typescript" ;
|
|
675
|
+
cs:code """
|
|
627
676
|
// [MyComponent]/types.ts
|
|
628
677
|
/** The value of the config context */
|
|
629
678
|
export interface ContextOptions {
|
|
@@ -632,11 +681,12 @@ export interface ContextOptions {
|
|
|
632
681
|
/** Toggles the baseline grid's visibility. */
|
|
633
682
|
toggleShowBaselineGrid: () => void;
|
|
634
683
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
684
|
+
"""
|
|
685
|
+
] ;
|
|
686
|
+
cs:do [
|
|
687
|
+
cs:description "create the provider props type within `types.ts`, accepting `children` at a minimum and more props as needed." ;
|
|
688
|
+
cs:language "typescript" ;
|
|
689
|
+
cs:code """
|
|
640
690
|
// [MyComponent]/types.ts
|
|
641
691
|
|
|
642
692
|
export interface ProviderProps {
|
|
@@ -644,10 +694,12 @@ export interface ProviderProps {
|
|
|
644
694
|
children: React.ReactNode;
|
|
645
695
|
// ...other props...
|
|
646
696
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
697
|
+
"""
|
|
698
|
+
] ;
|
|
699
|
+
cs:do [
|
|
700
|
+
cs:description "Create a `Context.tsx` file for the context definition." ;
|
|
701
|
+
cs:language "tsx" ;
|
|
702
|
+
cs:code """
|
|
651
703
|
// [MyComponent]/Context.tsx
|
|
652
704
|
import { createContext } from "react";
|
|
653
705
|
import type { ContextOptions } from "./types.js";
|
|
@@ -655,11 +707,12 @@ import type { ContextOptions } from "./types.js";
|
|
|
655
707
|
const Context = createContext<ContextOptions | undefined>(undefined);
|
|
656
708
|
|
|
657
709
|
export default Context;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
710
|
+
"""
|
|
711
|
+
] ;
|
|
712
|
+
cs:do [
|
|
713
|
+
cs:description "Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context." ;
|
|
714
|
+
cs:language "tsx" ;
|
|
715
|
+
cs:code """
|
|
663
716
|
// [MyComponent]/Provider.tsx
|
|
664
717
|
import Context from "./Context.js";
|
|
665
718
|
import { useProviderState } from "./hooks/useProviderState.js";
|
|
@@ -671,10 +724,12 @@ const Provider = ({ children }: ProviderProps) => {
|
|
|
671
724
|
};
|
|
672
725
|
|
|
673
726
|
export default Provider;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
727
|
+
"""
|
|
728
|
+
] ;
|
|
729
|
+
cs:do [
|
|
730
|
+
cs:description "Use `index.ts` to export the `Provider` as a named component that matches the folder name, casting it to the component type." ;
|
|
731
|
+
cs:language "typescript" ;
|
|
732
|
+
cs:code """
|
|
678
733
|
// [MyComponent]/types.ts
|
|
679
734
|
import type { ReactElement } from "react";
|
|
680
735
|
|
|
@@ -688,10 +743,12 @@ import type { [MyComponent] } from "./types.js";
|
|
|
688
743
|
|
|
689
744
|
export const [MyComponent] = Provider as [MyComponent]Component;
|
|
690
745
|
export default [MyComponent];
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
746
|
+
"""
|
|
747
|
+
] ;
|
|
748
|
+
cs:do [
|
|
749
|
+
cs:description "Create provider state hook types in `hooks/types.ts` for context providers." ;
|
|
750
|
+
cs:language "typescript" ;
|
|
751
|
+
cs:code """
|
|
695
752
|
// [MyComponent]/hooks/types.ts
|
|
696
753
|
import type { ContextOptions, ProviderProps } from "../types.js";
|
|
697
754
|
|
|
@@ -700,10 +757,12 @@ export type UseProviderStateProps = Omit<ProviderProps, "children">;
|
|
|
700
757
|
|
|
701
758
|
// The result of the provider state hook. This should match the context options defined in the main component types file.
|
|
702
759
|
export type UseProviderStateResult = ContextOptions;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
760
|
+
"""
|
|
761
|
+
] ;
|
|
762
|
+
cs:do [
|
|
763
|
+
cs:description "Create a provider state hook implementation." ;
|
|
764
|
+
cs:language "tsx" ;
|
|
765
|
+
cs:code """
|
|
707
766
|
// [MyComponent]/hooks/useProviderState.ts
|
|
708
767
|
import { useContext } from "react";
|
|
709
768
|
import type { UseProviderStateProps, UseProviderStateResult } from "./types.js";
|
|
@@ -716,37 +775,40 @@ const useProviderState = ({
|
|
|
716
775
|
}: UseProviderStateProps): UseProviderStateResult => {
|
|
717
776
|
// centralize the entire provider state here...
|
|
718
777
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
cs:
|
|
722
|
-
|
|
723
|
-
|
|
778
|
+
"""
|
|
779
|
+
] ;
|
|
780
|
+
cs:dont [
|
|
781
|
+
cs:description "Create a separate component file (e.g., `[MyComponent].tsx`) when `Provider.tsx` exists. The provider is the main component." ;
|
|
782
|
+
cs:code """
|
|
724
783
|
[MyComponent]/
|
|
725
784
|
├── [MyComponent].tsx
|
|
726
785
|
└── Provider.tsx
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
786
|
+
"""
|
|
787
|
+
] ;
|
|
788
|
+
cs:dont [
|
|
789
|
+
cs:description "Nest context-related files in a `context/` subfolder." ;
|
|
790
|
+
cs:code """
|
|
731
791
|
[MyComponent]/
|
|
732
792
|
└── context/ # Unnecessary nesting
|
|
733
793
|
├── Context.tsx
|
|
734
794
|
└── Provider.tsx
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
795
|
+
"""
|
|
796
|
+
] ;
|
|
797
|
+
cs:dont [
|
|
798
|
+
cs:description "Create multiple provider files within the same component folder." ;
|
|
799
|
+
cs:code """
|
|
739
800
|
[MyComponent]/
|
|
740
801
|
├── Provider.tsx
|
|
741
802
|
└── AnotherProvider.tsx # Only one provider per component
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
803
|
+
"""
|
|
804
|
+
] ;
|
|
805
|
+
cs:dont [
|
|
806
|
+
cs:description "Mix concerns of the Provider and its state." ;
|
|
807
|
+
cs:language "tsx" ;
|
|
808
|
+
cs:code """
|
|
747
809
|
exort const Provider = ({ children }: ProviderProps) => {
|
|
748
810
|
const [state, setState] = useState(...); // Bad: State logic mixed in
|
|
749
811
|
return <Context.Provider value={{ state, setState }}>{children}</Context.Provider>;
|
|
750
812
|
};
|
|
751
|
-
|
|
752
|
-
|
|
813
|
+
"""
|
|
814
|
+
] .
|