diff --git a/lib/simpleshop_theme_web/components/shop_components.ex b/lib/simpleshop_theme_web/components/shop_components.ex index ce3e38c..9f9d1d6 100644 --- a/lib/simpleshop_theme_web/components/shop_components.ex +++ b/lib/simpleshop_theme_web/components/shop_components.ex @@ -516,4 +516,269 @@ defmodule SimpleshopThemeWeb.ShopComponents do """ end + + @doc """ + Renders a product card with configurable variants. + + ## Attributes + + * `product` - Required. The product map with `name`, `image_url`, `price`, etc. + * `theme_settings` - Required. The theme settings map. + * `mode` - Either `:live` (default) or `:preview`. + * `variant` - The visual variant: + - `:default` - Collection page style with border, category, full details + - `:featured` - Home page style with hover lift, no border + - `:compact` - PDP related products with aspect-square, minimal info + - `:minimal` - Error 404 style, smallest, not clickable + * `show_category` - Show category label. Defaults based on variant. + * `show_badges` - Show product badges. Defaults based on variant. + * `show_delivery_text` - Show "Free delivery" text. Defaults based on variant. + * `clickable` - Whether the card navigates. Defaults based on variant. + + ## Examples + + <.product_card product={product} theme_settings={@theme_settings} /> + <.product_card product={product} theme_settings={@theme_settings} variant={:featured} mode={:preview} /> + """ + attr :product, :map, required: true + attr :theme_settings, :map, required: true + attr :mode, :atom, default: :live + attr :variant, :atom, default: :default + attr :show_category, :boolean, default: nil + attr :show_badges, :boolean, default: nil + attr :show_delivery_text, :boolean, default: nil + attr :clickable, :boolean, default: nil + + def product_card(assigns) do + # Apply variant defaults for nil values + defaults = variant_defaults(assigns.variant) + + assigns = + assigns + |> assign_new(:show_category_resolved, fn -> + if assigns.show_category == nil, do: defaults.show_category, else: assigns.show_category + end) + |> assign_new(:show_badges_resolved, fn -> + if assigns.show_badges == nil, do: defaults.show_badges, else: assigns.show_badges + end) + |> assign_new(:show_delivery_text_resolved, fn -> + if assigns.show_delivery_text == nil, + do: defaults.show_delivery_text, + else: assigns.show_delivery_text + end) + |> assign_new(:clickable_resolved, fn -> + if assigns.clickable == nil, do: defaults.clickable, else: assigns.clickable + end) + + ~H""" + <%= if @clickable_resolved do %> + <%= if @mode == :preview do %> + + <.product_card_inner + product={@product} + theme_settings={@theme_settings} + variant={@variant} + show_category={@show_category_resolved} + show_badges={@show_badges_resolved} + show_delivery_text={@show_delivery_text_resolved} + /> + + <% else %> + + <.product_card_inner + product={@product} + theme_settings={@theme_settings} + variant={@variant} + show_category={@show_category_resolved} + show_badges={@show_badges_resolved} + show_delivery_text={@show_delivery_text_resolved} + /> + + <% end %> + <% else %> +
+ <.product_card_inner + product={@product} + theme_settings={@theme_settings} + variant={@variant} + show_category={@show_category_resolved} + show_badges={@show_badges_resolved} + show_delivery_text={@show_delivery_text_resolved} + /> +
+ <% end %> + """ + end + + attr :product, :map, required: true + attr :theme_settings, :map, required: true + attr :variant, :atom, required: true + attr :show_category, :boolean, required: true + attr :show_badges, :boolean, required: true + attr :show_delivery_text, :boolean, required: true + + defp product_card_inner(assigns) do + ~H""" +
+ <%= if @show_badges do %> + <.product_badge product={@product} /> + <% end %> + {@product.name} + <%= if @theme_settings.hover_image && @product[:hover_image_url] do %> + {@product.name} + <% end %> +
+
+ <%= if @show_category && @product[:category] do %> +

