defmodule PetalComponents.Dropdown do use Phoenix.Component alias Phoenix.LiveView.JS alias PetalComponents.Heroicons import PetalComponents.Link @transition_in_base "transition transform ease-out duration-100" @transition_in_start "transform opacity-0 scale-95" @transition_in_end "transform opacity-100 scale-100" @transition_out_base "transition ease-in duration-75" @transition_out_start "transform opacity-100 scale-100" @transition_out_end "transform opacity-0 scale-95" # prop js_lib, :string, default: "alpine_js", options: ["alpine_js", "live_view_js"] # prop label, :string # prop placement, :string, default: options: ["left", "right"] # slot default # slot trigger_element @doc """ <.dropdown label="Dropdown" js_lib="alpine_js|live_view_js"> <.dropdown_menu_item link_type="button"> Button item with icon <.dropdown_menu_item link_type="a" to="/" label="a item" /> <.dropdown_menu_item link_type="live_patch" to="/" label="Live Patch item" /> <.dropdown_menu_item link_type="live_redirect" to="/" label="Live Redirect item" /> """ def dropdown(assigns) do assigns = assigns |> assign_new(:options_container_id, fn -> "dropdown_#{Ecto.UUID.generate()}" end) |> assign_new(:js_lib, fn -> "alpine_js" end) |> assign_new(:placement, fn -> "left" end) |> assign_new(:label, fn -> nil end) |> assign_new(:trigger_element, fn -> nil end) |> assign_new(:extra_assigns, fn -> assigns_to_attributes(assigns, ~w( options_container_id js_lib placement label trigger_element class )a) end) ~H"""
" absolute z-30 w-56 mt-2 bg-white dark:bg-gray-800 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"} role="menu" id={@options_container_id} aria-orientation="vertical" aria-labelledby="options-menu" >
<%= render_slot(@inner_block) %>
""" end def dropdown_menu_item(assigns) do assigns = assigns |> assign_new(:link_type, fn -> "button" end) |> assign_new(:inner_block, fn -> nil end) |> assign_new(:to, fn -> nil end) |> assign_new(:classes, fn -> dropdown_menu_item_classes() end) |> assign_new(:extra_assigns, fn -> assigns_to_attributes(assigns, [ :link_type, :classes ]) end) ~H""" <.link link_type={@link_type} to={@to} class={@classes} {@extra_assigns}> <%= if @inner_block do %> <%= render_slot(@inner_block) %> <% else %> <%= @label %> <% end %> """ end defp trigger_button_classes(nil, nil), do: "flex items-center text-gray-400 rounded-full hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-primary-500" defp trigger_button_classes(_label, nil), do: "inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm dark:text-gray-300 dark:bg-gray-900 dark:hover:bg-gray-800 dark:focus:bg-gray-800 hover:bg-gray-50 focus:outline-none" defp trigger_button_classes(_label, _trigger_element), do: "align-middle" defp dropdown_menu_item_classes(), do: "block flex gap-2 items-center self-start dark:hover:bg-gray-700 dark:text-gray-300 justify-start px-4 py-2 text-sm text-gray-700 transition duration-150 dark:bg-gray-800 ease-in-out hover:bg-gray-100 w-full text-left" defp js_attributes("container", "alpine_js", _options_container_id) do %{ "x-data": "{open: false}", "@keydown.escape.stop": "open = false", "@click.outside": "open = false" } end defp js_attributes("button", "alpine_js", _options_container_id) do %{ "@click": "open = !open", "x-bind:aria-expanded": "open.toString()" } end defp js_attributes("options_container", "alpine_js", _options_container_id) do %{ "x-cloak": true, "x-show": "open", "x-transition:enter": @transition_in_base, "x-transition:enter-start": @transition_in_start, "x-transition:enter-end": @transition_in_end, "x-transition:leave": @transition_out_base, "x-transition:leave-start": @transition_out_start, "x-transition:leave-end": @transition_out_end } end defp js_attributes("container", "live_view_js", options_container_id) do %{ "phx-click-away": JS.hide( to: "##{options_container_id}", transition: {@transition_out_base, @transition_out_start, @transition_out_end} ) } end defp js_attributes("button", "live_view_js", options_container_id) do %{ "phx-click": JS.toggle( to: "##{options_container_id}", display: "block", in: {@transition_in_base, @transition_in_start, @transition_in_end}, out: {@transition_out_base, @transition_out_start, @transition_out_end} ) } end defp js_attributes("options_container", "live_view_js", _options_container_id) do %{ style: "display: none;" } end defp placement_class("left"), do: "right-0 origin-top-right" defp placement_class("right"), do: "left-0 origin-top-left" end