Corex.DatePicker (Corex v0.1.0-beta.1)

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)}
end

Pass 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
end
defmodule 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
end

API 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")}
end

Styling

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

set_value(date_picker_id, value)

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>

set_value(socket, date_picker_id, value)

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

date_picker(assigns)

Renders a date picker component.

Attributes

  • id (:string) - The unique identifier for the date picker. Set automatically when using the field attr. Defaults to nil.
  • value (:string) - The initial value or the controlled value (ISO date string). Defaults to nil.
  • controlled (:boolean) - Whether the date picker is controlled. Only in LiveView, the on_value_change event is required. Defaults to false.
  • locale (:string) - The locale for date formatting. Defaults to nil.
  • time_zone (:string) - The time zone for date operations. Defaults to nil.
  • 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 to nil.
  • on_focus_change (:string) - The server event name when focus changes. Defaults to nil.
  • on_view_change (:string) - The server event name when the view changes. Defaults to nil.
  • name (:string) - The name attribute of the input element. Defaults to nil.
  • disabled (:boolean) - Whether the calendar is disabled. Defaults to false.
  • read_only (:boolean) - Whether the calendar is read-only. Defaults to false.
  • required (:boolean) - Whether the date picker is required. Defaults to false.
  • invalid (:boolean) - Whether the date picker is invalid. Defaults to false.
  • outside_day_selectable (:boolean) - Whether day outside the visible range can be selected. Defaults to false.
  • close_on_select (:boolean) - If true, close the popover when selection is complete. For selection_mode :multiple or :range, the default false keeps the panel open until dismissed unless you set this to true. Defaults to false.
  • min (:string) - The minimum date that can be selected (ISO date string). Defaults to nil.
  • max (:string) - The maximum date that can be selected (ISO date string). Defaults to nil.
  • focused_value (:string) - The initial focused date when the calendar opens (ISO date string). Used as default in the picker. Defaults to nil.
  • start_of_week (:integer) - The first day of the week (0=Sunday, 1=Monday, etc.). Defaults to 0.
  • fixed_weeks (:boolean) - Whether the calendar should have a fixed number of weeks (6 weeks). Defaults to true.
  • 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 to nil.
  • translation (:any) - Merges with default_translation/0 to override Zag and Corex strings; see the module section on localization. Defaults to nil.
  • trigger_aria_label (:string) - Overrides translation for 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 to nil.
  • input_aria_label (:string) - Overrides translation.input for the text input when the slot label is not used. Zag does not provide this; it is a Corex field. Defaults to nil.
  • range_start_label (:string) - When selection_mode is "range", overrides translation.range_start for the first field side label (default in default_translation/0 is From). Defaults to nil.
  • range_end_label (:string) - When selection_mode is "range", overrides translation.range_end (default To). Defaults to nil.
  • max_selected_dates (:integer) - When selection_mode is "multiple", limits how many days can be selected. Omit for no cap. Defaults to nil.
  • 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 to nil.
  • on_open_change (:string) - The server event name when the calendar opens or closes. Defaults to nil.
  • 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 to nil.
  • 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 to nil.
  • 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)

default_translation()

@spec default_translation() :: Corex.DatePicker.Translation.t()

Returns the merged default translatable strings. Uses gettext; override per call site with the translation attr.