defmodule Beacon.LiveAdmin.AdminComponents do @moduledoc """ Provides Admin UI components. This file contains new components and also overrided Core components. """ use Phoenix.Component alias Phoenix.LiveView.JS alias Beacon.LiveAdmin.CoreComponents alias Beacon.LiveAdmin.PageBuilder.Table use Gettext, backend: Beacon.LiveAdmin.Gettext import Beacon.LiveAdmin.Router, only: [beacon_live_admin_path: 3] defdelegate header(assigns), to: CoreComponents defdelegate flash_group(assigns), to: CoreComponents defdelegate hide_modal(assigns), to: CoreComponents defdelegate show(selector), to: CoreComponents defdelegate show(js, selector), to: CoreComponents defdelegate hide(selector), to: CoreComponents defdelegate hide(js, selector), to: CoreComponents defdelegate translate_errors(errors, field), to: CoreComponents defp icon(assigns), do: Beacon.LiveAdmin.StationUI.HTML.Icon.icon(assigns) defp input(assigns), do: Beacon.LiveAdmin.CoreComponents.input(assigns) @menu_link_active_class "inline-block p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active" @menu_link_regular_class "inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300" @doc false attr :socket, :map attr :flash, :map attr :beacon_layout, :map attr :live_action, :atom def layout_header(assigns) do ~H""" """ end @doc false attr :socket, :map attr :site, :atom attr :current_action, :atom attr :layout_id, :string def layout_menu(assigns) do assigns = assign(assigns, active_class: @menu_link_active_class, regular_class: @menu_link_regular_class ) ~H"""
""" end defp layout_menu_items(%{current_action: :new} = assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/layouts/#{@layout_id}")} class={@active_class}>Layout
  • """ end defp layout_menu_items(assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/layouts/#{@layout_id}")} class={if(@current_action == :edit, do: @active_class, else: @regular_class)}>Layout
  • <.link patch={beacon_live_admin_path(@socket, @site, "/layouts/#{@layout_id}/meta_tags")} class={if(@current_action == :meta_tags, do: @active_class, else: @regular_class)}>Meta Tags
  • <.link patch={beacon_live_admin_path(@socket, @site, "/layouts/#{@layout_id}/resource_links")} class={if(@current_action == :resource_links, do: @active_class, else: @regular_class)}> Resource Links
  • <.link patch={beacon_live_admin_path(@socket, @site, "/layouts/#{@layout_id}/revisions")} class={if(@current_action == :revisions, do: @active_class, else: @regular_class)}>Revisions
  • """ end @doc false attr :socket, :map attr :flash, :map attr :page, :any attr :live_action, :atom def page_header(assigns) do ~H""" """ end @doc false attr :socket, :map attr :site, :atom attr :current_action, :atom attr :page_id, :string def page_menu(assigns) do assigns = assign(assigns, active_class: @menu_link_active_class, regular_class: @menu_link_regular_class ) ~H"""
      <%= page_menu_items(assigns) %>
    """ end defp page_menu_items(%{current_action: :new} = assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}")} class={@active_class}>Page
  • """ end defp page_menu_items(assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}")} class={if(@current_action == :edit, do: @active_class, else: @regular_class)}>Page
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}/meta_tags")} class={if(@current_action == :meta_tags, do: @active_class, else: @regular_class)}>Meta Tags
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}/schema")} class={if(@current_action == :schema, do: @active_class, else: @regular_class)}>Schema
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}/variants")} class={if(@current_action == :variants, do: @active_class, else: @regular_class)}>Variants
  • <.link patch={beacon_live_admin_path(@socket, @site, "/pages/#{@page_id}/revisions")} class={if(@current_action == :revisions, do: @active_class, else: @regular_class)}>Revisions
  • """ end @doc false attr :socket, :map attr :flash, :map attr :component, :any attr :live_action, :atom def component_header(assigns) do ~H""" """ end @doc false attr :socket, :map attr :site, :atom attr :current_action, :atom attr :component_id, :string def component_menu(assigns) do assigns = assign(assigns, active_class: @menu_link_active_class, regular_class: @menu_link_regular_class ) ~H"""
      <%= component_menu_items(assigns) %>
    """ end defp component_menu_items(%{current_action: :new} = assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/components/#{@component_id}")} class={@active_class}>Component
  • """ end defp component_menu_items(assigns) do ~H"""
  • <.link patch={beacon_live_admin_path(@socket, @site, "/components/#{@component_id}")} class={if(@current_action == :edit, do: @active_class, else: @regular_class)}>Component
  • <.link patch={beacon_live_admin_path(@socket, @site, "/components/#{@component_id}/slots")} class={if(@current_action == :slots, do: @active_class, else: @regular_class)}>Slots
  • """ end @doc """ Renders a thumbnail image. """ attr :source, :string, default: nil def thumbnail(assigns) do ~H""" """ end @doc """ Renders flash notices. ## Examples <.flash kind={:info} flash={@flash} /> <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! """ attr :id, :string, default: "flash", doc: "the optional id of flash container" attr :flash, :map, default: %{}, doc: "the map of flash messages to display" attr :title, :string, default: nil attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" slot :inner_block, doc: "the optional inner block that renders the flash message" def flash(assigns) do ~H"""
    hide("##{@id}")} role="alert" class={[ "fixed top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1", @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900", @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900" ]} {@rest} >

    <.icon :if={@kind == :info} name="hero-information-circle-mini" class="w-4 h-4" /> <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="w-4 h-4" /> <%= @title %>

    <%= msg %>

    """ end @doc """ Renders a simple form. ## Examples <.simple_form for={@form} phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label="Email"/> <.input field={@form[:username]} label="Username" /> <:actions> <.button>Save """ attr :for, :any, required: true, doc: "the datastructure for the form" attr :as, :any, default: nil, doc: "the server side parameter to collect all input under" attr :rest, :global, include: ~w(autocomplete name rel action enctype method novalidate target), doc: "the arbitrary HTML attributes to apply to the form tag" slot :inner_block, required: true slot :actions, doc: "the slot for form actions, such as a submit button" def simple_form(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}>
    <%= render_slot(@inner_block, f) %>
    <%= render_slot(action, f) %>
    """ end @doc ~S""" Renders a table with generic styling. ## Examples <.table id="users" rows={@users}> <:col :let={user} label="id"><%= user.id %> <:col :let={user} label="username"><%= user.username %> """ attr :id, :string, required: true attr :rows, :list, required: true attr :row_id, :any, default: nil, doc: "the function for generating the row id" attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" attr :row_item, :any, default: &Function.identity/1, doc: "the function for mapping each row before calling the :col and :action slots" slot :col, required: true do attr :label, :string end slot :action, doc: "the slot for showing user actions in the last table column" def table(assigns) do assigns = with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) end ~H"""
    <%= col[:label] %> <%= gettext("Actions") %>
    <%= render_slot(col, @row_item.(row)) %>
    <%= render_slot(action, @row_item.(row)) %>
    """ end @doc """ Renders a data list. ## Examples <.list> <:item title="Title"><%= @post.title %> <:item title="Views"><%= @post.views %> """ slot :item, required: true do attr :title, :string, required: true end def list(assigns) do ~H"""
    <%= item.title %>
    <%= render_slot(item) %>
    """ end @doc """ Renders a back navigation link. ## Examples <.back navigate={~p"/posts"}>Back to posts """ attr :navigate, :any, required: true slot :inner_block, required: true def back(assigns) do ~H"""
    <.link navigate={@navigate} class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"> <.icon name="hero-arrow-left-solid" class="w-3 h-3" /> <%= render_slot(@inner_block) %>
    """ end @doc """ Renders a rounded white panel that is pinned to the bottom of the screen and scrolls. """ slot :inner_block, required: true attr :class, :string, default: "" def main_content(assigns) do ~H"""
    <%= render_slot(@inner_block) %>
    """ end @doc """ Renders a search input text to filter table results. """ attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true attr :placeholder, :string def table_search(assigns) do ~H""" <.simple_form :let={f} for={%{}} as={:search} phx-change="beacon:table-search"> <.input type="search" field={f[:query]} value={@table.query} autofocus={true} placeholder={@placeholder || "Search"} phx-debounce={200} /> """ end @doc """ Renders a select input to sort table results. """ attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true attr :options, :list, required: true def table_sort(assigns) do ~H""" <.simple_form :let={f} for={%{}} as={:sort} phx-change="beacon:table-sort">
    <.input type="select" field={f[:sort_by]} value={@table.sort_by} options={@options} />
    """ end @doc """ Renders pagination to nagivate table results. """ attr :socket, Phoenix.LiveView.Socket, required: true attr :page, Beacon.LiveAdmin.PageBuilder.Page, required: true attr :limit, :integer, default: 11 def table_pagination(assigns) do assigns = assign(assigns, :table, assigns.page.table) ~H"""
    1} class="flex flex-row justify-center space-x-6 pt-8 text-xl font-semibold pt-10 pb-10"> <.link :if={@table.current_page > 1} patch={Table.prev_path(@socket, @page)} class="border-b-4 border-transparent hover:text-blue-600 hover:border-blue-600 active:text-blue-700 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large" > <.icon name="hero-arrow-long-left-solid" class="mr-2" /> prev <.icon name="hero-arrow-long-left-solid" class="mr-2" /> prev <%= for page <- Beacon.LiveAdmin.PageBuilder.Table.nav_pages(@table.current_page, @table.page_count, @limit) do %> <.link patch={Table.goto_path(@socket, @page, page)} class={ if @table.current_page == page, do: "px-3 pb-0.5 pt-1.5 border-b-4 border-transparent hover:text-blue-600 hover:border-blue-600 active:text-blue-700 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large text-blue-700 border-blue-700", else: "px-3 pb-0.5 pt-1.5 border-b-4 border-transparent hover:text-blue-600 hover:border-blue-600 active:text-blue-700 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large" } > <%= page %> ... <% end %> <.link :if={@table.current_page < @table.page_count} patch={Table.next_path(@socket, @page)} class="border-b-4 border-transparent hover:text-blue-600 hover:border-blue-600 active:text-blue-800 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large" > next <.icon name="hero-arrow-long-right-solid" class="mr-2" /> next <.icon name="hero-arrow-long-right-solid" class="mr-2" />
    """ end @doc """ Renders a select input with the available sites to select. ## Examples <.site_selector selected_site="dev" options={[:dev, :dy]} /> """ attr :selected_site, :string, default: "" attr :options, :list, default: [] def site_selector(assigns) do ~H"""
    <.form id="site-selector-form" for={%{}} phx-change="change-site"> <.input type="select" name="site" options={@options} value={@selected_site} />
    """ end end