extract product.ex inline styles to CSS component classes (Phase 2)
Move ~80 inline style= attributes from product.ex into ~40 CSS classes in @layer components. Only genuinely dynamic values (hex colours, background-image URLs) remain as inline styles. Pre-declare CSS layer order in shop_root.html.heex so reset < components in the cascade. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fcd1b1ce80
commit
2af2d782d5
@ -1,11 +1,327 @@
|
|||||||
/* Component styles — extracted from inline styles in later phases.
|
/* Component styles — extracted from inline styles in product.ex, layout.ex, etc.
|
||||||
Each component gets its own section. */
|
Each component gets its own section. */
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
/* Phase 2: product cards, grid, badges, hero, categories */
|
/* ── Shared heading treatment ──
|
||||||
/* Phase 2: PDP, variant selector, gallery, accordion */
|
font-family + weight + tracking + colour used across
|
||||||
/* Phase 3: layout components (header, footer, nav, search) */
|
hero titles, section headings, collection headers, PDP, etc. */
|
||||||
/* Phase 3: cart components (drawer, items, summary) */
|
|
||||||
/* Phase 4: content components (contact, reviews, newsletter) */
|
.t-heading {
|
||||||
/* Phase 4: page templates (checkout success, etc.) */
|
font-family: var(--t-font-heading);
|
||||||
|
font-weight: var(--t-heading-weight);
|
||||||
|
letter-spacing: var(--t-heading-tracking);
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product card ── */
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background-color: var(--t-surface-raised);
|
||||||
|
border-radius: var(--t-radius-card);
|
||||||
|
|
||||||
|
&[data-variant="default"],
|
||||||
|
&[data-variant="compact"] {
|
||||||
|
border: 1px solid var(--t-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-variant="minimal"] {
|
||||||
|
border: 1px solid var(--t-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-variant="default"],
|
||||||
|
&[data-variant="featured"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-clickable] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .stretched-link {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-image-wrap {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-placeholder {
|
||||||
|
color: var(--t-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-category {
|
||||||
|
color: var(--t-text-tertiary);
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-title {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="default"] .product-card-title,
|
||||||
|
.product-card[data-variant="compact"] .product-card-title {
|
||||||
|
font-family: var(--t-font-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-delivery {
|
||||||
|
color: var(--t-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product prices (shared between cards and PDP) ── */
|
||||||
|
|
||||||
|
.product-price--sale {
|
||||||
|
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price--compare {
|
||||||
|
color: var(--t-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price--regular {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price--secondary {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-badge {
|
||||||
|
background-color: var(--t-sale-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Hero section ── */
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
padding: var(--space-2xl) var(--space-lg);
|
||||||
|
|
||||||
|
&[data-background="base"] {
|
||||||
|
background-color: var(--t-surface-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-background="sunken"] {
|
||||||
|
background-color: var(--t-surface-sunken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section--page {
|
||||||
|
padding-top: var(--space-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-pre-title {
|
||||||
|
font-family: var(--t-font-heading);
|
||||||
|
font-weight: var(--t-heading-weight);
|
||||||
|
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-description {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Category nav ── */
|
||||||
|
|
||||||
|
.category-nav-section {
|
||||||
|
padding: var(--space-xl) var(--space-lg);
|
||||||
|
background-color: var(--t-surface-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card {
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
font-family: var(--t-font-body);
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Featured products section ── */
|
||||||
|
|
||||||
|
.featured-section {
|
||||||
|
padding: var(--space-xl) var(--space-lg);
|
||||||
|
background-color: var(--t-surface-sunken);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-button {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
border: 1px solid var(--t-text-primary);
|
||||||
|
border-radius: var(--t-radius-button);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Image + text section ── */
|
||||||
|
|
||||||
|
.image-text-section {
|
||||||
|
padding: var(--space-2xl) var(--space-lg);
|
||||||
|
background-color: var(--t-surface-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text-image {
|
||||||
|
border-radius: var(--t-radius-image);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text-body {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent-link {
|
||||||
|
color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%));
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Collection header ── */
|
||||||
|
|
||||||
|
.collection-header-wrap {
|
||||||
|
background-color: var(--t-surface-raised);
|
||||||
|
border-color: var(--t-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-header-meta {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Breadcrumb ── */
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
|
||||||
|
& [aria-current="page"] {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Related products ── */
|
||||||
|
|
||||||
|
.related-section {
|
||||||
|
border-top: 1px solid var(--t-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── PDP gallery ── */
|
||||||
|
|
||||||
|
.pdp-gallery-frame {
|
||||||
|
border-radius: var(--t-radius-image);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdp-thumbnail {
|
||||||
|
border-radius: var(--t-radius-image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Variant selector ── */
|
||||||
|
|
||||||
|
.variant-label {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-label-value {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-swatch {
|
||||||
|
border-color: var(--t-border-default);
|
||||||
|
|
||||||
|
&[aria-pressed="true"] {
|
||||||
|
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
--tw-ring-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-btn {
|
||||||
|
border: 2px solid var(--t-border-default);
|
||||||
|
border-radius: var(--t-radius-button);
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&[aria-pressed="true"] {
|
||||||
|
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
background: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Quantity selector ── */
|
||||||
|
|
||||||
|
.qty-label {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-group {
|
||||||
|
border: 2px solid var(--t-border-default);
|
||||||
|
border-radius: var(--t-radius-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-btn {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-display {
|
||||||
|
border-color: var(--t-border-default);
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-in {
|
||||||
|
color: var(--t-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-out {
|
||||||
|
color: var(--t-sale-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Add to cart ── */
|
||||||
|
|
||||||
|
.atc-wrap {
|
||||||
|
background-color: var(--t-surface-base);
|
||||||
|
border-color: var(--t-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.atc-btn {
|
||||||
|
background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
color: var(--t-text-inverse);
|
||||||
|
border-radius: var(--t-radius-button);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: var(--t-border-default);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Accordion ── */
|
||||||
|
|
||||||
|
.accordion-summary {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-body {
|
||||||
|
color: var(--t-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product details ── */
|
||||||
|
|
||||||
|
.details-wrap {
|
||||||
|
border-top: 1px solid var(--t-border-subtle);
|
||||||
|
border-bottom: 1px solid var(--t-border-subtle);
|
||||||
|
border-color: var(--t-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table-row {
|
||||||
|
border-bottom: 1px solid var(--t-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-th {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-subheading {
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,10 @@
|
|||||||
) do %>
|
) do %>
|
||||||
<link rel="preload" href={preload.href} as="font" type="font/woff2" crossorigin />
|
<link rel="preload" href={preload.href} as="font" type="font/woff2" crossorigin />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<!-- Pre-declare layer order so reset < components regardless of load order -->
|
||||||
|
<style>
|
||||||
|
@layer properties, reset, primitives, tokens, theme, base, components, layout, utilities, overrides;
|
||||||
|
</style>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app-shop.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app-shop.css"} />
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/css/shop.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/css/shop.css"} />
|
||||||
<script defer phx-track-static src={~p"/assets/js/app.js"}>
|
<script defer phx-track-static src={~p"/assets/js/app.js"}>
|
||||||
|
|||||||
@ -63,7 +63,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<article
|
<article
|
||||||
class={card_classes(@variant)}
|
class={card_classes(@variant)}
|
||||||
style={card_style(@variant) <> if(@clickable_resolved, do: " position: relative;", else: "")}
|
data-variant={@variant}
|
||||||
|
data-clickable={@clickable_resolved || nil}
|
||||||
>
|
>
|
||||||
<.product_card_inner
|
<.product_card_inner
|
||||||
product={@product}
|
product={@product}
|
||||||
@ -102,7 +103,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|> assign(:has_hover_image, assigns.theme_settings.hover_image && hover_image != nil)
|
|> assign(:has_hover_image, assigns.theme_settings.hover_image && hover_image != nil)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class={image_container_classes(@variant)} style="z-index: 1;">
|
<div class={["product-card-image-wrap", image_container_classes(@variant)]}>
|
||||||
<%= if @show_badges do %>
|
<%= if @show_badges do %>
|
||||||
<.product_badge product={@product} />
|
<.product_badge product={@product} />
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -145,23 +146,19 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<div class={content_padding_class(@variant)}>
|
<div class={content_padding_class(@variant)}>
|
||||||
<%= if @show_category && Map.get(@product, :category) do %>
|
<%= if @show_category && Map.get(@product, :category) do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<p
|
<p class="product-card-category text-xs mb-1">
|
||||||
class="text-xs mb-1"
|
|
||||||
style="color: var(--t-text-tertiary); position: relative; z-index: 1;"
|
|
||||||
>
|
|
||||||
{@product.category}
|
{@product.category}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={"/collections/#{Slug.slugify(@product.category)}"}
|
navigate={"/collections/#{Slug.slugify(@product.category)}"}
|
||||||
class="text-xs mb-1 block hover:underline"
|
class="product-card-category text-xs mb-1 block hover:underline"
|
||||||
style="color: var(--t-text-tertiary); text-decoration: none; position: relative; z-index: 1;"
|
|
||||||
>
|
>
|
||||||
{@product.category}
|
{@product.category}
|
||||||
</.link>
|
</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h3 class={title_classes(@variant)} style={title_style(@variant)}>
|
<h3 class={["product-card-title", title_classes(@variant)]}>
|
||||||
<%= if @clickable do %>
|
<%= if @clickable do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<a
|
<a
|
||||||
@ -169,7 +166,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page="pdp"
|
phx-value-page="pdp"
|
||||||
class="stretched-link"
|
class="stretched-link"
|
||||||
style="color: inherit; text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
{@product.title}
|
{@product.title}
|
||||||
</a>
|
</a>
|
||||||
@ -177,7 +173,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.link
|
<.link
|
||||||
navigate={"/products/#{Map.get(@product, :slug) || Map.get(@product, :id)}"}
|
navigate={"/products/#{Map.get(@product, :slug) || Map.get(@product, :id)}"}
|
||||||
class="stretched-link"
|
class="stretched-link"
|
||||||
style="color: inherit; text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
{@product.title}
|
{@product.title}
|
||||||
</.link>
|
</.link>
|
||||||
@ -190,7 +185,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.product_price product={@product} variant={@variant} />
|
<.product_price product={@product} variant={@variant} />
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @show_delivery_text do %>
|
<%= if @show_delivery_text do %>
|
||||||
<p class="text-xs mt-1" style="color: var(--t-text-tertiary);">
|
<p class="product-card-delivery text-xs mt-1">
|
||||||
Made to order
|
Made to order
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -231,8 +226,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<%= cond do %>
|
<%= cond do %>
|
||||||
<% is_nil(@src) -> %>
|
<% is_nil(@src) -> %>
|
||||||
<div
|
<div
|
||||||
class={[@class, "flex items-center justify-center"]}
|
class={[@class, "product-card-placeholder flex items-center justify-center"]}
|
||||||
style="color: var(--t-text-tertiary);"
|
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={@alt}
|
aria-label={@alt}
|
||||||
>
|
>
|
||||||
@ -303,36 +297,33 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<% :default -> %>
|
<% :default -> %>
|
||||||
<div>
|
<div>
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span
|
<span class="product-price--sale text-lg font-bold">
|
||||||
class="text-lg font-bold"
|
|
||||||
style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));"
|
|
||||||
>
|
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm line-through ml-2" style="color: var(--t-text-tertiary);">
|
<span class="product-price--compare text-sm line-through ml-2">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-lg font-bold" style="color: var(--t-text-primary);">
|
<span class="product-price--regular text-lg font-bold">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% :featured -> %>
|
<% :featured -> %>
|
||||||
<p class="text-sm" style="color: var(--t-text-secondary);">
|
<p class="product-price--secondary text-sm">
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span class="line-through mr-1" style="color: var(--t-text-tertiary);">
|
<span class="product-price--compare line-through mr-1">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% :compact -> %>
|
<% :compact -> %>
|
||||||
<p class="font-bold" style="color: var(--t-text-primary);">
|
<p class="product-price--regular font-bold">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% :minimal -> %>
|
<% :minimal -> %>
|
||||||
<p class="text-xs" style="color: var(--t-text-secondary);">
|
<p class="product-price--secondary text-xs">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -359,22 +350,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp card_classes(:compact), do: "product-card group overflow-hidden cursor-pointer"
|
defp card_classes(:compact), do: "product-card group overflow-hidden cursor-pointer"
|
||||||
defp card_classes(:minimal), do: "product-card group overflow-hidden"
|
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),
|
defp image_container_classes(:compact),
|
||||||
do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative"
|
do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative"
|
||||||
|
|
||||||
@ -393,16 +368,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp title_classes(:compact), do: "font-semibold text-sm 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_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);"
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a responsive product grid container.
|
Renders a responsive product grid container.
|
||||||
|
|
||||||
@ -544,20 +509,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<%= case @variant do %>
|
<%= case @variant do %>
|
||||||
<% :default -> %>
|
<% :default -> %>
|
||||||
<section
|
<section class="hero-section text-center" data-background={@background}>
|
||||||
class="text-center"
|
<h1 class="t-heading text-3xl md:text-4xl mb-4">
|
||||||
style={"padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-#{@background});"}
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
class="text-3xl md:text-4xl mb-4"
|
|
||||||
style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p class="hero-description text-lg max-w-lg mx-auto mb-8">
|
||||||
class="text-lg max-w-lg mx-auto mb-8"
|
|
||||||
style="color: var(--t-text-secondary); line-height: 1.6;"
|
|
||||||
>
|
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<.hero_cta
|
<.hero_cta
|
||||||
@ -570,34 +526,25 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<% :page -> %>
|
<% :page -> %>
|
||||||
<div class="text-center" style="padding-top: var(--space-2xl);">
|
<div class="hero-section--page text-center">
|
||||||
<h1
|
<h1 class="t-heading text-4xl md:text-5xl mb-6">
|
||||||
class="text-4xl md:text-5xl font-bold mb-6"
|
|
||||||
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg mb-12 max-w-2xl mx-auto" style="color: var(--t-text-secondary);">
|
<p class="hero-description text-lg mb-12 max-w-2xl mx-auto">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% :error -> %>
|
<% :error -> %>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<%= if @pre_title do %>
|
<%= if @pre_title do %>
|
||||||
<h1
|
<h1 class="hero-pre-title text-8xl md:text-9xl mb-4">
|
||||||
class="text-8xl md:text-9xl font-bold mb-4"
|
|
||||||
style="font-family: var(--t-font-heading); color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); font-weight: var(--t-heading-weight);"
|
|
||||||
>
|
|
||||||
{@pre_title}
|
{@pre_title}
|
||||||
</h1>
|
</h1>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h2
|
<h2 class="t-heading text-3xl md:text-4xl mb-6">
|
||||||
class="text-3xl md:text-4xl font-bold mb-6"
|
|
||||||
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg mb-8 max-w-md mx-auto" style="color: var(--t-text-secondary);">
|
<p class="hero-description text-lg mb-8 max-w-md mx-auto">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<%= if @cta_text || @secondary_cta_text do %>
|
<%= if @cta_text || @secondary_cta_text do %>
|
||||||
@ -638,7 +585,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@page}
|
phx-value-page={@page}
|
||||||
class={hero_cta_classes(@variant)}
|
class={hero_cta_classes(@variant)}
|
||||||
style={hero_cta_style(@variant)}
|
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</button>
|
</button>
|
||||||
@ -646,7 +592,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.link
|
<.link
|
||||||
navigate={@href || "/"}
|
navigate={@href || "/"}
|
||||||
class={["inline-block", hero_cta_classes(@variant)]}
|
class={["inline-block", hero_cta_classes(@variant)]}
|
||||||
style={hero_cta_style(@variant) <> " text-decoration: none;"}
|
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -659,9 +604,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp hero_cta_classes(:secondary),
|
defp hero_cta_classes(:secondary),
|
||||||
do: "themed-button-outline px-8 py-3 font-semibold transition-all"
|
do: "themed-button-outline px-8 py-3 font-semibold transition-all"
|
||||||
|
|
||||||
defp hero_cta_style(:primary), do: ""
|
|
||||||
defp hero_cta_style(:secondary), do: ""
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a row of category circles for navigation.
|
Renders a row of category circles for navigation.
|
||||||
|
|
||||||
@ -682,7 +624,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def category_nav(assigns) do
|
def category_nav(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-base);">
|
<section class="category-nav-section">
|
||||||
<h2 class="sr-only">Shop by Category</h2>
|
<h2 class="sr-only">Shop by Category</h2>
|
||||||
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
|
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
|
||||||
<%= for category <- Enum.take(@categories, @limit) do %>
|
<%= for category <- Enum.take(@categories, @limit) do %>
|
||||||
@ -691,8 +633,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
href="#"
|
href="#"
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page="collection"
|
phx-value-page="collection"
|
||||||
class="flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
class="category-card flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
||||||
style="text-decoration: none; cursor: pointer;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
||||||
@ -704,18 +645,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span class="category-name text-sm font-medium">
|
||||||
class="text-sm font-medium"
|
|
||||||
style="font-family: var(--t-font-body); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{category.name}
|
{category.name}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={"/collections/#{category.slug}"}
|
navigate={"/collections/#{category.slug}"}
|
||||||
class="flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
class="category-card flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
||||||
style="text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
||||||
@ -727,10 +664,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span class="category-name text-sm font-medium">
|
||||||
class="text-sm font-medium"
|
|
||||||
style="font-family: var(--t-font-body); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{category.name}
|
{category.name}
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
@ -775,11 +709,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def featured_products_section(assigns) do
|
def featured_products_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-sunken);">
|
<section class="featured-section">
|
||||||
<h2
|
<h2 class="t-heading text-2xl mb-6">
|
||||||
class="text-2xl mb-6"
|
|
||||||
style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@ -800,16 +731,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<button
|
<button
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@cta_page}
|
phx-value-page={@cta_page}
|
||||||
class="px-6 py-3 font-medium transition-all"
|
class="outline-button px-6 py-3 font-medium transition-all"
|
||||||
style="background-color: transparent; color: var(--t-text-primary); border: 1px solid var(--t-text-primary); border-radius: var(--t-radius-button); cursor: pointer;"
|
|
||||||
>
|
>
|
||||||
{@cta_text}
|
{@cta_text}
|
||||||
</button>
|
</button>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={@cta_href}
|
navigate={@cta_href}
|
||||||
class="inline-block px-6 py-3 font-medium transition-all"
|
class="outline-button inline-block px-6 py-3 font-medium transition-all"
|
||||||
style="background-color: transparent; color: var(--t-text-primary); border: 1px solid var(--t-text-primary); border-radius: var(--t-radius-button); text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
{@cta_text}
|
{@cta_text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -855,10 +784,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def image_text_section(assigns) do
|
def image_text_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section
|
<section class="image-text-section grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||||
class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center"
|
|
||||||
style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);"
|
|
||||||
>
|
|
||||||
<%= if @image_position == :left do %>
|
<%= if @image_position == :left do %>
|
||||||
<.image_text_image image_url={@image_url} />
|
<.image_text_image image_url={@image_url} />
|
||||||
<.image_text_content
|
<.image_text_content
|
||||||
@ -889,8 +815,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp image_text_image(assigns) do
|
defp image_text_image(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
class="h-72 rounded-lg bg-cover bg-center"
|
class="image-text-image h-72 bg-cover bg-center"
|
||||||
style={"background-image: url('#{@image_url}'); border-radius: var(--t-radius-image);"}
|
style={"background-image: url('#{@image_url}');"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@ -906,13 +832,10 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp image_text_content(assigns) do
|
defp image_text_content(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2 class="t-heading text-2xl mb-4">
|
||||||
class="text-2xl mb-4"
|
|
||||||
style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-base mb-4" style="color: var(--t-text-secondary); line-height: 1.7;">
|
<p class="image-text-body text-base mb-4">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<%= if @link_text do %>
|
<%= if @link_text do %>
|
||||||
@ -921,16 +844,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
href="#"
|
href="#"
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@link_page}
|
phx-value-page={@link_page}
|
||||||
class="text-sm font-medium transition-colors"
|
class="accent-link text-sm font-medium transition-colors"
|
||||||
style="color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%)); text-decoration: none; cursor: pointer;"
|
|
||||||
>
|
>
|
||||||
{@link_text}
|
{@link_text}
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={@link_href || "/"}
|
navigate={@link_href || "/"}
|
||||||
class="text-sm font-medium transition-colors"
|
class="accent-link text-sm font-medium transition-colors"
|
||||||
style="color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%)); text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
{@link_text}
|
{@link_text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -959,22 +880,16 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def collection_header(assigns) do
|
def collection_header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div class="collection-header-wrap border-b">
|
||||||
class="border-b"
|
|
||||||
style="background-color: var(--t-surface-raised); border-color: var(--t-border-default);"
|
|
||||||
>
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<h1
|
<h1 class="t-heading text-3xl md:text-4xl mb-2">
|
||||||
class="text-3xl md:text-4xl font-bold mb-2"
|
|
||||||
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<%= if @subtitle do %>
|
<%= if @subtitle do %>
|
||||||
<p style="color: var(--t-text-secondary);">{@subtitle}</p>
|
<p class="collection-header-meta">{@subtitle}</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @product_count do %>
|
<%= if @product_count do %>
|
||||||
<p style="color: var(--t-text-secondary);">{@product_count} products</p>
|
<p class="collection-header-meta">{@product_count} products</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1053,11 +968,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def breadcrumb(assigns) do
|
def breadcrumb(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<nav aria-label="Breadcrumb" class="breadcrumb" style="color: var(--t-text-secondary);">
|
<nav aria-label="Breadcrumb" class="breadcrumb">
|
||||||
<ol>
|
<ol>
|
||||||
<%= for {item, _index} <- Enum.with_index(@items) do %>
|
<%= for {item, _index} <- Enum.with_index(@items) do %>
|
||||||
<%= if item[:current] do %>
|
<%= if item[:current] do %>
|
||||||
<li aria-current="page" style="color: var(--t-text-primary);">{item.label}</li>
|
<li aria-current="page">{item.label}</li>
|
||||||
<% else %>
|
<% else %>
|
||||||
<li>
|
<li>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
@ -1107,11 +1022,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def related_products_section(assigns) do
|
def related_products_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="py-12" style="border-top: 1px solid var(--t-border-default);">
|
<div class="related-section py-12">
|
||||||
<h2
|
<h2 class="t-heading text-2xl mb-6">
|
||||||
class="text-2xl font-bold mb-6"
|
|
||||||
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);"
|
|
||||||
>
|
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@ -1150,7 +1062,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<div class="pdp-gallery">
|
<div class="pdp-gallery">
|
||||||
<%!-- Image area (relative container for dots + desktop nav) --%>
|
<%!-- Image area (relative container for dots + desktop nav) --%>
|
||||||
<div class="relative" style="border-radius: var(--t-radius-image); overflow: hidden;">
|
<div class="pdp-gallery-frame relative">
|
||||||
<%!-- Scroll-snap carousel (2+ images) or single image --%>
|
<%!-- Scroll-snap carousel (2+ images) or single image --%>
|
||||||
<%= if length(@images) > 1 do %>
|
<%= if length(@images) > 1 do %>
|
||||||
<div
|
<div
|
||||||
@ -1217,8 +1129,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<div class="pdp-gallery-single">
|
<div class="pdp-gallery-single">
|
||||||
<%= if @images == [] do %>
|
<%= if @images == [] do %>
|
||||||
<div
|
<div
|
||||||
class="w-full h-full flex items-center justify-center"
|
class="product-card-placeholder w-full h-full flex items-center justify-center"
|
||||||
style="color: var(--t-text-tertiary);"
|
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={@product_name}
|
aria-label={@product_name}
|
||||||
>
|
>
|
||||||
@ -1274,7 +1185,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={"aspect-square bg-gray-200 overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
class={"aspect-square bg-gray-200 overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
||||||
style="border-radius: var(--t-radius-image);"
|
|
||||||
aria-label={"View image #{idx + 1} of #{length(@images)}"}
|
aria-label={"View image #{idx + 1} of #{length(@images)}"}
|
||||||
phx-click={
|
phx-click={
|
||||||
Phoenix.LiveView.JS.dispatch("pdp:scroll-to",
|
Phoenix.LiveView.JS.dispatch("pdp:scroll-to",
|
||||||
@ -1427,32 +1337,23 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
<h1
|
<h1 class="t-heading text-3xl md:text-4xl mb-4">
|
||||||
class="text-3xl md:text-4xl font-bold mb-4"
|
|
||||||
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
|
|
||||||
>
|
|
||||||
{@product.title}
|
{@product.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 mb-6">
|
<div class="flex items-center gap-4 mb-6">
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span
|
<span class="product-price--sale text-3xl font-bold">
|
||||||
class="text-3xl font-bold"
|
|
||||||
style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));"
|
|
||||||
>
|
|
||||||
{SimpleshopTheme.Cart.format_price(@price)}
|
{SimpleshopTheme.Cart.format_price(@price)}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xl line-through" style="color: var(--t-text-tertiary);">
|
<span class="product-price--compare text-xl line-through">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span class="sale-badge px-2 py-1 text-sm font-bold text-white rounded">
|
||||||
class="px-2 py-1 text-sm font-bold text-white rounded"
|
|
||||||
style="background-color: var(--t-sale-color);"
|
|
||||||
>
|
|
||||||
SAVE {round((@product.compare_at_price - @price) / @product.compare_at_price * 100)}%
|
SAVE {round((@product.compare_at_price - @price) / @product.compare_at_price * 100)}%
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-3xl font-bold" style="color: var(--t-text-primary);">
|
<span class="product-price--regular text-3xl font-bold">
|
||||||
{SimpleshopTheme.Cart.format_price(@price)}
|
{SimpleshopTheme.Cart.format_price(@price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -1490,10 +1391,10 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
def variant_selector(assigns) do
|
def variant_selector(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="block font-semibold mb-2" style="color: var(--t-text-primary);">
|
<div class="variant-label block font-semibold mb-2">
|
||||||
{@option_type.name}<span
|
{@option_type.name}<span
|
||||||
:if={@selected}
|
:if={@selected}
|
||||||
style="color: var(--t-text-secondary); font-weight: normal;"
|
class="variant-label-value"
|
||||||
>: {@selected}</span>
|
>: {@selected}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@ -1537,10 +1438,10 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-value-option={@option_name}
|
phx-value-option={@option_name}
|
||||||
phx-value-selected={@title}
|
phx-value-selected={@title}
|
||||||
class={[
|
class={[
|
||||||
"w-10 h-10 rounded-full border-2 transition-all relative",
|
"color-swatch w-10 h-10 rounded-full border-2 transition-all relative",
|
||||||
@selected && "ring-2 ring-offset-2"
|
@selected && "ring-2 ring-offset-2"
|
||||||
]}
|
]}
|
||||||
style={"background-color: #{@hex}; border-color: #{if @selected, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l))", else: "var(--t-border-default)"}; --tw-ring-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));"}
|
style={"background-color: #{@hex};"}
|
||||||
title={@title}
|
title={@title}
|
||||||
aria-label={"Select #{@title}"}
|
aria-label={"Select #{@title}"}
|
||||||
aria-pressed={to_string(@selected)}
|
aria-pressed={to_string(@selected)}
|
||||||
@ -1562,10 +1463,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click={if @mode == :shop, do: "select_option"}
|
phx-click={if @mode == :shop, do: "select_option"}
|
||||||
phx-value-option={@option_name}
|
phx-value-option={@option_name}
|
||||||
phx-value-selected={@title}
|
phx-value-selected={@title}
|
||||||
class={[
|
class="size-btn px-4 py-2 font-medium transition-all"
|
||||||
"px-4 py-2 font-medium transition-all"
|
|
||||||
]}
|
|
||||||
style={"border: 2px solid #{if @selected, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l))", else: "var(--t-border-default)"}; border-radius: var(--t-radius-button); color: var(--t-text-primary); background: #{if @selected, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1)", else: "transparent"};"}
|
|
||||||
aria-pressed={to_string(@selected)}
|
aria-pressed={to_string(@selected)}
|
||||||
>
|
>
|
||||||
{@title}
|
{@title}
|
||||||
@ -1596,28 +1494,21 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
def quantity_selector(assigns) do
|
def quantity_selector(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<label class="block font-semibold mb-2" style="color: var(--t-text-primary);">
|
<label class="qty-label block font-semibold mb-2">
|
||||||
Quantity
|
Quantity
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div
|
<div class="qty-group flex items-center">
|
||||||
class="flex items-center"
|
|
||||||
style="border: 2px solid var(--t-border-default); border-radius: var(--t-radius-input);"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="decrement_quantity"
|
phx-click="decrement_quantity"
|
||||||
disabled={@quantity <= @min}
|
disabled={@quantity <= @min}
|
||||||
aria-label="Decrease quantity"
|
aria-label="Decrease quantity"
|
||||||
class="px-4 py-2 disabled:opacity-30"
|
class="qty-btn px-4 py-2 disabled:opacity-30"
|
||||||
style="color: var(--t-text-primary);"
|
|
||||||
>
|
>
|
||||||
−
|
−
|
||||||
</button>
|
</button>
|
||||||
<span
|
<span class="qty-display px-4 py-2 border-x-2 min-w-12 text-center tabular-nums">
|
||||||
class="px-4 py-2 border-x-2 min-w-12 text-center tabular-nums"
|
|
||||||
style="border-color: var(--t-border-default); color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
{@quantity}
|
{@quantity}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@ -1625,16 +1516,15 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click="increment_quantity"
|
phx-click="increment_quantity"
|
||||||
disabled={@quantity >= @max}
|
disabled={@quantity >= @max}
|
||||||
aria-label="Increase quantity"
|
aria-label="Increase quantity"
|
||||||
class="px-4 py-2 disabled:opacity-30"
|
class="qty-btn px-4 py-2 disabled:opacity-30"
|
||||||
style="color: var(--t-text-primary);"
|
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<%= if @in_stock do %>
|
<%= if @in_stock do %>
|
||||||
<span class="text-sm" style="color: var(--t-text-tertiary);">In stock</span>
|
<span class="stock-in text-sm">In stock</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-sm font-semibold" style="color: var(--t-sale-color);">Out of stock</span>
|
<span class="stock-out text-sm font-semibold">Out of stock</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1663,20 +1553,16 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def add_to_cart_button(assigns) do
|
def add_to_cart_button(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div class={[
|
||||||
class={[
|
"atc-wrap mb-4",
|
||||||
"mb-4",
|
|
||||||
@sticky &&
|
@sticky &&
|
||||||
"sticky bottom-0 z-10 py-3 md:relative md:py-0 -mx-4 px-4 md:mx-0 md:px-0 border-t md:border-0"
|
"sticky bottom-0 z-10 py-3 md:relative md:py-0 -mx-4 px-4 md:mx-0 md:px-0 border-t md:border-0"
|
||||||
]}
|
]}>
|
||||||
style="background-color: var(--t-surface-base); border-color: var(--t-border-subtle);"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click={if @mode == :preview, do: open_cart_drawer_js(), else: "add_to_cart"}
|
phx-click={if @mode == :preview, do: open_cart_drawer_js(), else: "add_to_cart"}
|
||||||
disabled={@disabled}
|
disabled={@disabled}
|
||||||
class="w-full px-6 py-4 text-lg font-semibold transition-all"
|
class="atc-btn w-full px-6 py-4 text-lg font-semibold transition-all"
|
||||||
style={"background-color: #{if @disabled, do: "var(--t-border-default)", else: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l))"}; color: var(--t-text-inverse); border-radius: var(--t-radius-button); cursor: #{if @disabled, do: "not-allowed", else: "pointer"}; border: none;"}
|
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</button>
|
</button>
|
||||||
@ -1714,10 +1600,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
def accordion_item(assigns) do
|
def accordion_item(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<details open={@open} class="group">
|
<details open={@open} class="group">
|
||||||
<summary
|
<summary class="accordion-summary flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
||||||
class="flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden"
|
|
||||||
style="color: var(--t-text-primary);"
|
|
||||||
>
|
|
||||||
<span class="font-semibold">{@title}</span>
|
<span class="font-semibold">{@title}</span>
|
||||||
<svg
|
<svg
|
||||||
class="w-5 h-5 transition-transform duration-200 group-open:rotate-180"
|
class="w-5 h-5 transition-transform duration-200 group-open:rotate-180"
|
||||||
@ -1730,7 +1613,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="pb-4" style="color: var(--t-text-secondary);">
|
<div class="accordion-body pb-4">
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@ -1768,10 +1651,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div class="details-wrap mt-8 divide-y">
|
||||||
class="mt-8 divide-y"
|
|
||||||
style="border-top: 1px solid var(--t-border-subtle); border-bottom: 1px solid var(--t-border-subtle); border-color: var(--t-border-subtle);"
|
|
||||||
>
|
|
||||||
<.accordion_item title="Description" open={true}>
|
<.accordion_item title="Description" open={true}>
|
||||||
<p class="leading-relaxed">
|
<p class="leading-relaxed">
|
||||||
{@product.description}. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.
|
{@product.description}. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.
|
||||||
@ -1782,25 +1662,21 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.accordion_item title="Size Guide">
|
<.accordion_item title="Size Guide">
|
||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="border-bottom: 1px solid var(--t-border-subtle);">
|
<tr class="details-table-row">
|
||||||
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">
|
<th class="details-th text-left py-2 font-semibold">
|
||||||
Size
|
Size
|
||||||
</th>
|
</th>
|
||||||
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">
|
<th class="details-th text-left py-2 font-semibold">
|
||||||
Chest (cm)
|
Chest (cm)
|
||||||
</th>
|
</th>
|
||||||
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">
|
<th class="details-th text-left py-2 font-semibold">
|
||||||
Length (cm)
|
Length (cm)
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<%= for {size_row, idx} <- Enum.with_index(@sizes) do %>
|
<%= for {size_row, idx} <- Enum.with_index(@sizes) do %>
|
||||||
<tr style={
|
<tr class={idx < length(@sizes) - 1 && "details-table-row"}>
|
||||||
if idx < length(@sizes) - 1,
|
|
||||||
do: "border-bottom: 1px solid var(--t-border-subtle);",
|
|
||||||
else: ""
|
|
||||||
}>
|
|
||||||
<td class="py-2">{size_row.size}</td>
|
<td class="py-2">{size_row.size}</td>
|
||||||
<td class="py-2">{size_row.chest}</td>
|
<td class="py-2">{size_row.chest}</td>
|
||||||
<td class="py-2">{size_row.length}</td>
|
<td class="py-2">{size_row.length}</td>
|
||||||
@ -1814,13 +1690,13 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.accordion_item title="Shipping & Returns">
|
<.accordion_item title="Shipping & Returns">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold mb-1" style="color: var(--t-text-primary);">Delivery</p>
|
<p class="details-subheading font-semibold mb-1">Delivery</p>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.
|
Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold mb-1" style="color: var(--t-text-primary);">Returns</p>
|
<p class="details-subheading font-semibold mb-1">Returns</p>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
We offer a 30-day return policy. Items must be unused and in original packaging. Please contact us to arrange a return.
|
We offer a 30-day return policy. Items must be unused and in original packaging. Please contact us to arrange a return.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user