@canonical/code-standards 0.1.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.
@@ -0,0 +1,403 @@
1
+ @prefix cs: <http://pragma.canonical.com/codestandards#> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
+
7
+ # Storybook Category
8
+ cs:StorybookCategory a cs:Category ;
9
+ rdfs:label "Storybook"@en ;
10
+ rdfs:comment "Standards for documenting components using Storybook"@en ;
11
+ cs:slug "storybook" .
12
+
13
+ # Documentation Source Standard
14
+ cs:DocumentationSource a cs:CodeStandard ;
15
+ cs:name "storybook/documentation/source" ;
16
+ cs:hasCategory cs:StorybookCategory ;
17
+ cs:description "Component documentation must primarily come from TSDoc comments on the component signature and props. Storybook docs overrides should only be used when the documentation needs to differ between code and Storybook contexts." ;
18
+ cs:dos """
19
+ (Do) Use TSDoc comments for primary documentation.
20
+ ```typescript
21
+
22
+ // ContextualMenu/types.ts
23
+ export interface ContextualMenuProps extends HTMLAttributes<HTMLDivElement> {
24
+ /** The element that triggers the contextual menu */
25
+ children: ReactElement;
26
+ /** The items to display in the contextual menu */
27
+ items: MenuItem[];
28
+ }
29
+
30
+ // ContextualMenu/ContextualMenu.tsx
31
+ /**
32
+ * A wrapper component that adds a contextual menu to its children.
33
+ * The menu appears when the children are clicked or activated via keyboard.
34
+ */
35
+ const ContextualMenu = ({
36
+ children,
37
+ items,
38
+ }: ContextualMenuProps): ReactElement => {
39
+ // ...
40
+ };
41
+ ```
42
+ """ ;
43
+ cs:donts """
44
+ (Don't) Add documentation in Storybook parameters when TSDoc is sufficient.
45
+ ```typescript
46
+ export const Default: Story = {
47
+ parameters: {
48
+ docs: {
49
+ description: {
50
+ component: "A wrapper component that adds a contextual menu to its children" // Covered in TSDoc
51
+ }
52
+ }
53
+ }
54
+ };
55
+ ```
56
+ """ .
57
+
58
+ # Story Visibility Standard
59
+ cs:StoryVisibility a cs:CodeStandard ;
60
+ cs:name "storybook/story/visibility" ;
61
+ cs:hasCategory cs:StorybookCategory ;
62
+ cs:description "Stories that exist solely for testing or visual coverage but don't represent valid usage patterns must be hidden from documentation and the sidebar using tags." ;
63
+ cs:dos """
64
+ (Do) Hide test-only stories that don't represent valid usage.
65
+ ```typescript
66
+ export const FocusedState: Story = {
67
+ args: {
68
+ isOpen: true,
69
+ children: "Test Content"
70
+ },
71
+ tags: ["!dev", "!autodocs"],
72
+ play: async ({ canvasElement }) => {
73
+ const element = canvasElement.querySelector(".component");
74
+ element.focus();
75
+ }
76
+ };
77
+ ```
78
+ """ ;
79
+ cs:donts """
80
+ (Don't) Show implementation details or test states in documentation.
81
+ ```typescript
82
+ export const InternalTestState: Story = {
83
+ args: {
84
+ _internalProp: true, // Bad: Exposing internal state
85
+ children: "Test"
86
+ },
87
+ parameters: {
88
+ docs: {
89
+ description: { story: "Tests internal state" } // Bad: Implementation detail
90
+ }
91
+ }
92
+ };
93
+ ```
94
+ """ .
95
+
96
+ # Story Format Standard
97
+ cs:StoryFormat a cs:CodeStandard ;
98
+ cs:name "storybook/story/format" ;
99
+ cs:hasCategory cs:StorybookCategory ;
100
+ cs:description """Stories must use one of three formats, each serving specific needs:
101
+ 1. CSF3 (object-based) format must be used for standard component variations where args define the component state.
102
+ 2. Function-based format must be used when the story needs to directly control component rendering or wrap the component with custom elements.
103
+ 3. Template-based format must be used when multiple stories share the same logic but differ in their args, and the template differs from the component markup.""" ;
104
+ cs:dos """
105
+ (Do) Use the format that matches your specific use case.
106
+ ```typescript
107
+ // CSF3: For standard component variations through args
108
+ type Story = StoryObj<typeof meta>;
109
+
110
+ export const Default: Story = {
111
+ args: {
112
+ variant: "primary",
113
+ children: "Click me",
114
+ disabled: false
115
+ }
116
+ };
117
+
118
+ // Function-based: When story needs dynamic rendering logic
119
+ export const WithDynamicChildren = (args: ComponentProps) => {
120
+ const [items] = useState(["Item 1", "Item 2"]);
121
+ return (
122
+ <Component {...args}>
123
+ {items.map((item) => (
124
+ <ListItem key={item}>{item}</ListItem>
125
+ ))}
126
+ </Component>
127
+ );
128
+ };
129
+
130
+ // Template-based: When multiple stories share logic
131
+ const Template: StoryFn<typeof Component> = (args) => (
132
+ <div className="button-container">
133
+ <Label>Button:</Label>
134
+ <Component {...args} />
135
+ </div>
136
+ );
137
+
138
+ export const Primary = Template.bind({});
139
+ Primary.args = { variant: "primary" };
140
+ ```
141
+ """ ;
142
+ cs:donts """
143
+ (Don't) Use a format that doesn't match your use case.
144
+ ```typescript
145
+ // Bad: Using function format for simple args variation
146
+ export const SimpleButton = () => (
147
+ <Component variant="primary" disabled={false}>
148
+ Click me
149
+ </Component>
150
+ );
151
+
152
+ // Bad: Duplicating complex logic without template
153
+ export const First: Story = {
154
+ decorators: [(Story) => (
155
+ <div className="button-container">
156
+ <Label>Button:</Label>
157
+ <Story />
158
+ </div>
159
+ )]
160
+ };
161
+ ```
162
+ """ .
163
+
164
+ # Story Decorator Standard
165
+ cs:StoryDecorator a cs:CodeStandard ;
166
+ cs:name "storybook/story/decorator" ;
167
+ cs:hasCategory cs:StorybookCategory ;
168
+ cs:description "Decorators must be used to wrap stories in common context providers or layout elements. Decorators should generally be defined in `storybook/decorators.tsx` and be imported as `decorators` in the storybook file. Decorators may be defined inline in story files in limited circumstances when it would be difficult to globally define a semantic decorator (e.g., a decorator that provides mock data specific to that component)." ;
169
+ cs:dos """
170
+ (Do) Use decorators for static context or layout wrapping.
171
+ ```typescript
172
+
173
+ // storybook/decorators.tsx
174
+
175
+ export const theme = (Story: StoryFn<typeof Component>) => (
176
+ <ThemeProvider theme="dark">
177
+ <Story />
178
+ </ThemeProvider>
179
+ );
180
+
181
+ export const config = (Story: StoryFn<typeof Component>) => (
182
+ <ConfigProvider locale="en">
183
+ <Story />
184
+ </ConfigProvider>
185
+ );
186
+
187
+ // src/ui/<ComponentName>/<ComponentName>.stories.tsx
188
+
189
+ import * as decorators from "storybook/decorators.js";
190
+ export const WithTheme: Story = {
191
+ decorators: [decorators.theme],
192
+ args: {
193
+ variant: "primary"
194
+ }
195
+ };
196
+
197
+ // Component-level decorator in meta, applies to all stories in this file.
198
+ const meta = {
199
+ decorators: [decorators.config],
200
+ } satisfies Meta<typeof Component>;
201
+
202
+ export default meta;
203
+ ```
204
+ """ ;
205
+ cs:donts """
206
+ (Don't) Use function-based stories for static wrapping that could be done with decorators.
207
+ ```typescript
208
+ // Bad: Using function-based story for static wrapping
209
+ export const WithTheme = (args: ComponentProps) => (
210
+ <ThemeProvider theme="dark">
211
+ <Component {...args} />
212
+ </ThemeProvider>
213
+ );
214
+ ```
215
+ """ .
216
+
217
+ # Story Import Standard
218
+ cs:StoryImport a cs:CodeStandard ;
219
+ cs:name "storybook/story/import" ;
220
+ cs:hasCategory cs:StorybookCategory ;
221
+ cs:description "Stories must import their component as 'Component' to maintain a consistent, generic reference that is decoupled from the specific component name." ;
222
+ cs:dos """
223
+ (Do) Import the component generically as 'Component'.
224
+ ```typescript
225
+ import Component from "./SkipLink.js";
226
+
227
+ const meta = {
228
+ title: "SkipLink",
229
+ component: Component,
230
+ } satisfies Meta<typeof Component>;
231
+
232
+ export const Default: Story = {
233
+ args: {
234
+ children: "Skip to main content"
235
+ }
236
+ };
237
+ ```
238
+ """ ;
239
+ cs:donts """
240
+ (Don't) Import the component using its specific name.
241
+ ```typescript
242
+ import SkipLink from "./SkipLink.js";
243
+
244
+ const meta = {
245
+ title: "SkipLink",
246
+ component: SkipLink, // Bad: Using specific component name
247
+ } satisfies Meta<typeof SkipLink>;
248
+ ```
249
+ """ .
250
+
251
+ # Story Organization Standard
252
+ cs:StoryOrganization a cs:CodeStandard ;
253
+ cs:name "storybook/story/organization" ;
254
+ cs:hasCategory cs:StorybookCategory ;
255
+ cs:description "Stories must be organized into logical groups that demonstrate related features and variations. Each story should focus on a specific use case or feature, with clear naming that indicates what aspect of the component it demonstrates." ;
256
+ cs:dos """
257
+ (Do) Group related features with clear, descriptive names.
258
+ ```typescript
259
+ // Basic usage
260
+ export const Default: Story = {
261
+ args: {
262
+ mainId: "main",
263
+ children: "Skip to main content"
264
+ }
265
+ };
266
+
267
+ // Custom element targeting
268
+ export const CustomMainElement: Story = {
269
+ args: {
270
+ mainId: "my-main-element",
271
+ children: "Skip to main content"
272
+ }
273
+ };
274
+
275
+ // Content customization
276
+ export const CustomText: Story = {
277
+ args: {
278
+ mainId: "main",
279
+ children: "Jump to content"
280
+ }
281
+ };
282
+ ```
283
+ """ ;
284
+ cs:donts """
285
+ (Don't) Use unclear names or mix unrelated features in a single story.
286
+ ```typescript
287
+ // Bad: Unclear what this story demonstrates
288
+ export const Variant1: Story = {
289
+ args: {
290
+ mainId: "custom",
291
+ children: "Skip",
292
+ className: "special",
293
+ onClick: () => {},
294
+ style: { color: "red" }
295
+ }
296
+ };
297
+ ```
298
+ """ .
299
+
300
+ # Story Testing Standard
301
+ cs:StoryTesting a cs:CodeStandard ;
302
+ cs:name "storybook/story/testing" ;
303
+ cs:hasCategory cs:StorybookCategory ;
304
+ cs:description "Stories that test component behavior must use the `play` function to simulate user interactions and verify expected outcomes." ;
305
+ cs:dos """
306
+ (Do) Use play functions to test interactive behavior.
307
+ ```typescript
308
+ export const InteractionTest: Story = {
309
+ args: {
310
+ children: "Click Me",
311
+ onClick: () => {}
312
+ },
313
+ play: async ({ canvasElement, args }) => {
314
+ const button = canvasElement.querySelector("button");
315
+ await userEvent.click(button);
316
+ await expect(args.onClick).toHaveBeenCalled();
317
+ }
318
+ };
319
+ ```
320
+ """ ;
321
+ cs:donts """
322
+ (Don't) Test implementation details or internal state.
323
+ ```typescript
324
+ export const InternalTest: Story = {
325
+ play: async ({ component }) => {
326
+ // Bad: Testing internal implementation
327
+ expect(component._internalState).toBe(true);
328
+ }
329
+ };
330
+ ```
331
+ """ .
332
+
333
+ # Story Documentation Standard
334
+ cs:StoryDocumentation a cs:CodeStandard ;
335
+ cs:name "storybook/story/documentation" ;
336
+ cs:hasCategory cs:StorybookCategory ;
337
+ cs:description "Story documentation must focus on usage patterns and variations, not implementation details. Each story should demonstrate a specific use case or variation." ;
338
+ cs:dos """
339
+ (Do) Document usage patterns and variations.
340
+ ```typescript
341
+ export const WithCustomTrigger: Story = {
342
+ args: {
343
+ trigger: <button>Custom Trigger</button>
344
+ },
345
+ parameters: {
346
+ docs: {
347
+ description: {
348
+ story: "The component accepts a custom trigger element to replace the default button."
349
+ }
350
+ }
351
+ }
352
+ };
353
+ ```
354
+ """ ;
355
+ cs:donts """
356
+ (Don't) Document implementation details or internal behavior.
357
+ ```typescript
358
+ export const Default: Story = {
359
+ parameters: {
360
+ docs: {
361
+ description: {
362
+ story: "Uses React.createPortal internally to render the popup" // Bad: Implementation detail
363
+ }
364
+ }
365
+ }
366
+ };
367
+ ```
368
+ """ .
369
+
370
+ # Story Naming Standard
371
+ cs:StoryNaming a cs:CodeStandard ;
372
+ cs:name "storybook/story/naming" ;
373
+ cs:hasCategory cs:StorybookCategory ;
374
+ cs:description "Story names must be descriptive and follow a consistent pattern. Use PascalCase for story exports and natural language for story titles." ;
375
+ cs:dos """
376
+ (Do) Use clear, descriptive names that indicate the variation.
377
+ ```typescript
378
+ export const WithCustomStyles: Story = {
379
+ args: {
380
+ className: "custom",
381
+ children: "Styled Content"
382
+ },
383
+ parameters: {
384
+ docs: {
385
+ description: {
386
+ story: "Demonstrates custom styling options"
387
+ }
388
+ }
389
+ }
390
+ };
391
+ ```
392
+ """ ;
393
+ cs:donts """
394
+ (Don't) Use technical or implementation-focused names.
395
+ ```typescript
396
+ export const TestCase1: Story = { // Bad: Non-descriptive name
397
+ args: {
398
+ _testFlag: true, // Bad: Implementation detail
399
+ children: "Content"
400
+ }
401
+ };
402
+ ```
403
+ """ .
@@ -0,0 +1,165 @@
1
+ @prefix cs: <http://pragma.canonical.com/codestandards#> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
+
7
+ # Styling Category
8
+ cs:StylingCategory a cs:Category ;
9
+ rdfs:label "Styling"@en ;
10
+ rdfs:comment "Standards for implementing design system styling patterns"@en ;
11
+ cs:slug "styling" .
12
+
13
+ cs:TokenTypes a cs:CodeStandard ;
14
+ cs:name "styling/tokens/types" ;
15
+ cs:hasCategory cs:StylingCategory ;
16
+ cs:description """Design tokens follow a strict hierarchy:
17
+ - Primitive tokens: Raw values that form the foundation of the design system
18
+ - Semantic tokens: Map semantic concepts to primitive token values
19
+ - Component tokens: Map component properties to semantic token values
20
+ """ ;
21
+ cs:dos """
22
+ (Do) Define semantic tokens that map to primitive tokens.
23
+ ```
24
+ {
25
+ "color": {
26
+ "background": {
27
+ "default": {
28
+ "$type": "color",
29
+ "$value": "{color.neutral.100}"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+ """ ;
36
+ cs:donts """
37
+ (Don't) Skip the semantic layer by mapping component tokens directly to primitives.
38
+ ```
39
+ {
40
+ "button": {
41
+ "padding": {
42
+ "vertical": {
43
+ "$type": "dimension",
44
+ "$value": "{spacing.unit}" # Should reference semantic token like {spacing.vertical.medium}
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+ """ .
51
+
52
+ # Design Token Creation Standard
53
+ cs:DesignTokenCreation a cs:CodeStandard ;
54
+ cs:name "styling/tokens/creation" ;
55
+ cs:hasCategory cs:StylingCategory ;
56
+ cs:description """Design tokens must be created for all design decisions in a component. See css/properties/values for how these tokens are used in CSS implementation.""" ;
57
+ cs:dos """
58
+ (Do) Create component tokens that reference semantic tokens for design decisions.
59
+ ```
60
+ {
61
+ "button": {
62
+ "background": {
63
+ "$type": "color",
64
+ "$value": "{color.background.primary}"
65
+ }
66
+ }
67
+ }
68
+ ```
69
+ """ ;
70
+ cs:donts """
71
+ (Don't) Use raw values for design decisions.
72
+ ```
73
+ {
74
+ "button": {
75
+ "background": {
76
+ "$type": "color",
77
+ "$value": "#0066CC" # Should reference semantic token like {color.background.primary}
78
+ }
79
+ }
80
+ }
81
+ ```
82
+ """ .
83
+
84
+ # Theme Definition Standard
85
+ cs:ThemeDefinition a cs:CodeStandard ;
86
+ cs:name "styling/themes/definition" ;
87
+ cs:hasCategory cs:StylingCategory ;
88
+ cs:description """Themes are collections of semantic tokens that provide consistent styling across components. See css/themes/activation for implementation details.""" ;
89
+ cs:dos """
90
+ (Do) Define a theme as a complete set of semantic tokens.
91
+ ```
92
+ {
93
+ "theme": {
94
+ "canonical": {
95
+ "color": {
96
+ "background": {
97
+ "default": {
98
+ "$type": "color",
99
+ "$value": "{color.neutral.100}"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+ """ ;
108
+ cs:donts """
109
+ (Don't) Mix implementation details into theme definitions.
110
+ ```
111
+ {
112
+ "theme": {
113
+ "canonical": {
114
+ "class": "canonical", // Bad: CSS implementation detail
115
+ "container": "div", // Bad: HTML implementation detail
116
+ }
117
+ }
118
+ }
119
+ ```
120
+ """ .
121
+
122
+ # Token Scoping Standard
123
+ cs:TokenScoping a cs:CodeStandard ;
124
+ cs:name "styling/tokens/scoping" ;
125
+ cs:hasCategory cs:StylingCategory ;
126
+ cs:description """Design tokens must be scoped according to their type:
127
+ - Primitive tokens: Global scope (system-wide base values)
128
+ - Semantic tokens: Theme scope (theme-specific bindings to primitive tokens)
129
+ - Component tokens: Component scope (bindings to semantic tokens)
130
+ """ ;
131
+ cs:dos """
132
+ (Do) Define primitive tokens in global scope.
133
+ ```
134
+ {
135
+ "color": {
136
+ "neutral": {
137
+ "100": {
138
+ "$type": "color",
139
+ "$value": "#FFFFFF"
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+ """ ;
146
+ cs:donts """
147
+ (Don't) Define primitive tokens in theme scope.
148
+ ```
149
+ {
150
+ "theme": {
151
+ "canonical": {
152
+ "color": {
153
+ "neutral": {
154
+ "100": {
155
+ "$type": "color",
156
+ "$value": "#FFFFFF" # Should be defined in global scope
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+ """ .
165
+