|
| 1 | +# Component Conventions |
| 2 | + |
| 3 | +## Ground Rules |
| 4 | + |
| 5 | +- Always read existing code for the component you're working on before writing new code. For new components, pick a similar one as your template. |
| 6 | +- Inner HTML structure and class names are not public API — don't rely on them. |
| 7 | + |
| 8 | +## Component Structure |
| 9 | + |
| 10 | +Each public component lives in `src/<component-name>/index.tsx` and must: |
| 11 | + |
| 12 | +- Assign default prop values in the destructuring signature (not `defaultProps`), then pass them explicitly to the internal component |
| 13 | +- Call `useBaseComponent` for telemetry — pass `props` (primitive/enum values with defaults applied) and optionally `metadata` (derived counters or booleans). No state props, no PII, no user input strings. |
| 14 | +- Use `getExternalProps` or `getBaseProps` to strip internal `__`-prefixed props |
| 15 | +- Call `applyDisplayName` at the bottom of the file |
| 16 | +- Export the props interface as `${ComponentName}Props` |
| 17 | + |
| 18 | +Each public component has a private counterpart at `src/<component-name>/internal.tsx` for composition. Internal props are prefixed with `__`. The public component must not add behavior beyond what the internal component provides. |
| 19 | + |
| 20 | +Shared hooks, components, contexts, and helpers live in `src/internal/`. Always check there before building new shared code. |
| 21 | + |
| 22 | +## Props & Interfaces |
| 23 | + |
| 24 | +- Props interface: `${ComponentName}Props`, namespace sub-types under it (e.g. `${ComponentName}Props.Variant`) |
| 25 | +- Union types must be type aliases (no inline unions) |
| 26 | +- Array types must use `ReadonlyArray<T>` |
| 27 | +- Cast string props to string at runtime if rendered in JSX — React accepts JSX content there |
| 28 | +- Component return type must be exactly `JSX.Element` — `null` or arrays break the doc generator |
| 29 | +- Components exported as default from `index.tsx` are public; everything else is private |
| 30 | + |
| 31 | +## Events |
| 32 | + |
| 33 | +- Use `CancelableEventHandler<DetailType>` or `NonCancelableEventHandler<DetailType>` from `../internal/events` |
| 34 | +- All `on*` props must use these interfaces (build fails otherwise) |
| 35 | + |
| 36 | +## Refs |
| 37 | + |
| 38 | +- Expose via `${ComponentName}Props.Ref` interface |
| 39 | +- Use `useForwardFocus(ref, elementRef)` for simple focus delegation |
| 40 | +- For `React.forwardRef` generics, create a `${ComponentName}ForwardRefType` interface |
| 41 | + |
| 42 | +## Controllable Components |
| 43 | + |
| 44 | +- Controlled: requires `value` + `onChange` (e.g. Input) |
| 45 | +- Uncontrolled: internal state only (e.g. dropdown open) |
| 46 | +- Controllable: uncontrolled by default, controlled when `value` is set |
| 47 | + |
| 48 | +Use `useControllable` — read existing components for the pattern. |
| 49 | + |
| 50 | +## Element Queries |
| 51 | + |
| 52 | +Use `useContainerBreakpoints` instead of CSS media queries. Handle `null` on first render. |
| 53 | + |
| 54 | +## I18n |
| 55 | + |
| 56 | +Three string categories: |
| 57 | +- Static → `i18nStrings` property |
| 58 | +- Dynamic → functions in `i18nStrings` returning strings |
| 59 | +- Context-dependent → top-level props (out of scope for i18n provider) |
| 60 | + |
| 61 | +Use `useInternalI18n('<component>')` to consume. Test with `TestI18nProvider` from `src/i18n/testing`. |
| 62 | + |
| 63 | +## Dependencies |
| 64 | + |
| 65 | +Before adding any dependency: must support React 16.8+ and latest 3 major Chrome/Firefox/Edge, no global state, ESM preferred, no external resources (CSP). |
0 commit comments