defmodule BerrypodWeb.BlockEditorComponents do
@moduledoc """
Shared UI components for the block editor.
Used by both the admin page editor (`Admin.Pages.Editor`) and the
live page editor sidebar (`PageEditorHook` + `PageRenderer`).
All components accept an `event_prefix` attr to namespace phx-click
events. The admin editor uses `""` (default), the live sidebar uses
`"editor_"`.
"""
use Phoenix.Component
import BerrypodWeb.CoreComponents, only: [icon: 1]
alias Berrypod.Pages.{BlockEditor, BlockTypes}
# ── Block card ─────────────────────────────────────────────────
attr :block, :map, required: true
attr :idx, :integer, required: true
attr :total, :integer, required: true
attr :expanded, :any, required: true
attr :event_prefix, :string, default: ""
def block_card(assigns) do
block_type = BlockTypes.get(assigns.block["type"])
has_settings = BlockEditor.has_settings?(assigns.block)
expanded = MapSet.member?(assigns.expanded, assigns.block["id"])
assigns =
assigns
|> assign(:block_type, block_type)
|> assign(:has_settings, has_settings)
|> assign(:is_expanded, expanded)
~H"""
<.block_settings_form
:if={@is_expanded}
block={@block}
schema={@block_type.settings_schema}
event_prefix={@event_prefix}
/>
"""
end
# ── Settings form ──────────────────────────────────────────────
attr :block, :map, required: true
attr :schema, :list, required: true
attr :event_prefix, :string, default: ""
def block_settings_form(assigns) do
settings = BlockEditor.settings_with_defaults(assigns.block)
assigns = assign(assigns, :settings, settings)
~H"""
"""
end
# ── Field renderers ────────────────────────────────────────────
attr :field, :any, required: true
attr :value, :any, required: true
attr :block_id, :string, required: true
attr :event_prefix, :string, default: ""
def block_field(%{field: %{type: :select}} = assigns) do
~H"""
"""
end
def block_field(%{field: %{type: :textarea}} = assigns) do
~H"""
"""
end
def block_field(%{field: %{type: :number}} = assigns) do
~H"""
"""
end
def block_field(%{field: %{type: :repeater}} = assigns) do
items = if is_list(assigns.value), do: assigns.value, else: []
item_count = length(items)
assigns =
assigns
|> assign(:items, items)
|> assign(:item_count, item_count)
~H"""
{@field.label}
-
"""
end
def block_field(assigns) do
~H"""
"""
end
# ── Repeater item field ────────────────────────────────────────
attr :sub_field, :any, required: true
attr :item, :map, required: true
attr :field_key, :string, required: true
attr :block_id, :string, required: true
attr :index, :integer, required: true
attr :event_prefix, :string, default: ""
def repeater_item_field(assigns) do
value = assigns.item[assigns.sub_field.key] || ""
assigns = assign(assigns, :value, value)
~H"""
"""
end
# ── Block picker ───────────────────────────────────────────────
attr :allowed_blocks, :any, required: true
attr :filter, :string, required: true
attr :event_prefix, :string, default: ""
def block_picker(assigns) do
filter = String.downcase(assigns.filter)
filtered =
assigns.allowed_blocks
|> Enum.filter(fn {_type, def} ->
filter == "" or String.contains?(String.downcase(def.name), filter)
end)
|> Enum.sort_by(fn {_type, def} -> def.name end)
assigns = assign(assigns, :filtered_blocks, filtered)
~H"""
"""
end
end