add block previews, picker thumbnails and newsletter settings
All checks were successful
deploy / deploy (push) Successful in 1m30s
All checks were successful
deploy / deploy (push) Successful in 1m30s
Block cards now show a one-line content summary below the name. Block picker items include SVG wireframe thumbnails. Newsletter block marked as decorative with configurable title/description and form submission prevented on the shop side. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0a7982dfe8
commit
8f989d892d
@ -1453,11 +1453,23 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-card-name {
|
.block-card-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-card-name {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
min-width: 0;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-card-preview {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: color-mix(in oklch, var(--t-text-primary) 50%, transparent);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -1567,13 +1579,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-picker-thumb {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: color-mix(in oklch, var(--t-text-primary) 3%, transparent);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.block-picker-item > .size-5 {
|
.block-picker-item > .size-5 {
|
||||||
grid-row: 1 / -1;
|
|
||||||
margin-top: 0.0625rem;
|
margin-top: 0.0625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-picker-item-desc {
|
.block-picker-item-name {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-picker-item-desc {
|
||||||
|
grid-column: 1 / -1;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
color: color-mix(in oklch, var(--t-text-primary) 55%, transparent);
|
color: color-mix(in oklch, var(--t-text-primary) 55%, transparent);
|
||||||
@ -1606,6 +1630,26 @@
|
|||||||
|
|
||||||
/* Block settings panel */
|
/* Block settings panel */
|
||||||
|
|
||||||
|
.block-settings-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.375rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: color-mix(in oklch, var(--t-text-primary) 60%, transparent);
|
||||||
|
background: color-mix(in oklch, var(--t-accent) 8%, transparent);
|
||||||
|
border: 1px solid color-mix(in oklch, var(--t-accent) 20%, transparent);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 0.5rem 0.625rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-settings-hint svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--t-accent);
|
||||||
|
margin-top: 0.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
.block-card-settings {
|
.block-card-settings {
|
||||||
padding: 0.75rem 0.75rem 0.25rem;
|
padding: 0.75rem 0.75rem 0.25rem;
|
||||||
padding-left: 2.75rem;
|
padding-left: 2.75rem;
|
||||||
|
|||||||
@ -110,10 +110,27 @@ defmodule Berrypod.Pages.BlockTypes do
|
|||||||
},
|
},
|
||||||
"newsletter_card" => %{
|
"newsletter_card" => %{
|
||||||
name: "Newsletter signup",
|
name: "Newsletter signup",
|
||||||
description: "Email signup form for collecting subscriber addresses",
|
description: "Email signup form — currently decorative, does not collect emails",
|
||||||
icon: "hero-envelope",
|
icon: "hero-envelope",
|
||||||
allowed_on: :all,
|
allowed_on: :all,
|
||||||
settings_schema: []
|
hint:
|
||||||
|
"This block is decorative — form submissions aren't collected yet. Use it as a placeholder or remove it.",
|
||||||
|
settings_schema: [
|
||||||
|
%SettingsField{key: "title", label: "Title", type: :text, default: "Newsletter"},
|
||||||
|
%SettingsField{
|
||||||
|
key: "description",
|
||||||
|
label: "Description",
|
||||||
|
type: :textarea,
|
||||||
|
default:
|
||||||
|
"Sample newsletter signup. Replace with your own message to encourage subscribers."
|
||||||
|
},
|
||||||
|
%SettingsField{
|
||||||
|
key: "button_text",
|
||||||
|
label: "Button text",
|
||||||
|
type: :text,
|
||||||
|
default: "Subscribe"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"social_links_card" => %{
|
"social_links_card" => %{
|
||||||
name: "Social links",
|
name: "Social links",
|
||||||
|
|||||||
@ -15,6 +15,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
import BerrypodWeb.CoreComponents, only: [icon: 1]
|
import BerrypodWeb.CoreComponents, only: [icon: 1]
|
||||||
|
|
||||||
alias Berrypod.Pages.{BlockEditor, BlockTypes}
|
alias Berrypod.Pages.{BlockEditor, BlockTypes}
|
||||||
|
alias BerrypodWeb.BlockThumbnails
|
||||||
|
|
||||||
# ── Block card ─────────────────────────────────────────────────
|
# ── Block card ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -28,12 +29,14 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
block_type = BlockTypes.get(assigns.block["type"])
|
block_type = BlockTypes.get(assigns.block["type"])
|
||||||
has_settings = BlockEditor.has_settings?(assigns.block)
|
has_settings = BlockEditor.has_settings?(assigns.block)
|
||||||
expanded = MapSet.member?(assigns.expanded, assigns.block["id"])
|
expanded = MapSet.member?(assigns.expanded, assigns.block["id"])
|
||||||
|
preview = block_preview(assigns.block, block_type)
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
assigns
|
assigns
|
||||||
|> assign(:block_type, block_type)
|
|> assign(:block_type, block_type)
|
||||||
|> assign(:has_settings, has_settings)
|
|> assign(:has_settings, has_settings)
|
||||||
|> assign(:is_expanded, expanded)
|
|> assign(:is_expanded, expanded)
|
||||||
|
|> assign(:preview, preview)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
@ -49,9 +52,12 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
<.icon name={(@block_type && @block_type.icon) || "hero-puzzle-piece"} class="size-5" />
|
<.icon name={(@block_type && @block_type.icon) || "hero-puzzle-piece"} class="size-5" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div class="block-card-info">
|
||||||
<span class="block-card-name">
|
<span class="block-card-name">
|
||||||
{(@block_type && @block_type.name) || @block["type"]}
|
{(@block_type && @block_type.name) || @block["type"]}
|
||||||
</span>
|
</span>
|
||||||
|
<span :if={@preview} class="block-card-preview">{@preview}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="block-card-controls">
|
<span class="block-card-controls">
|
||||||
<button
|
<button
|
||||||
@ -111,6 +117,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
:if={@is_expanded}
|
:if={@is_expanded}
|
||||||
block={@block}
|
block={@block}
|
||||||
schema={@block_type.settings_schema}
|
schema={@block_type.settings_schema}
|
||||||
|
hint={@block_type[:hint]}
|
||||||
event_prefix={@event_prefix}
|
event_prefix={@event_prefix}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -121,6 +128,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
|
|
||||||
attr :block, :map, required: true
|
attr :block, :map, required: true
|
||||||
attr :schema, :list, required: true
|
attr :schema, :list, required: true
|
||||||
|
attr :hint, :string, default: nil
|
||||||
attr :event_prefix, :string, default: ""
|
attr :event_prefix, :string, default: ""
|
||||||
|
|
||||||
def block_settings_form(assigns) do
|
def block_settings_form(assigns) do
|
||||||
@ -129,6 +137,9 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="block-card-settings" id={"block-settings-#{@block["id"]}"}>
|
<div class="block-card-settings" id={"block-settings-#{@block["id"]}"}>
|
||||||
|
<p :if={@hint} class="block-settings-hint">
|
||||||
|
<.icon name="hero-information-circle-mini" class="size-4" /> {@hint}
|
||||||
|
</p>
|
||||||
<form phx-change={"#{@event_prefix}update_block_settings"}>
|
<form phx-change={"#{@event_prefix}update_block_settings"}>
|
||||||
<input type="hidden" name="block_id" value={@block["id"]} />
|
<input type="hidden" name="block_id" value={@block["id"]} />
|
||||||
|
|
||||||
@ -454,6 +465,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
phx-value-type={type}
|
phx-value-type={type}
|
||||||
class="block-picker-item"
|
class="block-picker-item"
|
||||||
>
|
>
|
||||||
|
<BlockThumbnails.block_thumbnail type={type} />
|
||||||
<.icon name={def.icon} class="size-5" />
|
<.icon name={def.icon} class="size-5" />
|
||||||
<span class="block-picker-item-name">{def.name}</span>
|
<span class="block-picker-item-name">{def.name}</span>
|
||||||
<span :if={def[:description]} class="block-picker-item-desc">
|
<span :if={def[:description]} class="block-picker-item-desc">
|
||||||
@ -564,4 +576,69 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ── Block preview ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Extracts a short one-line preview from block settings for display in the card
|
||||||
|
defp block_preview(_block, nil), do: nil
|
||||||
|
|
||||||
|
defp block_preview(block, block_type) do
|
||||||
|
settings = block["settings"] || %{}
|
||||||
|
schema = block_type.settings_schema
|
||||||
|
|
||||||
|
# Try text/textarea fields first, then selects, then repeaters
|
||||||
|
find_text_preview(settings, schema) ||
|
||||||
|
find_select_preview(settings, schema) ||
|
||||||
|
find_repeater_preview(settings, schema)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_text_preview(settings, schema) do
|
||||||
|
schema
|
||||||
|
|> Enum.filter(&(&1.type in [:text, :textarea]))
|
||||||
|
|> Enum.find_value(fn field ->
|
||||||
|
case settings[field.key] do
|
||||||
|
val when is_binary(val) and val != "" ->
|
||||||
|
truncate(val, 60)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_select_preview(settings, schema) do
|
||||||
|
schema
|
||||||
|
|> Enum.filter(&(&1.type == :select))
|
||||||
|
|> Enum.find_value(fn field ->
|
||||||
|
case settings[field.key] do
|
||||||
|
val when is_binary(val) and val != "" -> "#{field.label}: #{val}"
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_repeater_preview(settings, schema) do
|
||||||
|
schema
|
||||||
|
|> Enum.filter(&(&1.type == :repeater))
|
||||||
|
|> Enum.find_value(fn field ->
|
||||||
|
case settings[field.key] do
|
||||||
|
items when is_list(items) and items != [] ->
|
||||||
|
count = length(items)
|
||||||
|
"#{count} #{if count == 1, do: "item", else: "items"}"
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp truncate(str, max) do
|
||||||
|
str = String.replace(str, ~r/\s+/, " ") |> String.trim()
|
||||||
|
|
||||||
|
if String.length(str) > max do
|
||||||
|
String.slice(str, 0, max) <> "..."
|
||||||
|
else
|
||||||
|
str
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
478
lib/berrypod_web/components/block_thumbnails.ex
Normal file
478
lib/berrypod_web/components/block_thumbnails.ex
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
defmodule BerrypodWeb.BlockThumbnails do
|
||||||
|
@moduledoc """
|
||||||
|
Tiny SVG wireframe thumbnails for each block type.
|
||||||
|
|
||||||
|
Used in the block picker to give a visual hint of what each block looks like.
|
||||||
|
Each thumbnail is an 80x48 SVG with simple geometric shapes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Phoenix.Component
|
||||||
|
|
||||||
|
attr :type, :string, required: true
|
||||||
|
|
||||||
|
def block_thumbnail(assigns) do
|
||||||
|
~H"""
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 80 48"
|
||||||
|
class="block-picker-thumb"
|
||||||
|
role="img"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<.thumb_shapes type={@type} />
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Hero banner ──────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "hero"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="0" y="0" width="80" height="48" rx="2" fill="currentColor" opacity="0.08" />
|
||||||
|
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.35" />
|
||||||
|
<rect x="24" y="18" width="32" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="28" y="30" width="24" height="7" rx="2" fill="currentColor" opacity="0.25" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Featured products ────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "featured_products"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="2" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="22" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="42" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="62" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="4" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="24" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="44" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="64" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="4" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="24" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="44" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="64" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Image + text ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "image_text"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="2" y="4" width="34" height="40" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="42" y="8" width="32" height="4" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
<rect x="42" y="16" width="28" height="3" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="42" y="22" width="30" height="3" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="42" y="28" width="20" height="3" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="42" y="36" width="16" height="5" rx="2" fill="currentColor" opacity="0.2" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Category navigation ──────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "category_nav"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="4" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="23" y="18" width="18" height="12" rx="6" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="44" y="18" width="14" height="12" rx="6" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="61" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.15" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Newsletter signup ────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "newsletter_card"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="72"
|
||||||
|
height="40"
|
||||||
|
rx="3"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.12"
|
||||||
|
stroke-width="1"
|
||||||
|
/>
|
||||||
|
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
<rect x="24" y="18" width="32" height="3" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect
|
||||||
|
x="14"
|
||||||
|
y="28"
|
||||||
|
width="36"
|
||||||
|
height="8"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.1"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.15"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="52" y="28" width="16" height="8" rx="2" fill="currentColor" opacity="0.2" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Social links ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "social_links_card"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="72"
|
||||||
|
height="40"
|
||||||
|
rx="3"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.12"
|
||||||
|
stroke-width="1"
|
||||||
|
/>
|
||||||
|
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<circle cx="24" cy="30" r="5" fill="currentColor" opacity="0.15" />
|
||||||
|
<circle cx="36" cy="30" r="5" fill="currentColor" opacity="0.15" />
|
||||||
|
<circle cx="48" cy="30" r="5" fill="currentColor" opacity="0.15" />
|
||||||
|
<circle cx="60" cy="30" r="5" fill="currentColor" opacity="0.15" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Info card ────────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "info_card"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="72"
|
||||||
|
height="40"
|
||||||
|
rx="3"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.12"
|
||||||
|
stroke-width="1"
|
||||||
|
/>
|
||||||
|
<rect x="10" y="10" width="20" height="3" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="10" y="18" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="40" y="18" width="28" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="10" y="24" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="40" y="24" width="22" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="10" y="30" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="40" y="30" width="26" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Trust badges ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "trust_badges"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<circle cx="16" cy="18" r="7" fill="currentColor" opacity="0.1" />
|
||||||
|
<circle cx="40" cy="18" r="7" fill="currentColor" opacity="0.1" />
|
||||||
|
<circle cx="64" cy="18" r="7" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="8" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="32" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="56" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Customer reviews ─────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "reviews_section"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="2"
|
||||||
|
y="2"
|
||||||
|
width="24"
|
||||||
|
height="44"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="28"
|
||||||
|
y="2"
|
||||||
|
width="24"
|
||||||
|
height="44"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="54"
|
||||||
|
y="2"
|
||||||
|
width="24"
|
||||||
|
height="44"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="6" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="32" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="58" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="6" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="32" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="58" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Spacer ───────────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "spacer"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<line
|
||||||
|
x1="40"
|
||||||
|
y1="8"
|
||||||
|
x2="40"
|
||||||
|
y2="16"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.2"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-dasharray="2 2"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="30"
|
||||||
|
y="18"
|
||||||
|
width="20"
|
||||||
|
height="12"
|
||||||
|
rx="1"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.15"
|
||||||
|
stroke-width="0.5"
|
||||||
|
stroke-dasharray="2 2"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="40"
|
||||||
|
y1="32"
|
||||||
|
x2="40"
|
||||||
|
y2="40"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.2"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-dasharray="2 2"
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Divider ──────────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "divider"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<line
|
||||||
|
x1="8"
|
||||||
|
y1="24"
|
||||||
|
x2="72"
|
||||||
|
y2="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.25"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Button ───────────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "button"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="22" y="16" width="36" height="16" rx="4" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="28" y="22" width="24" height="4" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Video embed ──────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "video_embed"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="4" y="4" width="72" height="40" rx="2" fill="currentColor" opacity="0.08" />
|
||||||
|
<polygon points="34,18 34,32 48,25" fill="currentColor" opacity="0.25" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Product hero ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "product_hero"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="2" y="4" width="38" height="40" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="46" y="6" width="28" height="4" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
<rect x="46" y="14" width="16" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="46" y="24" width="28" height="8" rx="2" fill="currentColor" opacity="0.2" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Breadcrumb ───────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "breadcrumb"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="6" y="20" width="14" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="24" y="21" width="4" height="1.5" rx="0.5" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="32" y="20" width="18" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="54" y="21" width="4" height="1.5" rx="0.5" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="62" y="20" width="12" height="3" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Product grid ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: t} = assigns) when t in ~w(product_grid related_products) do
|
||||||
|
~H"""
|
||||||
|
<rect x="2" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="28" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="54" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="2" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="28" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="54" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Collection header ────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "collection_header"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="16" y="14" width="48" height="6" rx="1" fill="currentColor" opacity="0.3" />
|
||||||
|
<rect x="28" y="26" width="24" height="3" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Filter bar ───────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "filter_bar"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="4" y="18" width="14" height="12" rx="6" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="21" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect x="40" y="18" width="12" height="12" rx="6" fill="currentColor" opacity="0.1" />
|
||||||
|
<rect
|
||||||
|
x="58"
|
||||||
|
y="18"
|
||||||
|
width="18"
|
||||||
|
height="12"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.08"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.15"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Cart items ───────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "cart_items"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="4" y="4" width="12" height="12" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="20" y="6" width="30" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="20" y="12" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<line
|
||||||
|
x1="4"
|
||||||
|
y1="20"
|
||||||
|
x2="76"
|
||||||
|
y2="20"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="4" y="24" width="12" height="12" rx="2" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="20" y="26" width="24" height="3" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="20" y="32" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Order summary ────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "order_summary"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="72"
|
||||||
|
height="40"
|
||||||
|
rx="3"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="10" y="12" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="50" y="12" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="10" y="18" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<rect x="50" y="18" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
|
||||||
|
<line
|
||||||
|
x1="10"
|
||||||
|
y1="24"
|
||||||
|
x2="70"
|
||||||
|
y2="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="10" y="28" width="14" height="3" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="50" y="28" width="20" height="3" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="20" y="36" width="40" height="6" rx="2" fill="currentColor" opacity="0.2" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Contact form ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "contact_form"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="8"
|
||||||
|
y="4"
|
||||||
|
width="64"
|
||||||
|
height="8"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.08"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="8"
|
||||||
|
y="16"
|
||||||
|
width="64"
|
||||||
|
height="8"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.08"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="8"
|
||||||
|
y="28"
|
||||||
|
width="64"
|
||||||
|
height="12"
|
||||||
|
rx="2"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.08"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="8" y="43" width="20" height="5" rx="2" fill="currentColor" opacity="0.2" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Content body ─────────────────────────────────────────────────
|
||||||
|
defp thumb_shapes(%{type: "content_body"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect x="4" y="6" width="50" height="4" rx="1" fill="currentColor" opacity="0.25" />
|
||||||
|
<rect x="4" y="14" width="72" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="4" y="20" width="68" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="4" y="26" width="72" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="4" y="32" width="60" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
<rect x="4" y="38" width="66" height="2" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Fallback for any unmatched type ──────────────────────────────
|
||||||
|
defp thumb_shapes(assigns) do
|
||||||
|
~H"""
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="72"
|
||||||
|
height="40"
|
||||||
|
rx="3"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity="0.06"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect x="20" y="16" width="40" height="4" rx="1" fill="currentColor" opacity="0.2" />
|
||||||
|
<rect x="26" y="24" width="28" height="3" rx="1" fill="currentColor" opacity="0.12" />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -314,7 +314,7 @@ defmodule BerrypodWeb.ShopComponents.Content do
|
|||||||
<p class="card-text card-text--spaced">
|
<p class="card-text card-text--spaced">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<form class="card-inline-form">
|
<form class="card-inline-form" onsubmit="return false">
|
||||||
<.shop_input type="email" placeholder="your@email.com" class="email-input" />
|
<.shop_input type="email" placeholder="your@email.com" class="email-input" />
|
||||||
<.shop_button type="submit">{@button_text}</.shop_button>
|
<.shop_button type="submit">{@button_text}</.shop_button>
|
||||||
</form>
|
</form>
|
||||||
@ -329,7 +329,7 @@ defmodule BerrypodWeb.ShopComponents.Content do
|
|||||||
<p class="card-text card-text--spaced">
|
<p class="card-text card-text--spaced">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<form class="card-inline-form">
|
<form class="card-inline-form" onsubmit="return false">
|
||||||
<.shop_input type="email" placeholder="your@email.com" class="email-input" />
|
<.shop_input type="email" placeholder="your@email.com" class="email-input" />
|
||||||
<.shop_button type="submit">{@button_text}</.shop_button>
|
<.shop_button type="submit">{@button_text}</.shop_button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -303,7 +303,15 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp render_block(%{block: %{"type" => "newsletter_card"}} = assigns) do
|
defp render_block(%{block: %{"type" => "newsletter_card"}} = assigns) do
|
||||||
~H"<.newsletter_card />"
|
settings = assigns.block["settings"] || %{}
|
||||||
|
|
||||||
|
assigns =
|
||||||
|
assigns
|
||||||
|
|> assign(:title, settings["title"] || "Newsletter")
|
||||||
|
|> assign(:description, settings["description"] || "")
|
||||||
|
|> assign(:button_text, settings["button_text"] || "Subscribe")
|
||||||
|
|
||||||
|
~H"<.newsletter_card title={@title} description={@description} button_text={@button_text} />"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_block(%{block: %{"type" => "social_links_card"}} = assigns) do
|
defp render_block(%{block: %{"type" => "social_links_card"}} = assigns) do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user