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..275ec49e2a5
--- /dev/null
+++ b/assets/js/blocks/simple-price-filter/frontend.js
@@ -0,0 +1,75 @@
+/**
+ * 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' );
+ }
+
+ searchParams.forEach( ( _, key ) => {
+ if ( /query-[0-9]+-page/.test( key ) ) searchParams.delete( key );
+ } );
+
+ 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/assets/js/interactivity/index.js b/assets/js/interactivity/index.js
index 00008625110..4bddd1db71a 100644
--- a/assets/js/interactivity/index.js
+++ b/assets/js/interactivity/index.js
@@ -1,7 +1,6 @@
import registerDirectives from './directives';
import { init } from './router';
import { rawStore, afterLoads } from './store';
-
export { navigate } from './router';
export { store } from './store';
diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js
index ac2499cd932..13b0c3c2fc5 100644
--- a/bin/webpack-entries.js
+++ b/bin/webpack-entries.js
@@ -78,6 +78,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..d8528620990
--- /dev/null
+++ b/src/BlockTypes/SimplePriceFilter.php
@@ -0,0 +1,107 @@
+ 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 21d1a062335..edaa77cbfea 100644
--- a/src/BlockTypesController.php
+++ b/src/BlockTypesController.php
@@ -215,6 +215,7 @@ protected function get_block_types() {
'ReviewsByProduct',
'RelatedProducts',
'ProductDetails',
+ 'SimplePriceFilter',
'SingleProduct',
'StockFilter',
];
diff --git a/src/Interactivity/client-side-navigation.php b/src/Interactivity/client-side-navigation.php
new file mode 100644
index 00000000000..ab550070f61
--- /dev/null
+++ b/src/Interactivity/client-side-navigation.php
@@ -0,0 +1,9 @@
+';
+}
+add_action( 'wp_head', 'woocommerce_interactivity_add_client_side_navigation_meta_tag' );
diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php
index 2c7257273ef..b08551a5869 100644
--- a/woocommerce-gutenberg-products-block.php
+++ b/woocommerce-gutenberg-products-block.php
@@ -317,3 +317,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' );