Packages
corex
0.1.0-alpha.31
0.1.2
0.1.1
0.1.0
0.1.0-rc.1
0.1.0-rc.0
0.1.0-beta.5
0.1.0-beta.4
0.1.0-beta.3
0.1.0-beta.2
0.1.0-beta.1
0.1.0-alpha.33
0.1.0-alpha.32
0.1.0-alpha.31
0.1.0-alpha.30
0.1.0-alpha.29
0.1.0-alpha.28
0.1.0-alpha.27
0.1.0-alpha.26
0.1.0-alpha.25
0.1.0-alpha.24
0.1.0-alpha.23
0.1.0-alpha.22
0.1.0-alpha.21
0.1.0-alpha.20
0.1.0-alpha.19
0.1.0-alpha.18
0.1.0-alpha.17
0.1.0-alpha.16
0.1.0-alpha.15
0.1.0-alpha.14
0.1.0-alpha.13
0.1.0-alpha.12
0.1.0-alpha.11
0.1.0-alpha.10
0.1.0-alpha.9
0.1.0-alpha.8
0.1.0-alpha.7
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
Accessible and unstyled UI components library written in Elixir and TypeScript that integrates Zag.js state machines into the Phoenix Framework.
Current section
Files
Jump to
Current section
Files
lib/components/pin_input.ex
defmodule Corex.PinInput do
@moduledoc ~S'''
Phoenix implementation of [Zag.js Pin Input](https://zagjs.com/components/react/pin-input).
## Examples
### Basic
```heex
<.pin_input id="pin" count={4} class="pin-input">
<:label>Code</:label>
</.pin_input>
```
## Styling
Use data attributes to target elements:
```css
[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"] {}
```
If you wish to use the default Corex styling, you can use the class `pin-input` on the component.
This requires to install `Mix.Tasks.Corex.Design` first and import the component css file.
```css
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/pin-input.css";
```
You can then use modifiers
```heex
<.pin_input class="pin-input pin-input--accent pin-input--lg" count={4}>
<:label>Code</:label>
</.pin_input>
```
Learn more about modifiers and [Corex Design](https://corex-ui.com/components/pin-input#modifiers)
'''
defmodule Translation do
@moduledoc """
Translation struct for PinInput component strings.
Without gettext: `translation={%PinInput.Translation{ digit: "Box %{digit}" }}`
With gettext: `translation={%PinInput.Translation{ digit: "Digit %{digit}" }}` (add to catalog; component calls gettext at render)
"""
defstruct [:digit]
end
@doc type: :component
use Phoenix.Component
alias Corex.PinInput.Anatomy.{Control, HiddenInput, Input, Label, Props, Root}
alias Corex.PinInput.Connect
import Corex.Gettext, only: [gettext: 2]
import Corex.Helpers, only: [validate_value!: 1]
attr(:id, :string, required: false)
attr(:value, :list, default: [], doc: "Controlled value (list of single chars)")
attr(:default_value, :list, default: [], doc: "Uncontrolled initial value")
attr(:controlled, :boolean, default: false)
attr(:count, :integer, default: 4, doc: "Number of input boxes")
attr(:disabled, :boolean, default: false)
attr(:invalid, :boolean, default: false)
attr(:required, :boolean, default: false)
attr(:read_only, :boolean, default: false)
attr(:mask, :boolean, default: false)
attr(:otp, :boolean, default: false)
attr(:blur_on_complete, :boolean, default: false)
attr(:select_on_focus, :boolean, default: false)
attr(:name, :string, default: nil)
attr(:form, :string, default: nil)
attr(:dir, :string, default: nil, values: [nil, "ltr", "rtl"])
attr(:type, :string, default: "numeric", values: ["alphanumeric", "numeric", "alphabetic"])
attr(:placeholder, :string, default: "○")
attr(:on_value_change, :string, default: nil)
attr(:on_value_change_client, :string, default: nil)
attr(:on_value_complete, :string, default: nil)
attr(:translation, Corex.PinInput.Translation,
default: nil,
doc: "Override translatable strings"
)
attr(:rest, :global)
slot :label, required: false do
attr(:class, :string, required: false)
end
def pin_input(assigns) do
default_translation = %Translation{digit: "Digit %{digit}"}
assigns =
assigns
|> assign_new(:id, fn -> "pin-input-#{System.unique_integer([:positive])}" end)
|> assign_new(:dir, fn -> "ltr" end)
|> assign_new(:translation, fn -> default_translation end)
|> assign(:translation, merge_translation(assigns.translation, default_translation))
|> assign(:value, validate_value!(assigns[:value] || []))
|> assign(:default_value, validate_value!(assigns[:default_value] || []))
value_str = Enum.join(assigns.value, "")
default_value_str = Enum.join(assigns.default_value, "")
~H"""
<div
id={@id}
phx-hook="PinInput"
{@rest}
{Connect.props(%Props{
id: @id,
value: @value,
controlled: @controlled,
count: @count,
disabled: @disabled,
invalid: @invalid,
required: @required,
read_only: @read_only,
mask: @mask,
otp: @otp,
blur_on_complete: @blur_on_complete,
select_on_focus: @select_on_focus,
name: @name,
form: @form,
dir: @dir,
type: @type,
placeholder: @placeholder,
on_value_change: @on_value_change,
on_value_change_client: @on_value_change_client,
on_value_complete: @on_value_complete
})}
>
<div phx-update="ignore" {Connect.root(%Root{id: @id, dir: @dir})}>
<label :if={@label != []} {Connect.label(%Label{id: @id, dir: @dir})}>
{render_slot(@label)}
</label>
<input {Connect.hidden_input(%HiddenInput{id: @id, name: @name, value: if(@controlled, do: value_str, else: default_value_str)})} />
<div {Connect.control(%Control{id: @id, dir: @dir})}>
<input
:for={i <- 0..(@count - 1)}
type="text"
inputmode={@type}
maxlength="1"
autocomplete={if(@otp, do: "one-time-code", else: "off")}
{Connect.input(%Input{id: @id, index: i, aria_label: gettext(@translation.digit, digit: i + 1)})}
/>
</div>
</div>
</div>
"""
end
defp merge_translation(nil, default), do: default
defp merge_translation(partial, default) do
%Translation{
digit: partial.digit || default.digit
}
end
end