diff --git a/assets/css/shop/components.css b/assets/css/shop/components.css
index 6b5abe4..dac6456 100644
--- a/assets/css/shop/components.css
+++ b/assets/css/shop/components.css
@@ -578,11 +578,18 @@
position: relative;
cursor: pointer;
padding: 0;
+ display: inline-block;
+ text-decoration: none;
&[aria-pressed="true"] {
border-color: var(--t-accent);
--tw-ring-color: var(--t-accent);
}
+
+ &[aria-disabled="true"] {
+ opacity: 0.3;
+ cursor: not-allowed;
+ }
}
.size-btn {
@@ -594,11 +601,18 @@
font-weight: 500;
transition: all 0.2s ease;
cursor: pointer;
+ text-decoration: none;
&[aria-pressed="true"] {
border-color: var(--t-accent);
background: color-mix(in oklch, var(--t-accent) 10%, transparent);
}
+
+ &[aria-disabled="true"] {
+ opacity: 0.3;
+ cursor: not-allowed;
+ border-style: dashed;
+ }
}
/* ── Quantity selector ── */
@@ -643,9 +657,20 @@
color: var(--t-text-primary);
padding: 0.5rem 1rem;
border-inline: 2px solid var(--t-border-default);
- min-width: 3rem;
+ width: 3.5rem;
text-align: center;
font-variant-numeric: tabular-nums;
+ background: none;
+ border-top: none;
+ border-bottom: none;
+ font: inherit;
+ appearance: textfield;
+
+ &::-webkit-inner-spin-button,
+ &::-webkit-outer-spin-button {
+ appearance: none;
+ margin: 0;
+ }
}
.stock-in {
diff --git a/lib/berrypod_web/components/page_templates/pdp.html.heex b/lib/berrypod_web/components/page_templates/pdp.html.heex
index 7e82d79..59475c0 100644
--- a/lib/berrypod_web/components/page_templates/pdp.html.heex
+++ b/lib/berrypod_web/components/page_templates/pdp.html.heex
@@ -32,7 +32,7 @@
name="variant_id"
value={@selected_variant && @selected_variant.id}
/>
-
+ <%!-- quantity is provided by the quantity_selector input below --%>
<%!-- Dynamic variant selectors --%>
<%= for option_type <- @option_types do %>
@@ -41,6 +41,7 @@
selected={@selected_options[option_type.name]}
available={@available_options[option_type.name] || []}
mode={@mode}
+ option_urls={(@option_urls || %{})[option_type.name] || %{}}
/>
<% end %>
diff --git a/lib/berrypod_web/components/shop_components/product.ex b/lib/berrypod_web/components/shop_components/product.ex
index 4670e7f..f250d6b 100644
--- a/lib/berrypod_web/components/shop_components/product.ex
+++ b/lib/berrypod_web/components/shop_components/product.ex
@@ -1339,13 +1339,15 @@ defmodule BerrypodWeb.ShopComponents.Product do
Renders a variant selector for a single option type.
Shows color swatches for color-type options, text buttons for others.
- Disables unavailable options and fires `select_option` event on click.
+ In shop mode, each option is a patch link for progressive enhancement.
+ In preview mode, options are inert buttons.
## Attributes
* `option_type` - Required. Map with :name, :type, :values keys
* `selected` - Required. Currently selected value (string)
* `available` - Required. List of available values for this option
+ * `option_urls` - Optional. Map of value title => patch URL
* `mode` - Optional. :shop or :preview (default: :shop)
## Examples
@@ -1354,11 +1356,13 @@ defmodule BerrypodWeb.ShopComponents.Product do
option_type={%{name: "Size", type: :size, values: [%{title: "S"}, ...]}}
selected="M"
available={["S", "M", "L"]}
+ option_urls={%{"S" => "/products/foo?Size=S", ...}}
/>
"""
attr :option_type, :map, required: true
attr :selected, :string, required: true
attr :available, :list, required: true
+ attr :option_urls, :map, default: %{}
attr :mode, :atom, default: :shop
def variant_selector(assigns) do
@@ -1378,8 +1382,8 @@ defmodule BerrypodWeb.ShopComponents.Product do
hex={value[:hex] || "#888888"}
selected={value.title == @selected}
disabled={value.title not in @available}
- option_name={@option_type.name}
mode={@mode}
+ url={@option_urls[value.title]}
/>
<% else %>
<.size_button
@@ -1387,8 +1391,8 @@ defmodule BerrypodWeb.ShopComponents.Product do
title={value.title}
selected={value.title == @selected}
disabled={value.title not in @available}
- option_name={@option_type.name}
mode={@mode}
+ url={@option_urls[value.title]}
/>
<% end %>
@@ -1400,44 +1404,76 @@ defmodule BerrypodWeb.ShopComponents.Product do
attr :hex, :string, required: true
attr :selected, :boolean, required: true
attr :disabled, :boolean, required: true
- attr :option_name, :string, required: true
attr :mode, :atom, default: :shop
+ attr :url, :string, default: nil
defp color_swatch(assigns) do
~H"""
-
+ <%= cond do %>
+ <% @mode == :shop and not @disabled -> %>
+ <.link
+ patch={@url}
+ class="color-swatch"
+ style={"background-color: #{@hex};"}
+ title={@title}
+ aria-label={"Select #{@title}"}
+ aria-pressed={to_string(@selected)}
+ >
+
+ <% @mode == :shop -> %>
+
+ <% true -> %>
+
+ <% end %>
"""
end
attr :title, :string, required: true
attr :selected, :boolean, required: true
attr :disabled, :boolean, required: true
- attr :option_name, :string, required: true
attr :mode, :atom, default: :shop
+ attr :url, :string, default: nil
defp size_button(assigns) do
~H"""
-
+ <%= cond do %>
+ <% @mode == :shop and not @disabled -> %>
+ <.link
+ patch={@url}
+ class="size-btn"
+ aria-pressed={to_string(@selected)}
+ >
+ {@title}
+
+ <% @mode == :shop -> %>
+
+ {@title}
+
+ <% true -> %>
+
+ <% end %>
"""
end
@@ -1478,9 +1514,15 @@ defmodule BerrypodWeb.ShopComponents.Product do
>
−
-
- {@quantity}
-
+