Hotel booking range
Check-in / check-out with nightly prices under each day, booked nights that ranges can't cross, a 2-night minimum and a live total.
Result
Loading...
Live Editor
import { useMemo, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { DatePicker, addDays, formatDate, toDateKey } from '@elik56a/react-native-pickers'; import type { DateRange, MarkedDate } from '@elik56a/react-native-pickers'; const MIN_NIGHTS = 2; const nightly = (d: Date) => (d.getDay() === 5 || d.getDay() === 6 ? 189 : 129); export default function Booking() { const today = useMemo(() => new Date(), []); const [range, setRange] = useState<DateRange>({ start: null, end: null }); const marks = useMemo(() => { const out: Record<string, MarkedDate> = {}; for (let i = 0; i < 90; i++) { const d = addDays(today, i); out[toDateKey(d)] = i % 11 === 6 || i % 11 === 7 ? { disabled: true, label: 'โ' } : { label: `$${nightly(d)}` }; } return out; }, [today]); const nights = range.start && range.end ? Math.round((+range.end - +range.start) / 864e5) : 0; let total = 0; for (let i = 0; i < nights; i++) total += nightly(addDays(range.start!, i)); return ( <View style={styles.card}> <DatePicker mode="range" value={range} onChange={setRange} minDate={today} maxDate={addDays(today, 89)} minRangeDays={MIN_NIGHTS} markedDates={marks} dayHeight={50} /> <View style={styles.summary}> <Text style={styles.dates}> {range.start ? formatDate(range.start, undefined, { day: 'numeric', month: 'short' }) : 'Check-in'} {' โ '} {range.end ? formatDate(range.end, undefined, { day: 'numeric', month: 'short' }) : 'Check-out'} </Text> <Text style={styles.total}>{nights ? `${nights} nights ยท $${total}` : `Min. ${MIN_NIGHTS} nights`}</Text> </View> </View> ); } const styles = StyleSheet.create({ card: { gap: 8 }, summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 8 }, dates: { fontSize: 15, fontWeight: '600', color: '#6E685E' }, total: { fontSize: 15, fontWeight: '700', color: '#C8460D' }, });
What's going onโ
markedDatesputs the price under each day and marks booked daysdisabled. Range selection never crosses a disabled day: tapping past one starts a new range.minRangeDaysignores a check-out tap that's too close to check-in.dayHeight={50}leaves room for the label.- Prices are summed per night with
addDays, which is DST-safe.
As a sheet with Doneโ
On phones, open the calendar from a summary bar. Done stays disabled until both dates are chosen:
<DatePickerSheet
visible={open}
onClose={() => setOpen(false)}
title="Select dates"
confirmLabel="Save dates"
mode="range"
value={range}
onConfirm={setRange}
minDate={today}
minRangeDays={MIN_NIGHTS}
markedDates={marks}
dayHeight={50}
sheetProps={{ height: 0.75 }}
/>