@josecortez1/c42-react
v0.1.0
Published
React adapter for the headless @josecortez1/c42-core controllers.
Maintainers
Readme
@josecortez1/c42-react
React adapter for @josecortez1/c42-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.
npm install @josecortez1/c42-react @josecortez1/c42-core reactUsage
import { Accordion } from '@josecortez1/c42-react';
// Bring the component's base styles from @josecortez1/c42-core (optional, headless-friendly).
import '@josecortez1/c42-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,defaultValueforAccordion). They map straight to the@josecortez1/c42-coreAccordionOptionstype. - Events as
onXcallbacks — eachdata-c42-*DOM event becomes a callback receiving the unwrappedevent.detail(and the originalCustomEvent).accordion:change→onChange. childrenor default markup — pass your own markup aschildren. If you omit it, the wrapper renders the manifest's skeleton so the component works out of the box.asprop — override the root element/tag (defaults to the documented root, e.g."div"forAccordion,"nav"forNav).- 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:
import { C42, useC42, useC42Events } from '@josecortez1/c42-react';
import { Tabs } from '@josecortez1/c42-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:
node scripts/generate-wrappers.mjs # regenerate src/components/* and barrels
npm run build # vite library build (ESM + d.ts)License
MIT © Laravel42
