- Accordion
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- Checkbox
- Combobox
- Container
- CurrencyInput
- DistributionSlider
- Drawer
- Dropdown
- Grid
- Heading
- Image
- Input
- InputGroup
- Label
- Logo
- MapPin
- Modal
- NativeSelect
- NumberInput
- OtpInput
- PhoneInput
- Popover
- Progress
- PropertyCalendar
- RadioGroup
- RadioGroupCards
- ResponsiveModal
- ScrollArea
- SearchBar
- SearchBarFallback
- SearchInput
- Select
- Separator
- Spinner
- Switch
- Tabs
- Text
- Textarea
- Toast
- Toggle
- ToggleGroup
- Tooltip
PropertyCalendar
A booking-aware date range picker that extends Calendar with per-day availability states, stay requirements, and booking rules.
pnpm add @wandercom/design-system-web
import { PropertyCalendar } from '@wandercom/design-system-web/ui/property-calendar';
import type { AvailabilityDay, DateRange } from '@wandercom/design-system-web/ui/property-calendar';
import { useState } from 'react';
export function Example() {
const [value, setValue] = useState<DateRange>({ start: null, end: null });
return (
<PropertyCalendar
value={value}
onChange={setValue}
availability={availabilityData}
stayRequirements={{ minNights: 3, checkInDays: ['Saturday'] }}
onClose={() => setOpen(false)}
onClearDates={() => setValue({ start: null, end: null })}
/>
);
}Days at the end of a booking where the guest checks out have canCheckOut: true and isBlocked: true simultaneously. These should render as restricted with a "Check-out only" tooltip, not as unavailable with a strikethrough. Hover the last day of each booked block below to confirm.
Each calendar day is evaluated against availability data and the current selection state. Days resolve to one of four visual states:
available -- The day is selectable. Renders as a standard interactive calendar day with text-primary.
restricted -- The day is bookable but constrained for the current selection context. Rendered with text-secondary at full opacity, disabled (not clickable), and shows a tooltip explaining the constraint. Restriction tooltips have two distinct activation contexts:
In browse mode (value.start === null, no dates selected), check-in restriction labels appear immediately on hover:
- Days where check-in is restricted (
canCheckIn: false) -- shows "Check-out only" if the date is reachable as a checkout (bothcanCheckOut: trueand present in the precomputedvalidCheckoutDatesset), otherwise "Check-in unavailable" - Days with no reachable checkout from this date (
validCheckinDatesmiss) -- same tooltip logic as above - Days that don't match
checkInDaysinstayRequirements-- shows "Check-out only" if reachable as a checkout, otherwise "Check-in unavailable"
When a complete range is selected (value.start !== null && value.end !== null), no restriction labels are shown — dates render neutral with no tooltips.
In checkout selection mode (value.start !== null && value.end === null), checkout restriction tooltips are deferred: they do not appear immediately when a check-in date is clicked. They only activate after the user moves the cursor into the calendar section for the first time following check-in selection. Once that first mouse movement occurs, these restrictions appear on hover:
- Days where check-out is restricted (
canCheckOut: false) -- shows "Check-out unavailable" - Days that don't match
checkOutDaysinstayRequirements-- shows "Check-out unavailable" - Days that violate min/max night constraints -- shows constraint tooltip (e.g. "Min 3 nights", "Max 7 nights")
inactive -- The day is structurally disabled. Rendered with muted text, no strikethrough, not clickable. Used for:
- Past dates (before today)
- Dates before a selected check-in (when selecting check-out)
- Days blocked by booking rules such as
same-day-turnover,gap-nights,advance-booking, orquota-exhausted-- these also show a tooltip with the rule message
unavailable -- The day is booked, blocked, or under maintenance. Rendered with reduced opacity and a diagonal strikethrough. No tooltip — the strikethrough is sufficient. Applies to dates with status: "blocked", status: "maintenance", or isBlocked: true (when status !== "booked"). status: "booked" days are exceptions: those with canCheckIn: true render as available in browse mode. Those with canCheckOut: true render as restricted ("Check-out only") in browse mode only if they are reachable — meaning the backward scan from that date finds a valid check-in path within stay constraints, placing the date in the precomputed validCheckoutDates set. Interior booked days that happen to have canCheckOut: true but have no reachable check-in path (surrounded by other booked days) are excluded from that set and continue to render as strikethrough unavailable. When a check-in is selected, any booked+canCheckOut day where the stay interior is unobstructed (no blocked day between check-in and checkout) remains selectable as a checkout endpoint via validateCheckoutDate.
The base calendar content component, used standalone or composed into wrappers.
Wraps PropertyCalendar in a Popover with a paginated 2-month view and prev/next navigation arrows. Used by BookingPanel for desktop viewports.
Wraps PropertyCalendar in a Drawer with 12 months rendered vertically in a scrollable view. Navigation arrows are hidden; users scroll through months instead. Used by BookingPanelMobileBar for mobile viewports.
When both dates are selected and pricePerNight is provided, the calendar header shows the nightly rate.
When pricingLoading is true, a skeleton shimmer replaces the price per night in the header.
When booking rules are violated, the calendar displays an alert with the relevant error message.
interface DateRange {
start: Date | null;
end: Date | null;
}Per-day availability data keyed by date string in YYYY-MM-DD format.
interface AvailabilityDay {
date: string;
canCheckIn: boolean; // when true and status === "booked", overrides isBlocked for check-in validation
canCheckOut: boolean; // when true and status === "booked", overrides isBlocked for checkout validation
isBlocked?: boolean;
minNights?: number;
maxNights?: number;
nightPrice?: number;
status: 'available' | 'booked' | 'maintenance' | 'blocked';
}A booking rule that may restrict date selection. The message field is intended for display to the user.
interface BookingRule {
type:
| 'min-nights'
| 'max-nights'
| 'check-in-day'
| 'check-out-day'
| 'advance-booking'
| 'gap-nights'
| 'same-day-turnover'
| 'quota-exhausted';
message: string;
}Stay requirement constraints displayed as an info banner between the header and calendar.
interface StayRequirements {
minNights?: number;
maxNights?: number;
checkInDays?: string[];
checkOutDays?: string[];
}value
onChange
availability?:
availabilityLoading?:
rules?:
stayRequirements?:
numberOfMonths?:
hideNavigation?:
hideFooter?:
hideClose?:
hideWeekdays?:
fullWidth?:
weekStartsOn?:
minDate?:
maxDate?:
alert?:
info?:
pricePerNight?:
pricingLoading?:
onDatesSelected?:
className?:
onClose?:
onClearDates?:
Extends a subset of PropertyCalendar props plus:
open?:
onOpenChange?:
trigger?:
side?:
sideOffset?:
align?:
alignOffset?:
numberOfMonths?:
info?:
pricePerNight?:
pricingLoading?:
onDatesSelected?:
onClose?:
Extends a subset of PropertyCalendar props plus:
open?:
onOpenChange?:
trigger?:
title?:
description?:
footer?:
saveLabel?:
saveDisabled?:
info?:
onClose?:
PropertyCalendar inherits keyboard navigation from the underlying Calendar component (react-day-picker).
Unavailable days show a diagonal strikethrough with no tooltip. Booked days with canCheckIn: true render as available. Booked days with canCheckOut: true lose the strikethrough and render as restricted ("Check-out only") only when they are reachable — the backward scan must find a valid check-in path within stay constraints, placing the date in validCheckoutDates. Interior booked days with canCheckOut: true that have no such path remain strikethrough unavailable. Restricted days show contextual tooltips explaining the constraint (e.g. "Check-in unavailable", "Min 3 nights") while remaining visible at full opacity. In browse mode (no dates selected), check-in restriction tooltips appear on hover immediately. In checkout selection mode, checkout restriction tooltips are deferred until the user first moves the cursor into the calendar section after selecting a check-in date. When a complete date range is selected, no restriction tooltips are shown. Inactive days (past, maintenance, blocked, and certain booking-rule violations) render muted; those triggered by booking rules also show a tooltip with the rule message. The check-in and check-out input fields include aria-label attributes for screen reader support.