From 98c6bf4133e13477be5df6b2f11567d361964657 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 20 Jun 2023 21:02:31 +0200 Subject: [PATCH] Add Simple Price Filter block --- .../js/blocks/simple-price-filter/block.json | 18 ++++ .../js/blocks/simple-price-filter/frontend.js | 71 +++++++++++++ assets/js/blocks/simple-price-filter/index.js | 9 ++ .../js/blocks/simple-price-filter/style.scss | 67 +++++++++++++ bin/webpack-entries.js | 1 + src/BlockTypes/SimplePriceFilter.php | 99 +++++++++++++++++++ src/BlockTypesController.php | 1 + woocommerce-gutenberg-products-block.php | 3 + 8 files changed, 269 insertions(+) create mode 100644 assets/js/blocks/simple-price-filter/block.json create mode 100644 assets/js/blocks/simple-price-filter/frontend.js create mode 100644 assets/js/blocks/simple-price-filter/index.js create mode 100644 assets/js/blocks/simple-price-filter/style.scss create mode 100644 src/BlockTypes/SimplePriceFilter.php diff --git a/assets/js/blocks/simple-price-filter/block.json b/assets/js/blocks/simple-price-filter/block.json new file mode 100644 index 00000000000..d7a9774666c --- /dev/null +++ b/assets/js/blocks/simple-price-filter/block.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "name": "woocommerce/simple-price-filter", + "version": "1.0.0", + "title": "Simple Price filter", + "description": "Enable customers to filter the product grid by choosing a price range.", + "category": "woocommerce", + "keywords": [ "WooCommerce" ], + "textdomain": "woo-gutenberg-products-block", + "apiVersion": 2, + "viewScript": [ + "wc-simple-price-filter-block-frontend", + "wc-interactivity" + ], + "supports": { + "interactivity": true + } +} diff --git a/assets/js/blocks/simple-price-filter/frontend.js b/assets/js/blocks/simple-price-filter/frontend.js new file mode 100644 index 00000000000..e6639add867 --- /dev/null +++ b/assets/js/blocks/simple-price-filter/frontend.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { store, navigate } from '@woocommerce/interactivity'; + +const getHrefWithFilters = ( { state } ) => { + const { minPrice, maxPrice } = state.filters; + const url = new URL( window.location.href ); + const { searchParams } = url; + + if ( minPrice > 0 ) { + searchParams.set( 'min_price', minPrice ); + } else { + searchParams.delete( 'min_price' ); + } + + if ( maxPrice < state.filters.maxRange ) { + searchParams.set( 'max_price', maxPrice ); + } else { + searchParams.delete( 'max_price' ); + } + + return url.href; +}; + +store( { + state: { + filters: { + rangeStyle: ( { state } ) => { + const { minPrice, maxPrice, maxRange } = state.filters; + return [ + `--low: ${ ( 100 * minPrice ) / maxRange }%`, + `--high: ${ ( 100 * maxPrice ) / maxRange }%`, + ].join( ';' ); + }, + }, + }, + actions: { + filters: { + setMinPrice: ( { state, event } ) => { + const value = parseFloat( event.target.value ) || 0; + state.filters.minPrice = value; + }, + setMaxPrice: ( { state, event } ) => { + const value = + parseFloat( event.target.value ) || state.filters.maxRange; + state.filters.maxPrice = value; + }, + updateProducts: ( { state } ) => { + navigate( getHrefWithFilters( { state } ) ); + }, + reset: ( { state } ) => { + state.filters.minPrice = 0; + state.filters.maxPrice = state.filters.maxRange; + navigate( getHrefWithFilters( { state } ) ); + }, + updateActiveHandle: ( { state, event } ) => { + const { minPrice, maxPrice, maxRange } = state.filters; + const { target, offsetX } = event; + const xPos = offsetX / target.offsetWidth; + const minPos = minPrice / maxRange; + const maxPos = maxPrice / maxRange; + + state.filters.isMinActive = + Math.abs( xPos - minPos ) < Math.abs( xPos - maxPos ); + + state.filters.isMaxActive = ! state.filters.isMinActive; + }, + }, + }, +} ); diff --git a/assets/js/blocks/simple-price-filter/index.js b/assets/js/blocks/simple-price-filter/index.js new file mode 100644 index 00000000000..de30e2ed56f --- /dev/null +++ b/assets/js/blocks/simple-price-filter/index.js @@ -0,0 +1,9 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +registerBlockType( 'woocommerce/simple-price-filter', { + edit: () =>
Simple price filter
, + save: () => null, +} ); diff --git a/assets/js/blocks/simple-price-filter/style.scss b/assets/js/blocks/simple-price-filter/style.scss new file mode 100644 index 00000000000..0a92f493293 --- /dev/null +++ b/assets/js/blocks/simple-price-filter/style.scss @@ -0,0 +1,67 @@ +.wp-block-woocommerce-simple-price-filter { + --low: 0%; + --high: 100%; + --range-color: currentColor; + + .range { + position: relative; + margin: 15px 0; + + .range-bar { + position: relative; + height: 4px; + background: linear-gradient(90deg, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 100% / 100% 100%; + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: currentColor; + opacity: 0.2; + } + } + + input[type="range"] { + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 0; + margin: 0; + padding: 0; + + &.active { + z-index: 10; + } + } + + input[type="range" i] { + color: -internal-light-dark(rgb(16, 16, 16), rgb(255, 255, 255)); + padding: initial; + } + } + + .text { + display: flex; + align-items: center; + justify-content: space-between; + margin: 16px 0; + gap: 8px; + + input[type="text"] { + padding: 8px; + margin: 0; + width: auto; + max-width: 60px; + min-width: 0; + font-size: 0.875em; + border-width: 1px; + border-style: solid; + border-color: currentColor; + border-radius: 4px; + } + } +} diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js index 996f24b89a9..d1e69f8027d 100644 --- a/bin/webpack-entries.js +++ b/bin/webpack-entries.js @@ -64,6 +64,7 @@ const blocks = { 'reviews-by-product': { customDir: 'reviews/reviews-by-product', }, + 'simple-price-filter': {}, 'single-product': {}, 'stock-filter': {}, 'product-collection': { diff --git a/src/BlockTypes/SimplePriceFilter.php b/src/BlockTypes/SimplePriceFilter.php new file mode 100644 index 00000000000..851fb4c97e1 --- /dev/null +++ b/src/BlockTypes/SimplePriceFilter.php @@ -0,0 +1,99 @@ + array( + 'filters' => array( + 'minPrice' => $min_price, + 'maxPrice' => $max_price, + 'maxRange' => $max_range, + 'rangeStyle' => $range_style, + 'isMinActive' => true, + 'isMaxActive' => false, + ), + ), + ) + ); + + return << +

Filter by price

+
+
+ + +
+
+ + +
+ + +HTML; + } +} diff --git a/src/BlockTypesController.php b/src/BlockTypesController.php index 5e1fdc677db..a5ac22adb19 100644 --- a/src/BlockTypesController.php +++ b/src/BlockTypesController.php @@ -213,6 +213,7 @@ protected function get_block_types() { 'ReviewsByProduct', 'RelatedProducts', 'ProductDetails', + 'SimplePriceFilter', 'SingleProduct', 'StockFilter', ]; diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index 7fe8c4c6450..d05109ee304 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -318,3 +318,6 @@ function woocommerce_blocks_interactivity_setup() { } } add_action( 'plugins_loaded', 'woocommerce_blocks_interactivity_setup' ); + +// Enable the interactivity API. +add_filter( 'woocommerce_blocks_enable_interactivity_api', '__return_true' );