summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Rehnelt <noreply@maennchen1.de>2026-10-03 18:15:00 +0200
committerRoger Rehnelt <noreply@maennchen1.de>2026-10-03 18:15:00 +0200
commitbdc53ce840b142892ae384d96e2257f84611b600 (patch)
tree9f096d0258dbfd05133f9784bf6f5b685c53a1bf
parentc5a04225526941059a15dc7e7af362882a07942c (diff)
Produktfilter: Leiste mit Sortierung und Kategorie, einheitliche Abstände
Die Leiste über den Produktkacheln hat Sortierung und im Sortiment eine Kategorieauswahl; Felder gleich hoch und auf einer Grundlinie, am Handy unter einem umrandeten Knopf mit Zahl der aktiven Filter. Ohne Treffer nennt der Hinweis den Filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--views/css/frontend.css2
-rw-r--r--views/css/wpsg-theme.css54
-rw-r--r--views/mods/mod_productview/productview.js36
-rw-r--r--views/mods/mod_productview/productview_filter.phtml43
4 files changed, 115 insertions, 20 deletions
diff --git a/views/css/frontend.css b/views/css/frontend.css
index 3013aec..848fa66 100644
--- a/views/css/frontend.css
+++ b/views/css/frontend.css
@@ -768,7 +768,7 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max
.wpsg-pvf .wpsg-pvf__item { margin:0 0 1rem 0; padding:0; border:0; min-width:0; }
.wpsg-pvf .wpsg-pvf__title { margin:0 0 0.5em 0; padding:0; font-size:1em; font-weight:bold; }
.wpsg-pvf .wpsg-pvf__item ul { margin:0; padding-left:1.2em; }
-.wpsg-pvf .wpsg-pvf__price { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; }
+.wpsg-pvf .wpsg-pvf__price .wpsg-pvf__options { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; }
.wpsg-pvf .wpsg-pvf__price label { display:flex; align-items:center; gap:0.35em; }
.wpsg-pvf .wpsg-pvf__price input { width:7em; }
.wpsg-pvf .wpsg-pvf__price label > span { min-width:1.75em; }
diff --git a/views/css/wpsg-theme.css b/views/css/wpsg-theme.css
index 4515211..d40cf5b 100644
--- a/views/css/wpsg-theme.css
+++ b/views/css/wpsg-theme.css
@@ -649,18 +649,48 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max
/* Blöcke: Produktliste, Mini-Warenkorb, Kundenkonto */
.wpsg-block-product-list :where(.wpsg-product-list__count) { margin:0 0 var(--wpsg-gap); }
.wpsg-block-product-list :where(.wpsg-product-list__count:empty) { display:none; }
-/* Produktkacheln (Abfrage-Schleife, Kategorievorlage): Statuszeile des Filters und Filter als Leiste über den Kacheln */
-.wpsg-product-tiles :where(.wpsg-product-list__count) { margin:0 0 var(--wpsg-gap); }
-.wpsg-product-tiles :where(.wpsg-product-list__count:empty) { display:none; }
-.wpsg-product-tiles :where(.wpsg-block-product-filter) { margin:0 0 calc(var(--wpsg-gap) * 2) 0; }
-.wpsg-product-tiles :where(.wpsg-pvf__fields) { display:flex; flex-wrap:wrap; align-items:flex-start; gap:var(--wpsg-gap) calc(var(--wpsg-gap) * 2); margin-top:var(--wpsg-gap); }
-.wpsg-product-tiles :where(.wpsg-pvf__fields > .wpsg-pvf__item) { margin:0; }
-.wpsg-product-tiles :where(.wpsg-pvf__fields > .wpsg-pvf__actions) { align-self:flex-end; }
-.wpsg-pvf__summary { display:inline-flex; list-style:none; cursor:pointer; }
-.wpsg-pvf__summary::-webkit-details-marker { display:none; }
+/*
+ * Produktkacheln (Abfrage-Schleife, Kategorievorlage): Statuszeile und Filter als Leiste über den Kacheln.
+ * Abstände aus dem Theme (Blocklücke, Spacing-Presets); alle Felder und Knöpfe gleich hoch, auf einer Grundlinie.
+ * Höhere Spezifität als die Fluss-Regel des Kerns (:root :where(.is-layout-flow) > *), die sonst den Abstand nimmt.
+ */
+.wpsg-product-tiles { --wpsg-pvf-gap:var(--wp--style--block-gap, var(--wpsg-gap)); --wpsg-pvf-gap-small:var(--wp--preset--spacing--20, 0.5em); --wpsg-pvf-control:2.75em; }
+.wpsg-product-tiles > .wpsg-product-list__count { margin-block:0 var(--wpsg-pvf-gap); }
+.wpsg-product-tiles > .wpsg-product-list__count:empty { display:none; }
+.wpsg-product-tiles > .wpsg-block-product-filter { margin-block:var(--wp--preset--spacing--40, 1rem) var(--wp--preset--spacing--60, 2rem); }
+.wpsg-product-tiles .wpsg-pvf__fields { display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--wpsg-pvf-gap); padding:var(--wp--preset--spacing--40, 1rem); border:1px solid var(--wpsg-color-line-soft); border-radius:var(--wpsg-radius); background-color:var(--wpsg-color-surface); }
+.wpsg-product-tiles .wpsg-pvf__fields > * { margin:0; padding:0; border:0; min-width:0; }
+.wpsg-product-tiles .wpsg-pvf__title { display:block; margin:0 0 var(--wpsg-pvf-gap-small) 0; padding:0; font-size:inherit; font-weight:600; line-height:1.3; }
+.wpsg-product-tiles .wpsg-pvf__options { display:flex; flex-wrap:wrap; align-items:center; gap:var(--wpsg-pvf-gap-small) var(--wpsg-pvf-gap); min-height:var(--wpsg-pvf-control); }
+.wpsg-product-tiles .wpsg-pvf__price .wpsg-pvf__options { flex-wrap:nowrap; gap:var(--wpsg-pvf-gap-small); }
+.wpsg-product-tiles .wpsg-pvf__price label { display:flex; flex:1 1 0; align-items:center; gap:var(--wpsg-pvf-gap-small); min-width:0; }
+.wpsg-product-tiles .wpsg-pvf__price label > span { min-width:0; }
+.wpsg-product-tiles .wpsg-pvf__price input { flex:1 1 auto; width:6em; min-width:0; }
+.wpsg-product-tiles .wpsg-pvf__option { margin:0; }
+.wpsg-product-tiles .wpsg-pvf input[type="checkbox"] { width:1.25em; height:1.25em; margin:0; }
+/* Felder, Auswahllisten und Knöpfe gleich hoch */
+.wpsg-product-tiles .wpsg-pvf :is(input[type="number"], select, .wpsg-pvf__actions .wpsg_button) { box-sizing:border-box; height:var(--wpsg-pvf-control); margin:0; padding-block:0; }
+.wpsg-product-tiles .wpsg-pvf .wpsg-pvf__actions .wpsg_button { display:inline-flex; align-items:center; justify-content:center; }
+.wpsg-product-tiles .wpsg-pvf__actions { display:flex; flex-wrap:wrap; align-items:center; gap:var(--wpsg-pvf-gap); }
+.wpsg-product-tiles .wpsg-pvf__reset[hidden] { display:none; }
+/* Knopf „Filter“ (nur am Handy): umrandet, Pfeil dreht sich, Zahl der aktiven Filter */
+.wpsg-product-tiles .wpsg-pvf__summary, .wpsg-product-tiles .wpsg-pvf__toggle[open] > .wpsg-pvf__summary { box-sizing:border-box; display:inline-flex; align-items:center; gap:var(--wpsg-pvf-gap-small); min-height:var(--wpsg-pvf-control); padding:0 1.2em; border:1px solid currentColor; border-radius:var(--wpsg-radius); background:transparent; color:inherit; font-weight:600; list-style:none; cursor:pointer; }
+.wpsg-product-tiles .wpsg-pvf__summary::-webkit-details-marker { display:none; }
+.wpsg-product-tiles .wpsg-pvf__summary::after { content:""; width:0.45em; height:0.45em; border-right:2px solid currentColor; border-bottom:2px solid currentColor; transform:translateY(-25%) rotate(45deg); transition:transform 0.15s ease-out; }
+.wpsg-product-tiles .wpsg-pvf__toggle[open] > .wpsg-pvf__summary::after { transform:translateY(25%) rotate(-135deg); }
+.wpsg-product-tiles .wpsg-pvf__badge { display:inline-flex; align-items:center; justify-content:center; min-width:1.5em; padding:0 0.4em; border:1px solid currentColor; border-radius:999px; font-size:var(--wpsg-font-size-small); line-height:1.4; }
+.wpsg-product-tiles .wpsg-pvf__badge[hidden] { display:none; }
+.wpsg-product-tiles .wpsg-pvf__none { margin:0; }
+@media (max-width:781px) {
+ .wpsg-product-tiles .wpsg-pvf__toggle[open] > .wpsg-pvf__fields { margin-top:var(--wpsg-pvf-gap-small); }
+ .wpsg-product-tiles .wpsg-pvf__fields { flex-direction:column; align-items:stretch; }
+ .wpsg-product-tiles .wpsg-pvf__select select { width:100%; }
+ /* Feste Breite zählte sonst als Mindestbreite und schob die Zeile über den Rand */
+ .wpsg-product-tiles .wpsg-pvf__price input { flex-basis:5em; width:0; }
+ .wpsg-product-tiles .wpsg-pvf__actions .wpsg_button { flex:1 1 auto; }
+}
@media (min-width:782px) {
- .wpsg-pvf__toggle > .wpsg-pvf__summary { display:none; }
- .wpsg-product-tiles :where(.wpsg-pvf__fields) { margin-top:0; }
+ .wpsg-product-tiles .wpsg-pvf__toggle > .wpsg-pvf__summary, .wpsg-product-tiles .wpsg-pvf__toggle[open] > .wpsg-pvf__summary { display:none; }
}
.wpsg-block-product-list :where(.wpsg-product-list__items) { display:grid; grid-template-columns:repeat(var(--wpsg-columns, 3), minmax(0, 1fr)); gap:calc(var(--wpsg-gap) * 2) var(--wpsg-gap); list-style:none; margin:0; padding:0; }
.wpsg-block-product-list :where(.wpsg-product-list__item) { display:flex; flex-direction:column; gap:0.5em; min-width:0; margin:0; padding:0; }
@@ -711,7 +741,7 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max
.wpsg-pvf :where(.wpsg-pvf__item) { margin:0 0 var(--wpsg-gap) 0; padding:0; border:0; min-width:0; }
.wpsg-pvf :where(.wpsg-pvf__title) { margin:0 0 0.5em 0; padding:0; font-size:1em; font-weight:bold; }
.wpsg-pvf :where(.wpsg-pvf__item ul) { margin:0; padding-left:1.2em; }
-.wpsg-pvf :where(.wpsg-pvf__price) { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; }
+.wpsg-pvf :where(.wpsg-pvf__price .wpsg-pvf__options) { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; }
.wpsg-pvf :where(.wpsg-pvf__price label) { display:flex; align-items:center; gap:0.35em; }
.wpsg-pvf :where(.wpsg-pvf__price input) { width:7em; }
.wpsg-pvf :where(.wpsg-pvf__price label > span) { min-width:1.75em; }
diff --git a/views/mods/mod_productview/productview.js b/views/mods/mod_productview/productview.js
index fc58ec8..df33d92 100644
--- a/views/mods/mod_productview/productview.js
+++ b/views/mods/mod_productview/productview.js
@@ -175,6 +175,40 @@
}
+ // Zahl der aktiven Filter im Knopf „Filter“ und „Filter zurücksetzen“ der Leiste; der Filter bleibt beim Tauschen stehen
+ function updateBar( form ) {
+
+ const badge = form.querySelector( '.wpsg-pvf__badge' );
+ const reset = form.querySelector( '.wpsg-pvf__toggle .wpsg-pvf__reset' );
+ if ( !badge && !reset ) return;
+
+ const groups = {};
+
+ new FormData( form ).forEach( function( v, name ) {
+
+ if ( v === '' ) return;
+
+ const m = name.match( /^pvf\[(variants|attributs)\]\[(\d+)\]/ );
+
+ if ( name === 'pvc' ) groups.cat = true;
+ else if ( /^pvf\[price_(min|max)\]$/.test( name ) ) groups.price = true;
+ else if ( m ) groups[ m[ 1 ] + m[ 2 ] ] = true;
+
+ } );
+
+ const n = Object.keys( groups ).length;
+
+ if ( badge ) {
+
+ badge.hidden = ( n === 0 );
+ badge.querySelector( '.wpsg-pvf__badge-count' ).textContent = String( n );
+
+ }
+
+ if ( reset ) reset.hidden = ( n === 0 );
+
+ }
+
function applyFilter() {
const form = document.getElementById( 'wpsg_mod_productview_filter' );
@@ -194,6 +228,8 @@
boxes.forEach( function( box ) { refresh( box, { filter: values, page: 1 }, url, false ); } );
if ( lists.length ) refreshLists( lists, url );
+ updateBar( form );
+
history.pushState( { wpsgPv: true }, '', url );
return true;
diff --git a/views/mods/mod_productview/productview_filter.phtml b/views/mods/mod_productview/productview_filter.phtml
index 1cf98da..9341d53 100644
--- a/views/mods/mod_productview/productview_filter.phtml
+++ b/views/mods/mod_productview/productview_filter.phtml
@@ -25,16 +25,37 @@
<input type="hidden" name="<?php echo esc_attr($param); ?>" value="<?php echo esc_attr($value); ?>" />
<?php } ?>
- <?php /* Leiste über den Produktkacheln: am Handy unter „Filter“ zugeklappt (productview.js), breit offen */ $bBar = (($this->view['wpsg_mod_productview_filter']['bar']??false) === true); ?>
+ <?php /* Leiste über den Produktkacheln: am Handy unter „Filter“ zugeklappt (productview.js), breit offen */ $bBar = (($this->view['wpsg_mod_productview_filter']['bar']??false) === true); $nActive = intval($this->view['wpsg_mod_productview_filter']['active']??0); ?>
<?php if ($bBar) { ?>
<details class="wpsg-pvf__toggle" open>
- <summary class="wpsg-pvf__summary wpsg_button<?php echo wpsg_button_class(); ?>"><?php echo esc_html__('Filter', 'wpsg'); ?></summary>
+ <summary class="wpsg-pvf__summary"><?php echo esc_html__('Filter', 'wpsg'); ?><span class="wpsg-pvf__badge"<?php if ($nActive === 0) echo ' hidden'; ?>><span class="screen-reader-text">, </span><span class="wpsg-pvf__badge-count"><?php echo $nActive; ?></span><span class="screen-reader-text"> <?php echo esc_html__('aktiv', 'wpsg'); ?></span></span></summary>
<div class="wpsg-pvf__fields">
<?php } ?>
<?php foreach ($this->view['showFilter'] as $filter) { ?>
- <?php if ($filter['type'] === 'catfilter') { ?>
+ <?php if ($filter['type'] === 'catselect') { ?>
+ <div class="wpsg-pvf__item wpsg-pvf__select wpsg-pvf__catselect">
+ <label class="wpsg-pvf__title" for="wpsg_pvf_cat"><?php echo esc_html__('Kategorie', 'wpsg'); ?></label>
+ <select id="wpsg_pvf_cat" name="pvc">
+ <option value=""><?php echo esc_html__('Alle Kategorien', 'wpsg'); ?></option>
+ <?php foreach ($filter['cats'] as $arCat) { ?>
+ <option value="<?php echo intval($arCat['id']); ?>" <?php selected(intval($filter['value']), intval($arCat['id'])); ?>><?php echo esc_html($arCat['label']); ?></option>
+ <?php } ?>
+ </select>
+ </div>
+
+ <?php } else if ($filter['type'] === 'sort') { ?>
+ <div class="wpsg-pvf__item wpsg-pvf__select wpsg-pvf__sort">
+ <label class="wpsg-pvf__title" for="wpsg_pvf_sort"><?php echo esc_html__('Sortierung', 'wpsg'); ?></label>
+ <select id="wpsg_pvf_sort" name="pvs">
+ <?php $i = 0; foreach ($filter['orders'] as $key => $label) { /* Vorgabe ohne Wert, damit sie nicht in der Adresse steht */ ?>
+ <option value="<?php echo (($i ++ === 0)?'':esc_attr($key)); ?>" <?php selected($filter['value'], $key); ?>><?php echo esc_html($label); ?></option>
+ <?php } ?>
+ </select>
+ </div>
+
+ <?php } else if ($filter['type'] === 'catfilter') { ?>
<nav class="wpsg-pvf__item wpsg-pvf__cat" aria-label="<?php echo esc_attr__('Produktkategorie', 'wpsg'); ?>">
<h2 class="wpsg-pvf__title"><?php echo esc_html__('Produktkategorie', 'wpsg'); ?></h2>
<ul>
@@ -47,14 +68,17 @@
<?php } else if ($filter['type'] === 'pricefilter') { ?>
<fieldset class="wpsg-pvf__item wpsg-pvf__price">
<legend class="wpsg-pvf__title"><?php echo esc_html__('Preis', 'wpsg'); ?></legend>
- <label><span><?php echo esc_html__('von', 'wpsg'); ?></span> <input type="number" step="0.01" min="0" name="pvf[price_min]" value="<?php echo esc_attr((string)($arValues['price_min']??'')); ?>" placeholder="<?php echo esc_attr(wpsg_ff($filter['min'])); ?>" /></label>
- <label><span><?php echo esc_html__('bis', 'wpsg'); ?></span> <input type="number" step="0.01" min="0" name="pvf[price_max]" value="<?php echo esc_attr((string)($arValues['price_max']??'')); ?>" placeholder="<?php echo esc_attr(wpsg_ff($filter['max'])); ?>" /></label>
- <span class="wpsg-pvf__unit"><?php echo esc_html($currency); ?></span>
+ <div class="wpsg-pvf__options">
+ <label><span><?php echo esc_html__('von', 'wpsg'); ?></span> <input type="number" step="0.01" min="0" name="pvf[price_min]" value="<?php echo esc_attr((string)($arValues['price_min']??'')); ?>" placeholder="<?php echo esc_attr(wpsg_ff($filter['min'])); ?>" /></label>
+ <label><span><?php echo esc_html__('bis', 'wpsg'); ?></span> <input type="number" step="0.01" min="0" name="pvf[price_max]" value="<?php echo esc_attr((string)($arValues['price_max']??'')); ?>" placeholder="<?php echo esc_attr(wpsg_ff($filter['max'])); ?>" /></label>
+ <span class="wpsg-pvf__unit"><?php echo esc_html($currency); ?></span>
+ </div>
</fieldset>
<?php } else if ($filter['type'] === 'productvariants') { $variant = $filter['variant']; ?>
<fieldset class="wpsg-pvf__item wpsg-pvf__variant wpsg-pvf__variant--<?php echo (($filter['displayMode'] === wpsg_mod_productview::DISPLAYMODE_FILTER_SMALL)?'small':'checkbox'); ?>" data-group="variants" data-id="<?php echo intval($variant['id']); ?>">
<legend class="wpsg-pvf__title"><?php echo esc_html($variant['name']); ?></legend>
+ <div class="wpsg-pvf__options">
<?php foreach ($filter['variation'] as $variation) { $label = (wpsg_isSizedString($variation['shortname'])?$variation['shortname']:$variation['name']); ?>
<label class="wpsg-pvf__option">
<input type="checkbox" name="pvf[variants][<?php echo intval($variant['id']); ?>][]" value="<?php echo intval($variation['id']); ?>" <?php checked($checked('variants', intval($variant['id']), intval($variation['id']))); ?> />
@@ -65,17 +89,20 @@
<?php } ?>
</label>
<?php } ?>
+ </div>
</fieldset>
<?php } else if ($filter['type'] === 'productattributs') { $attribute = $filter['attribute']; ?>
<fieldset class="wpsg-pvf__item wpsg-pvf__attribute" data-group="attributs" data-id="<?php echo intval($attribute['id']); ?>">
<legend class="wpsg-pvf__title"><?php echo esc_html($attribute['name']); ?></legend>
+ <div class="wpsg-pvf__options">
<?php foreach ($attribute['auswahl'] as $index => $choice) { ?>
<label class="wpsg-pvf__option">
<input type="checkbox" name="pvf[attributs][<?php echo intval($attribute['id']); ?>][]" value="<?php echo esc_attr((string)$index); ?>" <?php checked($checked('attributs', intval($attribute['id']), (string)$index)); ?> />
<span><?php echo esc_html($choice); ?></span>
</label>
<?php } ?>
+ </div>
</fieldset>
<?php } ?>
@@ -83,7 +110,9 @@
<div class="wpsg-pvf__actions">
<button type="submit" class="wpsg_button<?php echo wpsg_button_class(); ?>"><?php echo esc_html__('Filtern', 'wpsg'); ?></button>
- <?php if (wpsg_isSizedArray($arValues)) { ?>
+ <?php if ($bBar) { /* Immer im Markup, productview.js blendet nach dem Filtern ein und aus */ ?>
+ <a class="wpsg-pvf__reset" href="<?php echo esc_url($this->view['wpsg_mod_productview_filter']['url']); ?>"<?php if ($nActive === 0) echo ' hidden'; ?>><?php echo esc_html__('Filter zurücksetzen', 'wpsg'); ?></a>
+ <?php } else if (wpsg_isSizedArray($arValues)) { ?>
<a class="wpsg-pvf__reset" href="<?php echo esc_url($this->view['wpsg_mod_productview_filter']['url']); ?>"><?php echo esc_html__('Filter zurücksetzen', 'wpsg'); ?></a>
<?php } ?>
</div>