You've already forked wc-bootstrap
Implement Phase 2: product archive and shop loop templates (Bootstrap 5)
Add 15 Twig template overrides for the product archive and shop loop: - archive-product: 3+9 grid layout with optional filter sidebar - content-product: card component with hook-based content injection - content-product-cat: category card with thumbnail - product-searchform: input-group with search icon button - loop/loop-start, loop-end: responsive row-cols grid - loop/header: archive title with description hook - loop/result-count: showing X-Y of Z with aria-relevant - loop/orderby: form-select-sm sort dropdown - loop/pagination: delegates to components/pagination.html.twig - loop/no-products-found: alert-info empty state - loop/add-to-cart: btn-primary-sm with AJAX data attributes - loop/price: fw-semibold with sale/regular markup - loop/rating: Bootstrap Icon stars (full, half, empty) - loop/sale-flash: badge bg-danger positioned overlay CSS additions: product card hover, sale badge z-index, star rating sizing, price del/ins styling, WooCommerce grid reset. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
47
templates/loop/add-to-cart.html.twig
Normal file
47
templates/loop/add-to-cart.html.twig
Normal file
@@ -0,0 +1,47 @@
|
||||
{#
|
||||
# Loop Add to Cart Button (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders the add-to-cart button within the product loop.
|
||||
#
|
||||
# Expected context:
|
||||
# product - WC_Product object with:
|
||||
# .add_to_cart_url() - Add to cart URL
|
||||
# .add_to_cart_text() - Button label
|
||||
# .is_purchasable() - Whether product can be purchased
|
||||
# .is_in_stock() - Whether product is in stock
|
||||
# .supports('ajax_add_to_cart') - Whether AJAX add to cart is supported
|
||||
# .get_id() - Product ID
|
||||
# args - Array with:
|
||||
# .quantity - Quantity (default: 1)
|
||||
# .class - CSS classes
|
||||
# .attributes - Additional HTML attributes
|
||||
# .aria-describedby_text - Accessibility description
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/add-to-cart.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% set quantity = args.quantity|default(1) %}
|
||||
{% set btn_class = 'btn btn-primary btn-sm w-100' %}
|
||||
|
||||
<a href="{{ product.add_to_cart_url()|esc_url }}"
|
||||
class="{{ btn_class }} {{ args.class|default('') }}"
|
||||
data-quantity="{{ quantity }}"
|
||||
data-product_id="{{ product.get_id() }}"
|
||||
{% if args.attributes is defined %}
|
||||
{% for attr, value in args.attributes %}
|
||||
{{ attr }}="{{ value|esc_attr }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
aria-label="{{ product.add_to_cart_text()|esc_attr }}"
|
||||
rel="nofollow">
|
||||
{{ product.add_to_cart_text() }}
|
||||
</a>
|
||||
|
||||
{% if args['aria-describedby_text'] is defined and args['aria-describedby_text'] %}
|
||||
<span id="woocommerce_loop_add_to_cart_link_describedby_{{ product.get_id() }}" class="visually-hidden">
|
||||
{{ args['aria-describedby_text'] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
25
templates/loop/header.html.twig
Normal file
25
templates/loop/header.html.twig
Normal file
@@ -0,0 +1,25 @@
|
||||
{#
|
||||
# Product Archive Header (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders the archive page title and optional description.
|
||||
#
|
||||
# Expected context:
|
||||
# show_page_title - Whether to display the title (boolean, filtered)
|
||||
# page_title - Archive page title string
|
||||
# archive_description - Optional archive description HTML
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/header.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
<header class="woocommerce-products-header mb-4">
|
||||
{% if show_page_title is not defined or show_page_title %}
|
||||
<h1 class="woocommerce-products-header__title page-title mb-2">
|
||||
{{ page_title|default('')|esc_html }}
|
||||
</h1>
|
||||
{% endif %}
|
||||
|
||||
{{ do_action('woocommerce_archive_description') }}
|
||||
</header>
|
||||
12
templates/loop/loop-end.html.twig
Normal file
12
templates/loop/loop-end.html.twig
Normal file
@@ -0,0 +1,12 @@
|
||||
{#
|
||||
# Product Loop End (Bootstrap 5 Override)
|
||||
#
|
||||
# Closes the product grid container opened by loop-start.html.twig.
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/loop-end.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
</div>
|
||||
17
templates/loop/loop-start.html.twig
Normal file
17
templates/loop/loop-start.html.twig
Normal file
@@ -0,0 +1,17 @@
|
||||
{#
|
||||
# Product Loop Start (Bootstrap 5 Override)
|
||||
#
|
||||
# Opens the product grid container using Bootstrap 5 row with responsive columns.
|
||||
#
|
||||
# Expected context:
|
||||
# columns - Number of columns (from wc_get_loop_prop)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/loop-start.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% set cols = columns|default(3) %}
|
||||
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-{{ cols < 3 ? cols : 3 }} row-cols-lg-{{ cols }} g-4 products">
|
||||
17
templates/loop/no-products-found.html.twig
Normal file
17
templates/loop/no-products-found.html.twig
Normal file
@@ -0,0 +1,17 @@
|
||||
{#
|
||||
# No Products Found (Bootstrap 5 Override)
|
||||
#
|
||||
# Displayed when the product archive has no results.
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/no-products-found.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
<div class="woocommerce-no-products-found">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="bi bi-info-circle me-2" aria-hidden="true"></i>
|
||||
{{ __('No products were found matching your selection.') }}
|
||||
</div>
|
||||
</div>
|
||||
40
templates/loop/orderby.html.twig
Normal file
40
templates/loop/orderby.html.twig
Normal file
@@ -0,0 +1,40 @@
|
||||
{#
|
||||
# Catalog Ordering / Sort Dropdown (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders the product sort-by dropdown as a Bootstrap 5 form-select.
|
||||
#
|
||||
# Expected context:
|
||||
# catalog_orderby_options - Associative array of { value: label } sort options
|
||||
# orderby - Currently selected orderby value
|
||||
# use_label - Whether to display a label (boolean)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/orderby.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
<form class="woocommerce-ordering d-inline-block" method="get">
|
||||
{% if use_label is defined and use_label %}
|
||||
<label for="woocommerce-orderby" class="form-label visually-hidden">
|
||||
{{ __('Sort by') }}
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<select name="orderby"
|
||||
id="woocommerce-orderby"
|
||||
class="form-select form-select-sm"
|
||||
aria-label="{{ __('Shop order') }}"
|
||||
onchange="this.form.submit()">
|
||||
{% if catalog_orderby_options is defined %}
|
||||
{% for value, label in catalog_orderby_options %}
|
||||
<option value="{{ value|esc_attr }}"{% if value == orderby %} selected{% endif %}>
|
||||
{{ label|esc_html }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
|
||||
<input type="hidden" name="paged" value="1" />
|
||||
{{ wc_query_string_form_fields() }}
|
||||
</form>
|
||||
24
templates/loop/pagination.html.twig
Normal file
24
templates/loop/pagination.html.twig
Normal file
@@ -0,0 +1,24 @@
|
||||
{#
|
||||
# Product Pagination (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders pagination for the product archive using Bootstrap 5 pagination component.
|
||||
#
|
||||
# Expected context:
|
||||
# total - Total number of pages
|
||||
# current - Current page number (1-based)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/pagination.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% set max_pages = total|default(1) %}
|
||||
{% set current_page = current|default(1) %}
|
||||
|
||||
{% if max_pages > 1 %}
|
||||
{% include 'components/pagination.html.twig' with {
|
||||
current_page: current_page,
|
||||
max_pages: max_pages
|
||||
} %}
|
||||
{% endif %}
|
||||
21
templates/loop/price.html.twig
Normal file
21
templates/loop/price.html.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{#
|
||||
# Loop Product Price (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders the product price within the shop loop.
|
||||
# Price HTML is pre-formatted by WooCommerce (includes sale/regular markup).
|
||||
#
|
||||
# Expected context:
|
||||
# product - WC_Product object with:
|
||||
# .get_price_html() - Formatted price HTML (includes <del>/<ins> for sales)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/price.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% if product is defined %}
|
||||
<span class="price fs-6 fw-semibold">
|
||||
{{ product.get_price_html()|raw }}
|
||||
</span>
|
||||
{% endif %}
|
||||
40
templates/loop/rating.html.twig
Normal file
40
templates/loop/rating.html.twig
Normal file
@@ -0,0 +1,40 @@
|
||||
{#
|
||||
# Loop Product Rating (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders star ratings for products in the shop loop.
|
||||
#
|
||||
# Expected context:
|
||||
# product - WC_Product object with:
|
||||
# .get_average_rating() - Average rating (0-5)
|
||||
# .get_review_count() - Number of reviews
|
||||
# reviews_enabled - Whether reviews/ratings are enabled (boolean)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/rating.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% if reviews_enabled is not defined or reviews_enabled %}
|
||||
{% if product is defined and product.get_average_rating() > 0 %}
|
||||
{% set rating = product.get_average_rating() %}
|
||||
{% set count = product.get_review_count() %}
|
||||
|
||||
<div class="wc-star-rating d-flex align-items-center gap-1 mb-1"
|
||||
role="img"
|
||||
aria-label="{{ __('%s out of 5 stars')|format(rating) }}">
|
||||
{% for i in 1..5 %}
|
||||
{% if i <= rating|round(0, 'floor') %}
|
||||
<i class="bi bi-star-fill text-warning" aria-hidden="true"></i>
|
||||
{% elseif i - rating < 1 %}
|
||||
<i class="bi bi-star-half text-warning" aria-hidden="true"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-star text-warning" aria-hidden="true"></i>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if count > 0 %}
|
||||
<small class="text-body-secondary">({{ count }})</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
33
templates/loop/result-count.html.twig
Normal file
33
templates/loop/result-count.html.twig
Normal file
@@ -0,0 +1,33 @@
|
||||
{#
|
||||
# Result Count (Bootstrap 5 Override)
|
||||
#
|
||||
# Displays the "Showing X-Y of Z results" text.
|
||||
#
|
||||
# Expected context:
|
||||
# total - Total number of products
|
||||
# per_page - Products per page
|
||||
# current - Current page number
|
||||
# orderedby - Whether results are currently sorted (optional)
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/result-count.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
<p class="woocommerce-result-count text-body-secondary mb-0"
|
||||
role="alert"
|
||||
aria-relevant="all"
|
||||
{% if orderedby is defined and orderedby %}data-is-sorted-by="true"{% endif %}>
|
||||
{% if total is defined %}
|
||||
{% set first = ((current|default(1) - 1) * per_page|default(total)) + 1 %}
|
||||
{% set last = current|default(1) * per_page|default(total) %}
|
||||
{% if last > total %}{% set last = total %}{% endif %}
|
||||
|
||||
{% if total <= per_page|default(total) or total == 0 %}
|
||||
{{ _n('Showing the single result', 'Showing all %d results', total)|format(total) }}
|
||||
{% else %}
|
||||
{{ __('Showing %1$d–%2$d of %3$d results')|format(first, last, total) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
21
templates/loop/sale-flash.html.twig
Normal file
21
templates/loop/sale-flash.html.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{#
|
||||
# Sale Badge (Bootstrap 5 Override)
|
||||
#
|
||||
# Renders a sale badge overlay on product cards.
|
||||
#
|
||||
# Expected context:
|
||||
# product - WC_Product object with:
|
||||
# .is_on_sale() - Whether product is currently on sale
|
||||
# post - Global post object
|
||||
#
|
||||
# WooCommerce PHP equivalent: loop/sale-flash.php
|
||||
#
|
||||
# @package WcBootstrap
|
||||
# @since 0.1.0
|
||||
#}
|
||||
|
||||
{% if product is defined and product.is_on_sale() %}
|
||||
<span class="badge bg-danger position-absolute top-0 start-0 m-2 onsale">
|
||||
{{ __('Sale!') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user