defmodule Shino.UI.Popover do @moduledoc """ Provides popover related components. > Wanna `DropdownMenu`? `Popover` + `Menu` = `DropdownMenu` ## Examples ```heex <.button variant="outline">Open Content ``` ## References * [shadcn/ui - Popover](https://ui.shadcn.com/docs/components/popover) * [shadcn/ui - Dropdown Menu](https://ui.shadcn.com/docs/components/dropdown-menu) * [@radix-ui/primitives - Popover](https://www.radix-ui.com/primitives/docs/components/popover) * [@radix-ui/primitives - Dropdown Menu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) """ use Shino.UI, :component defmodule Root do @moduledoc false defstruct [:id, :side, :align] end @doc """ The root contains all the parts of a popover. """ attr :id, :string, required: true attr :side, :string, values: ["top", "bottom", "left", "right"], default: "bottom" attr :align, :string, values: ["start", "center", "end"], default: "start" attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def root(assigns) do ~H"""
<%= render_slot(@inner_block, %Root{id: @id, side: @side, align: @align}) %>
""" end @doc """ Renders a popover trigger. """ attr :for, Root, required: true attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def trigger(assigns) do ~H""" """ end @doc """ Renders the content of a popover. """ attr :for, Root, required: true attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def content(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end defp toggle_popover(js \\ %JS{}, id) do selector_trigger = "##{id} .popover-trigger" selector_content = "##{id} .popover-content" js |> JS.toggle_attribute({"aria-expanded", "true", "false"}, to: selector_trigger) |> JS.toggle( in: {"transition duration-150 ease-in-out", "opacity-0 scale-95", "opacity-100 scale-100"}, out: {"transition duration-150 ease-in-out", "opacity-100 scale-100", "opacity-0 scale-95"}, to: selector_content, time: 150 ) end def hide_popover(js \\ %JS{}, root_or_id) def hide_popover(js, %Root{id: id}), do: hide_popover(js, id) def hide_popover(js, id) do selector_trigger = "##{id} .popover-trigger" selector_content = "##{id} .popover-content" js |> JS.set_attribute({"aria-expanded", "false"}, to: selector_trigger) |> JS.hide( transition: {"transition duration-150 ease-in-out", "opacity-100 scale-100", "opacity-0 scale-95"}, to: selector_content, time: 150 ) end @side_classes %{ "top" => "bottom-full mb-1", "bottom" => "top-full mt-1", "left" => "right-full mr-1", "right" => "left-full ml-1" } defp side_class(side), do: Map.fetch!(@side_classes, side) @align_classes %{ "start-horizontal" => "left-0", "center-horizontal" => "left-1/2 -translate-x-1/2", "end-horizontal" => "right-0", "start-vertical" => "top-0", "center-vertical" => "top-1/2 -translate-y-1/2", "end-vertical" => "bottom-0" } defp align_class(side, align) do key = cond do side in ["top", "bottom"] -> "#{align}-horizontal" side in ["left", "right"] -> "#{align}-vertical" end Map.fetch!(@align_classes, key) end end