@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.
package/docs/react.md ADDED
@@ -0,0 +1,766 @@
1
+ # React Standards
2
+
3
+ Standards for react development.
4
+
5
+ ## react/component/barrel-exports
6
+
7
+ The index.ts file must be a complete barrel export for the component folder, re-exporting all public APIs.
8
+
9
+ ### Do
10
+
11
+ (Do) Create an index.ts file that re-exports all public APIs from the component folder.
12
+ ```typescript
13
+ // index.ts
14
+ export { default as [MyComponent] } from './[MyComponent].js';
15
+ export type * from './types.js';
16
+ // If you have multiple components:
17
+ export { default as SubComponent } from './SubComponent.js';
18
+ ```
19
+
20
+ ### Don't
21
+
22
+ (Don't) Omit the index.ts file or fail to re-export all public APIs.
23
+ ```typescript
24
+ // Bad: index.ts only exports default, omits types and named exports
25
+ export { default } from './[MyComponent].js';
26
+ ```
27
+
28
+ (Don't) Use `export * from './types.js'` as it allows value exports, which is not expected for types files.
29
+ ```typescript
30
+ // Bad: Using export * from './types.js' allows value exports, which is not allowed
31
+ export * from './types.js';
32
+ ```
33
+
34
+ ---
35
+
36
+ ## react/component/class-name-construction
37
+
38
+ Component CSS class names must be constructed following a specific pattern:
39
+
40
+ 1. Base Class Constant: A `componentCssClassName` constant must be defined at the top of the component file. This constant holds the component's base class name, including the global `ds` scope (e.g., `"ds button"`).
41
+ 2. Array Construction: The `className` string must be built from an array of classes.
42
+ 3. Class Order: The classes in the array must be ordered from least to most specific to ensure a predictable CSS cascade:
43
+ a. Base Class: The `componentCssClassName` constant.
44
+ b. Modifier Classes: Classes derived from component props (e.g., `emphasis`, `severity`).
45
+ c. Consumer Classes: The `className` prop passed by the consumer.
46
+ 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.
47
+
48
+ ### Do
49
+
50
+ (Do) Follow the complete pattern for class name construction.
51
+ ```tsx
52
+ const componentCssClassName = "ds badge";
53
+
54
+ const Badge = ({
55
+ value,
56
+ className,
57
+ severity,
58
+ ...props
59
+ }: BadgeProps): React.ReactElement => {
60
+ return (
61
+ <span
62
+ className={[componentCssClassName, severity, className]
63
+ .filter(Boolean)
64
+ .join(" ")}
65
+ {...props}
66
+ >
67
+ {value}
68
+ </span>
69
+ );
70
+ };
71
+ ```
72
+
73
+ ### Don't
74
+
75
+ (Don't) Hardcode the base class name inside the JSX.
76
+ ```tsx
77
+ // Bad: Base class "ds badge" is hardcoded.
78
+ <span className={["ds badge", severity, className].filter(Boolean).join(" ")}>
79
+ ```
80
+
81
+ (Don't) Place the consumer `className` prop before other classes.
82
+ ```tsx
83
+ // Bad: Consumer class is first
84
+ <span className={[className, componentCssClassName, severity].filter(Boolean).join(" ")}>
85
+ ```
86
+
87
+ (Don't) Use string concatenation or template literals to add class names.
88
+ ```tsx
89
+ // Bad: Harder to read and maintain, vulnerable to inconsistent formatting
90
+ <span className={`${componentCssClassName} ${severity} ${className}`}>
91
+ ```
92
+
93
+ ---
94
+
95
+ ## react/component/dependencies
96
+
97
+ Component dependencies must follow a strict unidirectional flow:
98
+ - Subcomponents must be in a `common/` folder within the parent component's directory
99
+ - Dependencies can flow downwards (parent to subcomponent) or sideways (between siblings)
100
+ - Dependencies must not flow upwards (subcomponent to parent)
101
+
102
+ ### Do
103
+
104
+ (Do) Place subcomponents in a `common/` folder inside the parent component directory.
105
+ ```
106
+ Card/
107
+ ├── Card.tsx
108
+ ├── common/
109
+ │ ├── Header/
110
+ │ │ └── Header.tsx
111
+ │ ├── Footer/
112
+ │ │ └── Footer.tsx
113
+ │ └── utils/
114
+ │ └── helpers.ts
115
+ └── index.ts
116
+ ```
117
+
118
+ (Do) Allow subcomponents to depend on siblings or shared utilities within the same component scope.
119
+ ```typescript
120
+ // Header.tsx can import from utils/
121
+ import { helper } from '../utils/helpers.js';
122
+
123
+ // Footer.tsx can import from Header.tsx
124
+ import Header from '../Header.js';
125
+ ```
126
+
127
+ ### Don't
128
+
129
+ (Don't) Create dependencies that flow upwards from a subcomponent to its parent.
130
+ ```typescript
131
+ // Bad: Header.tsx in Card/common/ should not import from Card.tsx
132
+ import Card from '../../Card.js';
133
+ ```
134
+
135
+ (Don't) Allow external components to depend on the internal structure of another component.
136
+ ```typescript
137
+ // Bad: AnotherComponent should not import from Card's internal common folder
138
+ import Header from '../Card/common/Header.js';
139
+ ```
140
+
141
+ ---
142
+
143
+ ## react/component/file-naming
144
+
145
+ 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.
146
+
147
+ ### Do
148
+
149
+ (Do) Prefix component-specific files and use generic names for domain-level files.
150
+ ```
151
+ [MyComponent]/
152
+ ├── [MyComponent].tsx # Component-specific
153
+ ├── [MyComponent].stories.tsx # Component-specific
154
+ ├── [MyComponent].test.tsx # Component-specific
155
+ ├── Context.tsx # Domain-level
156
+ ├── types.ts # Domain-level
157
+ └── styles.css # Domain-level
158
+ ```
159
+
160
+ ### Don't
161
+
162
+ (Don't) Add redundant prefixes to domain-level files.
163
+ ```
164
+ [MyComponent]/
165
+ ├── [MyComponent].Context.tsx # Bad: Redundant prefix
166
+ ├── [MyComponent].types.ts # Bad: Redundant prefix
167
+ └── [MyComponent].styles.css # Bad: Redundant prefix
168
+ ```
169
+
170
+ ---
171
+
172
+ ## react/component/naming
173
+
174
+ Components must use PascalCase naming and be descriptive of their purpose.
175
+
176
+ ### Do
177
+
178
+ (Do) Use PascalCase and descriptive names for components:
179
+ UserProfile
180
+ NavigationBar
181
+ SearchResultList
182
+
183
+ ### Don't
184
+
185
+ (Don't) Use non-PascalCase or unclear names:
186
+ userProfile
187
+ navigation_bar
188
+ searchresultlist
189
+
190
+ ---
191
+
192
+ ## react/component/props
193
+
194
+ Component props must:
195
+ - Be documented with TSDoc comments
196
+ - Be destructured when used in markup
197
+ - Be spread to the root element when unused
198
+ - Follow type-specific patterns based on what the component renders
199
+
200
+ ### Do
201
+
202
+ (Do) Document props with TSDoc comments and use proper destructuring and spreading.
203
+ ```typescript
204
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
205
+ /** The button's text content */
206
+ label: string;
207
+ /** Optional icon to display before the label */
208
+ icon?: React.ReactNode;
209
+ /** Visual emphasis of the button */
210
+ emphasis?: ModifierFamily<"emphasis">;
211
+ }
212
+
213
+ const Button = ({
214
+ label,
215
+ icon,
216
+ emphasis,
217
+ className,
218
+ ...props // Spread unused HTML button props
219
+ }: ButtonProps) => (
220
+ <button
221
+ className={[componentCssClassName, emphasis, className].filter(Boolean).join(" ")}
222
+ {...props}
223
+ >
224
+ {icon}
225
+ <span>{label}</span>
226
+ </button>
227
+ );
228
+ ```
229
+
230
+ ### Don't
231
+
232
+ (Don't) Mix explicit and spread props or destructure props unnecessarily.
233
+ ```typescript
234
+ // Bad: Mixing explicit props with spread
235
+ const Button = (props: ButtonProps) => (
236
+ <button
237
+ className={props.className}
238
+ onClick={props.onClick} // Should be in ...props
239
+ {...props} // Now duplicates onClick
240
+ >
241
+ {props.label}
242
+ </button>
243
+ );
244
+
245
+ // Bad: Unnecessarily destructuring HTML props
246
+ const Button = ({
247
+ label,
248
+ className,
249
+ onClick, // Should be in ...props
250
+ onFocus, // Should be in ...props
251
+ disabled, // Should be in ...props
252
+ ...props
253
+ }: ButtonProps) => (
254
+ <button
255
+ className={className}
256
+ onClick={onClick} // Explicit when it could be spread
257
+ onFocus={onFocus} // Explicit when it could be spread
258
+ disabled={disabled} // Explicit when it could be spread
259
+ {...props}
260
+ >
261
+ {label}
262
+ </button>
263
+ );
264
+ ```
265
+
266
+ ---
267
+
268
+ ## react/component/props/html-rendering
269
+
270
+ Components that render HTML markup must extend the base HTML element props interface to enable passing native properties through spreading.
271
+
272
+ ### Do
273
+
274
+ (Do) Extend the appropriate React HTML props interface and add component-specific props.
275
+ ```typescript
276
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
277
+ /** The button label */
278
+ label: string;
279
+ }
280
+ ```
281
+
282
+ ### Don't
283
+
284
+ (Don't) Manually redefine standard HTML attributes that are already available through the base interface.
285
+ ```typescript
286
+ export interface ButtonProps {
287
+ /** The button label */
288
+ label: string;
289
+ onClick?: () => void; // Bad: Duplicates HTML button props
290
+ disabled?: boolean; // Bad: Duplicates HTML button props
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## react/component/props/wrapper
297
+
298
+ Wrapper components must use namespaced props for inner components and accept unscoped props for the wrapper element.
299
+
300
+ ### Do
301
+
302
+ (Do) Use namespaced props for inner components and unscoped props for the wrapper element.
303
+ ```tsx
304
+ interface ThumbnailSectionProps extends SectionProps {
305
+ /** Props for the thumbnail image */
306
+ imageProps: Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt"> & {
307
+ alt: string;
308
+ };
309
+ }
310
+
311
+ const ThumbnailSection = ({
312
+ imageProps,
313
+ className,
314
+ ...props
315
+ }: ThumbnailSectionProps) => (
316
+ <Section className={[componentCssClassName, className].filter(Boolean).join(" ")} {...props}>
317
+ <img {...imageProps} />
318
+ </Section>
319
+ );
320
+ ```
321
+
322
+ ### Don't
323
+
324
+ (Don't) Mix prop scopes between wrapper and inner components.
325
+ ```tsx
326
+ interface ThumbnailSectionProps {
327
+ src: string; // Bad: Unscoped image props
328
+ alt: string; // Bad: Unscoped image props
329
+ width: number; // Bad: Ambiguous - for image or section?
330
+ }
331
+
332
+ const ThumbnailSection = ({ src, alt, width, ...props }: ThumbnailSectionProps) => (
333
+ <Section {...props}>
334
+ <img src={src} alt={alt} width={width} />
335
+ </Section>
336
+ );
337
+ ```
338
+
339
+ ---
340
+
341
+ ## react/component/structure/context
342
+
343
+ 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).
344
+
345
+ ### Do
346
+
347
+ (Do) 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
+ ```
349
+ [MyComponent]/
350
+ ├── Context.tsx
351
+ ├── Provider.tsx
352
+ ├── index.ts
353
+ ├── types.ts
354
+ └── common/
355
+ └─ [SubComponent]/
356
+ ├── index.ts
357
+ ├── [SubComponent].tsx
358
+ └── types.ts
359
+ └── hooks/
360
+ ├── index.ts
361
+ ├── types.ts
362
+ └── useProviderState.ts
363
+ ```
364
+ (Do) Create the context type within `types.ts`.
365
+
366
+ ```typescript
367
+ // [MyComponent]/types.ts
368
+ /** The value of the config context */
369
+ export interface ContextOptions {
370
+ /** Whether the baseline grid should be shown */
371
+ showBaselineGrid: boolean;
372
+ /** Toggles the baseline grid's visibility. */
373
+ toggleShowBaselineGrid: () => void;
374
+ }
375
+ ```
376
+
377
+ (Do) create the provider props type within `types.ts`, accepting `children` at a minimum and more props as needed.
378
+
379
+ ```typescript
380
+ // [MyComponent]/types.ts
381
+
382
+ export interface ProviderProps {
383
+ // The child nodes which will have access to the provider state
384
+ children: React.ReactNode;
385
+ // ...other props...
386
+ }
387
+ ```
388
+
389
+ (Do) Create a `Context.tsx` file for the context definition.
390
+ ```tsx
391
+ // [MyComponent]/Context.tsx
392
+ import { createContext } from "react";
393
+ import type { ContextOptions } from "./types.js";
394
+
395
+ const Context = createContext<ContextOptions | undefined>(undefined);
396
+
397
+ export default Context;
398
+ ```
399
+
400
+
401
+ (Do) Use `Provider.tsx` as the main component file. The provider is responsible for wrapping children with the context.
402
+ ```tsx
403
+ // [MyComponent]/Provider.tsx
404
+ import Context from "./Context.js";
405
+ import { useProviderState } from "./hooks/useProviderState.js";
406
+ import type { ProviderProps } from "./types.js";
407
+
408
+ const Provider = ({ children }: ProviderProps) => {
409
+ const state = useProviderState();
410
+ return <Context.Provider value={state}>{children}</Context.Provider>;
411
+ };
412
+
413
+ export default Provider;
414
+ ```
415
+
416
+ (Do) Use `index.ts` to export the `Provider` as a named component that matches the folder name, casting it to the component type.
417
+ ```typescript
418
+ // [MyComponent]/types.ts
419
+ import type { ReactElement } from "react";
420
+
421
+ export type [MyComponent]Component = ((props: ProviderProps) => ReactElement) & {
422
+ SubComponent: (props: SubComponentProps) => ReactElement | null;
423
+ };
424
+
425
+ // [MyComponent]/index.ts
426
+ import Provider from "./Provider.js";
427
+ import type { [MyComponent] } from "./types.js";
428
+
429
+ export const [MyComponent] = Provider as [MyComponent]Component;
430
+ export default [MyComponent];
431
+ ```
432
+
433
+ (Do) Create provider state hook types in `hooks/types.ts` for context providers.
434
+ ```typescript
435
+ // [MyComponent]/hooks/types.ts
436
+ import type { ContextOptions, ProviderProps } from "../types.js";
437
+
438
+ // The props expected by the provider state hook. The children prop is omitted, as it is not passed to the hook - it is only rendered by the provider component.
439
+ export type UseProviderStateProps = Omit<ProviderProps, "children">;
440
+
441
+ // The result of the provider state hook. This should match the context options defined in the main component types file.
442
+ export type UseProviderStateResult = ContextOptions;
443
+ ```
444
+
445
+ (Do) Create a provider state hook implementation.
446
+ ```tsx
447
+ // [MyComponent]/hooks/useProviderState.ts
448
+ import { useContext } from "react";
449
+ import type { UseProviderStateProps, UseProviderStateResult } from "./types.js";
450
+
451
+ /**
452
+ * Hook to manage the state of the provider
453
+ */
454
+ const useProviderState = ({
455
+ // ..props...
456
+ }: UseProviderStateProps): UseProviderStateResult => {
457
+ // centralize the entire provider state here...
458
+ }
459
+ ```
460
+
461
+ ### Don't
462
+
463
+ (Don't) Create a separate component file (e.g., `[MyComponent].tsx`) when `Provider.tsx` exists. The provider is the main component.
464
+ ```
465
+ [MyComponent]/
466
+ ├── [MyComponent].tsx
467
+ └── Provider.tsx
468
+ ```
469
+
470
+ (Don't) Nest context-related files in a `context/` subfolder.
471
+ ```
472
+ [MyComponent]/
473
+ └── context/ # Unnecessary nesting
474
+ ├── Context.tsx
475
+ └── Provider.tsx
476
+ ```
477
+
478
+ (Don't) Create multiple provider files within the same component folder.
479
+ ```
480
+ [MyComponent]/
481
+ ├── Provider.tsx
482
+ └── AnotherProvider.tsx # Only one provider per component
483
+ ```
484
+
485
+ (Don't) Mix concerns of the Provider and its state.
486
+
487
+ ```tsx
488
+ exort const Provider = ({ children }: ProviderProps) => {
489
+ const [state, setState] = useState(...); // Bad: State logic mixed in
490
+ return <Context.Provider value={{ state, setState }}>{children}</Context.Provider>;
491
+ };
492
+ ```
493
+
494
+ ---
495
+
496
+ ## react/component/structure/folder
497
+
498
+ 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).
499
+
500
+ ### Do
501
+
502
+ (Do) Place all component-related files within a single folder named after the component.
503
+ ```bash
504
+ [MyComponent]/
505
+ ├── [MyComponent].tsx
506
+ ├── [MyComponent].stories.tsx
507
+ ├── [MyComponent].test.tsx
508
+ ├── index.ts
509
+ ├── styles.css
510
+ └── types.ts
511
+ ```
512
+
513
+ ### Don't
514
+
515
+ (Don't) Scatter component files across different parts of the application.
516
+ ```bash
517
+ # Bad: Files are not co-located
518
+ components/
519
+ ├── [MyComponent].tsx
520
+ stories/
521
+ └── [MyComponent].stories.tsx
522
+ styles/
523
+ └── [MyComponent].css
524
+ ```
525
+
526
+ ---
527
+
528
+ ## react/component/subcomponent-export-api
529
+
530
+ Subcomponents are **public** if they are intended to be directly used by the consumer of the parent component to construct a UI.
531
+
532
+ Subcomponents are **private** if they are internal implementation details of the parent component, and are not intended for direct use by the consumer.
533
+
534
+ Public subcomponents must be:
535
+ - Exported by attaching them to the parent component using dot notation.
536
+ - Named semantically.
537
+ - Kept to a single level of nesting.
538
+
539
+ Private subcomponents must remain internal to the component's implementation and not be exported by any file.
540
+
541
+ ### Do
542
+
543
+ (Do) Export public subcomponents by attaching them to the parent component using dot notation:
544
+ ```typescript
545
+ const Item = (props: ItemProps) => { /* ... */ };
546
+ const Accordion = (props: AccordionProps) => { /* ... */ };
547
+ Accordion.Item = Item;
548
+ export default Accordion;
549
+ ```
550
+
551
+ (Do) Use semantic, self-descriptive names for subcomponents:
552
+ ```typescript
553
+ Accordion.Item
554
+ Card.Header
555
+ Card.Footer
556
+ ```
557
+
558
+ (Do) Keep subcomponent nesting to a single level:
559
+ ```typescript
560
+ <Card>
561
+ <Card.Header />
562
+ <Card.Footer />
563
+ </Card>
564
+ ```
565
+
566
+ ### Don't
567
+
568
+ (Don't) Repeat the parent component name in subcomponent names:
569
+ ```typescript
570
+ Card.CardHeader = Header; // Bad: Redundant 'Card' prefix
571
+ ```
572
+
573
+ (Don't) Map a subcomponent to a different name (renaming):
574
+ ```typescript
575
+ Card.Top = Header; // Bad: Mapping 'Header' to 'Top' is not allowed
576
+ ```
577
+
578
+ (Don't) Use non-semantic or unclear subcomponent names:
579
+ ```
580
+ Card/
581
+ └── common/
582
+ ├── Part/ # Bad: Too vague, not semantic - what part?
583
+ ├── Element/ # Bad: Too vague, not semantic - what element?
584
+ ```
585
+
586
+ (Don't) Nest subcomponents more than one level deep:
587
+ ```typescript
588
+ <Card>
589
+ <Card.Header>
590
+ <Card.Header.Title />
591
+ </Card.Header>
592
+ </Card>
593
+ ```
594
+
595
+ (Don't) Export private subcomponents that are not intended for public use.
596
+ ```typescript
597
+ // Bad: Exporting internal-only subcomponents
598
+ export { InternalHelper };
599
+ ```
600
+
601
+ ---
602
+
603
+ ## react/component/tsdoc
604
+
605
+ 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.
606
+
607
+ ### Do
608
+
609
+ (Do) Use the description from the DSL ontology verbatim.
610
+ ```typescript
611
+ /**
612
+ * The label component is a compact, non-interactive visual element used to
613
+ * categorize content or indicate a status. Its primary role is metadata
614
+ * visualization. While it has similar visual properties to the Chip, it is
615
+ * purely informational and does not trigger actions or allow for removal.
616
+ *
617
+ * @implements dso:global.component.label
618
+ */
619
+ const Label = ({ children, criticality, className, ...props }: LabelProps) => (
620
+ <span className={[componentCssClassName, criticality, className].filter(Boolean).join(" ")} {...props}>
621
+ {children}
622
+ </span>
623
+ );
624
+ ```
625
+
626
+ ### Don't
627
+
628
+ (Don't) Write custom descriptions that deviate from the DSL.
629
+ ```typescript
630
+ // Bad: Custom title and paraphrased description
631
+ /**
632
+ * Label component
633
+ *
634
+ * A compact visual element for status indication.
635
+ */
636
+ ```
637
+
638
+ (Don't) Include @example blocks - stories fulfill this role.
639
+ ```typescript
640
+ // Bad: Examples belong in stories, not TSDoc
641
+ /**
642
+ * The label component is a compact...
643
+ *
644
+ * @example
645
+ * ```tsx
646
+ * <Label>Default</Label>
647
+ * <Label criticality="warning">Warning</Label>
648
+ * ```
649
+ */
650
+ ```
651
+
652
+ (Don't) Omit the @implements tag that links to the DSL.
653
+ ```typescript
654
+ // Bad: Missing @implements tag
655
+ /**
656
+ * The label component is a compact...
657
+ */
658
+ ```
659
+
660
+ ---
661
+
662
+ ## react/hooks/custom
663
+
664
+ Custom hooks must separate concerns at the domain level. All hooks within a ComponentDomain/hooks directory are considered within the domain scope and should focus on a single concern. Custom hooks must be used when:
665
+ - Logic needs to be shared between components
666
+ - Component logic becomes complex
667
+ - State management needs to be abstracted
668
+ - Side effects need to be encapsulated
669
+
670
+ All of the types for a domain level's hooks must be defined in the `hooks/types.ts` file of that folder.
671
+ Each hook must define a [HookName]Props and [HookName]Result type in `hooks/types.ts`.
672
+
673
+ ### Do
674
+
675
+ (Do) Create a custom hook that focuses on a single concern within the domain.
676
+ ```typescript
677
+ // [MyComponent]/hooks/useWindowFitment.ts
678
+ const useWindowFitment = ({
679
+ onBestPositionChange,
680
+ autoFit = false,
681
+ }: UseWindowFitmentProps): UseWindowFitmentResult => {
682
+ ```
683
+ (Do) Create hook types in `hooks/types.ts`.
684
+ ```typescript
685
+ // [MyComponent]/hooks/types.ts
686
+ export interface UseWindowFitmentProps {
687
+ /**
688
+ * Whether the popup should automatically fit into the viewport.
689
+ * If true, the hook will try to fit the popup into the viewport if it doesn't fit in the preferred directions.
690
+ * Defaults to false.
691
+ */
692
+ autoFit?: boolean;
693
+ /**
694
+ * An optional callback to be called when the best position of the popup changes.
695
+ */
696
+ onBestPositionChange?: (bestPosition?: BestPosition) => void;
697
+ }
698
+
699
+ export interface UseWindowFitmentResult {
700
+ /**
701
+ * A ref to be attached to the target element.
702
+ */
703
+ targetRef: RefObject<HTMLDivElement | null>;
704
+ /**
705
+ * A ref to be attached to the popup element.
706
+ */
707
+ popupRef: RefObject<HTMLDivElement | null>;
708
+ /**
709
+ * The calculated best possible position of the popup element.
710
+ */
711
+ bestPosition?: BestPosition;
712
+ /**
713
+ * The style object to be applied to the popup element.
714
+ */
715
+ popupPositionStyle: CSSProperties;
716
+ }
717
+ ```
718
+
719
+ ### Don't
720
+
721
+ (Don't) Create a custom hook for simple, non-reusable state.
722
+ ```tsx
723
+ // Bad: Unnecessary abstraction for a simple counter
724
+ const useCounter = () => {
725
+ const [count, setCount] = useState(0);
726
+ return { count, setCount };
727
+ };
728
+ ```
729
+
730
+ (Don't) Mix multiple concerns in a single hook
731
+ ```typescript
732
+ // Bad: Multiple concerns in one hook
733
+ const useUserData = () => {
734
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
735
+ const [profile, setProfile] = useState(null);
736
+ const [settings, setSettings] = useState({});
737
+ const [notifications, setNotifications] = useState([]);
738
+ return { isAuthenticated, profile, settings, notifications };
739
+ };
740
+ ```
741
+
742
+ ---
743
+
744
+ ## react/hooks/naming
745
+
746
+ The hook name must start with 'use' and clearly describe its purpose.
747
+
748
+ ### Do
749
+
750
+ (Do) Name custom hooks so the hook name starts with 'use' and is descriptive:
751
+ ```typescript
752
+ useWindowSize()
753
+ useAuthentication()
754
+ useFormValidation()
755
+ ```
756
+
757
+ ### Don't
758
+
759
+ (Don't) Name hooks without the 'use' prefix at the start of the hook name:
760
+ ```typescript
761
+ windowSize()
762
+ getAuth()
763
+ formValidation()
764
+ ```
765
+
766
+ ---