Packages
corex
0.1.0-alpha.25
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/editable.ex
defmodule Corex.Editable do
@moduledoc ~S'''
Phoenix implementation of [Zag.js Editable](https://zagjs.com/components/react/editable).
## Examples
### Basic
```heex
<.editable id="edit" value="Click to edit" class="editable">
<:label>Name</:label>
<:edit_trigger><.icon name="hero-pencil-square" class="icon" /></:edit_trigger>
<:submit_trigger><.icon name="hero-check" class="icon" /></:submit_trigger>
<:cancel_trigger><.icon name="hero-x-mark" class="icon" /></:cancel_trigger>
</.editable>
```
Required slots: `:label`, `:edit_trigger`, `:submit_trigger`, `:cancel_trigger`. Preview value is managed by the component and the Editable TS hook.
## Styling
Use data attributes: `[data-scope="editable"][data-part="root"]`, `area`, `label`, `input`, `preview`, `edit-trigger`, `control`, `submit-trigger`, `cancel-trigger`.
'''
@doc type: :component
use Phoenix.Component
alias Corex.Editable.Anatomy.{
Props,
Root,
Area,
Label,
Input,
Preview,
EditTrigger,
Triggers,
SubmitTrigger,
CancelTrigger
}
alias Corex.Editable.Connect
attr(:id, :string, required: false)
attr(:value, :string, default: "")
attr(:controlled, :boolean, default: false)
attr(:disabled, :boolean, default: false)
attr(:read_only, :boolean, default: false)
attr(:required, :boolean, default: false)
attr(:invalid, :boolean, default: false)
attr(:name, :string, default: nil)
attr(:form, :string, default: nil)
attr(:dir, :string, default: nil, values: [nil, "ltr", "rtl"])
attr(:edit, :boolean, default: false)
attr(:controlled_edit, :boolean, default: false)
attr(:default_edit, :boolean, default: false)
attr(:placeholder, :string, default: nil)
attr(:activation_mode, :string, default: nil, values: [nil, "dblclick", "focus"])
attr(:select_on_focus, :boolean, default: true)
attr(:on_value_change, :string, default: nil)
attr(:on_value_change_client, :string, default: nil)
attr(:rest, :global)
slot(:label, required: true)
slot(:edit_trigger, required: true)
slot(:submit_trigger, required: true)
slot(:cancel_trigger, required: true)
def editable(assigns) do
empty = String.trim(assigns[:value] || "") == ""
editing = assigns[:default_edit] || false
value_text = if(empty, do: assigns[:placeholder] || "", else: assigns[:value] || "")
assigns =
assigns
|> assign_new(:id, fn -> "editable-#{System.unique_integer([:positive])}" end)
|> assign_new(:dir, fn -> "ltr" end)
|> assign(:empty, empty)
|> assign(:editing, editing)
|> assign(:value_text, value_text)
~H"""
<div
id={@id}
phx-hook="Editable"
{@rest}
{Connect.props(%Props{
id: @id,
value: @value,
controlled: @controlled,
disabled: @disabled,
read_only: @read_only,
required: @required,
invalid: @invalid,
name: @name,
form: @form,
dir: @dir,
edit: @edit,
controlled_edit: @controlled_edit,
default_edit: @default_edit,
placeholder: @placeholder,
activation_mode: @activation_mode,
select_on_focus: @select_on_focus,
on_value_change: @on_value_change,
on_value_change_client: @on_value_change_client
})}
>
<div phx-update="ignore" {Connect.root(%Root{id: @id, dir: @dir})}>
<label {Connect.label(%Label{id: @id, dir: @dir})}>
{render_slot(@label)}
</label>
<div data-scope="editable" data-part="control">
<div {Connect.area(%Area{id: @id, dir: @dir, empty: @empty, editing: @editing, auto_resize: true})}>
<input type="text" {Connect.input(%Input{id: @id, disabled: @disabled, value: @value, placeholder: @placeholder, name: @name, form: @form, required: @required, read_only: @read_only, editing: @editing, aria_label: "editable input"})} />
<span {Connect.preview(%Preview{id: @id, dir: @dir, value_text: @value_text, empty: @empty, editing: @editing})}>
{@value_text}
</span>
</div>
<div {Connect.triggers(%Triggers{})}>
<button type="button" {Connect.edit_trigger(%EditTrigger{id: @id, dir: @dir, editing: @editing})}>
{render_slot(@edit_trigger)}
</button>
<button type="button" {Connect.submit_trigger(%SubmitTrigger{id: @id, dir: @dir, editing: @editing})}>
{render_slot(@submit_trigger)}
</button>
<button type="button" {Connect.cancel_trigger(%CancelTrigger{id: @id, dir: @dir, editing: @editing})}>
{render_slot(@cancel_trigger)}
</button>
</div>
</div>
</div>
</div>
"""
end
end