← alle entries

booking-section

FC-0002booking-flowblockv1.0.0

Receptuur: kalender + aanvraag-formulier met gedeelde periodeselectie in twee richtingen (kalenderklik vult de velden, getypte datum kleurt de kalender).

zelfde component — andere tokens

Beschikbaarheid

september 2026

oktober 2026

Geselecteerd In behandeling14 Niet beschikbaar

Reserveren

€ 120 / nacht

Maximaal 4 gasten.

€ 120 × 1 nacht
€ 120
Totaal
€ 120
blocks/booking/booking-section.tsx
/**
 * BookingSection — de eerste composes-receptuur (V5): kalender + aanvraag-blok
 * met gedeelde periodeselectie in twee richtingen. Een klik in de kalender vult
 * de datumvelden; een getypte datum kleurt de kalender.
 */

"use client";

import { useState } from "react";
import type * as React from "react";
import { DateRangeCalendar, type DateRange, type UnavailablePeriod } from "../../primitives/ui/date-range-calendar";
import { BookingInquiry, type BookingInquiryProps } from "./booking-inquiry";

export interface BookingSectionProps
  extends Omit<BookingInquiryProps, "dates" | "onDatesChange"> {
  unavailable?: UnavailablePeriod[];
  /** De gekozen periode raakt een onbeschikbare dag — het project kiest de melding. */
  onInvalidRange?: () => void;
  calendarTitle?: string;
  locale?: string;
  /** Voetnoot onder de kalender-legenda (bv. een schoonmaak-bufferregel). */
  calendarFootnote?: React.ReactNode;
}

export function BookingSection({
  unavailable = [],
  onInvalidRange,
  calendarTitle,
  locale,
  calendarFootnote,
  ...inquiry
}: BookingSectionProps) {
  const [dates, setDates] = useState<DateRange>({ start: null, end: null });

  return (
    <div className="fp-booking-section">
      <DateRangeCalendar
        unavailable={unavailable}
        value={dates}
        onChange={({ start, end }) => setDates({ start, end })}
        onInvalidRange={onInvalidRange}
        title={calendarTitle}
        locale={locale}
      >
        {calendarFootnote}
      </DateRangeCalendar>
      <BookingInquiry {...inquiry} dates={dates} onDatesChange={setDates} />
    </div>
  );
}
blocks/booking/booking.css
/**
 * Booking-blocks — stylesheet. Uitsluitend contract-tokens (tokens/CONTRACT.md).
 * Vereist primitives/ui/primitives.css + tokens/motion.css ernaast.
 */

.fp-booking {
  display: flex; flex-direction: column; gap: 1rem;
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.375rem;
}
.fp-booking__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.fp-booking__title { margin: 0; font-size: 0.9375rem; font-weight: 600; }
.fp-booking__price { margin: 0; font-size: 0.8125rem; color: var(--muted-foreground); }
.fp-booking__price strong { font-size: 1.25rem; font-weight: 600; color: var(--foreground); }

.fp-booking__dates { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
@media (max-width: 420px) { .fp-booking__dates { grid-template-columns: 1fr; } }

.fp-booking__summary {
  margin: 0; display: flex; flex-direction: column; gap: 0.5rem;
  background: var(--muted); border-radius: var(--radius);
  padding: 0.875rem 1rem; font-size: 0.8125rem;
}
.fp-booking__summary > div { display: flex; justify-content: space-between; gap: 1rem; }
.fp-booking__summary dt { color: var(--muted-foreground); }
.fp-booking__summary dd { margin: 0; font-variant-numeric: tabular-nums; }
.fp-booking__total {
  padding-top: 0.5rem; border-top: 1px solid var(--border); font-weight: 600;
}
.fp-booking__total dt { color: var(--foreground); }

.fp-booking__guest { display: flex; flex-direction: column; gap: 0.75rem; padding-top: 0.25rem; }

.fp-booking__error { margin: 0; font-size: 0.8125rem; color: var(--danger); }
.fp-booking__submit { width: 100%; }
.fp-booking__footnote { font-size: 0.75rem; color: var(--muted-foreground); text-align: center; }

.fp-booking--success { text-align: center; }
.fp-booking--success .fp-booking__title { color: var(--success); }
.fp-booking__text { margin: 0; font-size: 0.875rem; color: var(--muted-foreground); }

/* ── Sectie-receptuur: kalender + formulier ─────────────────────────────── */
.fp-booking-section {
  display: grid; grid-template-columns: 3fr 2fr; gap: 1rem; align-items: start;
}
@media (max-width: 860px) { .fp-booking-section { grid-template-columns: 1fr; } }