+ <%= @product.category %> +

+ <% end %> +

+ <%= @product.name %> +

+ <%= if @theme_settings.show_prices do %> + <.product_price product={@product} variant={@variant} /> + <% end %> + <%= if @show_delivery_text do %> +

+ Free delivery over £40 +

+ <% end %> +
+ """ + end + + attr :product, :map, required: true + + defp product_badge(assigns) do + ~H""" + <%= cond do %> + <% Map.get(@product, :in_stock, true) == false -> %> + Sold out + <% @product[:is_new] -> %> + New + <% @product[:on_sale] -> %> + Sale + <% true -> %> + <% end %> + """ + end + + attr :product, :map, required: true + attr :variant, :atom, required: true + + defp product_price(assigns) do + ~H""" + <%= case @variant do %> + <% :default -> %> +
+ <%= if @product.on_sale do %> + + £<%= @product.price / 100 %> + + + £<%= @product.compare_at_price / 100 %> + + <% else %> + + £<%= @product.price / 100 %> + + <% end %> +
+ <% :featured -> %> +

+ <%= if @product.on_sale do %> + £<%= @product.compare_at_price / 100 %> + <% end %> + £<%= @product.price / 100 %> +

+ <% :compact -> %> +

+ £<%= @product.price / 100 %> +

+ <% :minimal -> %> +

+ £<%= @product.price / 100 %> +

