Typed check-in and check-out dates where the minimum number of nights depends on the check-in date, such as a weekend, a holiday week or the high season. Use this when a rental host sets a longer minimum stay for some dates than for others.
registry/patterns/vacation-rental-minimum-stay/vacation-rental-minimum-stay.tsx
"use client"
import * as React from "react"
import { DateRangeField } from "@/components/ui/date-range-field"
import {
nightsBetween,
weekday,
type DateRange,
} from "@/components/ui/date-time-core"
export interface StayRule {
from: string
to: string
nights: number
reason: string
}
export interface VacationRentalMinimumStayProps {
value?: DateRange | null
defaultValue?: DateRange | null
onValueChange?: (stay: DateRange | null) => void
rules?: StayRule[]
weekendNights?: number
defaultNights?: number
locale?: string
}
export function minimumStay(
checkIn: string,
rules: StayRule[],
{ weekendNights = 2, defaultNights = 1 } = {}
): { nights: number; reason: string } {
// The longest matching rule wins.
const hit = rules
.filter((r) => checkIn >= r.from && checkIn <= r.to)
.sort((a, b) => b.nights - a.nights)[0]
if (hit) return { nights: hit.nights, reason: hit.reason }
if ([5, 6].includes(weekday(checkIn)) && weekendNights > defaultNights)
return { nights: weekendNights, reason: "weekend check-in" }
return { nights: defaultNights, reason: "standard stay" }
}
export function VacationRentalMinimumStay({
value,
defaultValue = null,
onValueChange,
rules = [],
weekendNights = 2,
defaultNights = 1,
locale,
}: VacationRentalMinimumStayProps) {
const [inner, setInner] = React.useState<DateRange | null>(defaultValue)
const stay = value === undefined ? inner : value
const [checkIn, setCheckIn] = React.useState<string | null>(
defaultValue?.from ?? null
)
const rule = checkIn
? minimumStay(checkIn, rules, { weekendNights, defaultNights })
: null
return (
<div className="flex flex-col gap-2">
<DateRangeField
label="Stay"
startLabel="Check-in"
endLabel="Check-out"
locale={locale}
value={stay}
minDays={rule ? rule.nights + 1 : undefined}
onValueChange={(next) => {
if (value === undefined) setInner(next)
if (next) setCheckIn(next.from)
onValueChange?.(next)
}}
/>
{rule ? (
<p role="status" className="text-muted-foreground text-sm">
Minimum stay for this check-in: {rule.nights} night
{rule.nights === 1 ? "" : "s"} ({rule.reason}).
{stay ? ` You chose ${nightsBetween(stay.from, stay.to)}.` : ""}
</p>
) : (
<p role="status" className="text-muted-foreground text-sm">
Enter a check-in date to see the minimum stay.
</p>
)}
</div>
)
}