defmodule Phoenix.UI.Components.Alert do @moduledoc """ Provides Alert component """ import Phoenix.UI.Components.Heroicon use Phoenix.UI, :component @default_action_icon_name "x-mark" @default_icon_color "inherity" @default_icon_variant "outline" @default_severity "info" @default_variant "standard" @doc """ Renders alert component. ## Examples ``` <.alert> ruh roh! ``` """ @spec alert(Socket.assigns()) :: Rendered.t() def alert(raw_assigns) do assigns = raw_assigns |> assign_new(:icon, fn -> [] end) |> assign_new(:severity, fn -> @default_severity end) |> assign_new(:variant, fn -> @default_variant end) |> build_action_attrs() |> build_action_wrapper_attrs() |> build_container_attrs() |> build_icon_attrs() |> build_icon_wrapper_attrs() ~H"""
<%= if assigns[:icon] do %>
<%= if is_slot?(@icon) do %> <%= render_slot(@icon) %> <% else %> <.heroicon {@icon_attrs} /> <% end %>
<% end %> <%= if assigns[:title] do %>
<%= if is_bitstring(@title) do %> <%= @title %> <% else %> <%= render_slot(@title) %> <% end %>
<% end %>
<%= if assigns[:content] do %> <%= render_slot(@content) %> <% else %> <%= render_slot(@inner_block) %> <% end %>
<%= if assigns[:action] do %>
<%= if is_slot?(@action) do %> <%= render_slot(@action) %> <% else %> <.heroicon {@action_attrs} /> <% end %>
<% end %>
""" end ### Container Attrs ########################## defp build_container_attrs(assigns) do class = build_class(~w( alert relative py-4 rounded-lg #{classes(:container, :color, assigns)} #{classes(:container, :spacing, assigns)} #{Map.get(assigns, :extend_class)} )) attrs = assigns |> assigns_to_attributes([ :action_attrs, :action_wrapper_attrs, :action, :color, :content, :extend_class, :icon_attrs, :icon_wrapper_attrs, :icon, :title, :variant ]) |> Keyword.put_new(:class, class) |> Keyword.put_new(:role, "alert") assign(assigns, :container_attrs, attrs) end ### Icon Attrs ########################## defp build_icon_attrs(%{icon: false} = assigns), do: assigns defp build_icon_attrs(%{icon: icon, severity: severity} = assigns) do extend_class = build_class(~w( alert-icon #{Keyword.get(icon, :extend_class)} )) attrs = icon |> assigns_to_attributes([:extend_class]) |> Keyword.put(:extend_class, extend_class) |> Keyword.put_new(:color, @default_icon_color) |> Keyword.put_new(:name, icon_mapping(severity)) |> Keyword.put_new(:variant, @default_icon_variant) assign(assigns, :icon_attrs, attrs) end defp build_icon_attrs(assigns), do: assigns ### Icon Wrapper Attrs ########################## defp build_icon_wrapper_attrs(%{icon: false} = assigns), do: assigns defp build_icon_wrapper_attrs(%{icon: _icon} = assigns) do class = build_class(~w( alert-icon-wrapper absolute left-4 #{classes(:icon_wrapper, :color, assigns)} #{classes(:icon_wrapper, :position, assigns)} )) assign(assigns, :icon_wrapper_attrs, %{class: class}) end defp build_icon_wrapper_attrs(assigns), do: assigns ### Action Attrs ########################## defp build_action_attrs(%{action: action} = assigns) do extend_class = build_class(~w( alert-action cursor-pointer #{Keyword.get(action, :extend_class)} )) attrs = action |> assigns_to_attributes([:extend_class]) |> Keyword.put(:extend_class, extend_class) |> Keyword.put_new(:name, @default_action_icon_name) |> Keyword.put_new(:variant, @default_icon_variant) assign(assigns, :action_attrs, attrs) end defp build_action_attrs(assigns), do: assigns ### Action Wrapper Attrs ########################## defp build_action_wrapper_attrs(%{action: _action} = assigns) do class = build_class(~w( alert-action-wrapper absolute right-4 transition-all ease-in-out duration-300 #{classes(:action_wrapper, :color, assigns)} #{classes(:action_wrapper, :position, assigns)} )) assign(assigns, :action_wrapper_attrs, %{class: class}) end defp build_action_wrapper_attrs(assigns), do: assigns ### CSS Classes ########################## # Container - Color defp classes(:container, :color, %{variant: "filled", severity: color}) do "bg-#{color}-600 dark:bg-#{color}-700 text-white" end defp classes(:container, :color, %{variant: "outlined", severity: color}) do "border border-#{color}-500 text-#{color}-800 dark:text-#{color}-200" end defp classes(:container, :color, %{variant: "standard", severity: color}) do "bg-#{color}-500/[.15] text-#{color}-800 dark:text-#{color}-200" end # Container - Spacing defp classes(:container, :spacing, %{icon: false, action: _}), do: "pl-4 pr-14" defp classes(:container, :spacing, %{icon: _, action: _}), do: "px-14" defp classes(:container, :spacing, %{icon: false}), do: "px-4" defp classes(:container, :spacing, %{icon: _}), do: "pl-14 pr-4" defp classes(:container, :spacing, %{action: _}), do: "pl-4 pr-14" defp classes(:container, :spacing, _assigns), do: "px-4" # Icon Wrapper - Color defp classes(:icon_wrapper, :color, %{variant: "outlined", severity: color}) do "text-#{color}-500" end defp classes(:icon_wrapper, :color, %{variant: "standard", severity: color}) do "text-#{color}-600" end # Icon Wrapper - Position defp classes(:icon_wrapper, :position, %{title: _}), do: "top-4" defp classes(:icon_wrapper, :position, _assigns), do: "top-1/2 -translate-y-1/2" # Action Wrapper - Color defp classes(:action_wrapper, :color, %{variant: "filled"}) do "text-white hover:text-white/75" end defp classes(:action_wrapper, :color, %{variant: "outlined", severity: color}) do "hover:text-#{color}-600/75" end defp classes(:action_wrapper, :color, %{variant: "standard", severity: color}) do "hover:text-#{color}-900/50 dark:hover:text-#{color}-200/50" end # Action Wrapper - Position defp classes(:action_wrapper, :position, %{title: _}), do: "top-4" defp classes(:action_wrapper, :position, _assigns), do: "top-1/2 -translate-y-1/2" defp classes(_element, _rule_group, _assigns), do: nil defp icon_mapping("error"), do: "exclamation-circle" defp icon_mapping("danger"), do: "exclamation-circle" defp icon_mapping("info"), do: "information-circle" defp icon_mapping("success"), do: "check-circle" defp icon_mapping("warning"), do: "exclamation-triangle" end