Packages

shadcn/ui-inspired component library for Phoenix LiveView with eject-based distribution. CSS-first theme system, 829 components across 20+ categories — Calendar, Chart, Animation, DnD, Editor, Collaboration, Typography, Navigation, Background, Surface, and more.

Current section

Files

Jump to
phia_ui lib phia_ui components calendar date_time_picker.ex
Raw

lib/phia_ui/components/calendar/date_time_picker.ex

defmodule PhiaUi.Components.DateTimePicker do
@moduledoc """
Date-and-time selection input — styled native `<input type="datetime-local">`.
Wraps the browser's native datetime-local picker with PhiaUI's input styling
(borders, focus ring, size variants, error state). Supports standalone use or
Phoenix form field integration with changeset error display.
Present in every major UI kit: Mantine `DateTimePicker`, Ant Design `DatePicker`
(showTime), MUI `DateTimePicker`, React Aria `DateRangePicker`.
Zero JavaScript — uses the native browser datetime-local control.
## Examples
<%!-- Standalone --%>
<.date_time_picker id="start" name="start_at" label="Start Date & Time" value="2026-03-04T10:30" />
<%!-- With seconds precision --%>
<.date_time_picker id="ts" name="timestamp" with_seconds={true} />
<%!-- Form field integration --%>
<.date_time_picker
id="event_at"
name={@form[:scheduled_at].name}
value={@form[:scheduled_at].value}
errors={Enum.map(@form[:scheduled_at].errors, &translate_error/1)}
label="Scheduled At"
/>
"""
use Phoenix.Component
import PhiaUi.ClassMerger, only: [cn: 1]
# ---------------------------------------------------------------------------
# Attributes
# ---------------------------------------------------------------------------
attr(:id, :string, default: nil, doc: "HTML `id` for the input and label `for` link.")
attr(:name, :string, default: nil, doc: "HTML `name` for form submission.")
attr(:value, :string,
default: nil,
doc: "Current datetime value as `\"YYYY-MM-DDTHH:MM\"` or `\"YYYY-MM-DDTHH:MM:SS\"`."
)
attr(:label, :string, default: nil, doc: "Label text rendered above the input.")
attr(:description, :string, default: nil, doc: "Helper text rendered below the label.")
attr(:errors, :list, default: [], doc: "List of error message strings.")
attr(:with_seconds, :boolean,
default: false,
doc: "When `true`, sets `step=\"1\"` so seconds are shown in the native picker."
)
attr(:disabled, :boolean, default: false, doc: "Disables the input.")
attr(:size, :atom,
default: :default,
values: [:sm, :default, :lg],
doc: "Input height/padding: `:sm` (h-8), `:default` (h-10), `:lg` (h-12)."
)
attr(:class, :string, default: nil, doc: "Additional CSS classes merged onto the input.")
attr(:rest, :global,
include: ~w(required placeholder phx-debounce min max),
doc: "HTML attributes forwarded to the `<input>` element."
)
# ---------------------------------------------------------------------------
# Component
# ---------------------------------------------------------------------------
def date_time_picker(assigns) do
~H"""
<div class="space-y-2">
<label
:if={@label}
for={@id}
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{@label}
</label>
<p :if={@description} class="text-sm text-muted-foreground">
{@description}
</p>
<input
type="datetime-local"
id={@id}
name={@name}
value={@value}
disabled={@disabled}
step={if @with_seconds, do: "1"}
class={cn([input_class(@size), error_class(@errors), @class])}
{@rest}
/>
<p :for={error <- @errors} class="text-sm font-medium text-destructive">
{error}
</p>
</div>
"""
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp input_class(:sm) do
"flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 text-xs " <>
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 " <>
"focus-visible:ring-ring focus-visible:ring-offset-2 " <>
"disabled:cursor-not-allowed disabled:opacity-50"
end
defp input_class(:default) do
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm " <>
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 " <>
"focus-visible:ring-ring focus-visible:ring-offset-2 " <>
"disabled:cursor-not-allowed disabled:opacity-50"
end
defp input_class(:lg) do
"flex h-12 w-full rounded-md border border-input bg-background px-4 py-3 text-base " <>
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 " <>
"focus-visible:ring-ring focus-visible:ring-offset-2 " <>
"disabled:cursor-not-allowed disabled:opacity-50"
end
defp error_class([]), do: nil
defp error_class(_), do: "border-destructive focus-visible:ring-destructive"
end