@mena-ai/booking-widget-react
v1.0.0
Published
React wrapper for the <mena-booking> embeddable booking widget. SSR-safe, maps props to attributes, and re-emits booking events as onCompleted/onError callbacks.
Maintainers
Readme
@mena-ai/booking-widget-react
React wrapper for the <mena-booking>
embeddable booking widget. It maps React props to the element's attributes, re-emits the
widget's booking:completed / booking:error events as onCompleted / onError callbacks,
and is SSR-safe (the widget mounts client-side only).
Install
npm i @mena-ai/booking-widget-reactThe core web component (@mena-ai/booking-widget) is a regular dependency and is installed
transitively — you do not need to add it yourself.
Requires React >=18 (declared as a peer dependency).
Usage
Next.js (App Router)
The widget upgrades to a custom element in the browser, so it lives in a Client Component:
'use client';
import { MenaBooking } from '@mena-ai/booking-widget-react';
export function Booking() {
return (
<MenaBooking
publishableKey="mena_pk_live_…"
locale="pt"
preset="calm"
onCompleted={(d) => console.info('booked', d.bookingReference, d.status)}
onError={(e) => console.warn('booking error', e.scope, e.code)}
/>
);
}Render <Booking /> from any server or client component in your tree — it produces no markup
on the server and upgrades on mount (see SSR below).
Plain React (Vite / CRA)
No 'use client' directive is needed outside Next.js:
import { MenaBooking } from '@mena-ai/booking-widget-react';
export function App() {
return (
<MenaBooking
publishableKey="mena_pk_live_…"
locale="en"
onCompleted={(d) => console.info('booked', d.bookingReference, d.status)}
onError={(e) => console.warn('booking error', e.scope, e.code)}
/>
);
}Props
| Prop | Type | Required | Notes |
| ---------------- | ------------------------------------------ | -------- | --------------------------------------------------------------------------- |
| publishableKey | string | yes | Your mena_pk_… key. Maps to the publishable-key attribute. |
| preset | string | no | calm (default) · clinical · minimal · warm · bold · dark. |
| locale | 'en' \| 'pt' | no | UI language. Defaults to the widget's own default. |
| therapistId | string | no | Lock the flow to a single therapist (therapist-id). |
| serviceId | string | no | Pre-select a service (service-id). |
| apiBase | string | no | Override the API origin (api-base). For self-hosted / testing. |
| className | string | no | Passed through to the host element. |
| style | CSSProperties | no | Passed through to the host element (use for --mb-* tokens — see Theming). |
| id | string | no | Passed through to the host element. |
| ref | Ref<MenaBookingElement> | no | Forwarded to the underlying custom element. |
| onCompleted | (detail: BookingCompletedDetail) => void | no | Fired when a booking succeeds. |
| onError | (detail: BookingErrorDetail) => void | no | Fired on a config or data error. |
Callback detail shapes
type BookingCompletedDetail = {
bookingReference: string;
status: 'pending' | 'scheduled';
};
type BookingErrorDetail = {
scope: 'config' | 'data';
code?: string;
status: number;
};onCompleted / onError are always read from the latest render, so you can pass inline
closures without forcing the widget to re-initialise.
Reactivity after mount
Only locale and preset are reactive after the widget has mounted — changing them
re-themes / re-localises the live widget in place.
Changing publishableKey, therapistId, serviceId, or apiBase does not
restart an already-running flow. To apply a new value, force a fresh element by giving it a
React key derived from those props:
<MenaBooking
key={`${publishableKey}:${therapistId ?? ''}:${serviceId ?? ''}:${apiBase ?? ''}`}
publishableKey={publishableKey}
therapistId={therapistId}
serviceId={serviceId}
apiBase={apiBase}
/>When the key changes, React unmounts the old element and mounts a new one, which boots the
flow with the new configuration.
Theming
Theme via CSS custom properties on the style prop, or via ::part() from your stylesheet:
<MenaBooking
publishableKey="mena_pk_live_…"
style={{ '--mb-primary': '#0a6e5c', '--mb-radius': '12px' } as React.CSSProperties}
/>mena-booking::part(cta) {
text-transform: uppercase;
}The full list of --mb-* tokens (26) and the exported shadow ::part() names live in the
core package README — see
Theming → Tokens
and ::part() styling.
Note: TypeScript's
CSSPropertiesdoesn't allow arbitrary--*keys by default, hence theas React.CSSPropertiescast above when setting custom properties inline.
SSR / client-only mount
<MenaBooking> renders nothing on the server (and on the first hydration render). The
custom element is defined and the host element is created only after the component mounts in
the browser. This keeps SSR/SSG safe — there is no window / customElements / HTMLElement
access during server rendering, and no hydration mismatch — and the widget "upgrades" into
place on mount. A node-environment test (tests/ssr.test.tsx) asserts that
renderToStaticMarkup(<MenaBooking … />) returns '' and never throws.
See also
Underlying web component, attributes, theming tokens, parts, accessibility, and browser
support: @mena-ai/booking-widget.
