Adding the module below will allow you to add the following drop down menu with little effort to your Liveview project.
Here is what we are going to make.
Here is what we are going to make.
Lets add a Dropdown Function Component.
Assuming you are using the default tailwind css library just copy this module and you are ready to go.
Place this module in your project’s web components folder and replace the YourAppWeb
with your projects namespace.
defmodule YourAppWeb.MenuComponents do
# Gives use our foundation for our function component.
use Phoenix.Component
# Will be using this to toggle open and close without server side code
alias Phoenix.LiveView.JS
# We want to give our component an id so we can target it with our JS function.
attr :id, :string, required: true
# A way to add your own styles
attr :class, :string, default: nil
# A slot for creating <:item> elements
slot :item, required: true
# The content of our button
slot :inner_block, required: true
def dropdown(assigns) do
~H"""
<div id={"#{@id}-dropdown"} class={["relative", @class]}>
<button
phx-click={show("##{@id}-dropdown-menu") |> JS.set_attribute({"aria-expanded", "true"})}
phx-click-away={hide("##{@id}-dropdown-menu") |> JS.set_attribute({"aria-expanded", "false"})}
type="button"
class="-m-1.5 block p-1.5 text-zinc-400 dark:text-zinc-300 hover:text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full hover:text-zinc-900 dark:hover:text-zinc-100 transition duration-300 ease-in-out transition-colors"
id={"#{@id}-dropdown-button"}
aria-expanded="false"
aria-haspopup="true"
>
<span class="sr-only">Open dropdown</span>
<%= render_slot(@inner_block) %>
</button>
<div
id={"#{@id}-dropdown-menu"}
class="hidden absolute right-0 z-10 mt-0.5 w-32 origin-top-right rounded-md bg-white dark:bg-zinc-700 py-2 shadow-lg ring-1 ring-zinc-900/5 dark:ring-zinc-100/5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="#{@id}-dropdown-menu-button"
tabindex="-1"
>
<div
:for={item <- @item}
role="menuitem"
tabindex="-1"
class="hover:bg-zinc-200 dark:hover:bg-zinc-800 transition duration-300 ease-in-out transition-colors"
>
<%= render_slot(item) %>
</div>
</div>
</div>
"""
end
# These following functions came for the core components by the phx.new generator. You could easily import these functions instead of defining them here.
defp show(js \\ %JS{}, selector) do
JS.show(js,
to: selector,
time: 300,
transition:
{"transition-all transform ease-out duration-300",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
"opacity-100 translate-y-0 sm:scale-100"}
)
end
defp hide(js \\ %JS{}, selector) do
JS.hide(js,
to: selector,
time: 200,
transition:
{"transition-all transform ease-in duration-200",
"opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
end
To use
<YourAppWeb.MenuComponents.dropdown id={id} class="ml-auto">
<span>Dropdown menu</span>
<:item>
<.link
class="block px-3 py-1 text-sm/6 text-zinc-900 dark:text-zinc-100"
patch={~p"/"}
>
Somelink
</.link>
</:item>
<:item>
<.link
class="block px-3 py-1 text-sm/6 text-zinc-900 dark:text-zinc-100"
patch={~p"/"}
>
Somelink
</.link>
</:item>
</YourAppWeb.Web.MenuComponents.dropdown>
Bonus points, import your module so you can just simply use <.dropdown>
in your templates.