@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/docs/react.md
CHANGED
|
@@ -8,7 +8,7 @@ The index.ts file must be a complete barrel export for the component folder, re-
|
|
|
8
8
|
|
|
9
9
|
### Do
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Create an index.ts file that re-exports all public APIs from the component folder.
|
|
12
12
|
```typescript
|
|
13
13
|
// index.ts
|
|
14
14
|
export { default as [MyComponent] } from './[MyComponent].js';
|
|
@@ -19,13 +19,13 @@ export { default as SubComponent } from './SubComponent.js';
|
|
|
19
19
|
|
|
20
20
|
### Don't
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Omit the index.ts file or fail to re-export all public APIs.
|
|
23
23
|
```typescript
|
|
24
24
|
// Bad: index.ts only exports default, omits types and named exports
|
|
25
25
|
export { default } from './[MyComponent].js';
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Use `export * from './types.js'` as it allows value exports, which is not expected for types files.
|
|
29
29
|
```typescript
|
|
30
30
|
// Bad: Using export * from './types.js' allows value exports, which is not allowed
|
|
31
31
|
export * from './types.js';
|
|
@@ -47,7 +47,7 @@ Component CSS class names must be constructed following a specific pattern:
|
|
|
47
47
|
|
|
48
48
|
### Do
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Follow the complete pattern for class name construction.
|
|
51
51
|
```tsx
|
|
52
52
|
const componentCssClassName = "ds badge";
|
|
53
53
|
|
|
@@ -72,19 +72,19 @@ const Badge = ({
|
|
|
72
72
|
|
|
73
73
|
### Don't
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Hardcode the base class name inside the JSX.
|
|
76
76
|
```tsx
|
|
77
77
|
// Bad: Base class "ds badge" is hardcoded.
|
|
78
78
|
<span className={["ds badge", severity, className].filter(Boolean).join(" ")}>
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Place the consumer `className` prop before other classes.
|
|
82
82
|
```tsx
|
|
83
83
|
// Bad: Consumer class is first
|
|
84
84
|
<span className={[className, componentCssClassName, severity].filter(Boolean).join(" ")}>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Use string concatenation or template literals to add class names.
|
|
88
88
|
```tsx
|
|
89
89
|
// Bad: Harder to read and maintain, vulnerable to inconsistent formatting
|
|
90
90
|
<span className={`${componentCssClassName} ${severity} ${className}`}>
|
|
@@ -101,7 +101,7 @@ Component dependencies must follow a strict unidirectional flow:
|
|
|
101
101
|
|
|
102
102
|
### Do
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Place subcomponents in a `common/` folder inside the parent component directory.
|
|
105
105
|
```
|
|
106
106
|
Card/
|
|
107
107
|
├── Card.tsx
|
|
@@ -115,7 +115,7 @@ Card/
|
|
|
115
115
|
└── index.ts
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
Allow subcomponents to depend on siblings or shared utilities within the same component scope.
|
|
119
119
|
```typescript
|
|
120
120
|
// Header.tsx can import from utils/
|
|
121
121
|
import { helper } from '../utils/helpers.js';
|
|
@@ -126,13 +126,13 @@ import Header from '../Header.js';
|
|
|
126
126
|
|
|
127
127
|
### Don't
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
Create dependencies that flow upwards from a subcomponent to its parent.
|
|
130
130
|
```typescript
|
|
131
131
|
// Bad: Header.tsx in Card/common/ should not import from Card.tsx
|
|
132
132
|
import Card from '../../Card.js';
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
Allow external components to depend on the internal structure of another component.
|
|
136
136
|
```typescript
|
|
137
137
|
// Bad: AnotherComponent should not import from Card's internal common folder
|
|
138
138
|
import Header from '../Card/common/Header.js';
|
|
@@ -146,7 +146,7 @@ Component folder and file naming is based on scope. Component-specific files (im
|
|
|
146
146
|
|
|
147
147
|
### Do
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
Prefix component-specific files and use generic names for domain-level files.
|
|
150
150
|
```
|
|
151
151
|
[MyComponent]/
|
|
152
152
|
├── [MyComponent].tsx # Component-specific
|
|
@@ -159,7 +159,7 @@ Component folder and file naming is based on scope. Component-specific files (im
|
|
|
159
159
|
|
|
160
160
|
### Don't
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
Add redundant prefixes to domain-level files.
|
|
163
163
|
```
|
|
164
164
|
[MyComponent]/
|
|
165
165
|
├── [MyComponent].Context.tsx # Bad: Redundant prefix
|
|
@@ -171,18 +171,21 @@ Component folder and file naming is based on scope. Component-specific files (im
|
|
|
171
171
|
|
|
172
172
|
## react/component/naming
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
Use PascalCase and descriptive names for components:
|
|
175
|
+
UserProfile
|
|
176
|
+
NavigationBar
|
|
177
|
+
SearchResultList
|
|
175
178
|
|
|
176
179
|
### Do
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
Use PascalCase and descriptive names for components:
|
|
179
182
|
UserProfile
|
|
180
183
|
NavigationBar
|
|
181
184
|
SearchResultList
|
|
182
185
|
|
|
183
186
|
### Don't
|
|
184
187
|
|
|
185
|
-
|
|
188
|
+
Use non-PascalCase or unclear names:
|
|
186
189
|
userProfile
|
|
187
190
|
navigation_bar
|
|
188
191
|
searchresultlist
|
|
@@ -199,7 +202,7 @@ Component props must:
|
|
|
199
202
|
|
|
200
203
|
### Do
|
|
201
204
|
|
|
202
|
-
|
|
205
|
+
Document props with TSDoc comments and use proper destructuring and spreading.
|
|
203
206
|
```typescript
|
|
204
207
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
205
208
|
/** The button's text content */
|
|
@@ -229,7 +232,7 @@ const Button = ({
|
|
|
229
232
|
|
|
230
233
|
### Don't
|
|
231
234
|
|
|
232
|
-
|
|
235
|
+
Mix explicit and spread props or destructure props unnecessarily.
|
|
233
236
|
```typescript
|
|
234
237
|
// Bad: Mixing explicit props with spread
|
|
235
238
|
const Button = (props: ButtonProps) => (
|
|
@@ -271,7 +274,7 @@ Components that render HTML markup must extend the base HTML element props inter
|
|
|
271
274
|
|
|
272
275
|
### Do
|
|
273
276
|
|
|
274
|
-
|
|
277
|
+
Extend the appropriate React HTML props interface and add component-specific props.
|
|
275
278
|
```typescript
|
|
276
279
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
277
280
|
/** The button label */
|
|
@@ -281,7 +284,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
281
284
|
|
|
282
285
|
### Don't
|
|
283
286
|
|
|
284
|
-
|
|
287
|
+
Manually redefine standard HTML attributes that are already available through the base interface.
|
|
285
288
|
```typescript
|
|
286
289
|
export interface ButtonProps {
|
|
287
290
|
/** The button label */
|
|
@@ -299,7 +302,7 @@ Wrapper components must use namespaced props for inner components and accept uns
|
|
|
299
302
|
|
|
300
303
|
### Do
|
|
301
304
|
|
|
302
|
-
|
|
305
|
+
Use namespaced props for inner components and unscoped props for the wrapper element.
|
|
303
306
|
```tsx
|
|
304
307
|
interface ThumbnailSectionProps extends SectionProps {
|
|
305
308
|
/** Props for the thumbnail image */
|
|
@@ -321,7 +324,7 @@ const ThumbnailSection = ({
|
|
|
321
324
|
|
|
322
325
|
### Don't
|
|
323
326
|
|
|
324
|
-
|
|
327
|
+
Mix prop scopes between wrapper and inner components.
|
|
325
328
|
```tsx
|
|
326
329
|
interface ThumbnailSectionProps {
|
|
327
330
|
src: string; // Bad: Unscoped image props
|
|
@@ -344,7 +347,7 @@ Context providers must use `Provider.tsx` as the main component file instead of
|
|
|
344
347
|
|
|
345
348
|
### Do
|
|
346
349
|
|
|
347
|
-
|
|
350
|
+
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.`
|
|
348
351
|
```
|
|
349
352
|
[MyComponent]/
|
|
350
353
|
├── Context.tsx
|
|
@@ -361,8 +364,8 @@ Context providers must use `Provider.tsx` as the main component file instead of
|
|
|
361
364
|
├── types.ts
|
|
362
365
|
└── useProviderState.ts
|
|
363
366
|
```
|
|
364
|
-
(Do) Create the context type within `types.ts`.
|
|
365
367
|
|
|
368
|
+
Create the context type within `types.ts`.
|
|
366
369
|
```typescript
|
|
367
370
|
// [MyComponent]/types.ts
|
|
368
371
|
/** The value of the config context */
|
|
@@ -374,8 +377,7 @@ export interface ContextOptions {
|
|
|
374
377
|
}
|
|
375
378
|
```
|
|
376
379
|
|
|
377
|
-
|
|
378
|
-
|
|
380
|
+
create the provider props type within `types.ts`, accepting `children` at a minimum and more props as needed.
|
|
379
381
|
```typescript
|
|
380
382
|
// [MyComponent]/types.ts
|
|
381
383
|
|
|
@@ -386,7 +388,7 @@ export interface ProviderProps {
|
|
|
386
388
|
}
|
|
387
389
|
```
|
|
388
390
|
|
|
389
|
-
|
|
391
|
+
Create a `Context.tsx` file for the context definition.
|
|
390
392
|
```tsx
|
|
391
393
|
// [MyComponent]/Context.tsx
|
|
392
394
|
import { createContext } from "react";
|
|
@@ -397,8 +399,7 @@ const Context = createContext<ContextOptions | undefined>(undefined);
|
|
|
397
399
|
export default Context;
|
|
398
400
|
```
|
|
399
401
|
|
|
400
|
-
|
|
401
|
-
(Do) Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context.
|
|
402
|
+
Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context.
|
|
402
403
|
```tsx
|
|
403
404
|
// [MyComponent]/Provider.tsx
|
|
404
405
|
import Context from "./Context.js";
|
|
@@ -413,7 +414,7 @@ const Provider = ({ children }: ProviderProps) => {
|
|
|
413
414
|
export default Provider;
|
|
414
415
|
```
|
|
415
416
|
|
|
416
|
-
|
|
417
|
+
Use `index.ts` to export the `Provider` as a named component that matches the folder name, casting it to the component type.
|
|
417
418
|
```typescript
|
|
418
419
|
// [MyComponent]/types.ts
|
|
419
420
|
import type { ReactElement } from "react";
|
|
@@ -430,7 +431,7 @@ export const [MyComponent] = Provider as [MyComponent]Component;
|
|
|
430
431
|
export default [MyComponent];
|
|
431
432
|
```
|
|
432
433
|
|
|
433
|
-
|
|
434
|
+
Create provider state hook types in `hooks/types.ts` for context providers.
|
|
434
435
|
```typescript
|
|
435
436
|
// [MyComponent]/hooks/types.ts
|
|
436
437
|
import type { ContextOptions, ProviderProps } from "../types.js";
|
|
@@ -442,7 +443,7 @@ export type UseProviderStateProps = Omit<ProviderProps, "children">;
|
|
|
442
443
|
export type UseProviderStateResult = ContextOptions;
|
|
443
444
|
```
|
|
444
445
|
|
|
445
|
-
|
|
446
|
+
Create a provider state hook implementation.
|
|
446
447
|
```tsx
|
|
447
448
|
// [MyComponent]/hooks/useProviderState.ts
|
|
448
449
|
import { useContext } from "react";
|
|
@@ -460,14 +461,14 @@ const useProviderState = ({
|
|
|
460
461
|
|
|
461
462
|
### Don't
|
|
462
463
|
|
|
463
|
-
|
|
464
|
+
Create a separate component file (e.g., `[MyComponent].tsx`) when `Provider.tsx` exists. The provider is the main component.
|
|
464
465
|
```
|
|
465
466
|
[MyComponent]/
|
|
466
467
|
├── [MyComponent].tsx
|
|
467
468
|
└── Provider.tsx
|
|
468
469
|
```
|
|
469
470
|
|
|
470
|
-
|
|
471
|
+
Nest context-related files in a `context/` subfolder.
|
|
471
472
|
```
|
|
472
473
|
[MyComponent]/
|
|
473
474
|
└── context/ # Unnecessary nesting
|
|
@@ -475,15 +476,14 @@ const useProviderState = ({
|
|
|
475
476
|
└── Provider.tsx
|
|
476
477
|
```
|
|
477
478
|
|
|
478
|
-
|
|
479
|
+
Create multiple provider files within the same component folder.
|
|
479
480
|
```
|
|
480
481
|
[MyComponent]/
|
|
481
482
|
├── Provider.tsx
|
|
482
483
|
└── AnotherProvider.tsx # Only one provider per component
|
|
483
484
|
```
|
|
484
485
|
|
|
485
|
-
|
|
486
|
-
|
|
486
|
+
Mix concerns of the Provider and its state.
|
|
487
487
|
```tsx
|
|
488
488
|
exort const Provider = ({ children }: ProviderProps) => {
|
|
489
489
|
const [state, setState] = useState(...); // Bad: State logic mixed in
|
|
@@ -499,7 +499,7 @@ Each component must reside in its own folder, which contains all related files:
|
|
|
499
499
|
|
|
500
500
|
### Do
|
|
501
501
|
|
|
502
|
-
|
|
502
|
+
Place all component-related files within a single folder named after the component.
|
|
503
503
|
```bash
|
|
504
504
|
[MyComponent]/
|
|
505
505
|
├── [MyComponent].tsx
|
|
@@ -512,7 +512,7 @@ Each component must reside in its own folder, which contains all related files:
|
|
|
512
512
|
|
|
513
513
|
### Don't
|
|
514
514
|
|
|
515
|
-
|
|
515
|
+
Scatter component files across different parts of the application.
|
|
516
516
|
```bash
|
|
517
517
|
# Bad: Files are not co-located
|
|
518
518
|
components/
|
|
@@ -540,7 +540,7 @@ Private subcomponents must remain internal to the component's implementation and
|
|
|
540
540
|
|
|
541
541
|
### Do
|
|
542
542
|
|
|
543
|
-
|
|
543
|
+
Export public subcomponents by attaching them to the parent component using dot notation
|
|
544
544
|
```typescript
|
|
545
545
|
const Item = (props: ItemProps) => { /* ... */ };
|
|
546
546
|
const Accordion = (props: AccordionProps) => { /* ... */ };
|
|
@@ -548,14 +548,14 @@ Accordion.Item = Item;
|
|
|
548
548
|
export default Accordion;
|
|
549
549
|
```
|
|
550
550
|
|
|
551
|
-
|
|
551
|
+
Use semantic, self-descriptive names for subcomponents
|
|
552
552
|
```typescript
|
|
553
553
|
Accordion.Item
|
|
554
554
|
Card.Header
|
|
555
555
|
Card.Footer
|
|
556
556
|
```
|
|
557
557
|
|
|
558
|
-
|
|
558
|
+
Keep subcomponent nesting to a single level
|
|
559
559
|
```typescript
|
|
560
560
|
<Card>
|
|
561
561
|
<Card.Header />
|
|
@@ -565,17 +565,17 @@ Card.Footer
|
|
|
565
565
|
|
|
566
566
|
### Don't
|
|
567
567
|
|
|
568
|
-
|
|
568
|
+
Repeat the parent component name in subcomponent names
|
|
569
569
|
```typescript
|
|
570
570
|
Card.CardHeader = Header; // Bad: Redundant 'Card' prefix
|
|
571
571
|
```
|
|
572
572
|
|
|
573
|
-
|
|
573
|
+
Map a subcomponent to a different name (renaming)
|
|
574
574
|
```typescript
|
|
575
575
|
Card.Top = Header; // Bad: Mapping 'Header' to 'Top' is not allowed
|
|
576
576
|
```
|
|
577
577
|
|
|
578
|
-
|
|
578
|
+
Use non-semantic or unclear subcomponent names
|
|
579
579
|
```
|
|
580
580
|
Card/
|
|
581
581
|
└── common/
|
|
@@ -583,7 +583,7 @@ Card/
|
|
|
583
583
|
├── Element/ # Bad: Too vague, not semantic - what element?
|
|
584
584
|
```
|
|
585
585
|
|
|
586
|
-
|
|
586
|
+
Nest subcomponents more than one level deep
|
|
587
587
|
```typescript
|
|
588
588
|
<Card>
|
|
589
589
|
<Card.Header>
|
|
@@ -592,7 +592,7 @@ Card/
|
|
|
592
592
|
</Card>
|
|
593
593
|
```
|
|
594
594
|
|
|
595
|
-
|
|
595
|
+
Export private subcomponents that are not intended for public use.
|
|
596
596
|
```typescript
|
|
597
597
|
// Bad: Exporting internal-only subcomponents
|
|
598
598
|
export { InternalHelper };
|
|
@@ -606,7 +606,7 @@ Component TSDoc documentation must use the description from the design system on
|
|
|
606
606
|
|
|
607
607
|
### Do
|
|
608
608
|
|
|
609
|
-
|
|
609
|
+
Use the description from the DSL ontology verbatim.
|
|
610
610
|
```typescript
|
|
611
611
|
/**
|
|
612
612
|
* The label component is a compact, non-interactive visual element used to
|
|
@@ -625,7 +625,7 @@ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
|
|
|
625
625
|
|
|
626
626
|
### Don't
|
|
627
627
|
|
|
628
|
-
|
|
628
|
+
Write custom descriptions that deviate from the DSL.
|
|
629
629
|
```typescript
|
|
630
630
|
// Bad: Custom title and paraphrased description
|
|
631
631
|
/**
|
|
@@ -635,21 +635,19 @@ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
|
|
|
635
635
|
*/
|
|
636
636
|
```
|
|
637
637
|
|
|
638
|
-
|
|
638
|
+
Include @example blocks - stories fulfill this role.
|
|
639
639
|
```typescript
|
|
640
640
|
// Bad: Examples belong in stories, not TSDoc
|
|
641
641
|
/**
|
|
642
642
|
* The label component is a compact...
|
|
643
643
|
*
|
|
644
644
|
* @example
|
|
645
|
-
* ```tsx
|
|
646
645
|
* <Label>Default</Label>
|
|
647
646
|
* <Label criticality="warning">Warning</Label>
|
|
648
|
-
* ```
|
|
649
647
|
*/
|
|
650
648
|
```
|
|
651
649
|
|
|
652
|
-
|
|
650
|
+
Omit the @implements tag that links to the DSL.
|
|
653
651
|
```typescript
|
|
654
652
|
// Bad: Missing @implements tag
|
|
655
653
|
/**
|
|
@@ -672,7 +670,7 @@ Each hook must define a [HookName]Props and [HookName]Result type in `hooks/type
|
|
|
672
670
|
|
|
673
671
|
### Do
|
|
674
672
|
|
|
675
|
-
|
|
673
|
+
Create a custom hook that focuses on a single concern within the domain.
|
|
676
674
|
```typescript
|
|
677
675
|
// [MyComponent]/hooks/useWindowFitment.ts
|
|
678
676
|
const useWindowFitment = ({
|
|
@@ -680,7 +678,8 @@ const useWindowFitment = ({
|
|
|
680
678
|
autoFit = false,
|
|
681
679
|
}: UseWindowFitmentProps): UseWindowFitmentResult => {
|
|
682
680
|
```
|
|
683
|
-
|
|
681
|
+
|
|
682
|
+
Create hook types in `hooks/types.ts`.
|
|
684
683
|
```typescript
|
|
685
684
|
// [MyComponent]/hooks/types.ts
|
|
686
685
|
export interface UseWindowFitmentProps {
|
|
@@ -718,7 +717,7 @@ export interface UseWindowFitmentResult {
|
|
|
718
717
|
|
|
719
718
|
### Don't
|
|
720
719
|
|
|
721
|
-
|
|
720
|
+
Create a custom hook for simple, non-reusable state.
|
|
722
721
|
```tsx
|
|
723
722
|
// Bad: Unnecessary abstraction for a simple counter
|
|
724
723
|
const useCounter = () => {
|
|
@@ -727,7 +726,7 @@ const useCounter = () => {
|
|
|
727
726
|
};
|
|
728
727
|
```
|
|
729
728
|
|
|
730
|
-
|
|
729
|
+
Mix multiple concerns in a single hook
|
|
731
730
|
```typescript
|
|
732
731
|
// Bad: Multiple concerns in one hook
|
|
733
732
|
const useUserData = () => {
|
|
@@ -747,7 +746,7 @@ The hook name must start with 'use' and clearly describe its purpose.
|
|
|
747
746
|
|
|
748
747
|
### Do
|
|
749
748
|
|
|
750
|
-
|
|
749
|
+
Name custom hooks so the hook name starts with 'use' and is descriptive
|
|
751
750
|
```typescript
|
|
752
751
|
useWindowSize()
|
|
753
752
|
useAuthentication()
|
|
@@ -756,7 +755,7 @@ useFormValidation()
|
|
|
756
755
|
|
|
757
756
|
### Don't
|
|
758
757
|
|
|
759
|
-
|
|
758
|
+
Name hooks without the 'use' prefix at the start of the hook name
|
|
760
759
|
```typescript
|
|
761
760
|
windowSize()
|
|
762
761
|
getAuth()
|