lib/layout_builder.ex

defmodule LayoutBuilder do
  use Phoenix.LiveComponent
  use PetalComponents
  import LayoutBuilder.Gettext
  alias LayoutBuilder.Layout.Layout

  @moduledoc """
  `LayoutBuilder` allows you to create a %Layout.Layout{} containing %Layout.Row{} and %Layout.Row{}.
  """

  @doc """
  LayoutBuilder component

  ## Examples

      <.layout_builder />

  """

  slot :content, doc: "Content to show inside each col" do
    attr :class, :string, doc: "Custom classes to add"
  end

  attr :title, :atom, default: :h2, doc: "<hx> title level", values: [:h1, :h2, :h3, :h4, :h5, :h6]
  attr :title_class, :string, default: "", doc: "classes to add to <hx>", examples: ["text-2xl font-medium"]
  attr :show_title, :boolean, default: true, doc: "show title and paddings", examples: [false]
  attr :class, :string, default: "", doc: "classes to pass to layout builder", examples: ["mt-6"]
  attr :row_class, :string, default: "flex", doc: "classes to pass to layout builder", examples: ["mt-6"]
  attr :edit_layout, :boolean, default: true, doc: "show buttons to add / remove cols and rows from layout builder and show background colors"
  attr :layout, Layout, required: true, doc: "layout built", examples: [%Layout{}]
  def render(assigns) do
    assigns = assigns
    |> assign_new(:above, fn -> nil end)
    ~H"""
    <article class={@class}>
      <%= if @show_title do %>
        <%= Phoenix.HTML.Tag.content_tag(@title, class: "text-sm font-medium text-gray-500 dark:text-gray-400") do %>
          <%= gettext("Layout builder") %>
        <% end %>
      <% end %>
      <div class={"mt-1 max-w-3xl w-full flex flex-col bg-white dark:bg-black #{if @show_title, do: "p-6", else: ""}"}>
          <%= for row <- @layout.rows do %>
            <%= if @above do %>
              <div class="flex flex-wrap">
                <%= render_slot(@above, row) %>
              </div>
            <% end %>
            <div class={if @edit_layout do "bg-gray-100 dark:bg-gray-900 rounded-lg mb-3 " else "" end <> "#{@row_class} gap-x-3"}>
              <%= for col <- row.cols do %>
                <%= if @edit_layout do %>
                  <div class="rounded-lg bg-gray-200 dark:bg-gray-800 flex-1 m-3">
                    <%= render_slot(@content, col) %>
                  </div>
                <% else %>
                  <%= render_slot(@content, col) %>
                <% end %>
              <% end %>
              <%= if @edit_layout do %>
                <.button color="gray" class="w-20 h-12 m-3" phx-value-row={row.id} phx-click="add_column" phx-target={@myself}>
                  <Heroicons.plus class="w-6 h-6" />
                </.button>
              <% end %>
            </div>
          <% end %>
          <%= if @edit_layout do %>
            <.button color="gray" class="w-full h-12 m-3" phx-click="add_row" phx-target={@myself}>
              <Heroicons.plus class="w-6 h-6" />
            </.button>
          <% end %>
        </div>
    </article>
    """
  end

  @spec handle_event(String.t(), map, Elixir.Phoenix.LiveView.Socket.t()) :: {:noreply, Elixir.Phoenix.LiveView.Socket.t()}
  def handle_event("add_column", %{"row" => id}, socket) do
    row = Enum.find(socket.assigns.layout.rows, fn row -> row.id == id end)
    layout = Layout.add_col(socket.assigns.layout, row)
    send(self(), %{layout: layout})
    {:noreply, socket}
  end

  def handle_event("add_row", _, socket) do
    layout = Layout.add_row(socket.assigns.layout)
    send(self(), %{layout: layout})
    {:noreply, assign(socket, :layout, layout)}
  end
end