@canonical/code-standards 0.1.0 → 0.1.1
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 +20 -19
- package/docs/icons.md +37 -41
- package/docs/index.md +2 -1
- package/docs/packaging.md +643 -0
- package/docs/react.md +54 -58
- 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
|
|
@@ -175,14 +175,14 @@ Components must use PascalCase naming and be descriptive of their purpose.
|
|
|
175
175
|
|
|
176
176
|
### Do
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
Use PascalCase and descriptive names for components:
|
|
179
179
|
UserProfile
|
|
180
180
|
NavigationBar
|
|
181
181
|
SearchResultList
|
|
182
182
|
|
|
183
183
|
### Don't
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
Use non-PascalCase or unclear names:
|
|
186
186
|
userProfile
|
|
187
187
|
navigation_bar
|
|
188
188
|
searchresultlist
|
|
@@ -199,7 +199,7 @@ Component props must:
|
|
|
199
199
|
|
|
200
200
|
### Do
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
Document props with TSDoc comments and use proper destructuring and spreading.
|
|
203
203
|
```typescript
|
|
204
204
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
205
205
|
/** The button's text content */
|
|
@@ -229,7 +229,7 @@ const Button = ({
|
|
|
229
229
|
|
|
230
230
|
### Don't
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
Mix explicit and spread props or destructure props unnecessarily.
|
|
233
233
|
```typescript
|
|
234
234
|
// Bad: Mixing explicit props with spread
|
|
235
235
|
const Button = (props: ButtonProps) => (
|
|
@@ -271,7 +271,7 @@ Components that render HTML markup must extend the base HTML element props inter
|
|
|
271
271
|
|
|
272
272
|
### Do
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
Extend the appropriate React HTML props interface and add component-specific props.
|
|
275
275
|
```typescript
|
|
276
276
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
277
277
|
/** The button label */
|
|
@@ -281,7 +281,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
281
281
|
|
|
282
282
|
### Don't
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
Manually redefine standard HTML attributes that are already available through the base interface.
|
|
285
285
|
```typescript
|
|
286
286
|
export interface ButtonProps {
|
|
287
287
|
/** The button label */
|
|
@@ -299,7 +299,7 @@ Wrapper components must use namespaced props for inner components and accept uns
|
|
|
299
299
|
|
|
300
300
|
### Do
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
Use namespaced props for inner components and unscoped props for the wrapper element.
|
|
303
303
|
```tsx
|
|
304
304
|
interface ThumbnailSectionProps extends SectionProps {
|
|
305
305
|
/** Props for the thumbnail image */
|
|
@@ -321,7 +321,7 @@ const ThumbnailSection = ({
|
|
|
321
321
|
|
|
322
322
|
### Don't
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
Mix prop scopes between wrapper and inner components.
|
|
325
325
|
```tsx
|
|
326
326
|
interface ThumbnailSectionProps {
|
|
327
327
|
src: string; // Bad: Unscoped image props
|
|
@@ -344,7 +344,7 @@ Context providers must use `Provider.tsx` as the main component file instead of
|
|
|
344
344
|
|
|
345
345
|
### Do
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
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
348
|
```
|
|
349
349
|
[MyComponent]/
|
|
350
350
|
├── Context.tsx
|
|
@@ -361,8 +361,8 @@ Context providers must use `Provider.tsx` as the main component file instead of
|
|
|
361
361
|
├── types.ts
|
|
362
362
|
└── useProviderState.ts
|
|
363
363
|
```
|
|
364
|
-
(Do) Create the context type within `types.ts`.
|
|
365
364
|
|
|
365
|
+
Create the context type within `types.ts`.
|
|
366
366
|
```typescript
|
|
367
367
|
// [MyComponent]/types.ts
|
|
368
368
|
/** The value of the config context */
|
|
@@ -374,8 +374,7 @@ export interface ContextOptions {
|
|
|
374
374
|
}
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
create the provider props type within `types.ts`, accepting `children` at a minimum and more props as needed.
|
|
379
378
|
```typescript
|
|
380
379
|
// [MyComponent]/types.ts
|
|
381
380
|
|
|
@@ -386,7 +385,7 @@ export interface ProviderProps {
|
|
|
386
385
|
}
|
|
387
386
|
```
|
|
388
387
|
|
|
389
|
-
|
|
388
|
+
Create a `Context.tsx` file for the context definition.
|
|
390
389
|
```tsx
|
|
391
390
|
// [MyComponent]/Context.tsx
|
|
392
391
|
import { createContext } from "react";
|
|
@@ -397,8 +396,7 @@ const Context = createContext<ContextOptions | undefined>(undefined);
|
|
|
397
396
|
export default Context;
|
|
398
397
|
```
|
|
399
398
|
|
|
400
|
-
|
|
401
|
-
(Do) Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context.
|
|
399
|
+
Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context.
|
|
402
400
|
```tsx
|
|
403
401
|
// [MyComponent]/Provider.tsx
|
|
404
402
|
import Context from "./Context.js";
|
|
@@ -413,7 +411,7 @@ const Provider = ({ children }: ProviderProps) => {
|
|
|
413
411
|
export default Provider;
|
|
414
412
|
```
|
|
415
413
|
|
|
416
|
-
|
|
414
|
+
Use `index.ts` to export the `Provider` as a named component that matches the folder name, casting it to the component type.
|
|
417
415
|
```typescript
|
|
418
416
|
// [MyComponent]/types.ts
|
|
419
417
|
import type { ReactElement } from "react";
|
|
@@ -430,7 +428,7 @@ export const [MyComponent] = Provider as [MyComponent]Component;
|
|
|
430
428
|
export default [MyComponent];
|
|
431
429
|
```
|
|
432
430
|
|
|
433
|
-
|
|
431
|
+
Create provider state hook types in `hooks/types.ts` for context providers.
|
|
434
432
|
```typescript
|
|
435
433
|
// [MyComponent]/hooks/types.ts
|
|
436
434
|
import type { ContextOptions, ProviderProps } from "../types.js";
|
|
@@ -442,7 +440,7 @@ export type UseProviderStateProps = Omit<ProviderProps, "children">;
|
|
|
442
440
|
export type UseProviderStateResult = ContextOptions;
|
|
443
441
|
```
|
|
444
442
|
|
|
445
|
-
|
|
443
|
+
Create a provider state hook implementation.
|
|
446
444
|
```tsx
|
|
447
445
|
// [MyComponent]/hooks/useProviderState.ts
|
|
448
446
|
import { useContext } from "react";
|
|
@@ -460,14 +458,14 @@ const useProviderState = ({
|
|
|
460
458
|
|
|
461
459
|
### Don't
|
|
462
460
|
|
|
463
|
-
|
|
461
|
+
Create a separate component file (e.g., `[MyComponent].tsx`) when `Provider.tsx` exists. The provider is the main component.
|
|
464
462
|
```
|
|
465
463
|
[MyComponent]/
|
|
466
464
|
├── [MyComponent].tsx
|
|
467
465
|
└── Provider.tsx
|
|
468
466
|
```
|
|
469
467
|
|
|
470
|
-
|
|
468
|
+
Nest context-related files in a `context/` subfolder.
|
|
471
469
|
```
|
|
472
470
|
[MyComponent]/
|
|
473
471
|
└── context/ # Unnecessary nesting
|
|
@@ -475,15 +473,14 @@ const useProviderState = ({
|
|
|
475
473
|
└── Provider.tsx
|
|
476
474
|
```
|
|
477
475
|
|
|
478
|
-
|
|
476
|
+
Create multiple provider files within the same component folder.
|
|
479
477
|
```
|
|
480
478
|
[MyComponent]/
|
|
481
479
|
├── Provider.tsx
|
|
482
480
|
└── AnotherProvider.tsx # Only one provider per component
|
|
483
481
|
```
|
|
484
482
|
|
|
485
|
-
|
|
486
|
-
|
|
483
|
+
Mix concerns of the Provider and its state.
|
|
487
484
|
```tsx
|
|
488
485
|
exort const Provider = ({ children }: ProviderProps) => {
|
|
489
486
|
const [state, setState] = useState(...); // Bad: State logic mixed in
|
|
@@ -499,7 +496,7 @@ Each component must reside in its own folder, which contains all related files:
|
|
|
499
496
|
|
|
500
497
|
### Do
|
|
501
498
|
|
|
502
|
-
|
|
499
|
+
Place all component-related files within a single folder named after the component.
|
|
503
500
|
```bash
|
|
504
501
|
[MyComponent]/
|
|
505
502
|
├── [MyComponent].tsx
|
|
@@ -512,7 +509,7 @@ Each component must reside in its own folder, which contains all related files:
|
|
|
512
509
|
|
|
513
510
|
### Don't
|
|
514
511
|
|
|
515
|
-
|
|
512
|
+
Scatter component files across different parts of the application.
|
|
516
513
|
```bash
|
|
517
514
|
# Bad: Files are not co-located
|
|
518
515
|
components/
|
|
@@ -540,7 +537,7 @@ Private subcomponents must remain internal to the component's implementation and
|
|
|
540
537
|
|
|
541
538
|
### Do
|
|
542
539
|
|
|
543
|
-
|
|
540
|
+
Export public subcomponents by attaching them to the parent component using dot notation
|
|
544
541
|
```typescript
|
|
545
542
|
const Item = (props: ItemProps) => { /* ... */ };
|
|
546
543
|
const Accordion = (props: AccordionProps) => { /* ... */ };
|
|
@@ -548,14 +545,14 @@ Accordion.Item = Item;
|
|
|
548
545
|
export default Accordion;
|
|
549
546
|
```
|
|
550
547
|
|
|
551
|
-
|
|
548
|
+
Use semantic, self-descriptive names for subcomponents
|
|
552
549
|
```typescript
|
|
553
550
|
Accordion.Item
|
|
554
551
|
Card.Header
|
|
555
552
|
Card.Footer
|
|
556
553
|
```
|
|
557
554
|
|
|
558
|
-
|
|
555
|
+
Keep subcomponent nesting to a single level
|
|
559
556
|
```typescript
|
|
560
557
|
<Card>
|
|
561
558
|
<Card.Header />
|
|
@@ -565,17 +562,17 @@ Card.Footer
|
|
|
565
562
|
|
|
566
563
|
### Don't
|
|
567
564
|
|
|
568
|
-
|
|
565
|
+
Repeat the parent component name in subcomponent names
|
|
569
566
|
```typescript
|
|
570
567
|
Card.CardHeader = Header; // Bad: Redundant 'Card' prefix
|
|
571
568
|
```
|
|
572
569
|
|
|
573
|
-
|
|
570
|
+
Map a subcomponent to a different name (renaming)
|
|
574
571
|
```typescript
|
|
575
572
|
Card.Top = Header; // Bad: Mapping 'Header' to 'Top' is not allowed
|
|
576
573
|
```
|
|
577
574
|
|
|
578
|
-
|
|
575
|
+
Use non-semantic or unclear subcomponent names
|
|
579
576
|
```
|
|
580
577
|
Card/
|
|
581
578
|
└── common/
|
|
@@ -583,7 +580,7 @@ Card/
|
|
|
583
580
|
├── Element/ # Bad: Too vague, not semantic - what element?
|
|
584
581
|
```
|
|
585
582
|
|
|
586
|
-
|
|
583
|
+
Nest subcomponents more than one level deep
|
|
587
584
|
```typescript
|
|
588
585
|
<Card>
|
|
589
586
|
<Card.Header>
|
|
@@ -592,7 +589,7 @@ Card/
|
|
|
592
589
|
</Card>
|
|
593
590
|
```
|
|
594
591
|
|
|
595
|
-
|
|
592
|
+
Export private subcomponents that are not intended for public use.
|
|
596
593
|
```typescript
|
|
597
594
|
// Bad: Exporting internal-only subcomponents
|
|
598
595
|
export { InternalHelper };
|
|
@@ -606,7 +603,7 @@ Component TSDoc documentation must use the description from the design system on
|
|
|
606
603
|
|
|
607
604
|
### Do
|
|
608
605
|
|
|
609
|
-
|
|
606
|
+
Use the description from the DSL ontology verbatim.
|
|
610
607
|
```typescript
|
|
611
608
|
/**
|
|
612
609
|
* The label component is a compact, non-interactive visual element used to
|
|
@@ -625,7 +622,7 @@ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
|
|
|
625
622
|
|
|
626
623
|
### Don't
|
|
627
624
|
|
|
628
|
-
|
|
625
|
+
Write custom descriptions that deviate from the DSL.
|
|
629
626
|
```typescript
|
|
630
627
|
// Bad: Custom title and paraphrased description
|
|
631
628
|
/**
|
|
@@ -635,21 +632,19 @@ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
|
|
|
635
632
|
*/
|
|
636
633
|
```
|
|
637
634
|
|
|
638
|
-
|
|
635
|
+
Include @example blocks - stories fulfill this role.
|
|
639
636
|
```typescript
|
|
640
637
|
// Bad: Examples belong in stories, not TSDoc
|
|
641
638
|
/**
|
|
642
639
|
* The label component is a compact...
|
|
643
640
|
*
|
|
644
641
|
* @example
|
|
645
|
-
* ```tsx
|
|
646
642
|
* <Label>Default</Label>
|
|
647
643
|
* <Label criticality="warning">Warning</Label>
|
|
648
|
-
* ```
|
|
649
644
|
*/
|
|
650
645
|
```
|
|
651
646
|
|
|
652
|
-
|
|
647
|
+
Omit the @implements tag that links to the DSL.
|
|
653
648
|
```typescript
|
|
654
649
|
// Bad: Missing @implements tag
|
|
655
650
|
/**
|
|
@@ -672,7 +667,7 @@ Each hook must define a [HookName]Props and [HookName]Result type in `hooks/type
|
|
|
672
667
|
|
|
673
668
|
### Do
|
|
674
669
|
|
|
675
|
-
|
|
670
|
+
Create a custom hook that focuses on a single concern within the domain.
|
|
676
671
|
```typescript
|
|
677
672
|
// [MyComponent]/hooks/useWindowFitment.ts
|
|
678
673
|
const useWindowFitment = ({
|
|
@@ -680,7 +675,8 @@ const useWindowFitment = ({
|
|
|
680
675
|
autoFit = false,
|
|
681
676
|
}: UseWindowFitmentProps): UseWindowFitmentResult => {
|
|
682
677
|
```
|
|
683
|
-
|
|
678
|
+
|
|
679
|
+
Create hook types in `hooks/types.ts`.
|
|
684
680
|
```typescript
|
|
685
681
|
// [MyComponent]/hooks/types.ts
|
|
686
682
|
export interface UseWindowFitmentProps {
|
|
@@ -718,7 +714,7 @@ export interface UseWindowFitmentResult {
|
|
|
718
714
|
|
|
719
715
|
### Don't
|
|
720
716
|
|
|
721
|
-
|
|
717
|
+
Create a custom hook for simple, non-reusable state.
|
|
722
718
|
```tsx
|
|
723
719
|
// Bad: Unnecessary abstraction for a simple counter
|
|
724
720
|
const useCounter = () => {
|
|
@@ -727,7 +723,7 @@ const useCounter = () => {
|
|
|
727
723
|
};
|
|
728
724
|
```
|
|
729
725
|
|
|
730
|
-
|
|
726
|
+
Mix multiple concerns in a single hook
|
|
731
727
|
```typescript
|
|
732
728
|
// Bad: Multiple concerns in one hook
|
|
733
729
|
const useUserData = () => {
|
|
@@ -747,7 +743,7 @@ The hook name must start with 'use' and clearly describe its purpose.
|
|
|
747
743
|
|
|
748
744
|
### Do
|
|
749
745
|
|
|
750
|
-
|
|
746
|
+
Name custom hooks so the hook name starts with 'use' and is descriptive
|
|
751
747
|
```typescript
|
|
752
748
|
useWindowSize()
|
|
753
749
|
useAuthentication()
|
|
@@ -756,7 +752,7 @@ useFormValidation()
|
|
|
756
752
|
|
|
757
753
|
### Don't
|
|
758
754
|
|
|
759
|
-
|
|
755
|
+
Name hooks without the 'use' prefix at the start of the hook name
|
|
760
756
|
```typescript
|
|
761
757
|
windowSize()
|
|
762
758
|
getAuth()
|