Corex.DatePicker
(Corex v0.1.0-beta.2)
View Source
Phoenix implementation of Zag.js Date Picker.
Examples
Basic Usage
<.date_picker id="my-date-picker">
<:label>Select a date</:label>
<:trigger>
<.heroicon name="hero-calendar" />
</:trigger>
<:prev_trigger>
<.heroicon name="hero-chevron-left" class="icon" />
</:prev_trigger>
<:next_trigger>
<.heroicon name="hero-chevron-right" class="icon" />
</:next_trigger>
</.date_picker>Controlled Mode
<.date_picker
id="my-date-picker"
controlled
value={@date_value}
on_value_change="date_changed">
<:label>Select a date</:label>
<:trigger>
<.heroicon name="hero-calendar" />
</:trigger>
<:prev_trigger>
<.heroicon name="hero-chevron-left" class="icon" />
</:prev_trigger>
<:next_trigger>
<.heroicon name="hero-chevron-right" class="icon" />
</:next_trigger>
</.date_picker>def handle_event("date_changed", %{"value" => value}, socket) do
{:noreply, assign(socket, :date_value, value)}
endPass an ISO-8601 string, or a Date struct via Date.to_iso8601/1 (for a single day use ~D[...]):
<.date_picker
id="due"
controlled
value={@due && Date.to_iso8601(@due)}
on_value_change="date_changed"
>
<:label>Due</:label>
<:trigger>
<.heroicon name="hero-calendar" />
</:trigger>
<:prev_trigger>
<.heroicon name="hero-chevron-left" class="icon" />
</:prev_trigger>
<:next_trigger>
<.heroicon name="hero-chevron-right" class="icon" />
</:next_trigger>
</.date_picker>assign(socket, :due, ~D[2024-01-15])Phoenix Form Integration
When using with Phoenix forms, set the form id in to_form/2 (for example to_form(changeset, as: :name, id: "my-form")) and use id={@form.id} on <.form>.
Controller
Build the form from an Ecto changeset:
def form_page(conn, _params) do
form =
%MyApp.Form.DateForm{}
|> MyApp.Form.DateForm.changeset(%{})
|> Phoenix.Component.to_form(as: :date_form, id: "date-form")
render(conn, :form_page, form: form)
end<.form :let={f} for={@form} id={@form.id} action={@action} method="post">
<.date_picker field={f[:date]} class="date-picker" trigger_aria_label="Select date" input_aria_label="Select date">
<:label>Date</:label>
<:trigger>
<.heroicon name="hero-calendar" class="icon" />
</:trigger>
<:prev_trigger>
<.heroicon name="hero-chevron-left" class="icon" />
</:prev_trigger>
<:next_trigger>
<.heroicon name="hero-chevron-right" class="icon" />
</:next_trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.date_picker>
<button type="submit">Submit</button>
</.form>Live View
When using in a Live view you must add controlled mode. Prefer building the form from an Ecto changeset (see "With Ecto changeset" below).
With Ecto changeset
When using Ecto changeset for validation and inside a Live view you must enable the controlled mode.
This allows the Live View to be the source of truth and the component to be in sync accordingly.
First create your schema and changeset:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :birth_date, :date
timestamps(type: :utc_datetime)
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :birth_date])
|> validate_required([:name, :birth_date])
end
enddefmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
alias MyApp.Accounts.User
def mount(_params, _session, socket) do
{:ok, assign(socket, :form, to_form(User.changeset(%User{}, %{})))}
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def render(assigns) do
~H"""
<.form for={@form} id={@form.id} phx-change="validate">
<.date_picker field={@form[:birth_date]} class="date-picker" controlled>
<:label>Birth date</:label>
<:trigger>
<.heroicon name="hero-calendar" class="icon" />
</:trigger>
<:prev_trigger>
<.heroicon name="hero-chevron-left" class="icon" />
</:prev_trigger>
<:next_trigger>
<.heroicon name="hero-chevron-right" class="icon" />
</:next_trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.date_picker>
</.form>
"""
end
endAPI Control
In order to use the API, you must use an id on the component
Client-side
<button phx-click={Corex.DatePicker.set_value("my-date-picker", "2024-01-15")}>
Set Date
</button>Server-side
def handle_event("set_date", _, socket) do
{:noreply, Corex.DatePicker.set_value(socket, "my-date-picker", "2024-01-15")}
endStyling
Use data attributes to target elements:
[data-scope="date-picker"][data-part="root"] {}
[data-scope="date-picker"][data-part="label"] {}
[data-scope="date-picker"][data-part="control"] {}
[data-scope="date-picker"][data-part="input"] {}
[data-scope="date-picker"][data-part="trigger"] {}
[data-scope="date-picker"][data-part="positioner"] {}
[data-scope="date-picker"][data-part="content"] {}If you wish to use the default Corex styling, you can use the class date-picker on the component.
This requires to install Mix.Tasks.Corex.Design first and import the component css file.
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/date-picker.css";You can then use modifiers
<.date_picker class="date-picker date-picker--accent date-picker--lg" id="my-date-picker">
</.date_picker>In selection_mode "range", the control shows two fields with optional range_start_label and range_end_label (overrides the translation map’s range labels, defaulting to From / To with gettext). In "multiple", a single field shows a comma‑separated list of the formatted selected dates. Use max_selected_dates to cap how many days can be selected in multiple mode; omit for no cap.
Localization and translation
Pass translation={%Corex.DatePicker.Translation{}} to override any string. The component merges with Corex.DatePicker.default_translation/0 (Zag’s translations for open/close, prev/next, view, month/year, week, placeholders, and input). Without gettext, the defaults are English. With gettext, call translation={%Corex.DatePicker.Translation{ open_calendar: gettext("Open calendar") }} for partial overrides.
The trigger_aria_label and input_aria_label attributes are merged with translation and sent to the client in data-translation (JSON): they set the open/close trigger strings and the input label (translation.open_calendar, translation.close_calendar, and translation.input).
Summary
API
Sets the date picker value from client-side. Returns a Phoenix.LiveView.JS command.
Sets the date picker value from server-side. Pushes a LiveView event.
Functions
Renders a date picker component.
Returns the merged default translatable strings. Uses gettext; override per call site with the translation attr.
API
Sets the date picker value from client-side. Returns a Phoenix.LiveView.JS command.
Examples
<button phx-click={Corex.DatePicker.set_value("my-date-picker", "2024-01-15")}>
Set Date
</button>
Sets the date picker value from server-side. Pushes a LiveView event.
Examples
def handle_event("set_date", _params, socket) do
socket = Corex.DatePicker.set_value(socket, "my-date-picker", "2024-01-15")
{:noreply, socket}
end
def handle_event("set_birthdate", _params, socket) do
socket = Corex.DatePicker.set_value(socket, "my-date-picker", ~D[2024-01-15])
{:noreply, socket}
end
Functions
Renders a date picker component.
Attributes
id(:string) - The unique identifier for the date picker. Set automatically when using the field attr. Defaults tonil.value(:string) - The initial value or the controlled value (ISO date string). Defaults tonil.controlled(:boolean) - Whether the date picker is controlled. Only in LiveView, the on_value_change event is required. Defaults tofalse.locale(:string) - The locale for date formatting. Defaults tonil.time_zone(:string) - The time zone for date operations. Defaults tonil.dir(:string) - The direction of the date picker. When nil, derived from document (html lang + config :rtl_locales). Defaults to"ltr". Must be one of"ltr", or"rtl".on_value_change(:string) - The server event name when the value changes. Defaults tonil.on_focus_change(:string) - The server event name when focus changes. Defaults tonil.on_view_change(:string) - The server event name when the view changes. Defaults tonil.name(:string) - The name attribute of the input element. Defaults tonil.disabled(:boolean) - Whether the calendar is disabled. Defaults tofalse.read_only(:boolean) - Whether the calendar is read-only. Defaults tofalse.required(:boolean) - Whether the date picker is required. Defaults tofalse.invalid(:boolean) - Whether the date picker is invalid. Defaults tofalse.outside_day_selectable(:boolean) - Whether day outside the visible range can be selected. Defaults tofalse.close_on_select(:boolean) - If true, close the popover when selection is complete. Forselection_mode:multiple or :range, the default false keeps the panel open until dismissed unless you set this to true. Defaults tofalse.min(:string) - The minimum date that can be selected (ISO date string). Defaults tonil.max(:string) - The maximum date that can be selected (ISO date string). Defaults tonil.focused_value(:string) - The initial focused date when the calendar opens (ISO date string). Used as default in the picker. Defaults tonil.start_of_week(:integer) - The first day of the week (0=Sunday, 1=Monday, etc.). Defaults to0.fixed_weeks(:boolean) - Whether the calendar should have a fixed number of weeks (6 weeks). Defaults totrue.selection_mode(:string) - The selection mode of the calendar. Defaults to"single". Must be one of"single","multiple", or"range".placeholder(:string) - The placeholder text to display in the input. Defaults tonil.translation(:any) - Merges withdefault_translation/0to override Zag and Corex strings; see the module section on localization. Defaults tonil.trigger_aria_label(:string) - Overridestranslationfor the popover button’s accessible name (same in open and closed state). If unset, the client uses open/close strings from the translation (Zag: open vs. closed calendar labels). Defaults tonil.input_aria_label(:string) - Overridestranslation.inputfor the text input when the slot label is not used. Zag does not provide this; it is a Corex field. Defaults tonil.range_start_label(:string) - Whenselection_modeis "range", overridestranslation.range_startfor the first field side label (default indefault_translation/0is From). Defaults tonil.range_end_label(:string) - Whenselection_modeis "range", overridestranslation.range_end(default To). Defaults tonil.max_selected_dates(:integer) - Whenselection_modeis "multiple", limits how many days can be selected. Omit for no cap. Defaults tonil.view(:string) - The initial view of the calendar (day, month, or year); passed to the client as the default view. Defaults to"day". Must be one of"day","month", or"year".min_view(:string) - The minimum view of the calendar. Defaults to"day". Must be one of"day","month", or"year".max_view(:string) - The maximum view of the calendar. Defaults to"year". Must be one of"day","month", or"year".positioning(Corex.Positioning) - Positioning options for the date picker content. Defaults to%Corex.Positioning{hide_when_detached: true, strategy: "fixed", placement: "bottom", gutter: 8, shift: 0, overflow_padding: 0, arrow_padding: 4, flip: true, slide: true, overlap: false, same_width: false, fit_viewport: true}.on_visible_range_change(:string) - The server event name when the visible range changes. Defaults tonil.on_open_change(:string) - The server event name when the calendar opens or closes. Defaults tonil.on_value_change_client(:string) - Fires a window-bubbling CustomEvent with this name when the value changes (optional; use with on_value_change for both). Defaults tonil.on_open_change_client(:string) - Fires a window-bubbling CustomEvent with this name when the calendar opens or closes (optional; use with on_open_change for both). Defaults tonil.errors(:list) - List of error messages to display. Defaults to[].field(Phoenix.HTML.FormField) - A form field struct from the form, e.g. @form[:birth_date]. Sets id, name, value, and errors from the field; enables controlled mode for LiveView.- Global attributes are accepted.
Slots
label- Accepts attributes:class(:string)
error- Accepts attributes:class(:string)
trigger- Accepts attributes:class(:string)
prev_trigger- Accepts attributes:class(:string)
next_trigger- Accepts attributes:class(:string)
@spec default_translation() :: Corex.DatePicker.Translation.t()
Returns the merged default translatable strings. Uses gettext; override per call site with the translation attr.