A full-month calendar grid where multiple individual (non-contiguous) dates can be toggled on/off.
Selected dates are rendered as individual filled rounded rectangles using
bg-primary/20 text-primary font-semibold. This is distinct from
RangeCalendar, which selects a contiguous band of dates.
The grid is SUN-first (Sun Mon Tue Wed Thu Fri Sat). Adjacent-month days are
rendered faded (opacity-40) to fill the first and last rows.
Month and year navigation is provided through navigation buttons (on_prev,
on_next) and/or <select> dropdowns (on_month_change, on_year_change).
State (current month/year, selected dates) lives entirely in the parent
LiveView.
Example
<.multi_select_calendar
year={@year}
month={@month}
selected_dates={@selected_dates}
on_select="toggle_date"
on_prev="prev_month"
on_next="next_month"
on_month_change="month_changed"
on_year_change="year_changed"
/>LiveView event handlers
def handle_event("toggle_date", %{"date" => iso}, socket) do
date = Date.from_iso8601!(iso)
selected =
if date in socket.assigns.selected_dates do
List.delete(socket.assigns.selected_dates, date)
else
[date | socket.assigns.selected_dates]
end
{:noreply, assign(socket, selected_dates: selected)}
end
def handle_event("prev_month", _params, socket) do
{y, m} = prev_ym(socket.assigns.year, socket.assigns.month)
{:noreply, assign(socket, year: y, month: m)}
end
def handle_event("next_month", _params, socket) do
{y, m} = next_ym(socket.assigns.year, socket.assigns.month)
{:noreply, assign(socket, year: y, month: m)}
end
def handle_event("month_changed", %{"month" => m}, socket) do
{:noreply, assign(socket, month: String.to_integer(m))}
end
def handle_event("year_changed", %{"year" => y}, socket) do
{:noreply, assign(socket, year: String.to_integer(y))}
endZero JavaScript
This component is entirely server-rendered. No JS hook is required.
Summary
Functions
Renders a single-month calendar grid supporting multiple individual date selections.
Functions
Renders a single-month calendar grid supporting multiple individual date selections.
All geometry (leading/trailing days, per-cell selection state) is computed server-side before the template renders.
Attributes
year(:integer) (required) - 4-digit year of the displayed month.month(:integer) (required) - Month number (1–12).selected_dates(:list) - List ofDate.t()values that are currently selected. Defaults to[].on_select(:string) -phx-clickevent name fired when any current-month day is clicked. The LiveView receives%{"date" => "YYYY-MM-DD"}. Clicking a selected date again is intended to deselect it — the toggle logic lives in the parent.Defaults to
nil.on_prev(:string) -phx-clickevent name for the previous-month navigation button. Defaults tonil.on_next(:string) -phx-clickevent name for the next-month navigation button. Defaults tonil.on_month_change(:string) -phx-changeevent name for the month<select>dropdown. Defaults tonil.on_year_change(:string) -phx-changeevent name for the year<select>dropdown. Defaults tonil.min_year(:integer) - Earliest year shown in the year<select>dropdown. Defaults to2000.max_year(:integer) - Latest year shown in the year<select>dropdown. Defaults to2040.class(:string) - Additional CSS classes merged onto the root element. Defaults tonil.Global attributes are accepted. HTML attributes forwarded to the root
<div>.