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"""
{render_slot(List.first(@menu))}
"""
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"""
"""
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"""
-
{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)}
"""
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