defmodule PlausibleWeb.Components.Generic do @moduledoc """ Generic reusable components """ use Phoenix.Component, global_prefixes: ~w(x-) @notice_themes %{ gray: %{ bg: "bg-white dark:bg-gray-800", icon: "text-gray-400", title_text: "text-gray-800 dark:text-gray-400", body_text: "text-gray-700 dark:text-gray-500 leading-5" }, yellow: %{ bg: "bg-yellow-50 dark:bg-yellow-100", icon: "text-yellow-400", title_text: "text-sm text-yellow-800 dark:text-yellow-900", body_text: "text-sm text-yellow-700 dark:text-yellow-800 leading-5" }, red: %{ bg: "bg-red-100", icon: "text-red-700", title_text: "text-sm text-red-800 dark:text-red-900", body_text: "text-sm text-red-700 dark:text-red-800" } } @button_themes %{ "primary" => "bg-indigo-600 text-white hover:bg-indigo-700 focus-visible:outline-indigo-600", "bright" => "border border-gray-200 bg-gray-100 dark:bg-gray-300 text-gray-800 hover:bg-gray-200 focus-visible:outline-gray-100", "danger" => "border border-gray-300 dark:border-gray-500 text-red-700 bg-white dark:bg-gray-900 hover:text-red-500 dark:hover:text-red-400 focus:border-blue-300 dark:text-red-500 active:text-red-800" } @button_base_class "whitespace-nowrap truncate inline-flex items-center justify-center gap-x-2 font-medium rounded-md px-3.5 py-2.5 text-sm shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:bg-gray-400 dark:disabled:text-white dark:disabled:text-gray-400 dark:disabled:bg-gray-700" attr(:type, :string, default: "button") attr(:theme, :string, default: "primary") attr(:class, :string, default: "") attr(:disabled, :boolean, default: false) attr(:mt?, :boolean, default: true) attr(:rest, :global, include: ~w(name)) slot(:inner_block) def button(assigns) do assigns = assign(assigns, button_base_class: @button_base_class, theme_class: @button_themes[assigns.theme] ) ~H""" """ end attr(:href, :string, required: true) attr(:class, :string, default: "") attr(:theme, :string, default: "primary") attr(:disabled, :boolean, default: false) attr(:method, :string, default: "get") attr(:mt?, :boolean, default: true) attr(:rest, :global) slot(:inner_block) def button_link(assigns) do extra = if assigns.method == "get" do [] else [ "data-csrf": Phoenix.Controller.get_csrf_token(), "data-method": assigns.method, "data-to": assigns.href ] end assigns = assign(assigns, extra: extra) theme_class = if assigns.disabled do "bg-gray-400 text-white dark:text-white dark:text-gray-400 dark:bg-gray-700 cursor-not-allowed" else @button_themes[assigns.theme] end onclick = if assigns.disabled do "return false;" else assigns[:onclick] end assigns = assign(assigns, onclick: onclick, button_base_class: @button_base_class, theme_class: theme_class ) ~H""" <.link href={@href} onclick={@onclick} class={[ @mt? && "mt-6", @button_base_class, @theme_class, @class ]} {@extra} {@rest} > {render_slot(@inner_block)} """ end attr(:slug, :string, required: true) def docs_info(assigns) do ~H""" """ end attr(:title, :any, default: nil) attr(:theme, :atom, default: :yellow) attr(:dismissable_id, :any, default: nil) attr(:class, :string, default: "") attr(:rest, :global) slot(:inner_block) def notice(assigns) do assigns = assign(assigns, :theme, Map.fetch!(@notice_themes, assigns.theme)) ~H"""

{@title}

{render_slot(@inner_block)}

""" end attr(:href, :string, default: "#") attr(:new_tab, :boolean, default: false) attr(:class, :string, default: "") attr(:rest, :global) attr(:method, :string, default: "get") slot(:inner_block) def styled_link(assigns) do ~H""" <.unstyled_link new_tab={@new_tab} href={@href} method={@method} class={"text-indigo-600 hover:text-indigo-700 dark:text-indigo-500 dark:hover:text-indigo-600 " <> @class} {@rest} > {render_slot(@inner_block)} """ end attr :class, :string, default: "" slot :button, required: true do attr(:class, :string) end slot :menu, required: true do attr(:class, :string) end def dropdown(assigns) do assigns = assign(assigns, :menu_class, assigns.menu |> List.first() |> Map.get(:class, "")) ~H"""
""" end attr(:href, :string) attr(:class, :string, default: "") attr(:new_tab, :boolean, default: false) attr(:disabled, :boolean, default: false) attr(:rest, :global, include: ~w(method)) slot(:inner_block, required: true) @base_class "block rounded-lg text-sm/6 text-gray-900 ui-disabled:text-gray-500 dark:text-gray-100 dark:ui-disabled:text-gray-400 px-3.5 py-1.5" @clickable_class "hover:bg-gray-100 dark:hover:bg-gray-700" def dropdown_item(assigns) do assigns = if assigns[:disabled] do assign(assigns, :state, "disabled") else assign(assigns, :state, "") end if assigns[:href] && !assigns[:disabled] do assigns = assign(assigns, :class, [assigns[:class], @base_class, @clickable_class]) ~H""" <.unstyled_link class={@class} new_tab={@new_tab} href={@href} x-on:click="close()" data-ui-state={@state} {@rest} > {render_slot(@inner_block)} """ else assigns = assign(assigns, :class, [assigns[:class], @base_class]) ~H"""
{render_slot(@inner_block)}
""" end end def dropdown_divider(assigns) do ~H""" """ end attr(:href, :string, required: true) attr(:new_tab, :boolean, default: false) attr(:class, :string, default: nil) attr(:rest, :global) attr(:method, :string, default: "get") slot(:inner_block) def unstyled_link(assigns) do extra = if assigns.method == "get" do [] else [ "data-csrf": Phoenix.Controller.get_csrf_token(), "data-method": assigns.method, "data-to": assigns.href ] end assigns = assign(assigns, extra: extra) if assigns[:new_tab] do assigns = assign(assigns, :icon_class, icon_class(assigns)) ~H""" <.link class={[ "inline-flex items-center gap-x-0.5", @class ]} href={@href} target="_blank" rel="noopener noreferrer" {@extra} {@rest} > {render_slot(@inner_block)} """ else ~H""" <.link class={@class} href={@href} {@extra} {@rest}>{render_slot(@inner_block)} """ end end attr(:class, :any, default: "") attr(:rest, :global) def spinner(assigns) do ~H""" """ end def settings_tiles(assigns) do ~H"""
{render_slot(@inner_block)}
""" end attr :docs, :string, default: nil slot :inner_block, required: true slot :title, required: true slot :subtitle, required: true attr :feature_mod, :atom, default: nil attr :site, :any attr :conn, :any def tile(assigns) do ~H"""
<.title> {render_slot(@title)} <.docs_info :if={@docs} slug={@docs} />
{render_slot(@subtitle)}
<%= if @feature_mod do %> <% end %>
{render_slot(@inner_block)}
""" end attr(:sticky?, :boolean, default: true) slot(:inner_block, required: true) slot(:tooltip_content, required: true) def tooltip(assigns) do wrapper_data = if assigns[:sticky?], do: "{sticky: false, hovered: false}", else: "{hovered: false}" show_inner = if assigns[:sticky?], do: "hovered || sticky", else: "hovered" assigns = assign(assigns, wrapper_data: wrapper_data, show_inner: show_inner) ~H"""
{render_slot(List.first(@tooltip_content))}
{render_slot(@inner_block)}
""" end attr(:rest, :global, include: ~w(fill stroke stroke-width)) attr(:name, :atom, required: true) attr(:outline, :boolean, default: true) attr(:solid, :boolean, default: false) attr(:mini, :boolean, default: false) def dynamic_icon(assigns) do apply(Heroicons, assigns.name, [assigns]) end attr(:width, :integer, default: 100) attr(:height, :integer, default: 100) attr(:id, :string, default: "shuttle") defp icon_class(link_assigns) do classes = List.wrap(link_assigns[:class]) |> Enum.join(" ") if String.contains?(classes, "text-sm") or String.contains?(classes, "text-xs") do ["w-3 h-3"] else ["w-4 h-4"] end end slot(:item, required: true) def focus_list(assigns) do ~H"""
  1. {render_slot(item)}
""" end slot :title slot :subtitle slot :inner_block, required: true slot :footer attr :rest, :global def focus_box(assigns) do ~H"""
<.title :if={@title != []}> {render_slot(@title)}
{render_slot(@subtitle)}
{render_slot(@inner_block)}
{render_slot(@inner_block)}
{render_slot(@footer)}
""" end attr :rest, :global attr :width, :string, default: "min-w-full" attr :rows, :list, default: [] attr :row_attrs, :any, default: nil slot :thead, required: false slot :tbody, required: true slot :inner_block, required: false def table(assigns) do ~H""" {render_slot(@thead)} {render_slot(@tbody, item)} {render_slot(@inner_block)}
""" end slot :inner_block, required: true attr :truncate, :boolean, default: false attr :max_width, :string, default: "" attr :height, :string, default: "" attr :actions, :boolean, default: nil attr :hide_on_mobile, :boolean, default: nil attr :rest, :global def td(assigns) do max_width = cond do assigns.max_width != "" -> assigns.max_width assigns.truncate -> "max-w-sm" true -> "" end assigns = assign(assigns, max_width: max_width) ~H"""
{render_slot(@inner_block)}
{render_slot(@inner_block)}
""" end slot :inner_block, required: true attr :invisible, :boolean, default: false attr :hide_on_mobile, :boolean, default: nil def th(assigns) do class = if assigns[:invisible] do "invisible" else "px-6 first:pl-0 last:pr-0 py-3 text-left text-sm font-medium" end assigns = assign(assigns, class: class) ~H""" {render_slot(@inner_block)} """ end attr :set_to, :boolean, default: false attr :disabled?, :boolean, default: false slot :inner_block, required: true def toggle_submit(assigns) do ~H"""
{render_slot(@inner_block)}
""" end attr :href, :string, default: nil attr :rest, :global, include: ~w(method disabled) def edit_button(assigns) do if assigns[:href] do ~H""" <.unstyled_link href={@href} {@rest}> """ else ~H""" """ end end attr :href, :string, default: nil attr :rest, :global, include: ~w(method disabled) def delete_button(assigns) do if assigns[:href] do ~H""" <.unstyled_link href={@href} {@rest}> """ else ~H""" """ end end attr :filter_text, :string, default: "" attr :placeholder, :string, default: "" attr :filtering_enabled?, :boolean, default: true slot :inner_block, required: false def filter_bar(assigns) do ~H"""
{render_slot(@inner_block)}
""" end slot :inner_block, required: true attr :class, :any, default: nil def h2(assigns) do ~H"""

{render_slot(@inner_block)}

""" end slot :inner_block, required: true attr :class, :any, default: nil def title(assigns) do ~H""" <.h2 class={["text-lg font-medium text-gray-900 dark:text-gray-100 leading-7", @class]}> {render_slot(@inner_block)} """ end end