# @42/react

React adapter for [`@42/core`](../core) — the headless, framework-agnostic UI
controllers. Every wrapper renders the documented `data-c42-*` markup and bridges
the matching vanilla-TS controller into React's lifecycle: the controller is
instantiated on mount, re-synced when options change (via `update()` when the
controller supports it, otherwise re-created), and torn down on unmount.

The component wrappers are **generated** from `packages/core/manifest.json` by
`scripts/generate-wrappers.mjs`, so the React surface always tracks the core API.

```bash
npm install @42/react @42/core react
```

## Usage

```tsx
import { Accordion } from '@42/react';
// Bring the component's base styles from @42/core (optional, headless-friendly).
import '@42/core/accordion/style.css';

export function Faq() {
  return (
    <Accordion
      multiple
      defaultValue="shipping"
      onChange={(detail) => console.log('open panels:', detail.value)}
    >
      <div data-c42-accordion-item data-value="shipping">
        <button data-c42-accordion-trigger>Shipping</button>
        <div data-c42-accordion-panel>Ships in 24h.</div>
      </div>
      <div data-c42-accordion-item data-value="returns">
        <button data-c42-accordion-trigger>Returns</button>
        <div data-c42-accordion-panel>30-day return policy.</div>
      </div>
    </Accordion>
  );
}
```

- **Options as props** — every controller option is a typed prop
  (`multiple`, `collapsible`, `defaultValue` for `Accordion`). They map straight
  to the `@42/core` `AccordionOptions` type.
- **Events as `onX` callbacks** — each `data-c42-*` DOM event becomes a callback
  receiving the unwrapped `event.detail` (and the original `CustomEvent`).
  `accordion:change` → `onChange`.
- **`children` or default markup** — pass your own markup as `children`. If you
  omit it, the wrapper renders the manifest's skeleton so the component works
  out of the box.
- **`as` prop** — override the root element/tag (defaults to the documented
  root, e.g. `"div"` for `Accordion`, `"nav"` for `Nav`).
- **Extra props** — anything else (e.g. `id`, `aria-*`, `style`) is forwarded to
  the rendered root element.

## Low-level escape hatches

For controllers without a dedicated wrapper, or for full control, use the
generic primitives:

```tsx
import { C42, useC42, useC42Events } from '@42/react';
import { Tabs } from '@42/core/tabs';

// Declarative:
<C42 controller={Tabs} options={{ activationMode: 'manual' }}
     events={{ 'tabs:change': (d) => console.log(d) }}>
  {/* …data-c42-tabs markup… */}
</C42>;

// Imperative hook:
function MyTabs() {
  const ref = useC42(Tabs, { orientation: 'vertical' }, ['vertical']);
  useC42Events(ref, { 'tabs:change': (detail) => console.log(detail) });
  return <div ref={ref}>{/* …markup… */}</div>;
}
```

### `useC42(ControllerClass, options?, deps?)`

Returns a ref to attach to the controller's root element. On mount it runs
`new ControllerClass(el, options)`; on unmount it calls `destroy()`. When any
value in `deps` changes it re-syncs: it calls `instance.update(options)` when the
controller exposes `update()`, otherwise it destroys and re-creates the instance.
SSR-safe — the DOM is only touched inside `useEffect`.

## Development

The wrappers are generated, not hand-written:

```bash
node scripts/generate-wrappers.mjs   # regenerate src/components/* and barrels
npm run build                        # vite library build (ESM + d.ts)
```

## License

MIT © [Laravel42](https://laravel42.com)
