refactor: extract product_grid to shared ShopComponents module

Create a reusable product grid component that handles responsive
column layouts. Supports two modes:

- Theme-based columns: Uses theme_settings.grid_columns for lg breakpoint
- Fixed columns: Use columns={:fixed_4} for 2/4 column layouts

Used in: home (featured), collection, pdp (related), error (suggestions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 14:45:34 +00:00
parent 9746bf183c
commit b2869514cb
5 changed files with 95 additions and 33 deletions

View File

@@ -781,4 +781,82 @@ defmodule SimpleshopThemeWeb.ShopComponents do
defp title_style(:featured), do: "color: var(--t-text-primary);"
defp title_style(:compact), do: "font-family: var(--t-font-heading); color: var(--t-text-primary);"
defp title_style(:minimal), do: "color: var(--t-text-primary);"
@doc """
Renders a responsive product grid container.
This component wraps product cards in a responsive grid layout. It supports
theme-based column settings or fixed column layouts for specific use cases.
## Attributes
* `theme_settings` - Optional. When provided, uses `grid_columns` setting for lg breakpoint.
* `columns` - Optional. Fixed column count for lg breakpoint (overrides theme_settings).
Use `:fixed_4` for a fixed 2/4 column layout (error page, related products).
* `gap` - Optional. Gap size. Defaults to standard gap.
* `class` - Optional. Additional CSS classes to add.
## Slots
* `inner_block` - Required. The product cards to render inside the grid.
## Examples
<.product_grid theme_settings={@theme_settings}>
<%= for product <- @products do %>
<.product_card product={product} theme_settings={@theme_settings} />
<% end %>
</.product_grid>
<.product_grid columns={:fixed_4} gap="gap-6">
...
</.product_grid>
"""
attr :theme_settings, :map, default: nil
attr :columns, :atom, default: nil
attr :gap, :string, default: nil
attr :class, :string, default: nil
slot :inner_block, required: true
def product_grid(assigns) do
~H"""
<div class={grid_classes(@theme_settings, @columns, @gap, @class)}>
<%= render_slot(@inner_block) %>
</div>
"""
end
defp grid_classes(theme_settings, columns, gap, extra_class) do
base = "product-grid grid"
cols =
cond do
columns == :fixed_4 ->
"grid-cols-2 md:grid-cols-4"
theme_settings != nil ->
responsive_cols = "grid-cols-1 sm:grid-cols-2"
lg_cols =
case theme_settings.grid_columns do
"2" -> "lg:grid-cols-2"
"3" -> "lg:grid-cols-3"
"4" -> "lg:grid-cols-4"
_ -> "lg:grid-cols-3"
end
"#{responsive_cols} #{lg_cols}"
true ->
"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
end
gap_class = gap || ""
[base, cols, gap_class, extra_class]
|> Enum.reject(&is_nil/1)
|> Enum.reject(&(&1 == ""))
|> Enum.join(" ")
end
end