defmodule Skua.Components.Date do @moduledoc """ A date input — a hidden ISO `` that carries the value plus a trigger that opens a keyboard-navigable calendar (W3C APG date-grid pattern). <.date_input field={@form[:due_on]} label="Due date" /> <.date_input field={@form[:start]} min="2026-01-01" max="2026-12-31" /> The hidden input holds `yyyy-mm-dd`, so `phx-change`/`phx-submit` and Ecto `:date` casting work unchanged. The calendar grid supports arrow keys (±1 day / ±1 week), Home/End (week), PageUp/PageDown (month), and Enter/Space to pick. """ use Phoenix.Component alias Skua.Components.Form alias Skua.Field attr :field, Phoenix.HTML.FormField, default: nil attr :id, :string, default: nil attr :name, :string, default: nil attr :value, :any, default: nil, doc: "ISO yyyy-mm-dd" attr :min, :string, default: nil attr :max, :string, default: nil attr :today, :string, default: nil attr :label, :string, default: nil attr :hint, :string, default: nil attr :errors, :list, default: nil attr :required, :boolean, default: false attr :placeholder, :string, default: "Pick a date…" attr :time, :boolean, default: false, doc: "also pick a time (datetime value)" attr :time_format, :string, default: "12", values: ~w(12 24), doc: "12h AM/PM or 24h military" def date_input(assigns) do assigns = assigns |> assign_new(:time, fn -> false end) |> assign_new(:time_format, fn -> "12" end) |> Field.normalize() |> then(fn a -> assign(a, :id, a.id || "sk-date") end) assigns = assigns |> assign(:value, iso(assigns.value)) |> assign(:describedby, describedby(assigns)) ~H"""
{@label}
{@hint}
""" end @doc """ A date **and time** picker — a time bar (hour / minute / AM·PM, or 24h military) sits above the calendar. The hidden value is an ISO datetime (`yyyy-mm-ddThh:mm`). <.datetime_input field={@form[:starts_at]} label="Starts" /> <.datetime_input field={@form[:starts_at]} time_format="24" /> """ attr :field, Phoenix.HTML.FormField, default: nil attr :id, :string, default: nil attr :name, :string, default: nil attr :value, :any, default: nil, doc: "ISO yyyy-mm-ddThh:mm" attr :min, :string, default: nil attr :max, :string, default: nil attr :today, :string, default: nil attr :label, :string, default: nil attr :hint, :string, default: nil attr :errors, :list, default: nil attr :required, :boolean, default: false attr :placeholder, :string, default: "Pick a date & time…" attr :time_format, :string, default: "12", values: ~w(12 24) def datetime_input(assigns) do assigns |> assign(:time, true) |> assign_new(:placeholder, fn -> "Pick a date & time…" end) |> date_input() end defp iso(%Date{} = d), do: Date.to_iso8601(d) defp iso(%NaiveDateTime{} = d), do: NaiveDateTime.to_iso8601(d) defp iso(%DateTime{} = d), do: DateTime.to_iso8601(d) defp iso(value) when is_binary(value), do: value defp iso(_), do: nil defp describedby(%{errors: errors, id: id}) when errors != [], do: "#{id}-error" defp describedby(%{hint: hint, id: id}) when not is_nil(hint), do: "#{id}-hint" defp describedby(_), do: nil end