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

Server-rendered calendar component for date selection with keyboard navigation.

The calendar grid is built entirely server-side using Elixir's `Date` module.
State (current month, selected date or range) lives in the parent LiveView.
Day clicks and month navigation fire `phx-click` events back to the LiveView.

Supports two selection modes:

- `:single` — selects a single `Date.t()` value
- `:range`  — highlights an interval between `:range_start` and `:range_end`

Keyboard navigation (Arrow keys, Enter, Home, End) is provided by the
`PhiaCalendar` JS hook registered on the root element.

## When to use

Use `calendar/1` as the foundational date-picking primitive. Higher-level
components compose it:

- `date_picker/1` — wraps `calendar/1` in a popover with a trigger button
- `date_range_picker/1` — renders two `calendar/1` instances side-by-side
  for range selection

Use `calendar/1` directly when you want a permanently visible calendar
(e.g. a booking widget, an event scheduler, a date navigator).

## ARIA

The grid uses `role="grid"`. Day-of-week headers use `role="columnheader"`.
Individual day cells use `role="gridcell"` with `aria-selected` and
`aria-disabled` reflecting state. Disabled buttons have the `disabled` HTML
attribute to prevent interaction and remove them from the tab order.

## Single-date example

    defmodule MyAppWeb.BookingLive do
      use Phoenix.LiveView

      def mount(_params, _session, socket) do
        today = Date.utc_today()
        {:ok, assign(socket,
          current_month: Date.beginning_of_month(today),
          selected_date: nil
        )}
      end

      def handle_event("date-selected", %{"date" => iso}, socket) do
        date = Date.from_iso8601!(iso)
        {:noreply, assign(socket, selected_date: date)}
      end

      def handle_event("calendar-prev-month", %{"month" => iso}, socket) do
        {:noreply, assign(socket, current_month: Date.from_iso8601!(iso))}
      end

      def handle_event("calendar-next-month", %{"month" => iso}, socket) do
        {:noreply, assign(socket, current_month: Date.from_iso8601!(iso))}
      end
    end

    <%!-- Template --%>
    <.calendar
      id="booking-calendar"
      current_month={@current_month}
      value={@selected_date}
      on_change="date-selected"
      min={Date.utc_today()}
    />
    <p :if={@selected_date}>
      Selected: {Calendar.strftime(@selected_date, "%B %d, %Y")}
    </p>

## Range selection example

    <.calendar
      id="vacation-range"
      current_month={@current_month}
      mode="range"
      range_start={@check_in}
      range_end={@check_out}
      on_change="date-selected"
      min={Date.utc_today()}
    />

## Disabling specific dates

Pass `disabled_dates` to block holiday or unavailable dates:

    <.calendar
      id="appointment-picker"
      value={@appointment_date}
      disabled_dates={@fully_booked_dates}
      min={Date.utc_today()}
      max={Date.add(Date.utc_today(), 90)}
      on_change="date-selected"
    />

## Hook setup

    # app.js
    import PhiaCalendar from "./hooks/calendar"
    let liveSocket = new LiveSocket("/live", Socket, {
      hooks: { PhiaCalendar }
    })

# `calendar`

Renders a server-rendered, accessible calendar grid for date selection.

All calendar geometry (weeks, leading/trailing days, cell states) is computed
server-side in `build_weeks/1` and `compute_cell_states/4` before the
template renders. This keeps the HEEx template declarative and free of logic.

The `PhiaCalendar` hook handles keyboard navigation: Arrow keys move focus
between day cells, Enter selects the focused cell, Home/End jump to the
first/last day of the displayed month.

## Attributes

* `id` (`:string`) - Unique calendar ID. Used as the hook anchor and as a prefix for
  the embedded calendar in `date_picker/1`. Must be unique per page.

  Defaults to `"calendar"`.
* `value` (`:any`) - Selected date (`Date.t()`) in `:single` mode, or `nil` for no selection. Defaults to `nil`.
* `current_month` (`:any`) - Currently displayed month (`Date.t()`). When `nil`, defaults to the month
  containing `value`, or the current month if `value` is also nil.
  The LiveView must update this assign in response to prev/next month events.

  Defaults to `nil`.
* `mode` (`:string`) - Selection mode:
  - `"single"` — highlights one selected date
  - `"range"`  — highlights the interval between `range_start` and `range_end`

  Defaults to `"single"`. Must be one of `"single"`, or `"range"`.
* `range_start` (`:any`) - Range selection start date (`Date.t()`) — only used in `mode="range"`. Defaults to `nil`.
* `range_end` (`:any`) - Range selection end date (`Date.t()`) — only used in `mode="range"`. Defaults to `nil`.
* `min` (`:any`) - Minimum selectable date (`Date.t()`). Dates before this are disabled. Defaults to `nil`.
* `max` (`:any`) - Maximum selectable date (`Date.t()`). Dates after this are disabled. Defaults to `nil`.
* `disabled_dates` (`:list`) - Explicit list of `Date.t()` values that cannot be selected regardless of
  `min`/`max`. Use for holidays, fully-booked slots, or blackout dates.

  Defaults to `[]`.
* `on_change` (`:string`) - `phx-click` event name fired when a day button is clicked.
  The LiveView receives `%{"date" => "YYYY-MM-DD"}`.

  Defaults to `"calendar-change"`.
* `class` (`:string`) - Additional CSS classes merged onto the root element via `cn/1`. Defaults to `nil`.
* Global attributes are accepted. HTML attributes forwarded to the root div element.

---

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