Packages
noora
0.60.0
0.86.0
0.85.0
0.84.1
0.84.0
0.83.2
0.83.1
0.83.0
0.82.7
0.82.6
0.82.5
0.82.4
0.82.3
0.82.2
0.82.1
0.82.0
0.81.4
0.81.3
0.81.2
0.81.1
0.81.0
0.80.1
0.80.0
0.79.1
0.79.0
0.78.1
0.78.0
0.77.4
0.77.3
0.77.2
0.77.1
0.77.0
0.76.0
0.75.0
0.74.0
0.73.0
0.72.0
0.71.0
0.70.0
0.69.1
0.69.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.2
0.64.1
0.64.0
0.63.2
0.63.1
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.1
0.53.0
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.3
0.44.2
0.44.1
0.44.0
0.43.0
0.42.0
0.41.0
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.1
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.1
0.32.0
0.31.0
0.30.0
0.29.2
0.29.1
0.29.0
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
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
A component library for Phoenix LiveView applications
Current section
Files
Jump to
Current section
Files
lib/noora/modal.ex
defmodule Noora.Modal do
@moduledoc """
Renders a modal component with customizable headers, content, and footers, supporting various header types, sizes, and event handling.
## Example
```elixir
<.modal id="user-profile-modal" title="Edit Profile" description="Update your personal information">
<:trigger>
<.button>Open Modal</.button>
</:trigger>
<:header_button>
<.button on_click="save-profile">Save</.button>
</:header_button>
<form id="profile-form">
<.input name="name" label="Name" />
<.input name="email" label="Email" type="email" />
</form>
<:footer>
<.modal_footer>
<:action>
<.button variant="ghost">Cancel</.button>
<.button variant="primary">Save Changes</.button>
</:action>
</.modal_footer>
</:footer>
</.modal>
```
"""
use Phoenix.Component
import Noora.DismissIcon
import Noora.Icon
import Noora.Utils
attr(:id, :string, required: true, doc: "The modal's unique identifier.")
attr(:title, :string, default: nil, doc: "Title of the modal")
attr(:description, :string,
default: nil,
doc: "Description of the modal. Only visible when header_size is 'large'"
)
attr(:header_type, :string,
values: ~w(default icon success info warning error),
default: "default",
doc: "Type of the header"
)
attr(:header_size, :string, values: ~w(small large), default: "large", doc: "Size of the header")
attr(:on_dismiss, :string, default: nil, doc: "Event to emit when the dismiss icon is clicked.")
attr(:on_open_change, :string,
default: nil,
doc: "An optional event to fire when the modal is opened or closed."
)
attr(:rest, :global, doc: "Additional HTML attributes")
slot(:trigger,
required: true,
doc: "The modal's trigger. Should be a button that accepts the attributes provided by the slot."
)
slot(:header_icon, doc: "Icon to be rendered in the header when type is 'icon'")
slot(:header_button)
slot(:footer, required: false, doc: "The modal's footer element.")
slot(:inner_block)
def modal(assigns) do
~H"""
<.portal id={@id <> "-portal"} target={"#" <> @id <> "-body"}>
{render_slot(@inner_block)}
</.portal>
<.portal
:if={has_slot_content?(@footer, assigns)}
id={@id <> "-footer-portal"}
target={"#" <> @id <> "-footer"}
>
{render_slot(@footer)}
</.portal>
<div
id={@id}
class="noora-modal"
phx-hook="NooraModal"
data-close-on-escape
data-close-on-interact-outside
data-prevent-scroll
data-on-open-change={@on_open_change}
phx-update="ignore"
{@rest}
>
{render_slot(@trigger, %{"data-part" => "trigger"})}
<div data-part="backdrop"></div>
<div data-part="positioner">
<div data-part="content">
<.modal_header
:if={@title}
title={@title}
description={@description}
type={@header_type}
size={@header_size}
on_dismiss={@on_dismiss}
>
<:header_button>{render_slot(@header_button)}</:header_button>
{render_slot(@header_icon)}
</.modal_header>
<div id={@id <> "-body"} data-part="body"></div>
<div :if={has_slot_content?(@footer, assigns)} id={@id <> "-footer"}></div>
</div>
</div>
</div>
"""
end
attr(:title, :string, required: true, doc: "Title of the modal")
attr(:description, :string,
default: nil,
doc: "Description of the modal. Only visible when size is 'large'"
)
attr(:type, :string,
values: ~w(default icon success info warning error),
default: "default",
doc: "Type of the header"
)
attr(:size, :string, values: ~w(small large), default: "large", doc: "Size of the header")
attr(:on_dismiss, :string, default: nil, doc: "Event to emit when the dismiss icon is clicked.")
slot(:inner_block, doc: "Icon to be rendered in the header when type is 'icon'")
slot(:header_button)
defp modal_header(assigns) do
~H"""
<div data-part="header" data-type={@type} data-size={@size}>
<.modal_header_icon :if={@type != "default"} type={@type}>
{render_slot(@inner_block)}
</.modal_header_icon>
<div data-part="header-content">
<div data-part="row">
<span data-part="title">{@title}</span>
<%= if not has_slot_content?(@header_button, assigns) do %>
<.dismiss_icon data-part="close-trigger" phx-click={@on_dismiss} />
<% end %>
</div>
<div :if={@size == "large"} data-part="description">
{@description}
</div>
</div>
<%= if has_slot_content?(@header_button, assigns) do %>
{render_slot(@header_button)}
<% end %>
</div>
"""
end
defp modal_header_icon(%{type: "icon"} = assigns) do
~H"""
<div data-part="icon">
{render_slot(@inner_block)}
</div>
"""
end
defp modal_header_icon(%{type: type} = assigns) when type in ["error", "info"] do
~H"""
<div data-part="icon">
<.alert_circle />
</div>
"""
end
defp modal_header_icon(%{type: "success"} = assigns) do
~H"""
<div data-part="icon">
<.circle_check />
</div>
"""
end
defp modal_header_icon(%{type: "warning"} = assigns) do
~H"""
<div data-part="icon">
<.alert_triangle />
</div>
"""
end
attr(:type, :string, values: ~w(default stretch), default: "default", doc: "Type of the footer")
slot(:action, doc: "Actions to be rendered in the footer")
slot(:inner_block)
def modal_footer(assigns) do
~H"""
<div data-part="footer" data-type={@type}>
<div :if={@type == "default"}>
{render_slot(@inner_block)}
</div>
<div data-part="actions">
<div :for={action <- @action}>
{render_slot(action)}
</div>
</div>
</div>
"""
end
end