Corex. ColorPicker
(Corex v0.1.2)
View Source
Phoenix implementation of Zag.js Color Picker.
Anatomy
Basic
<.color_picker
value="rgb(25, 9, 192, 0.9)"
label="Select Color (RGBA)"
presets={["#ff0000", "#00ff00", "#0000ff", "rgb(25, 9, 192, 0.9)"]}
class="color-picker"
/>Style
Target elements via data attributes:
[data-scope="color-picker"][data-part="root"] {}
[data-scope="color-picker"][data-part="label"] {}
[data-scope="color-picker"][data-part="control"] {}
[data-scope="color-picker"][data-part="trigger"] {}
[data-scope="color-picker"][data-part="content"] {}
[data-scope="color-picker"][data-part="area"] {}
[data-scope="color-picker"][data-part="channel-slider"] {}
[data-scope="color-picker"][data-part="swatch-trigger"] {}
[data-scope="color-picker"][data-part="error"] {}Import the Corex design:
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/color-picker.css";API
Requires a stable id on <.color_picker>.
| Function | Action | Returns |
|---|---|---|
set_value/2 | Set color (client) | %Phoenix.LiveView.JS{} |
set_value/3 | Set color (server) | socket |
set_value
<.action phx-click={Corex.ColorPicker.set_value("color-picker-api", "#ff0000")} class="button button--sm">
Set red
</.action>
<.color_picker id="color-picker-api" value="#000000" label="Color" class="color-picker" />def handle_event("set_color", %{"color" => hex}, socket) do
{:noreply, Corex.ColorPicker.set_value(socket, "color-picker-api", hex)}
endEvents
Server events
| Event | When | Payload |
|---|---|---|
on_value_change="color_value_changed" | Color changes | %{"id" => id, "valueAsString" => value} |
on_open_change="color_open_changed" | Open state changes | %{"id" => id, "open" => open} |
on_value_change
<.color_picker
class="color-picker"
value="#3b82f6"
label="Color"
on_value_change="color_value_changed"
/>def handle_event("color_value_changed", %{"valueAsString" => value}, socket) do
{:noreply, assign(socket, :color, value)}
endon_open_change
<.color_picker
class="color-picker"
value="#3b82f6"
label="Color"
on_open_change="color_open_changed"
/>def handle_event("color_open_changed", %{"open" => open}, socket) do
{:noreply, assign(socket, :color_picker_open, open)}
endClient events
| Event | When | event.detail |
|---|---|---|
on_value_change_client="color-value-changed" | Color changes | id, valueAsString |
on_open_change_client="color-open-changed" | Open state changes | id, open |
on_format_change_client="color-format-changed" | Format changes | id, format |
on_pointer_down_outside_client="color-pointer-down-outside" | Pointer down outside | id, ... |
on_focus_outside_client="color-focus-outside" | Focus outside | id, ... |
on_interact_outside_client="color-interact-outside" | Interact outside | id, ... |
on_value_change_client
<.color_picker
id="color-picker-events-client"
class="color-picker"
value="#3b82f6"
label="Color"
on_value_change_client="color-value-changed"
/>document.getElementById("color-picker-events-client")?.addEventListener("color-value-changed", (e) => {
console.log(e.detail);
});on_open_change_client
<.color_picker
id="color-picker-open-events-client"
class="color-picker"
value="#3b82f6"
label="Color"
on_open_change_client="color-open-changed"
/>document.getElementById("color-picker-open-events-client")?.addEventListener("color-open-changed", (e) => {
console.log(e.detail);
});Form
Set the form id in to_form/2 and use <.form for={@form}>. Use field={@form[:color]} so the picker name matches the form. For Ecto validation in LiveView, add phx-change on the form so params stay in sync.
For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:color])} when you want alert borders after validation.
Phoenix form (changeset)
Heex
<.form
:let={f}
for={@form}
action="/color-picker/form"
method="post"
>
<.color_picker
field={f[:color]}
label="Color"
class="color-picker"
presets={["#ff0000", "#00ff00", "#0000ff"]}
>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.color_picker>
<.action type="submit" class="button button--accent">
Submit
</.action>
</.form>Elixir
def color_picker_form_page(conn, _params) do
phoenix_form =
Phoenix.Component.to_form(%{"color" => "#3b82f6"},
as: :color_picker_phoenix,
id: "color-picker-form-phoenix"
)
render(conn, :color_picker_form_page, phoenix_form: phoenix_form)
end
def color_picker_form_submit(conn, params) do
if is_map(params["color_picker_phoenix"]) do
color = params["color_picker_phoenix"]["color"] || ""
conn
|> put_flash(:info, "Submitted: color=#{inspect(color)}")
|> redirect(to: "/color-picker/form")
end
endEcto changeset (validation)
Heex
<.form
:let={f}
for={@form}
action="/color-picker/form"
method="post"
>
<.color_picker
field={f[:color]}
label="Color"
class="color-picker"
presets={["#ff0000", "#00ff00", "#0000ff"]}
>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.color_picker>
<.action type="submit" class="button button--accent">
Submit
</.action>
</.form>Elixir
def color_picker_form_validate_page(conn, _params) do
changeset =
MyApp.Form.ColorPickerForm.changeset_validate(%MyApp.Form.ColorPickerForm{}, %{})
form =
Phoenix.Component.to_form(changeset,
as: :color_picker_validate,
id: "color-picker-validate-form"
)
render(conn, :color_picker_form_page, form: form)
end
def color_picker_form_validate_create(conn, %{"color_picker_validate" => params}) do
case MyApp.Form.ColorPickerForm.changeset_validate(%MyApp.Form.ColorPickerForm{}, params) do
%Ecto.Changeset{valid?: true} = changeset ->
data = Ecto.Changeset.apply_changes(changeset)
conn
|> put_flash(:info, "Saved: color=#{data.color}")
|> redirect(to: "/settings")
changeset ->
changeset = Map.put(changeset, :action, :insert)
form =
Phoenix.Component.to_form(changeset,
as: :color_picker_validate,
id: "color-picker-validate-form"
)
render(conn, :color_picker_form_page, form: form)
end
endEcto
defmodule MyApp.Form.ColorPickerForm do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :color, :string, default: "#3b82f6"
end
def changeset(form, attrs \\ %{}) do
form
|> cast(attrs, [:color])
|> validate_required([:color])
end
def changeset_validate(form, attrs \\ %{}) do
form
|> cast(attrs, [:color])
|> validate_required([:color])
|> validate_alpha_max_50()
end
defp validate_alpha_max_50(changeset) do
with value when is_binary(value) <- get_field(changeset, :color),
[_, alpha] <-
Regex.run(~r/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/, value),
{float_val, _} <- Float.parse(alpha),
true <- float_val > 0.5 do
add_error(changeset, :color, "maximum alpha allowed is 50%")
else
_ -> changeset
end
end
endNative form (plain HTML)
<form
action="/color-picker/form"
method="post"
>
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
<.color_picker
name="color_picker_form[color]"
value="#3b82f6"
label="Color"
class="color-picker"
/>
<.action type="submit" class="button button--accent">
Submit
</.action>
</form>Elixir
def color_picker_form_submit(conn, %{"color_picker_form" => %{"color" => color}}) do
conn
|> put_flash(:info, "Submitted: color=#{color}")
|> redirect(to: "/color-picker/form")
end
Summary
API
Set the picker value from a control (phx-click). value is a color string (e.g. hex).
Set the value from handle_event.
Components
Attributes
id(:string) - The id of the color picker.value(:string) - Initial color string sent asdata-default-valuefor the hook. Defaults to"#000000".name(:string) - The name attribute for form submission. Defaults tonil.label(:string) - Label for the color picker trigger. Defaults to"Select Color".close_on_select(:boolean) - Defaults totrue.open_auto_focus(:boolean) - Defaults totrue.disabled(:boolean) - Defaults tofalse.invalid(:boolean) - Defaults tofalse.read_only(:boolean) - Defaults tofalse.required(:boolean) - Defaults tofalse.dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".positioning(:map) - 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: false, offset: nil}.presets(:list) - Defaults to[].class(:string) - Defaults tonil.on_value_change(:string) - Defaults tonil.on_value_change_client(:string) - Defaults tonil.on_value_change_end(:string) - Defaults tonil.on_value_change_end_client(:string) - Defaults tonil.on_open_change(:string) - Defaults tonil.on_open_change_client(:string) - Defaults tonil.on_format_change(:string) - Defaults tonil.on_format_change_client(:string) - Defaults tonil.on_pointer_down_outside(:string) - Defaults tonil.on_pointer_down_outside_client(:string) - Defaults tonil.on_focus_outside(:string) - Defaults tonil.on_focus_outside_client(:string) - Defaults tonil.on_interact_outside(:string) - Defaults tonil.on_interact_outside_client(:string) - Defaults tonil.translation(Corex.ColorPicker.Translation) - Override translatable strings. Defaults tonil.errors(:list) - Error messages to display (non-field API). Defaults to[].field(Phoenix.HTML.FormField) - A form field, e.g. f[:color] or @form[:color].- Global attributes are accepted.
Slots
error- Accepts attributes:class(:string)
API
Set the picker value from a control (phx-click). value is a color string (e.g. hex).
<.action phx-click={Corex.ColorPicker.set_value("my-color-picker", "#226677")}>Set</.action>
<.color_picker id="my-color-picker" class="color-picker" label="Color" value="#000000" />document.getElementById("my-color-picker")?.dispatchEvent(
new CustomEvent("corex:color-picker:set-value", {
bubbles: false,
detail: { value: "#226677" },
})
);
Set the value from handle_event.
<.action phx-click="pick_color" phx-value-value="#226677">Set</.action>
<.color_picker id="my-color-picker" class="color-picker" label="Color" value="#000000" />def handle_event("pick_color", %{"value" => v}, socket) do
{:noreply, Corex.ColorPicker.set_value(socket, "my-color-picker", v)}
end