Packages
corex
0.1.0-rc.1
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/toggle.ex
defmodule Corex.Toggle do
@moduledoc ~S'''
Phoenix implementation of [Zag.js Toggle](https://zagjs.com/components/react/toggle).
## Anatomy
<!-- tabs-open -->
### Minimal
```heex
<.toggle class="toggle">
lorem
</.toggle>
```
### With indicator
```heex
<.toggle class="toggle">
<:indicator><.heroicon name="hero-bold" /></:indicator>
Bold
</.toggle>
<.toggle class="toggle">
<:indicator><.heroicon name="hero-bold" /></:indicator>
<span class="sr-only">Bold</span>
</.toggle>
```
### Dual label
Set `data-toggle-dual-label` on the host to swap visible label text when pressed. Use a second `<span data-pressed>` for the on-state label.
```heex
<.toggle class="toggle" data-toggle-dual-label>
<span>lorem</span>
<span data-pressed>donec</span>
</.toggle>
```
<!-- tabs-close -->
## API
Requires a stable `id` on `<.toggle>`.
| Function | Action | Returns |
| -------- | ------ | ------- |
| [`set_pressed/2`](#set_pressed/2) | Set pressed state (client) | `%Phoenix.LiveView.JS{}` |
| [`set_pressed/3`](#set_pressed/3) | Set pressed state (server) | `socket` |
| [`toggle_pressed/2`](#toggle_pressed/2) | Toggle pressed (client) | `%Phoenix.LiveView.JS{}` |
| [`toggle_pressed/3`](#toggle_pressed/3) | Toggle pressed (server) | `socket` |
## Events
Pick an event name and pass it to `on_*` on `<.toggle>`.
### Server events
| Event | When | Payload |
| ----- | ---- | ------- |
| `on_pressed_change="toggle_pressed_changed"` | Pressed state changes | `%{"id" => id, "pressed" => boolean}` |
<!-- tabs-open -->
### on_pressed_change
```heex
<.toggle
class="toggle"
controlled
pressed={false}
on_pressed_change="toggle_pressed_changed"
>
lorem
</.toggle>
```
```elixir
def handle_event("toggle_pressed_changed", %{"pressed" => pressed}, socket) do
p = pressed == true or pressed == "true"
{:noreply, assign(socket, :pressed, p)}
end
```
<!-- tabs-close -->
### Client events
| Event | When | `event.detail` |
| ----- | ---- | -------------- |
| `on_pressed_change_client="toggle-client-changed"` | Pressed state changes | `id`, `pressed` |
<!-- tabs-open -->
### on_pressed_change_client
```heex
<.toggle
id="toggle-on-pressed-change-client"
class="toggle"
on_pressed_change_client="toggle-client-changed"
>
lorem
</.toggle>
```
```javascript
const el = document.getElementById("toggle-on-pressed-change-client");
el?.addEventListener("toggle-client-changed", (e) => console.log(e.detail));
```
<!-- tabs-close -->
## Patterns
<!-- tabs-open -->
### Controlled
For server-owned pressed state, set `controlled`, bind `pressed`, and handle `on_pressed_change` in LiveView.
```heex
<.toggle
class="toggle"
controlled
pressed={@pressed}
on_pressed_change="toggle_patterns_pressed"
>
lorem
</.toggle>
```
```elixir
def mount(_params, _session, socket) do
{:ok, assign(socket, :pressed, false)}
end
def handle_event("toggle_patterns_pressed", %{"pressed" => pressed}, socket) do
{:noreply, assign(socket, :pressed, pressed == true or pressed == "true")}
end
```
<!-- tabs-close -->
## Style
Target parts with `data-scope` and `data-part`, or use Corex Design: import tokens and `toggle.css`, then set `class="toggle"` on `<.toggle>`.
```css
[data-scope="toggle"][data-part="root"] {}
[data-scope="toggle"][data-part="indicator"] {}
```
```css
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/toggle.css";
```
Stack modifiers on the host (`class` on `<.toggle>`).
<!-- tabs-open -->
### Color
| Modifier | Classes |
| -------- | ------- |
| Default | `toggle` |
| Accent | `toggle toggle--accent` |
| Brand | `toggle toggle--brand` |
| Alert | `toggle toggle--alert` |
| Info | `toggle toggle--info` |
| Success | `toggle toggle--success` |
### Size
| Modifier | Classes |
| -------- | ------- |
| SM | `toggle toggle--sm` |
| MD | `toggle toggle--md` |
| LG | `toggle toggle--lg` |
| XL | `toggle toggle--xl` |
### Rounded
| Modifier | Classes |
| -------- | ------- |
| None | `toggle toggle--rounded-none` |
| SM | `toggle toggle--rounded-sm` |
| MD | `toggle toggle--rounded-md` |
| LG | `toggle toggle--rounded-lg` |
| XL | `toggle toggle--rounded-xl` |
| Full | `toggle toggle--rounded-full` |
<!-- tabs-close -->
'''
@doc type: :component
use Phoenix.Component
import Corex.Api.Doc
alias Corex.Toggle.Anatomy.{Indicator, Props, Root}
alias Corex.Toggle.Connect
alias Phoenix.LiveView
alias Phoenix.LiveView.JS
attr(:id, :string, required: false)
attr(:pressed, :boolean,
default: false,
doc: "Initial or controlled pressed state"
)
attr(:controlled, :boolean, default: false)
attr(:disabled, :boolean, default: false)
attr(:dir, :string,
default: nil,
values: [nil, "ltr", "rtl"],
doc: "When nil, derived from document"
)
attr(:on_pressed_change, :string, default: nil)
attr(:on_pressed_change_client, :string, default: nil)
attr(:rest, :global)
slot(:inner_block, required: false)
slot :indicator, required: false do
attr(:class, :string, required: false)
end
def toggle(assigns) do
assigns =
assigns
|> assign_new(:id, fn -> "toggle-#{System.unique_integer([:positive])}" end)
|> assign_new(:dir, fn -> "ltr" end)
~H"""
<div
id={@id}
phx-hook="Toggle"
data-loading
phx-mounted={Phoenix.LiveView.JS.ignore_attributes(["data-loading"])}
{@rest}
{Connect.props(%Props{
id: @id,
controlled: @controlled,
pressed: @pressed,
disabled: @disabled,
dir: @dir,
on_pressed_change: @on_pressed_change,
on_pressed_change_client: @on_pressed_change_client
})}
>
<button
phx-mounted={Connect.ignore_root(%Root{id: @id, dir: @dir, pressed: @pressed, disabled: @disabled})}
{Connect.root(%Root{id: @id, dir: @dir, pressed: @pressed, disabled: @disabled})}
>
<span
:if={@indicator != []}
class={Map.get(Enum.at(@indicator, 0), :class)}
phx-mounted={Connect.ignore_indicator(%Indicator{id: @id, dir: @dir, pressed: @pressed, disabled: @disabled})}
{Connect.indicator(%Indicator{id: @id, dir: @dir, pressed: @pressed, disabled: @disabled})}
>
{render_slot(@indicator)}
</span>
{render_slot(@inner_block)}
</button>
</div>
"""
end
api_doc(~S"""
Set pressed state from a control (`phx-click`).
```heex
<.action phx-click={Corex.Toggle.set_pressed("my-toggle", true)}>On</.action>
<.toggle id="my-toggle" class="toggle">Label</.toggle>
```
```javascript
document.getElementById("my-toggle")?.dispatchEvent(
new CustomEvent("corex:toggle:set-pressed", {
bubbles: false,
detail: { pressed: true },
})
);
```
""")
def set_pressed(toggle_id, pressed) when is_binary(toggle_id) and is_boolean(pressed) do
JS.dispatch("corex:toggle:set-pressed",
to: "##{toggle_id}",
detail: %{pressed: pressed},
bubbles: false
)
end
api_doc(~S"""
Set pressed state from `handle_event`.
```heex
<.action phx-click="press_toggle">On</.action>
<.toggle id="my-toggle" class="toggle">Label</.toggle>
```
```elixir
def handle_event("press_toggle", _, socket) do
{:noreply, Corex.Toggle.set_pressed(socket, "my-toggle", true)}
end
```
""")
def set_pressed(socket, toggle_id, pressed)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(toggle_id) and
is_boolean(pressed) do
LiveView.push_event(socket, "toggle_set_pressed", %{
id: toggle_id,
pressed: pressed
})
end
api_doc(~S"""
Flip pressed state from a control (`phx-click`).
```heex
<.action phx-click={Corex.Toggle.toggle_pressed("my-toggle")}>Toggle</.action>
<.toggle id="my-toggle" class="toggle">Label</.toggle>
```
```javascript
document.getElementById("my-toggle")?.dispatchEvent(
new CustomEvent("corex:toggle:toggle-pressed", { bubbles: false })
);
```
""")
def toggle_pressed(toggle_id) when is_binary(toggle_id) do
JS.dispatch("corex:toggle:toggle-pressed",
to: "##{toggle_id}",
bubbles: false
)
end
api_doc(~S"""
Flip pressed state from `handle_event`.
```heex
<.action phx-click="flip_toggle">Toggle</.action>
<.toggle id="my-toggle" class="toggle">Label</.toggle>
```
```elixir
def handle_event("flip_toggle", _, socket) do
{:noreply, Corex.Toggle.toggle_pressed(socket, "my-toggle")}
end
```
""")
def toggle_pressed(socket, toggle_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(toggle_id) do
LiveView.push_event(socket, "toggle_toggle_pressed", %{id: toggle_id})
end
end