Packages
petal_components
2.7.2
4.7.0
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/slide_over.ex
defmodule PetalComponents.SlideOver do
use Phoenix.Component
alias Phoenix.LiveView.JS
attr(:origin, :string,
default: "right",
values: ["left", "right", "top", "bottom"],
doc: "slideover point of origin"
)
attr(:close_slide_over_target, :string,
default: nil,
doc:
"close_slide_over_target allows you to target a specific live component for the close event to go to. eg: close_slide_over_target={@myself}"
)
attr(:close_on_click_away, :boolean,
default: true,
doc: "whether the slideover should close when a user clicks away"
)
attr(:close_on_escape, :boolean,
default: true,
doc: "whether the slideover should close when a user hits escape"
)
attr(:title, :string, default: nil, doc: "slideover title")
attr(:max_width, :string,
default: "md",
values: ["sm", "md", "lg", "xl", "2xl", "full"],
doc: "sets container max-width"
)
attr(:class, :any, default: nil, doc: "CSS class")
attr(:hide, :boolean, default: false, doc: "slideover is hidden")
attr(:rest, :global)
slot(:inner_block, required: false)
def slide_over(assigns) do
~H"""
<div
{@rest}
phx-mounted={!@hide && show_slide_over(@origin)}
phx-remove={hide_slide_over(@origin, @close_slide_over_target)}
class="hidden pc-slide-over"
id="slide-over"
>
<div id="slide-over-overlay" class="pc-slideover__overlay" aria-hidden="true"></div>
<div
class={["pc-slideover__wrapper", get_margin_classes(@origin), @class]}
role="dialog"
aria-label="slide-over-content-wrapper"
aria-modal="true"
>
<div
id="slide-over-content"
class={get_classes(@max_width, @origin, @class)}
phx-click-away={@close_on_click_away && hide_slide_over(@origin, @close_slide_over_target)}
phx-window-keydown={@close_on_escape && hide_slide_over(@origin, @close_slide_over_target)}
phx-key="escape"
>
<!-- Header -->
<div class="pc-slideover__header">
<div class="pc-slideover__header__container">
<div class="pc-slideover__header__text">
<%= @title %>
</div>
<button
type="button"
phx-click={hide_slide_over(@origin, @close_slide_over_target)}
class="pc-slideover__header__button"
>
<div class="sr-only">Close</div>
<svg class="pc-slideover__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-slideover__content">
<%= render_slot(@inner_block) %>
</div>
</div>
</div>
</div>
"""
end
def show_slide_over(origin) do
{start_class, end_class} = get_transition_classes(origin)
%JS{}
|> JS.show(to: "#slide-over")
|> JS.show(
to: "#slide-over-overlay",
transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"}
)
|> JS.show(
to: "#slide-over-content",
transition: {
"transition-all transform ease-out duration-300",
start_class,
end_class
}
)
|> JS.add_class("overflow-hidden", to: "body")
|> JS.focus_first(to: "#slide-over-content")
end
# The live view that calls <.slide_over> will need to handle the "close_slide_over" event. eg:
# def handle_event("close_slide_over", _, socket) do
# {:noreply, push_patch(socket, to: Routes.moderate_users_path(socket, :index))}
# end
def hide_slide_over(origin, close_slide_over_target \\ nil) do
{end_class, start_class} = get_transition_classes(origin)
js =
JS.remove_class("overflow-hidden", to: "body")
|> JS.hide(
transition: {
"ease-in duration-200",
"opacity-100",
"opacity-0"
},
to: "#slide-over-overlay"
)
|> JS.hide(
transition: {
"ease-in duration-200",
start_class,
end_class
},
to: "#slide-over-content"
)
|> JS.hide(to: "#slide-over")
if close_slide_over_target do
JS.push(js, "close_slide_over", target: close_slide_over_target)
else
JS.push(js, "close_slide_over")
end
end
defp get_transition_classes(origin) do
case origin do
"left" -> {"-translate-x-full", "translate-x-0"}
"right" -> {"translate-x-full", "translate-x-0"}
"top" -> {"-translate-y-full", "translate-y-0"}
"bottom" -> {"translate-y-full", "translate-y-0"}
end
end
defp get_classes(max_width, origin, class) do
base_classes = "pc-slideover__box"
slide_over_classes =
case origin do
"left" -> "fixed left-0 inset-y-0 transform -translate-x-full"
"right" -> "fixed right-0 inset-y-0 transform translate-x-full"
"top" -> "fixed inset-x-0 top-0 transform -translate-y-full"
"bottom" -> "fixed inset-x-0 bottom-0 transform translate-y-full"
end
max_width_class =
case origin do
x when x in ["left", "right"] ->
"pc-slideover__box--#{max_width}"
x when x in ["top", "bottom"] ->
""
end
custom_classes = class
[slide_over_classes, max_width_class, base_classes, custom_classes]
end
defp get_margin_classes(margin) do
case margin do
"left" -> "mr-10"
"right" -> "ml-10"
"top" -> "mb-10"
"bottom" -> "mt-10"
end
end
end