Saltar al contenido principal

<EstimateBreakdown>

Hierarchical pre-reservation pricing breakdown. Reads the current ticket-type selection via useSelectionEstimate(), walks each ticket type's active waves cheapest-first, and renders the result as a tree: per ticket type → wave-split lines → subtotal → grand total + always-visible disclaimer.

Layout-neutral by design — ships no position, no sticky/fixed behavior, no opinions about where on the page it lives. Inline panel, sticky drawer, sidebar, modal — all are the consumer's choice.

Import

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

Basic usage

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

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

Props

Accepts all HTMLAttributes<HTMLDivElement> plus:

NameTypeRequiredDefaultDescription
asChildbooleanNofalseSwap the root element via Radix Slot.
childrenReactNode | ((estimate: SelectionEstimate) => ReactNode)NoOverride the default rendering. Function form receives the live SelectionEstimate.
emptyFallbackReactNodeNoLocale string estimate.empty in a divRendered when no quantity is selected. Pass null to render nothing.
now() => DateNo() => new Date()Wall-clock source forwarded to useSelectionEstimate. Use to freeze time in tests / Storybook.
disclaimerReactNode | nullNoLocale string estimate.disclaimerOverride the disclaimer text near the total. Pass null to hide entirely — not recommended.
classNamestringNoAppended to the SDK's rift-estimate-breakdown class.
…restHTMLAttributes<HTMLDivElement>NoForwarded to the root element.

disclaimer

The default text reads:

"We pick the cheapest available tier first. Final price (including any fees) is confirmed when you reserve."

The mention of "any fees" is deliberate — server-side modifiers (platform fees, taxes, discounts applied in the pricing engine) aren't visible in the client-side estimate. They appear in the reservation/checkout response's breakdown.steps. The disclaimer sets that expectation up front so the post-reservation total isn't a surprise.

Override the text when your jurisdiction requires specific phrasing. Hiding it entirely (disclaimer={null}) opts out of the regulatory-friendly default — do so only if you display equivalent copy elsewhere on the page.

now

Active-wave filtering depends on the current wall clock. The default (() => new Date()) ticks with real time. For Storybook scenarios or unit tests, pass a frozen function so the aggregation is deterministic:

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

children (function form)

Receives the live SelectionEstimate and returns custom rendering. Use when the default tree doesn't fit — e.g. a side-by-side comparison table or an inline summary line.

<EstimateBreakdown>
{(estimate) => (
<p>
{estimate.ticketTypes.length} ticket type
{estimate.ticketTypes.length === 1 ? "" : "s"} ·{" "}
<strong>{formatCurrency(estimate.subtotal)}</strong>
</p>
)}
</EstimateBreakdown>

Render output

Default rendering (sketch):

General admission
1× Early bird $80.00
1× Regular $100.00
Subtotal $180.00
VIP
2× Standard $400.00
─────────────────────────────────
Total $580.00

We pick the cheapest available tier first. Final price
(including any fees) is confirmed when you reserve.

The component wraps in <div class="rift-estimate-breakdown"> (or the slot root). The internal tree uses .rift-breakdown-tree__* classes shared with <ReservationSummary> so consumers can style both with one CSS ruleset.

Behavior

  • Data source. Reads useSelectionEstimate(), which composes useTicketTypes() and the event-scoped selection from <RiftEvent>.
  • Empty state. Renders emptyFallback (default: the estimate.empty locale string) when no quantity is selected.
  • Active-wave filtering. useTicketTypes filters waves by getNow() against startsAt/endsAt — expired and not-yet-live waves never contribute to the displayed price.
  • Greedy-fill correctness. When a ticket type's quantity exceeds the cheapest wave's remaining, the breakdown splits: one line per wave consumed, each at its own unitPrice. This mirrors the reservation service's fulfillment.
  • Line-item only. Modifier steps (server-side fees, taxes) aren't included — the disclaimer covers it. Post-reservation, <PriceChangedWarning> surfaces the delta if the authoritative total differs.
  • No layout opinions. No position, no sticky behavior. The consumer's container decides placement.

Examples

Inline panel next to the picker

<div className="grid grid-cols-[1fr_320px] gap-6">
<AvailabilityList />
<aside>
<EstimateBreakdown />
<CaptchaWidget />
<ReserveButton requireCaptcha />
</aside>
</div>

Sticky bottom drawer (consumer-owned layout)

<div className="pb-32">
<AvailabilityList />
</div>
<div className="fixed inset-x-0 bottom-0 bg-surface border-t p-4">
<EstimateBreakdown />
<ReserveButton requireCaptcha />
</div>

The component is unopinionated — the sticky behavior is the consumer's CSS, not the SDK's.

Custom empty state

<EstimateBreakdown
emptyFallback={
<div className="text-muted">Add tickets to see your breakdown.</div>
}
/>

Override disclaimer per jurisdiction

<EstimateBreakdown disclaimer="IVA y comisiones se confirman al pagar." />

Frozen clock in Storybook

<EstimateBreakdown now={() => new Date("2026-07-01T12:00:00Z")} />
  • useSelectionEstimate — the underlying hook; exported from @feelrift/react.
  • useTicketTypes — the aggregated view this builds on.
  • <ReservationSummary> — the post-reservation counterpart; shares the same render primitive so the visual contract matches.
  • Pricing and estimates — walks the wave-tier model, the greedy-fill algorithm, and the disclaimer requirement.