PhiaUi.Components.DateRangePicker (phia_ui v0.1.17)

Copy Markdown View Source

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).

Summary

Functions

Renders a dual-calendar date range picker.

Functions

date_range_picker(assigns)

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.