Files
Stiletto/snippets/product-item.liquid
T

483 lines
18 KiB
Plaintext
Raw Normal View History

2026-08-12 02:53:06 +00:00
{%- doc -%}
Renders a product item.
@param {object} prod - The product object. We use 'prod' to avoid collision with the global 'product' object.
@param {string} product_columns_desktop - The number of columns in the product grid on desktop.
@param {string} product_columns_mobile - The number of columns in the product grid on mobile.
@param {string} aspect_ratio - The image aspect ratio.
@param {boolean} [has_grid_reveal] - Whether the item should animate.
@param {string} custom_color_key_map - A list of custom color names for swatches.
@param {string} custom_color_value_map - A list of custom color values for swatches.
@param {boolean} [quick_view_is_beneath] - Whether the quick view displays below the product info (instead of overlayed on the image).
@param {boolean} [placeholder] - Whether the item is being rendered as a placeholder.
{%- enddoc -%}
2025-11-12 18:26:25 +08:00
{%- liquid
assign prod_compare_at_price = prod.compare_at_price
assign prod_price = prod.price
assign prod_price = prod.metafields.app--168074346497.min_auto_discounted_price.value | default: prod.price
if prod.metafields.app--168074346497.discount_percentage.value > 0.01
assign deducted_percentage = 1.0 | minus: prod.metafields.app--168074346497.discount_percentage.value
assign prod_price = prod.price | divided_by: 100.0 | times: deducted_percentage | times: 100.0 | ceil
assign prod_compare_at_price = prod.price
if prod.compare_at_price > prod_compare_at_price
assign prod_compare_at_price = prod.compare_at_price
endif
endif
2026-08-12 02:53:06 +00:00
assign has_grid_reveal = has_grid_reveal | default: false, allow_false: true
assign quick_view_is_beneath = quick_view_is_beneath | default: false, allow_false: true
assign placeholder = placeholder | default: false, allow_false: true
2025-11-12 18:26:25 +08:00
if settings.product_listing_show_second_image_on_hover and prod.media.size > 1
assign show_multiple_images = true
2026-08-12 02:53:06 +00:00
else
assign show_multiple_images = false
2025-11-12 18:26:25 +08:00
endif
2026-08-12 02:53:06 +00:00
assign mobile_column_count = product_columns_mobile | plus: 0
2025-11-12 18:26:25 +08:00
case product_columns_desktop
when '5'
assign sizes = '(max-width: 720px) calc((90vw - 12px) /[[mcc]]), (max-width: 1400px) calc((93.4vw - (12px * 3)) / 5), 270px' | replace: '[[mcc]]', mobile_column_count
when '3'
assign sizes = '(max-width: 720px) calc((90vw - 12px) /[[mcc]]), (max-width: 1400px) calc((93.4vw - (12px * 3)) / 3), 450px' | replace: '[[mcc]]', mobile_column_count
when '2'
assign sizes = '(max-width: 720px) calc((90vw - 12px) /[[mcc]]), (max-width: 1400px) calc((93.4vw - (12px * 3)) / 2), 690px' | replace: '[[mcc]]', mobile_column_count
else
# Using a 4 item grid as default for sizes
assign sizes = '(max-width: 720px) calc((90vw - 12px) /[[mcc]]), (max-width: 1400px) calc((93.4vw - (12px * 3)) / 4), 304px' | replace: '[[mcc]]', mobile_column_count
endcase
assign has_hover_swatches = false
assign has_hover_chips = false
assign show_sibling_swatches = false
assign sibling_products = prod.metafields.stiletto.sibling_collection.value.products | default: prod.metafields.stiletto.siblings_collection.value.products
# Sibling swatches will override other swatches if these conditions are met
if settings.enable_product_card_sibling_swatches and settings.siblings_option_name != blank and sibling_products.size > 0
assign has_hover_swatches = true
assign show_sibling_swatches = true
assign first_swatchified_option = settings.siblings_option_name | downcase
assign displayed_swatch_count = sibling_products.size
elsif settings.enable_product_card_swatches
capture first_swatchified_option
render 'get-first-styled-option', prod: prod, styled_options: settings.swatch_options
endcapture
assign displayed_swatch_count = prod.options_by_name[first_swatchified_option].values.size
if displayed_swatch_count > 0
assign has_hover_swatches = true
endif
endif
if settings.show_product_card_chips
capture first_chipped_option
render 'get-first-styled-option', prod: prod, styled_options: settings.product_card_chip_options
endcapture
assign displayed_chip_count = prod.options_by_name[first_chipped_option].values.size
if displayed_chip_count > 0
assign has_hover_chips = true
endif
endif
if quick_view_is_beneath
assign has_hover_swatches = false
assign has_hover_chips = false
endif
if prod.has_only_default_variant
assign product_has_variants = false
else
assign product_has_variants = true
endif
# Quick add takes precedence over quick view
# Use quick view for products with variants and sold out products
if settings.enable_quick_add and prod.available
if product_has_variants
assign quick_shop_type = 'quick-view'
assign quick_shop_button_text = 'products.product.choose_options' | t
else
assign quick_shop_type = 'quick-add'
assign quick_shop_button_text = 'products.product.add_to_cart' | t
endif
capture quick_shop_button_hoverless_icon
render 'icon', icon: 'quick-add'
endcapture
elsif settings.enable_quick_view
assign quick_shop_type = 'quick-view'
assign quick_shop_button_text = 'products.product.quick_view' | t
capture quick_shop_button_hoverless_icon
render 'icon', icon: 'quick-view'
endcapture
else
assign quick_shop_type = 'none'
endif
-%}
2026-08-12 02:53:06 +00:00
{%- if quick_shop_type != 'none' -%}
{%- liquid
capture quick_shop_button_attributes
echo 'data-quick-shop-trigger="[[qst]]"' | replace: '[[qst]]', quick_shop_type
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
if quick_shop_type == 'quick-add'
echo 'data-product-id="[[pid]]"' | replace: '[[pid]]', prod.variants[0].id
else
echo 'data-product-url="[[pu]]"' | replace: '[[pu]]', prod.url
endif
endcapture
-%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- capture quick_shop_button -%}
2025-11-12 18:26:25 +08:00
<button
2026-08-12 02:53:06 +00:00
class="product-item__quick-view btn btn--tertiary btn--x-small"
2025-11-12 18:26:25 +08:00
{{ quick_shop_button_attributes }}
tabindex="0"
>
<span>{{ quick_shop_button_text }}</span>
<div class="btn__loading-wrap">
<div class="btn__loading-bar"></div>
</div>
</button>
2026-08-12 02:53:06 +00:00
{%- endcapture -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- capture quick_shop_button_hoverless -%}
2025-11-12 18:26:25 +08:00
<button
2026-08-12 02:53:06 +00:00
class="product-item__quick-view--hoverless btn btn--rounded"
2025-11-12 18:26:25 +08:00
{{ quick_shop_button_attributes }}
tabindex="0"
title="{{ quick_shop_button_text }}"
>
{{ quick_shop_button_hoverless_icon }}
</button>
2026-08-12 02:53:06 +00:00
{%- endcapture -%}
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- liquid
# Wrap add to cart buttons in a product form for no-js, or if purchase confirmation and quick cart are disabled
2025-11-12 18:26:25 +08:00
if quick_shop_type == 'quick-add'
capture product_form_tags
echo '<input class="product-item__product-form-variant-input" name="id" type="hidden" value="[[vi]]">' | replace: '[[vi]]', prod.variants[0].id
endcapture
capture quick_shop_button
form 'product', prod, class: 'product-item__product-form'
echo product_form_tags
echo quick_shop_button
endform
endcapture
capture quick_shop_button_hoverless
form 'product', prod, class: 'product-item__product-form'
echo product_form_tags
echo quick_shop_button_hoverless
endform
endcapture
endif
2026-08-12 02:53:06 +00:00
capture product_item_class
echo 'product-item'
if has_hover_swatches
echo ' product-item--with-hover-swatches'
endif
if has_hover_chips
echo ' product-item--with-hover-chips'
endif
if settings.enable_border_on_hover
echo ' product-item--border-on-hover'
endif
echo ' animation animation--item'
if has_grid_reveal
echo ' animation--item-initial'
endif
endcapture
-%}
2025-11-12 18:26:25 +08:00
<div
2026-08-12 02:53:06 +00:00
class="{{ product_item_class }}"
2025-11-12 18:26:25 +08:00
style="--z-index-item: {{ forloop.index }};"
>
<div class="product-item__inner">
2026-08-12 02:53:06 +00:00
<div class="product-item__media {%- if show_multiple_images %} product-item__media--multiple-images{%- endif %}">
<a
class="product-item__image-link"
href="{{ prod.url | within: collection }}"
aria-label="{{ prod.title }}"
>
2025-11-12 18:26:25 +08:00
{%- liquid
assign image_wrapper_class = 'product-item__image'
if show_multiple_images
assign image_wrapper_class = image_wrapper_class | append: ' product-item__image--one'
2026-08-12 02:53:06 +00:00
assign second_image = prod.media[1]
2025-11-12 18:26:25 +08:00
if prod.featured_media != prod.media[0]
2026-08-12 02:53:06 +00:00
assign second_image = prod.media[0]
2025-11-12 18:26:25 +08:00
endif
endif
-%}
2026-08-12 02:53:06 +00:00
{%- render 'image',
2025-11-12 18:26:25 +08:00
image: prod.featured_media,
wrapper_class: image_wrapper_class,
aspect_ratio: settings.product_listing_aspect_ratio,
2026-08-12 02:53:06 +00:00
focal_point: 'image_presentation',
2025-11-12 18:26:25 +08:00
object_fit: settings.product_listing_media_fit,
include_placeholder: true,
sizes: sizes,
src_set_type: 'grid'
2026-08-12 02:53:06 +00:00
-%}
2025-11-12 18:26:25 +08:00
{%- if show_multiple_images -%}
2026-08-12 02:53:06 +00:00
{%- render 'image',
2025-11-12 18:26:25 +08:00
image: second_image,
wrapper_class: 'product-item__image product-item__image--two',
aspect_ratio: settings.product_listing_aspect_ratio,
2026-08-12 02:53:06 +00:00
focal_point: 'image_presentation',
2025-11-12 18:26:25 +08:00
object_fit: settings.product_listing_media_fit,
include_placeholder: true,
sizes: sizes,
no_lazy_load: true,
src_set_type: 'grid'
2026-08-12 02:53:06 +00:00
-%}
2025-11-12 18:26:25 +08:00
{%- endif -%}
</a>
2026-08-12 02:53:06 +00:00
{%- unless placeholder -%}
2025-11-12 18:26:25 +08:00
{%- render 'product-badges',
prod: prod,
show_sale_badge: settings.product_listing_show_sale_badge,
show_custom_badges: settings.product_listing_show_custom_badges,
show_sold_out_badge: settings.product_listing_show_sold_out_badge
-%}
2026-08-12 02:53:06 +00:00
{%- endunless -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if quick_shop_type != 'none' and quick_view_is_beneath != true -%}
2025-11-12 18:26:25 +08:00
<div class="product-item__hover-action-wrap">
2026-08-12 02:53:06 +00:00
{{- quick_shop_button -}}
2025-11-12 18:26:25 +08:00
</div>
2026-08-12 02:53:06 +00:00
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if quick_shop_type != 'none' -%}
{{- quick_shop_button_hoverless -}}
{%- endif -%}
2025-11-12 18:26:25 +08:00
</div>
<div class="product-item__meta">
2026-08-12 02:53:06 +00:00
<div class="product-item__text ta-c">
<h4 class="ff-body product-item__product-title ff-product-listing-title">
2025-11-12 18:26:25 +08:00
{%- if placeholder -%}
2026-08-12 02:53:06 +00:00
{{- 'homepage.onboarding.product_title' | t -}}
2025-11-12 18:26:25 +08:00
{%- else -%}
<a href="{{ prod.url | within: collection }}">{{ prod.title }}</a>
{%- endif -%}
</h4>
2026-08-12 02:53:06 +00:00
{%- if settings.product_listing_show_vendor and prod.vendor -%}
2025-11-12 18:26:25 +08:00
<h5 class="ff-body fs-body-60 product-item__product-vendor">
2026-08-12 02:53:06 +00:00
{{- prod.vendor -}}
2025-11-12 18:26:25 +08:00
</h5>
2026-08-12 02:53:06 +00:00
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- liquid
if settings.product_listing_show_rating
render 'product-rating', prod: prod
endif
2025-11-12 18:26:25 +08:00
assign price_content_type = 'price'
if prod.available
if prod.price == 0 and prod.price_varies == false
if settings.zero_dollar_listing_price_display == 'replace'
assign price_content_type = 'custom'
assign custom_price_content = 'products.inventory.zero_price_custom_label' | t
elsif settings.zero_dollar_listing_price_display == 'hide'
assign price_content_type = 'hide'
assign custom_price_content = blank
endif
endif
else
if settings.sold_out_listing_price_display == 'hide'
assign price_content_type = 'hide'
assign custom_price_content = blank
elsif settings.sold_out_listing_price_display == 'replace'
assign price_content_type = 'custom'
assign custom_price_content = 'products.inventory.sold_out_price_custom_label' | t
endif
endif
2026-08-12 02:53:06 +00:00
-%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if price_content_type == 'price' -%}
<p class="product-item__price">
{%- if placeholder -%}
{{- 9999 | money -}}
{%- elsif prod_compare_at_price > prod_price -%}
2025-11-12 18:26:25 +08:00
<span class="visually-hidden">{{ 'products.product.regular_price' | t }}</span>
2026-08-12 02:53:06 +00:00
{%- liquid
2025-11-12 18:26:25 +08:00
# Extend Shopify's detection for varied compare_at_price
assign has_null_compare_at_price = false
2026-08-12 02:53:06 +00:00
2025-11-12 18:26:25 +08:00
for variant in prod.variants
assign variant_compare_at_price = variant.compare_at_price
assign variant_price = variant.metafields.app--168074346497.auto_discounted_price.value | default: variant.price
if variant.metafields.app--168074346497.discount_type.value != nil and variant.metafields.app--168074346497.discount_type.value != "fixed" and prod.metafields.app--168074346497.discount_percentage.value > 0.01
assign deducted_percentage = 1.0 | minus: prod.metafields.app--168074346497.discount_percentage.value
if variant.metafields.app--168074346497.discount_percentage.value > 0.01
assign deducted_percentage = 1.0 | minus: variant.metafields.app--168074346497.discount_percentage.value
endif
assign variant_price = variant.price | divided_by: 100.0 | times: deducted_percentage | times: 100.0 | ceil
assign variant_compare_at_price = variant.price
if variant.compare_at_price > variant_compare_at_price
assign variant_compare_at_price = variant.compare_at_price
endif
endif
if variant_price < variant.price and variant_compare_at_price == blank
assign variant_compare_at_price = variant.price
endif
if variant_compare_at_price == null
assign has_null_compare_at_price = true
break
endif
2026-08-12 02:53:06 +00:00
2025-11-12 18:26:25 +08:00
endfor
2026-08-12 02:53:06 +00:00
-%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- unless prod.compare_at_price_varies or has_null_compare_at_price -%}
2025-11-12 18:26:25 +08:00
<s class="t-subdued">{{ prod_compare_at_price | money }}</s>
2026-08-12 02:53:06 +00:00
{%- endunless -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if prod.price_varies or prod.compare_at_price_varies or has_null_compare_at_price -%}
2025-11-12 18:26:25 +08:00
{%- assign sale_price = prod_price | money -%}
<span class="sale">
{{- 'products.product.on_sale_from_html' | t: price: sale_price -}}
</span>
2026-08-12 02:53:06 +00:00
{%- else -%}
2025-11-12 18:26:25 +08:00
<span class="sale">{{ prod_price | money }}</span>
2026-08-12 02:53:06 +00:00
{%- endif -%}
{%- else -%}
{%- if prod.price_varies -%}
2025-11-12 18:26:25 +08:00
{%- assign price = prod_price | money -%}
2026-08-12 02:53:06 +00:00
{{- 'products.product.from_lowest_price_html' | t: lowest_price: price -}}
{%- else -%}
2025-11-12 18:26:25 +08:00
<span class="product-item__price">{{ prod_price | money }}</span>
2026-08-12 02:53:06 +00:00
{%- endif -%}
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if settings.product_listing_show_discount -%}
2025-11-12 18:26:25 +08:00
<span class="displayed-discount fs-body-50">
{%- render 'get-display-discount', prod: prod, format: settings.product_listing_discount_format -%}
</span>
2026-08-12 02:53:06 +00:00
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- render 'unit-price', item: prod.selected_or_first_available_variant -%}
2025-11-12 18:26:25 +08:00
</p>
2026-08-12 02:53:06 +00:00
{%- elsif price_content_type == 'custom' -%}
{%- if prod.price_varies and prod_price == 0 -%}
2025-11-12 18:26:25 +08:00
{%- assign price = prod_price | money -%}
{%- assign custom_price_content = 'products.inventory.zero_price_custom_label' | t -%}
<p class="product-item__price">
2026-08-12 02:53:06 +00:00
{%- liquid
2025-11-12 18:26:25 +08:00
capture from_lowest_price_html
echo 'products.product.from_lowest_price_html' | t: lowest_price: custom_price_content
endcapture
if prod_compare_at_price > prod_price
echo '<span class="sale">[[flph]]</span>' | replace: '[[flph]]', from_lowest_price_html
else
echo from_lowest_price_html
endif
2026-08-12 02:53:06 +00:00
-%}
2025-11-12 18:26:25 +08:00
</p>
2026-08-12 02:53:06 +00:00
{%- else -%}
{%- if custom_price_content != blank -%}
2025-11-12 18:26:25 +08:00
<p class="product-item__price">{{ custom_price_content }}</p>
2026-08-12 02:53:06 +00:00
{%- endif -%}
{%- endif -%}
{%- endif -%}
2025-11-12 18:26:25 +08:00
2026-08-12 02:53:06 +00:00
{%- if has_hover_swatches or has_hover_chips -%}
{%- liquid
2025-11-12 18:26:25 +08:00
assign option_string = blank
if has_hover_swatches
assign swatch_string = 'products.product.swatch_count' | t: count: displayed_swatch_count, swatch_name: first_swatchified_option
assign option_string = option_string | append: swatch_string
endif
if has_hover_swatches and has_hover_chips
assign delimiter_string = 'products.product.swatch_and_chip_delimiter' | t
assign option_string = option_string | append: delimiter_string
endif
if has_hover_chips
assign chip_string = 'products.product.chip_count' | t: count: displayed_chip_count, chip_name: first_chipped_option
assign option_string = option_string | append: chip_string
endif
%}
2026-08-12 02:53:06 +00:00
2025-11-12 18:26:25 +08:00
<h5 class="ff-body fs-body-50 product-item__swatch-count">
2026-08-12 02:53:06 +00:00
{{- option_string -}}
2025-11-12 18:26:25 +08:00
</h5>
2026-08-12 02:53:06 +00:00
{%- endif -%}
2025-11-12 18:26:25 +08:00
</div>
{%- if has_hover_swatches or has_hover_chips -%}
<div class="product-item__variant-info">
{%- if has_hover_swatches -%}
{%- render 'product-item-swatches',
prod: prod,
option_name: first_swatchified_option,
show_sibling_swatches: show_sibling_swatches,
2026-08-12 02:53:06 +00:00
sibling_products: sibling_products,
custom_color_key_map: custom_color_key_map,
custom_color_value_map: custom_color_value_map
2025-11-12 18:26:25 +08:00
-%}
{%- endif -%}
{%- if has_hover_chips -%}
{%- render 'product-item-chips', prod: prod, option_name: first_chipped_option -%}
{%- endif -%}
</div>
{%- endif -%}
2026-08-12 02:53:06 +00:00
{%- liquid
if quick_shop_type != 'none' and quick_view_is_beneath
echo quick_shop_button
endif
-%}
2025-11-12 18:26:25 +08:00
</div>
2026-08-12 02:53:06 +00:00
2025-11-12 18:26:25 +08:00
<div class="product-item__hover-container"></div>
</div>
</div>