+ <% end %> + """ + end + + defp variant_defaults(:default), + do: %{show_category: true, show_badges: true, show_delivery_text: true, clickable: true} + + defp variant_defaults(:featured), + do: %{show_category: false, show_badges: true, show_delivery_text: true, clickable: true} + + defp variant_defaults(:compact), + do: %{show_category: false, show_badges: false, show_delivery_text: false, clickable: true} + + defp variant_defaults(:minimal), + do: %{show_category: false, show_badges: false, show_delivery_text: false, clickable: false} + + defp card_classes(:default), do: "product-card group overflow-hidden transition-all" + defp card_classes(:featured), do: "product-card group overflow-hidden transition-all hover:-translate-y-1" + defp card_classes(:compact), do: "product-card group overflow-hidden cursor-pointer" + defp card_classes(:minimal), do: "product-card group overflow-hidden" + + defp card_style(:default), + do: "background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card); cursor: pointer;" + + defp card_style(:featured), + do: "background-color: var(--t-surface-raised); border-radius: var(--t-radius-card); cursor: pointer;" + + defp card_style(:compact), + do: "background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);" + + defp card_style(:minimal), + do: "background-color: var(--t-surface-raised); border: 1px solid var(--t-border-subtle); border-radius: var(--t-radius-card);" + + defp image_container_classes(:compact), + do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative" + + defp image_container_classes(:minimal), + do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative" + + defp image_container_classes(_), + do: "product-image-container bg-gray-200 overflow-hidden relative" + + defp content_padding_class(:compact), do: "p-3" + defp content_padding_class(:minimal), do: "p-2" + defp content_padding_class(_), do: "" + + defp title_classes(:default), do: "font-semibold mb-2" + defp title_classes(:featured), do: "text-sm font-medium mb-1" + defp title_classes(:compact), do: "font-semibold text-sm mb-1" + defp title_classes(:minimal), do: "text-xs font-semibold truncate" + + defp title_style(:default), do: "font-family: var(--t-font-heading); color: var(--t-text-primary);" + 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);" end diff --git a/lib/simpleshop_theme_web/live/theme_live/preview_pages/collection.html.heex b/lib/simpleshop_theme_web/live/theme_live/preview_pages/collection.html.heex index 80ea4d8..dc36b33 100644 --- a/lib/simpleshop_theme_web/live/theme_live/preview_pages/collection.html.heex +++ b/lib/simpleshop_theme_web/live/theme_live/preview_pages/collection.html.heex @@ -62,68 +62,13 @@ end ]}> <%= for product <- @preview_data.products do %> -
-
- - <%= cond do %> - <% not product.in_stock -> %> - Sold out - <% product.on_sale -> %> - Sale - <% true -> %> - <% end %> - - {product.name} - - <%= if @theme_settings.hover_image && product[:hover_image_url] do %> - {product.name} - <% end %> -
-
-

- <%= product.category %> -

-

- <%= product.name %> -

- <%= if @theme_settings.show_prices do %> -
- <%= if product.on_sale do %> - - £<%= product.price / 100 %> - - - £<%= product.compare_at_price / 100 %> - - <% else %> - - £<%= product.price / 100 %> - - <% end %> -
- <% end %> -

- Free delivery over £40 -

-
-
+ <.product_card + product={product} + theme_settings={@theme_settings} + mode={:preview} + variant={:default} + show_category={true} + /> <% end %> diff --git a/lib/simpleshop_theme_web/live/theme_live/preview_pages/error.html.heex b/lib/simpleshop_theme_web/live/theme_live/preview_pages/error.html.heex index f305931..83df93d 100644 --- a/lib/simpleshop_theme_web/live/theme_live/preview_pages/error.html.heex +++ b/lib/simpleshop_theme_web/live/theme_live/preview_pages/error.html.heex @@ -42,35 +42,12 @@
<%= for product <- Enum.take(@preview_data.products, 4) do %> -
-
- {product.name} - <%= if @theme_settings.hover_image && product[:hover_image_url] do %> - {product.name} - <% end %> -
-
-

- <%= product.name %> -

- <%= if @theme_settings.show_prices do %> -

- £<%= product.price / 100 %> -

- <% end %> -
-
+ <.product_card + product={product} + theme_settings={@theme_settings} + mode={:preview} + variant={:minimal} + /> <% end %>
diff --git a/lib/simpleshop_theme_web/live/theme_live/preview_pages/home.html.heex b/lib/simpleshop_theme_web/live/theme_live/preview_pages/home.html.heex index e97e9ac..8e49838 100644 --- a/lib/simpleshop_theme_web/live/theme_live/preview_pages/home.html.heex +++ b/lib/simpleshop_theme_web/live/theme_live/preview_pages/home.html.heex @@ -62,53 +62,12 @@ end ]}> <%= for product <- Enum.take(@preview_data.products, 4) do %> -
-
- <%= if product[:is_new] do %> - New - <% end %> - <%= if product.on_sale do %> - Sale - <% end %> - {product.name} - <%= if @theme_settings.hover_image && product[:hover_image_url] do %> - {product.name} - <% end %> -
-
-

- <%= product.name %> -

- <%= if @theme_settings.show_prices do %> -

- <%= if product.on_sale do %> - £<%= product.compare_at_price / 100 %> - <% end %> - £<%= product.price / 100 %> -

- <% end %> -

- Free delivery over £40 -

-
-
+ <.product_card + product={product} + theme_settings={@theme_settings} + mode={:preview} + variant={:featured} + /> <% end %> diff --git a/lib/simpleshop_theme_web/live/theme_live/preview_pages/pdp.html.heex b/lib/simpleshop_theme_web/live/theme_live/preview_pages/pdp.html.heex index 062b7f8..059d7e0 100644 --- a/lib/simpleshop_theme_web/live/theme_live/preview_pages/pdp.html.heex +++ b/lib/simpleshop_theme_web/live/theme_live/preview_pages/pdp.html.heex @@ -404,42 +404,12 @@
<%= for related_product <- Enum.slice(@preview_data.products, 1, 4) do %> - -
- {related_product.name} - <%= if @theme_settings.hover_image && related_product[:hover_image_url] do %> - {related_product.name} - <% end %> -
-
-

- <%= related_product.name %> -

- <%= if @theme_settings.show_prices do %> -

- £<%= related_product.price / 100 %> -

- <% end %> -
-
+ <.product_card + product={related_product} + theme_settings={@theme_settings} + mode={:preview} + variant={:compact} + /> <% end %>