` | `forwardRef` component | `ForwardRefComponent<'button', ButtonProps>` |
| `StyledFC` | Functional component (no ref forwarding) with `StyleProps` | `StyledFC` |
- `E` — Intrinsic element type string (e.g. `'div'`, `'button'`, `'svg'`).
- `P` — Custom component props typedef.
- `R` — Ref element type. Defaults to `HTMLElement`. Only specify when different (e.g. `SVGSVGElement`).
### Decision Tree
```
Is it a forwardRef component?
├─ Yes → ForwardRefComponent<'element', Props>
└─ No → Does it accept StyleProps?
├─ Yes → StyledFC
└─ No → React.FC (plain React type, no utility needed)
```
## Workflow
### Step 1: Add the `@typedef` Block
Add a JSDoc `@typedef` block before the component declaration. Include all custom props with their types and descriptions.
```javascript
/**
* @typedef {Object} ComponentNameProps
* @property {type} [propName] - Description of the property.
* @property {type} [propName=default] - Description with default value.
*/
```
**Property type reference:**
- `{React.ReactNode}` - React children or renderable content
- `{boolean}` - Boolean flags
- `{string}` - String values
- `{number}` - Numeric values
- `{(event: React.MouseEvent) => void}` - Callback with typed signature
- `{'option1' | 'option2'}` - String enums
- `{'sm' | 'md' | 'lg'}` - Size variants
- `{React.ReactNode | React.ReactNode[]}` - Single or array
- `{React.RefObject}` - Ref objects
**Forbidden types** — never use these; always spell out the full shape:
- `{any}`, `{unknown}`, `{function}`, `{Object}`, `{object}`
- `{Record}`, `{{ [key: string]: unknown }}`
### Step 2: Add the `@type` Annotation
Add a `@type` annotation immediately before the component declaration using the appropriate utility type.
**`ForwardRefComponent`** — `forwardRef` + rest props spread onto a native element. The element type (`'button'` below) determines which native HTML props the user can pass:
```javascript
/**
* @type {ForwardRefComponent<'button', ButtonProps>}
*/
const Button = forwardRef((inProps, ref) => {
const { disabled, selected, size, variant, ...rest } = useDefaultProps({ props: inProps, name: 'Button' });
// ...
return (
→ element type is 'button'
{...rest} // ← rest props spread onto native element → ForwardRefComponent
/>
);
});
```
**`StyledFC`** — no `forwardRef`, no ref parameter:
```javascript
/**
* @type {StyledFC}
*/
const Tooltip = (inProps) => { // ← plain function, no ref → StyledFC
const { children, label, placement, ...rest } = useDefaultProps({ props: inProps, name: 'Tooltip' });
// ...
return (
{children}
{label}
);
};
```
If neither utility type fits (e.g. generic type parameters), define the type inline. See `src/tabs/Tabs.js` for an example.
### Step 3: Add Type Test File
Create `packages/react/__type-tests__/components/{component-name}.test-d.tsx`:
```tsx
import React, { createRef } from "react";
import { ComponentName } from "@tonic-ui/react";
// Basic usage
content;
// With custom props
;
// With ref
const ref = createRef();
;
// With style props
;
```
The type tests are compiled with `tsconfig.json` in the `__type-tests__/` directory using `strict: true` and `noEmit: true` — they only need to compile without errors.
**Type test rules:**
1. **Never manually specify types** — all types must be inferred from JSDoc. This verifies JSDoc correctness.
2. **Exception: `createRef()`** — ref type parameters are needed to test ref assignability.
3. **Use `@ts-expect-error` for negative tests** — verify invalid values produce type errors.
## Implementation Rules
1. **Implementation is the source of truth** — trust the code over docs when they disagree.
2. **Use brackets for optional props** — `[propName]` indicates optional.
3. **Document defaults** — `[propName=default]` syntax.
4. **Align with `useDefaultProps`** — verify defaults match.
5. **Callback signatures must be typed** — e.g. `{React.ChangeEventHandler}`, never `{function}`.
6. **Hook return types must be typed** — define a `@typedef` for the return shape.
## Validation
After modifying JSDoc type definitions:
1. **Build**: `yarn build` (generates `.d.ts` from JSDoc)
2. **Type-check**: `yarn test:types` (validates against `.test-d.tsx` files)
## Reference Files
Well-typed components to use as reference:
- `src/link/Link.js` — `ForwardRefComponent<'a', LinkProps>`
- `src/button/Button.js` — `ForwardRefComponent<'button', ButtonProps>`
- `src/checkbox/Checkbox.js` — `ForwardRefComponent<'label', CheckboxProps>` (has prop conflicts, handled automatically)
- `src/accordion/AccordionBody.js` — `ForwardRefComponent<'div', AccordionBodyProps>` (wrapper component)
- `src/tooltip/Tooltip.js` — `StyledFC` (no ref forwarding)
- `src/icon/Icon.js` — `ForwardRefComponent<'svg', IconProps, SVGSVGElement>` (custom ref type)
- `src/tabs/Tabs.js` — Generic component (inline type, not using utility)