npm package: @resira/ui
Description: React UI components for the Resira booking platform
Latest version: 0.4.1

# @resira/ui v0.3.2

React booking UI for Resira. It includes a ready-to-embed widget, modal flow, provider, hooks, and lower-level components for custom booking experiences.

## Installation

```bash
npm install @resira/ui@0.3.2 @resira/sdk@0.3.1
```

Peer dependencies:

- react >= 18
- react-dom >= 18

## Quick start

```tsx
import { ResiraProvider, ResiraBookingWidget } from "@resira/ui";
import "@resira/ui/styles.css";

export function App() {
  return (
    <ResiraProvider
      apiKey="resira_live_abc123"
      resourceId="prop-1"
      domain="rental"
    >
      <ResiraBookingWidget />
    </ResiraProvider>
  );
}
```

You can also use the higher-level modal component:

```tsx
import { BookingModal } from "@resira/ui";
import "@resira/ui/styles.css";

<BookingModal apiKey="resira_live_abc123" domain="watersport" />;
```

## Supported domains

| Domain | Flow |
| --- | --- |
| `rental` | Calendar -> guest details -> terms -> payment |
| `restaurant` | Time slots -> guest details -> terms -> payment |
| `watersport` | Service -> time -> guest details -> terms -> payment |
| `service` | Service -> time -> guest details -> terms -> payment |

## ResiraProvider

`ResiraProvider` initializes the SDK client, loads public property configuration, resolves theme and locale values, and provides booking state to child components.

```tsx
<ResiraProvider
  apiKey="resira_live_..."
  resourceId="resource-id"
  domain="rental"
  config={{
    theme: {
      primaryColor: "#18181b",
      borderRadius: "16px",
    },
    locale: {
      bookNow: "Book now",
    },
    serviceLayout: "vertical",
    visibleServiceCount: 4,
    groupServicesByCategory: true,
    stripePublishableKey: "pk_live_...",
    showTerms: true,
    showWaiver: false,
    depositPercent: 50,
  }}
>
  <ResiraBookingWidget />
</ResiraProvider>
```

### Provider props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `apiKey` | `string` | Yes | Resira public API key |
| `resourceId` | `string` | No | Default resource or property ID. Omit for catalog mode |
| `domain` | `"rental" \| "restaurant" \| "watersport" \| "service"` | Yes | Booking flow type |
| `config` | `ResiraProviderConfig` | No | Theme, locale, layout, payment, and rendering overrides |
| `onClose` | `() => void` | No | Close callback for modal-driven flows |

### Important config options

| Config field | Type | Description |
| --- | --- | --- |
| `theme` | `ResiraTheme` | Theme token overrides |
| `locale` | `ResiraLocale` | Text and i18n overrides |
| `domainConfig` | `DomainConfig` | Domain-specific rules such as min/max party size or duration |
| `classNames` | `ResiraClassNames` | CSS class overrides |
| `serviceLayout` | `"vertical" \| "horizontal"` | Product selector layout |
| `visibleServiceCount` | `number` | Number of visible services before scrolling |
| `groupServicesByCategory` | `boolean` | Group services by linked equipment type |
| `renderServiceCard` | `(product, selected) => ReactNode` | Custom service card rendering |
| `baseUrl` | `string` | Optional API origin override |
| `baseUrls` | `Array<string \| WeightedBaseUrl>` | Optional ordered fallback origin list |
| `stripePublishableKey` | `string` | Stripe publishable key override |
| `termsText` | `string` | Terms text override |
| `waiverText` | `string` | Waiver text override |
| `showWaiver` | `boolean` | Show waiver checkbox |
| `showTerms` | `boolean` | Show terms checkbox |
| `showRemainingSpots` | `boolean` | Show remaining capacity labels |
| `depositPercent` | `number` | Percent charged upfront |

## Styling and customization

Import the stylesheet once:

```tsx
import "@resira/ui/styles.css";
```

The main customization layers are:

- `theme` tokens on `ResiraProvider`
- CSS custom properties under `.resira-root`
- `classNames` and `renderServiceCard` for deeper control

For more styling details, see [CUSTOMIZATION.md](./CUSTOMIZATION.md).

## Payment and remote config

The provider loads public configuration such as:

- Stripe publishable key
- deposit percentage
- terms requirements
- refund policy

You can override those values locally through `config` when needed.

## Hooks and components

Main exports:

- `ResiraProvider`
- `useResira`
- `ResiraBookingWidget`
- `BookingModal`
- `BookingCalendar`
- `TimeSlotPicker`
- `ResourcePicker`
- `ProductSelector`
- `GuestForm`
- `validateGuestForm`
- `WaiverConsent`
- `PaymentForm`
- `SummaryPreview`
- `ConfirmationView`
- `useAvailability`
- `useReservation`
- `useResources`
- `useProducts`
- `usePaymentIntent`
- `DishShowcase`
- `useDish`
- `useDishes`

## DishShowcase

The `DishShowcase` component renders a trigger button that opens a nested modal for browsing dishes with 3D model previews and AR viewing. It uses `<model-viewer>` for the 3D experience.

### Prerequisites

Load the `<model-viewer>` web component in your page:

```html
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0/model-viewer.min.js"></script>
```

### Single dish (by ID)

The `dishId` is shown on the dish page in the admin dashboard for easy copying.

```tsx
import { DishShowcase } from "@resira/ui";

<DishShowcase dishId="dish-uuid">
  View in 3D
</DishShowcase>
```

### Browse all dishes

```tsx
<DishShowcase showAll>
  Explore Our Menu
</DishShowcase>
```

### Filter by category

```tsx
<DishShowcase showAll category="Main">
  View Main Courses
</DishShowcase>
```

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dishId` | `string` | — | Single dish ID, opens directly to 3D viewer |
| `showAll` | `boolean` | `false` | Show all dishes in a browsable grid |
| `category` | `string` | — | Filter dishes by category |
| `className` | `string` | — | Custom CSS class on the trigger button |
| `style` | `CSSProperties` | — | Custom inline styles on the trigger button |
| `children` | `ReactNode` | required | Button label / content |

### Inside a BookingModal

`DishShowcase` works as a nested modal — it renders above the booking modal with its own overlay and focus trap:

```tsx
<BookingModal apiKey="resira_live_..." domain="restaurant">
  <DishShowcase dishId="dish-uuid" style={{ marginBottom: 12 }}>
    Preview this dish in 3D
  </DishShowcase>
</BookingModal>
```

## Notes

- The widget supports catalog mode when `resourceId` is omitted.
- Service and watersport flows group services by linked equipment category by default.
- Loading and error states now announce themselves to assistive technologies on the main widget surfaces.
- Client-side API origin stickiness/load-balancing has been removed; routing is deterministic unless you pass explicit fallback origins.
- The package is intended for browser usage and ships CSS separately via `@resira/ui/styles.css`.