diff --git a/.wp-env.json b/.wp-env.json
index 8a150435888..3aca2bc3c53 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -1,6 +1,7 @@
{
"core": null,
"plugins": [
+ "https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip",
"https://downloads.wordpress.org/plugin/woocommerce.latest-stable.zip",
"https://github.com/WP-API/Basic-Auth/archive/master.zip",
"./tests/mocks/woo-test-helper",
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..48d173205ee
--- /dev/null
+++ b/assets/js/blocks/simple-price-filter/block.json
@@ -0,0 +1,11 @@
+{
+ "$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
+}
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/assets/js/blocks/simple-price-filter/view.js b/assets/js/blocks/simple-price-filter/view.js
new file mode 100644
index 00000000000..fd7e72edf89
--- /dev/null
+++ b/assets/js/blocks/simple-price-filter/view.js
@@ -0,0 +1,72 @@
+/**
+ * External dependencies
+ */
+import { store } from '@woocommerce/interactivity/store';
+import { navigate } from '@woocommerce/interactivity/router';
+
+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 }%`,
+ };
+ },
+ },
+ },
+ 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/bin/webpack-configs.js b/bin/webpack-configs.js
index 1cae71dab42..2fadb1961fb 100644
--- a/bin/webpack-configs.js
+++ b/bin/webpack-configs.js
@@ -786,6 +786,8 @@ const getInteractivityAPIConfig = ( options = {} ) => {
return {
entry: {
runtime: './assets/js/interactivity',
+ 'simple-price-filter':
+ './assets/js/blocks/simple-price-filter/view.js',
},
output: {
filename: 'woo-directives-[name].js',
diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js
index 3b03f77d997..943050bbe0d 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': {
isExperimental: true,
},
diff --git a/src/BlockTypes/SimplePriceFilter.php b/src/BlockTypes/SimplePriceFilter.php
new file mode 100644
index 00000000000..aeae0fdfef8
--- /dev/null
+++ b/src/BlockTypes/SimplePriceFilter.php
@@ -0,0 +1,116 @@
+ array(
+ 'filters' => array(
+ 'minPrice' => $min_price,
+ 'maxPrice' => $max_price,
+ 'maxRange' => $max_range,
+ 'rangeStyle' => $range_style,
+ 'isMinActive' => true,
+ 'isMaxActive' => false,
+ ),
+ ),
+ )
+ );
+
+ return "
+
+
Filter by price
+
+
+
+
+
+
+
+ ";
+ }
+
+ /**
+ * Get the frontend script handle for this block type.
+ *
+ * @param string $key Data to get, or default to everything.
+ *
+ * @return null
+ */
+ public function get_block_type_script( $key = null ) {
+ $script = [
+ 'handle' => 'simple-price-filter-view',
+ 'path' => 'build/woo-directives-simple-price-filter.js',
+ 'dependencies' => [ 'woo-directives-runtime' ],
+ ];
+ return $key ? $script[ $key ] : $script;
+ }
+}
diff --git a/src/BlockTypesController.php b/src/BlockTypesController.php
index 5ad32e5558a..87752e50dbc 100644
--- a/src/BlockTypesController.php
+++ b/src/BlockTypesController.php
@@ -215,6 +215,7 @@ protected function get_block_types() {
'RelatedProducts',
'ProductDetails',
'StockFilter',
+ 'SimplePriceFilter',
];
$block_types = array_merge( $block_types, Cart::get_cart_block_types(), Checkout::get_checkout_block_types() );
diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php
index 2572935d2f6..45aa2bc4280 100644
--- a/woocommerce-gutenberg-products-block.php
+++ b/woocommerce-gutenberg-products-block.php
@@ -290,3 +290,10 @@ function woocommerce_blocks_plugin_outdated_notice() {
// Include the Interactivity API.
require_once __DIR__ . '/src/Interactivity/woo-directives.php';
+// Enable client-side navigation.
+add_filter(
+ 'client_side_navigation',
+ function () {
+ return true;
+ }
+);