Packages
petal_components
1.0.2
4.6.2
4.6.1
4.6.0
4.5.0
4.4.0
4.3.0
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.12
4.0.11
4.0.10
4.0.6
4.0.5
4.0.4
4.0.3
4.0.1
3.2.2
3.2.1
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
2.9.3
2.9.2
retired
2.9.1
retired
2.9.0
retired
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.1
2.2.0
2.1.2
2.1.1
2.1.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.1
1.7.0
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.14
1.2.13
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.19.10
0.19.9
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.1
0.14.0
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
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.1
0.2.0
0.1.0
Shadcn-style Phoenix LiveView components that AI assistants can actually use. Pair with the MCP server so AI coding tools can inspect the real component API.
Current section
Files
Jump to
Current section
Files
lib/petal_components/modal.ex
defmodule PetalComponents.Modal do
use Phoenix.Component
alias Phoenix.LiveView.JS
import PetalComponents.Helpers
attr(:title, :string, default: nil, doc: "modal title")
attr(:close_modal_target, :string,
default: nil,
doc:
"close_modal_target allows you to target a specific live component for the close event to go to. eg: close_modal_target={@myself}"
)
attr(:max_width, :string,
default: "md",
values: ["sm", "md", "lg", "xl", "2xl", "full"],
doc: "modal max width"
)
attr(:rest, :global)
slot(:inner_block, required: false)
def modal(assigns) do
assigns =
assigns
|> assign(:classes, get_classes(assigns))
~H"""
<div {@rest} id="modal">
<div id="modal-overlay" class="pc-modal__overlay" aria-hidden="true"></div>
<div class="pc-modal__wrapper" role="dialog" aria-modal="true">
<div
id="modal-content"
class={@classes}
phx-click-away={hide_modal(@close_modal_target)}
phx-window-keydown={hide_modal(@close_modal_target)}
phx-key="escape"
>
<!-- Header -->
<div class="pc-modal__header">
<div class="pc-modal__header__container">
<div class="pc-modal__header__text">
<%= @title %>
</div>
<button phx-click={hide_modal(@close_modal_target)} class="pc-modal__header__button">
<div class="sr-only">Close</div>
<svg class="pc-modal__header__close-svg">
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
</svg>
</button>
</div>
</div>
<!-- Content -->
<div class="pc-modal__content">
<%= render_slot(@inner_block) %>
</div>
</div>
</div>
</div>
"""
end
# The live view that calls <.modal> will need to handle the "close_modal" event. eg:
# def handle_event("close_modal", _, socket) do
# {:noreply, push_patch(socket, to: Routes.moderate_users_path(socket, :index))}
# end
def hide_modal(close_modal_target \\ nil) do
js =
%JS{}
|> JS.remove_class("overflow-hidden", to: "body")
|> JS.remove_class("animate-fade-in", to: "#modal-overlay")
|> JS.hide(
transition: {
"ease-in duration-200",
"opacity-100",
"opacity-0"
},
to: "#modal-overlay"
)
|> JS.hide(
transition: {
"ease-in duration-200",
"opacity-100 translate-y-0 md:scale-100",
"opacity-0 translate-y-4 md:translate-y-0 md:scale-95"
},
to: "#modal-content"
)
if close_modal_target do
JS.push(js, "close_modal", target: close_modal_target)
else
JS.push(js, "close_modal")
end
end
# We are unsure of what the best practice is for using this.
# Open to suggestions/PRs
def show_modal(js \\ %JS{}) do
js
|> JS.add_class("overflow-hidden", to: "body")
|> JS.show(
transition: {
"ease-in duration-300",
"opacity-0",
"opacity-100"
},
to: "#modal-overlay"
)
|> JS.show(
transition: {
"transition ease-in-out duration-200",
"opacity-0 translate-y-4",
"opacity-100 translate-y-0"
},
to: "#modal-content"
)
end
defp get_classes(assigns) do
opts = %{
max_width: assigns[:max_width] || "md",
class: assigns[:class] || ""
}
base_classes = "pc-modal__box"
max_width_class = "pc-modal__box--#{opts.max_width}"
custom_classes = opts.class
build_class([max_width_class, base_classes, custom_classes])
end
end