Corex.PinInput (Corex v0.1.0)

View Source

Phoenix implementation of Zag.js Pin Input.

Anatomy

Basic

<.pin_input count={4} class="pin-input">
  <:label>Code</:label>
</.pin_input>

API

Set a stable id when using imperative API helpers or on_* events. With field={@form[:code]}, the field id is used automatically.

FunctionActionReturns
set_value/2Set cell values (client)%Phoenix.LiveView.JS{}
set_value/3Set cell values (server)socket
clear/1Clear all cells (client)%Phoenix.LiveView.JS{}
clear/2Clear all cells (server)socket
value/1Read values (client)%Phoenix.LiveView.JS{}
value/2Read values (client, opts)%Phoenix.LiveView.JS{}
value/3Read values (server)socket

Events

Pick an event name and pass it to on_* on <.pin_input>.

Server events

EventWhenPayload
on_value_change="pin_changed"Any cell changes%{"id" => id, "value" => list}
on_value_complete="pin_complete"All cells filled%{"id" => id, "value" => list}

on_value_change

<.pin_input count={4} class="pin-input" on_value_change="pin_changed">
  <:label>Code</:label>
</.pin_input>
def handle_event("pin_changed", %{"id" => _id, "value" => value}, socket) do
  {:noreply, assign(socket, :pin, value)}
end

Client events

EventWhenevent.detail
on_value_change_client="pin-changed"Any cell changesid, value
on_value_complete_client="pin-complete"All cells filledid, value

Form

Use field={f[:code]} inside <.form> so the hidden input name and validation align with Phoenix forms.

For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:code])} when you want alert borders after validation.

<.form for={@form} phx-change="validate">
  <.pin_input field={@form[:code]} count={4} class="pin-input">
    <:label>Verification code</:label>
  </.pin_input>
</.form>

Style

Use data attributes to target elements:

[data-scope="pin-input"][data-part="root"] {}
[data-scope="pin-input"][data-part="label"] {}
[data-scope="pin-input"][data-part="control"] {}
[data-scope="pin-input"][data-part="input"] {}
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/pin-input.css";

Stack modifiers on the host (class on <.pin_input>).

Color

ModifierClasses
Defaultpin-input
Accentpin-input pin-input--accent
Brandpin-input pin-input--brand
Alertpin-input pin-input--alert
Infopin-input pin-input--info
Successpin-input pin-input--success

Size

ModifierClasses
SMpin-input pin-input--sm
MDpin-input pin-input--md
LGpin-input pin-input--lg
XLpin-input pin-input--xl

Radius

ModifierClasses
Nonepin-input pin-input--rounded-none
SMpin-input pin-input--rounded-sm
MDpin-input pin-input--rounded-md
LGpin-input pin-input--rounded-lg
XLpin-input pin-input--rounded-xl
Fullpin-input pin-input--rounded-full

The value assign is the initial cell contents. Standalone mode uses data-default-value; field={@form[:code]} uses Zag controlled data-value and resyncs on patch via updateProps({ value }). Use the controlled assign only for non-form LiveView with on_value_change.

Summary

API

Clear all cells from phx-click. Dispatches corex:pin-input:clear.

Clear all cells from handle_event (pin_input_clear).

Replace cell values from phx-click. Dispatches corex:pin-input:set-value; value accepts a nonempty string list, a comma string, graphemes string, etc. (normalized like the form helper).

Replace cell values from handle_event (pin_input_set_value).

Same as value/2 with default respond_to:.

Read the current value from phx-click. Dispatches corex:pin-input:value. Optional respond_to: :server, :client, or :both.

Read the current value from handle_event (pin_input_value). Same replies as value/2.

Components

pin_input(assigns)

