Corex.Dialog
(Corex v0.1.0-beta.5)
View Source
Phoenix implementation of Zag.js Dialog.
Examples
Basic Usage
With top-level slots:
<.dialog id="my-dialog" class="dialog">
<:trigger>Open Dialog</:trigger>
<:title>Dialog Title</:title>
<:description>
This is a dialog description that explains what the dialog is about.
</:description>
<:content>
<p>Dialog content goes here. You can add any content you want inside the dialog.</p>
</:content>
<:close_trigger>
<.heroicon name="hero-x-mark" class="icon" />
</:close_trigger>
</.dialog>With title, description, and close trigger inside content (use the same id as the dialog):
<.dialog id="my-dialog" class="dialog">
<:trigger>Open Dialog</:trigger>
<:content>
<.dialog_title id="my-dialog">Dialog Title</.dialog_title>
<.dialog_description id="my-dialog">
This is a dialog description that explains what the dialog is about.
</.dialog_description>
<p>Dialog content goes here. You can add any content you want inside the dialog.</p>
<.dialog_close_trigger id="my-dialog">
<.heroicon name="hero-x-mark" class="icon" />
</.dialog_close_trigger>
</:content>
</.dialog>Controlled Mode
<.dialog
id="my-dialog"
controlled
open={@dialog_open}
on_open_change="dialog_changed">
<:trigger>Open Dialog</:trigger>
<:content>
<:title>Dialog Title</:title>
<:description>Dialog description goes here.</:description>
<p>Dialog content</p>
<:close_trigger>Close</:close_trigger>
</:content>
</.dialog>def handle_event("dialog_changed", %{"open" => open}, socket) do
{:noreply, assign(socket, :dialog_open, open)}
endAnimation
Set animation on dialog (instant, js, or custom).
instant- Zag toggles the nativehiddenattribute, no animation.js- Web Animations API drives opacity/scale viaanimation_options(Corex.Animation.Scale).custom- the hook never re-applieshidden; the consumer drives the animation by listening to theCustomEventwhose type ison_open_change_client. Thedetailshape is:// event.detail (DialogOpenChangedDetail) { id, open, previousOpen }Closed visibility is provided by CSS baselines on
[data-state="closed"](seee2e/assets/corex/components/dialog.css), so the consumer only needs to drive the transition itself. Use the Scale helpers exported fromcorex(mirroring how Accordion / Tree view use the Height helpers).
import { animate } from "motion"
import {
findDialogBackdrop,
findDialogContent,
animateScaleOpen,
animateScaleClose,
} from "corex"
const reducedMotion = () =>
window.matchMedia("(prefers-reduced-motion: reduce)").matches
document.addEventListener("my-dialog-open-changed", (e) => {
const { id, open } = e.detail
const root = document.getElementById(id)
if (!root) return
const backdrop = findDialogBackdrop(root)
const content = findDialogContent(root)
if (open) {
if (backdrop)
animateScaleOpen(backdrop, { animator: animate, duration: 0.5, easing: "ease-out" })
if (content) {
animateScaleOpen(content, {
animator: animate,
duration: 0.7,
easing: [0.16, 1, 0.3, 1],
scaleStart: 0.7,
scaleEnd: 1,
})
if (!reducedMotion())
animate(
content,
{ y: [60, 0], filter: ["blur(12px)", "blur(0px)"] },
{ duration: 0.7, easing: [0.16, 1, 0.3, 1] },
)
}
} else {
if (backdrop)
animateScaleClose(backdrop, { animator: animate, duration: 0.4, easing: "ease-in" })
if (content) {
animateScaleClose(content, {
animator: animate,
duration: 0.35,
easing: "ease-in",
scaleStart: 0.8,
scaleEnd: 1,
})
if (!reducedMotion())
animate(
content,
{ y: [0, 40], filter: ["blur(0px)", "blur(12px)"] },
{ duration: 0.35, easing: "ease-in" },
)
}
}
})API Control
In order to use the API, you must use an id on the component
Client-side
<button phx-click={Corex.Dialog.set_open("my-dialog", true)}>
Open Dialog
</button>Server-side
def handle_event("open_dialog", _, socket) do
{:noreply, Corex.Dialog.set_open(socket, "my-dialog", true)}
endStyling
Use data attributes to target elements:
[data-scope="dialog"][data-part="root"] {}
[data-scope="dialog"][data-part="trigger"] {}
[data-scope="dialog"][data-part="backdrop"] {}
[data-scope="dialog"][data-part="positioner"] {}
[data-scope="dialog"][data-part="content"] {}
[data-scope="dialog"][data-part="title"] {}
[data-scope="dialog"][data-part="description"] {}
[data-scope="dialog"][data-part="close-trigger"] {}If you wish to use the default Corex styling, you can use the class dialog on the component.
This requires to install Mix.Tasks.Corex.Design first and import the component css file.
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/dialog.css";You can then use modifiers
<.dialog class="dialog dialog--accent dialog--lg">
Summary
Components
Renders a dialog component.
Renders the dialog close button. Use inside <:content> when not using the top-level <:close_trigger> slot. Pass the same id as the parent dialog.
Renders the dialog description. Use inside <:content> when not using the top-level <:description> slot. Pass the same id as the parent dialog.
Renders the dialog title. Use inside <:content> when not using the top-level <:title> slot. Pass the same id as the parent dialog.
API
Sets the dialog open state from client-side. Returns a Phoenix.LiveView.JS command.
Sets the dialog open state from server-side. Pushes a LiveView event.
Components
Renders a dialog component.
Attributes
id(:string) - The id of the dialog, useful for API to identify the dialog.open(:boolean) - The initial open state or the controlled open state. Defaults tofalse.controlled(:boolean) - Whether the dialog is controlled. Only in LiveView, the on_open_change event is required. Defaults tofalse.modal(:boolean) - Whether the dialog is modal. Defaults tofalse.close_on_interact_outside(:boolean) - Whether to close the dialog when clicking outside. Defaults totrue.close_on_escape(:boolean) - Whether to close the dialog when pressing Escape. Defaults totrue.prevent_scroll(:boolean) - Whether to prevent body scroll when dialog is open. Defaults tofalse.restore_focus(:boolean) - Whether to restore focus when dialog closes. Defaults totrue.dir(:string) - The direction of the dialog. When nil, derived from document (html lang + config :rtl_locales). Defaults tonil. Must be one ofnil,"ltr", or"rtl".on_open_change(:string) - Server event name when the open state changes. Payload:%{id, open, previousOpen}(TS:DialogOpenChangedDetail). Defaults tonil.on_open_change_client(:string) - DOM event name dispatched when the open state changes.event.detailmatchesDialogOpenChangedDetail. Required foranimation="custom". Defaults tonil.animation(:string) - Open and close: native hidden (instant), Web Animations viaCorex.Animation.Scale(js), or events only (custom). Defaults to"js". Must be one of"instant","js", or"custom".animation_options(Corex.Animation.Scale) - Wired to the host whenanimationisjsonly. Custom transitions ignore this assign. SeeCorex.Animation.Scale(opacity, scale, timing,block_interaction). Defaults to%Corex.Animation.Scale{duration: 0.3, easing: "ease", opacity_start: 0.0, opacity_end: 1.0, scale_start: 0.96, scale_end: 1.0, block_interaction: false}.translation(Corex.Dialog.Translation) - Override translatable strings. Defaults tonil.aria_label(:string) - Accessible name when no visible dialog title is rendered; defaults to a translated Dialog label. Defaults tonil.- Global attributes are accepted.
Slots
trigger(required) - Accepts attributes:class(:string)aria_label(:string)
content(required) - Accepts attributes:class(:string)
title- Accepts attributes:class(:string)
description- Accepts attributes:class(:string)
close_trigger- Accepts attributes:class(:string)
Renders the dialog close button. Use inside <:content> when not using the top-level <:close_trigger> slot. Pass the same id as the parent dialog.
Attributes
id(:string) (required)dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".aria_label(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
Renders the dialog description. Use inside <:content> when not using the top-level <:description> slot. Pass the same id as the parent dialog.
Attributes
id(:string) (required)dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".- Global attributes are accepted.
Slots
inner_block(required)
Renders the dialog title. Use inside <:content> when not using the top-level <:title> slot. Pass the same id as the parent dialog.
Attributes
id(:string) (required)dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".- Global attributes are accepted.
Slots
inner_block(required)
API
Sets the dialog open state from client-side. Returns a Phoenix.LiveView.JS command.
Examples
<button phx-click={Corex.Dialog.set_open("my-dialog", true)}>
Open Dialog
</button>
Sets the dialog open state from server-side. Pushes a LiveView event.
Examples
def handle_event("open_dialog", _params, socket) do
socket = Corex.Dialog.set_open(socket, "my-dialog", true)
{:noreply, socket}
end