# `PhiaUi.Components.DateRangePicker`
[🔗](https://github.com/charlenopires/PhiaUI/blob/v0.1.17/lib/phia_ui/components/calendar/date_range_picker.ex#L1)

Date range picker with dual-calendar server-side rendering.

Renders two side-by-side monthly calendar grids for selecting a start and
end date. The calendar grid is built server-side using Elixir's `Date`
module. All state (current view month, selected from/to) lives in the
parent LiveView. Day clicks and month navigation fire `phx-click` events.

## When to use

Use `date_range_picker/1` whenever users need to select a date interval:

- Hotel booking (check-in / check-out)
- Report date range (start period / end period)
- Project timeline (kickoff / deadline)
- Vacation request (from / to)
- Analytics dashboard date filter

For single-date selection, use `date_picker/1` instead.

## State management

The LiveView manages a two-click selection protocol:

1. First click sets `from`, clears `to`
2. Second click (on a date ≥ `from`) sets `to`
3. Any subsequent click resets: sets new `from`, clears `to`

## Complete example — booking range

    defmodule MyAppWeb.BookingLive do
      use Phoenix.LiveView

      def mount(_params, _session, socket) do
        {:ok, assign(socket,
          view_month: Date.beginning_of_month(Date.utc_today()),
          date_from: nil,
          date_to: nil
        )}
      end

      def handle_event("select-date", %{"date" => date_str}, socket) do
        date = Date.from_iso8601!(date_str)
        socket =
          cond do
            # No start date yet — set it
            is_nil(socket.assigns.date_from) ->
              assign(socket, date_from: date, date_to: nil)
            # Start set, no end, and the clicked date is >= start — set end
            is_nil(socket.assigns.date_to) and Date.compare(date, socket.assigns.date_from) != :lt ->
              assign(socket, date_to: date)
            # Otherwise reset: start a new range
            true ->
              assign(socket, date_from: date, date_to: nil)
          end
        {:noreply, socket}
      end

      def handle_event("change-month", %{"dir" => "next"}, socket) do
        {:noreply, assign(socket, view_month: Date.shift(socket.assigns.view_month, month: 1))}
      end

      def handle_event("change-month", %{"dir" => "prev"}, socket) do
        {:noreply, assign(socket, view_month: Date.shift(socket.assigns.view_month, month: -1))}
      end
    end

    <%!-- Template --%>
    <.date_range_picker
      id="booking-range"
      view_month={@view_month}
      from={@date_from}
      to={@date_to}
      on_change="select-date"
      on_month_change="change-month"
      min_date={Date.utc_today()}
    />

    <p :if={@date_from && @date_to}>
      Booking: {Calendar.strftime(@date_from, "%B %d")} –
               {Calendar.strftime(@date_to, "%B %d, %Y")}
      ({Date.diff(@date_to, @date_from)} nights)
    </p>

## Range highlight

When both `from` and `to` are set, days between the endpoints receive a
`bg-accent` background. The endpoint buttons themselves receive
`bg-primary text-primary-foreground` and rounded corners that connect
visually to the range stripe (`rounded-r-none` on `from`, `rounded-l-none`
on `to`).

# `date_range_picker`

Renders a dual-calendar date range picker.

Displays two side-by-side monthly grids. The left grid shows `view_month`;
the right grid shows the month immediately following. Both grids share the
same `from`/`to` selection state and highlight the range consistently.

Month navigation applies to both calendars simultaneously — advancing or
retreating one month at a time.

## Attributes

* `id` (`:string`) (required) - Unique DOM id for the root element.
* `view_month` (`:any`) (required) - `Date.t()` representing the first of the two displayed months.
  The second month is derived automatically as `view_month + 1`.
  Update this assign in response to `on_month_change` events.

* `from` (`:any`) - Selected range start date (`Date.t()`) or `nil` before the user's first click. Defaults to `nil`.
* `to` (`:any`) - Selected range end date (`Date.t()`) or `nil` before the user's second click. Defaults to `nil`.
* `on_change` (`:string`) - `phx-click` event name fired when a day cell is clicked.
  The LiveView receives `%{"date" => "YYYY-MM-DD"}`.

  Defaults to `"date-range-changed"`.
* `on_month_change` (`:string`) - `phx-click` event name for month navigation buttons.
  The LiveView receives `%{"dir" => "next" | "prev"}`.

  Defaults to `"date-range-month"`.
* `min_date` (`:any`) - Minimum selectable date (`Date.t()`). Days before this are rendered disabled. Defaults to `nil`.
* `max_date` (`:any`) - Maximum selectable date (`Date.t()`). Days after this are rendered disabled. Defaults to `nil`.
* `locale` (`:string`) - Locale for month/day labels (currently informational — labels are hardcoded in English). Defaults to `"en"`.
* `class` (`:string`) - Additional CSS classes for the root wrapper. Defaults to `nil`.
* Global attributes are accepted. HTML attributes forwarded to the root div.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
