Saltar al contenido principal

<AvailabilityList>

Flat per-ticket-type picker. Defaults render one <TicketRow> per unique ticket-type ID with the cheapest available price as the "From $X" entry display. Wave concept (pricing tiers) is collapsed internally via useTicketTypes(); attendees never see wave names in the picker.

Selection state lives one level up on <RiftEvent> so the picker and its reserve action can be siblings in any layout (sticky CTA, two-column with persistent summary, separate panels).

Import

import { AvailabilityList } from "@feelrift/react";

Basic usage

import { AvailabilityList, RiftEvent, RiftProvider } from "@feelrift/react";

<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<AvailabilityList />
</RiftEvent>
</RiftProvider>;

Props

Accepts all HTMLAttributes<HTMLDivElement> plus:

NameTypeRequiredDefaultDescription
asChildbooleanNofalseSwap the root element via Radix Slot.
childrenReactNode | ((ticketTypes: readonly AggregatedTicketType[]) => ReactNode)NoReplace the default rendering. Function form receives the aggregated, currently-active ticket-type list.
now() => DateNo() => new Date()Wall-clock source for active-wave filtering. Forwarded to useTicketTypes. Use to freeze time in tests / Storybook.
classNamestringNoAppended to the SDK's rift-availability-list class.
…restHTMLAttributes<HTMLDivElement>NoForwarded to the root element.

children

Three precedence rules apply, highest first:

  1. Function child(ticketTypes: readonly AggregatedTicketType[]) => ReactNode. Receives the aggregated per-ticket-type list (waves already collapsed, currently-active waves only). Use for full layout control without re-implementing the aggregation.
  2. JSX child — composed inside the root wrapper. Use for static header/footer additions around the default list.
  3. No child — the SDK's default flat list of <TicketRow> renders.

asChild composes orthogonally — it swaps the outer wrapper element, not the inner contents.

now

Active-wave filtering depends on the current wall clock. The default (() => new Date()) ticks with real time. Override for test scenarios or Storybook stories where determinism matters:

<AvailabilityList now={() => new Date("2026-07-01T12:00:00Z")} />

Render output

Wraps content in <div class="rift-availability-list"> (or the slot root). Inside:

  • Loading state (isLoading && !data): single <div class="rift-availability-list__loading">…</div>.
  • Error / no data state: single <div class="rift-availability-list__error"> carrying the generic error string from the locale table.
  • Default data state: one <TicketRow> per ticket type inside <div class="rift-availability-list__tickets">. No wave headers, no wave names — the picker speaks ticket types only.

Behavior

  • Aggregated view. Built on useTicketTypes(), which collapses waves to one entry per ticket-type ID and filters by getNow() against each wave's startsAt/endsAt. Cheapest active wave's price becomes the row's "From $X". Stepper clamps at min(maxPerOrder, totalAvailable), where maxPerOrder is the most permissive active cap with available inventory.
  • Selection lives on <RiftEvent>. Reads and writes selection via useAvailabilitySelection(); doesn't provide the context. Any sibling (sticky reserve button, <EstimateBreakdown>, custom cart) reads the same scope. See <RiftEvent> — Event-scoped selection.
  • Selection is ephemeral. Lives in React state on <RiftEvent>, not the persisted store — reservations supersede it.
  • No fetch trigger. Mounting <AvailabilityList> does NOT trigger an explicit refetch; useAvailability() (which useTicketTypes composes) decides when to refetch based on its options (default: mount-time fetch + cache TTL).

Note The wave-tier breakdown surfaces in <EstimateBreakdown> once a quantity is selected. The picker stays flat; the breakdown explains the math.

Examples

Custom rendering via function children

import {
AvailabilityList,
useAvailabilitySelection,
type AggregatedTicketType,
} from "@feelrift/react";

<AvailabilityList>
{(ticketTypes) =>
ticketTypes.map((tt) => <MyTicketRow key={tt.id} ticketType={tt} />)
}
</AvailabilityList>;

function MyTicketRow({ ticketType }: { ticketType: AggregatedTicketType }) {
const { selection, setQuantity } = useAvailabilitySelection();
const quantity = selection.get(ticketType.id) ?? 0;
return (
<div>
<span>{ticketType.name}</span>
<span>From ${ticketType.fromPrice / 100}</span>
<button onClick={() => setQuantity(ticketType.id, quantity + 1)}>
+
</button>
<span>{quantity}</span>
<button
onClick={() => setQuantity(ticketType.id, Math.max(0, quantity - 1))}
>

</button>
</div>
);
}

Slot the root element

<AvailabilityList asChild>
<article className="card" />
</AvailabilityList>

Programmatic selection reset

function ClearButton() {
const { clearSelection } = useAvailabilitySelection();
return <button onClick={clearSelection}>Clear selection</button>;
}
  • <EstimateBreakdown> — pricing breakdown that appears as soon as a quantity is selected.
  • <TicketRow> — the per-ticket-type row the default layout renders.
  • <ReserveButton> — reads the selection context to build the reservation request.
  • useTicketTypes — the aggregated view this component consumes. Public types exported from @feelrift/react.
  • useAvailabilitySelection — hook for reading/writing the selection from custom components. Public types exported from @feelrift/react.
  • Pricing and estimates — the wave-tier model and the algorithm behind the aggregation.