make PDP variant selection work without JS
All checks were successful
deploy / deploy (push) Successful in 1m3s
All checks were successful
deploy / deploy (push) Successful in 1m3s
Variant options (colour, size) are now URL params handled via handle_params instead of phx-click events. Swatches and size buttons render as patch links in shop mode, so changing variants works as plain navigation without JS. Quantity is now a number input that submits with the form. Unavailable variants render as disabled spans. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 %>
|
||||
</div>
|
||||
@@ -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"""
|
||||
<button
|
||||
type="button"
|
||||
phx-click={if @mode == :shop, do: "select_option"}
|
||||
phx-value-option={@option_name}
|
||||
phx-value-selected={@title}
|
||||
class="color-swatch"
|
||||
style={"background-color: #{@hex};"}
|
||||
title={@title}
|
||||
aria-label={"Select #{@title}"}
|
||||
aria-pressed={to_string(@selected)}
|
||||
>
|
||||
</button>
|
||||
<%= 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)}
|
||||
>
|
||||
</.link>
|
||||
<% @mode == :shop -> %>
|
||||
<span
|
||||
class="color-swatch"
|
||||
style={"background-color: #{@hex};"}
|
||||
title={"#{@title} (unavailable)"}
|
||||
aria-label={"#{@title} (unavailable)"}
|
||||
aria-disabled="true"
|
||||
/>
|
||||
<% true -> %>
|
||||
<button
|
||||
type="button"
|
||||
class="color-swatch"
|
||||
style={"background-color: #{@hex};"}
|
||||
title={@title}
|
||||
aria-label={"Select #{@title}"}
|
||||
aria-pressed={to_string(@selected)}
|
||||
/>
|
||||
<% 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"""
|
||||
<button
|
||||
type="button"
|
||||
phx-click={if @mode == :shop, do: "select_option"}
|
||||
phx-value-option={@option_name}
|
||||
phx-value-selected={@title}
|
||||
class="size-btn"
|
||||
aria-pressed={to_string(@selected)}
|
||||
>
|
||||
{@title}
|
||||
</button>
|
||||
<%= cond do %>
|
||||
<% @mode == :shop and not @disabled -> %>
|
||||
<.link
|
||||
patch={@url}
|
||||
class="size-btn"
|
||||
aria-pressed={to_string(@selected)}
|
||||
>
|
||||
{@title}
|
||||
</.link>
|
||||
<% @mode == :shop -> %>
|
||||
<span
|
||||
class="size-btn"
|
||||
aria-disabled="true"
|
||||
>
|
||||
{@title}
|
||||
</span>
|
||||
<% true -> %>
|
||||
<button
|
||||
type="button"
|
||||
class="size-btn"
|
||||
aria-pressed={to_string(@selected)}
|
||||
>
|
||||
{@title}
|
||||
</button>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -1478,9 +1514,15 @@ defmodule BerrypodWeb.ShopComponents.Product do
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span class="qty-display">
|
||||
{@quantity}
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value={@quantity}
|
||||
min={@min}
|
||||
max={@max}
|
||||
class="qty-display"
|
||||
aria-label="Quantity"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="increment_quantity"
|
||||
|
||||
Reference in New Issue
Block a user