Attributes

  • id (:string)
  • value (:list) - Initial or controlled value (list of single-character strings). Padded to count for the hook. Defaults to [].
  • controlled (:boolean) - Opt-in LiveView controlled mode (data-value). Requires re-assigning value on on_value_change. Not used with field={...}. Defaults to false.
  • count (:integer) - Number of input boxes. Defaults to 4.
  • disabled (:boolean) - Defaults to false.
  • invalid (:boolean) - Defaults to false.
  • required (:boolean) - Defaults to false.
  • read_only (:boolean) - Defaults to false.
  • mask (:boolean) - Defaults to false.
  • otp (:boolean) - Defaults to false.
  • blur_on_complete (:boolean) - Defaults to false.
  • select_on_focus (:boolean) - Defaults to false.
  • name (:string) - Defaults to nil.
  • form (:string) - Defaults to nil.
  • dir (:string) - Defaults to nil.Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "vertical". Must be one of "horizontal", or "vertical".
  • type (:string) - Defaults to "numeric". Must be one of "alphanumeric", "numeric", or "alphabetic".
  • placeholder (:string) - Defaults to "○".
  • on_value_change (:string) - Defaults to nil.
  • on_value_change_client (:string) - Defaults to nil.
  • on_value_complete (:string) - Defaults to nil.
  • on_value_complete_client (:string) - Defaults to nil.
  • translation (Corex.PinInput.Translation) - Override translatable strings. Defaults to nil.
  • errors (:list) - Error messages to display (non-field API). Defaults to [].
  • field (Phoenix.HTML.FormField) - A form field, e.g. f[:code] or @form[:code].
  • Global attributes are accepted.

Slots

  • label - Accepts attributes:
    • class (:string)
  • error - Accepts attributes:
    • class (:string)

API

clear(pin_input_id)

Clear all cells from phx-click. Dispatches corex:pin-input:clear.

<.action phx-click={Corex.PinInput.clear("my-pin")}>Clear</.action>
<.pin_input id="my-pin" count={4} class="pin-input" />

clear(socket, pin_input_id)

Clear all cells from handle_event (pin_input_clear).

def handle_event("clear_pin", _, socket) do
  {:noreply, Corex.PinInput.clear(socket, "my-pin")}
end

set_value(pin_input_id, value)

Replace cell values from phx-click. Dispatches corex:pin-input:set-value; value accepts a nonempty string list, a comma string, graphemes string, etc. (normalized like the form helper).

<.action phx-click={Corex.PinInput.set_value("my-pin", ["1", "2", "", ""])}>Prefill</.action>
<.pin_input id="my-pin" count={4} class="pin-input" />
document.getElementById("my-pin")?.dispatchEvent(
  new CustomEvent("corex:pin-input:set-value", {
    bubbles: false,
    detail: { value: ["1", "2", "", ""] },
  })
);

set_value(socket, pin_input_id, value)

Replace cell values from handle_event (pin_input_set_value).

def handle_event("fill_pin", _, socket) do
  {:noreply, Corex.PinInput.set_value(socket, "my-pin", ["0", "0", "0", "0"])}
end

value(pin_input_id)

Same as value/2 with default respond_to:.

value(pin_input_id, opts)

Read the current value from phx-click. Dispatches corex:pin-input:value. Optional respond_to: :server, :client, or :both.

ReplyPayload
Serverpin_input_value_response%{"id" => id, "value" => cells, "valueAsString" => str}
Clientpin-input-value on the hostsame fields in detail
<.action phx-click={Corex.PinInput.value("my-pin")}>Read</.action>
<.pin_input id="my-pin" count={4} class="pin-input" />
def handle_event("pin_input_value_response", %{"id" => _, "valueAsString" => s}, socket) do
  {:noreply, assign(socket, :otp, s)}
end

value(socket, pin_input_id, opts \\ [])

Read the current value from handle_event (pin_input_value). Same replies as value/2.

ReplyPayload
pin_input_value_response%{"id" => id, "value" => cells, "valueAsString" => str}
def handle_event("read_pin", _, socket) do
  {:noreply, Corex.PinInput.value(socket, "my-pin", respond_to: :server)}
end