Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Experiment: Simple price filter block using the Interactivity API #9926

Draft
wants to merge 25 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c582ed
Update Interactivity API JS files
DAreRodz Jun 19, 2023
cd0af51
Disable TS checks in the Interactivity API for now
DAreRodz Jun 19, 2023
5528a2f
Add new SSR files
DAreRodz Jun 19, 2023
e6d5a25
Replace wp_ prefixes with wc_ ones
DAreRodz Jun 19, 2023
68fb185
Replace wp- prefix with wc-
DAreRodz Jun 19, 2023
bf443b8
Replace guternberg_ prefix with woocommerce_
DAreRodz Jun 19, 2023
2e070ff
Remove file comments from Gutenberg
DAreRodz Jun 19, 2023
ada935d
Rename files with `wp` prefix
DAreRodz Jun 19, 2023
9185199
Fix code to load Interactivity API php files
DAreRodz Jun 19, 2023
fd08faa
Remove TODO comments
DAreRodz Jun 19, 2023
901151b
Replace @wordpress with @woocommerce
DAreRodz Jun 19, 2023
4f653f5
Update Webpack configuration
DAreRodz Jun 19, 2023
8920120
Fix directive prefix
DAreRodz Jun 19, 2023
9d14a7e
Remove interactivity folder from tsconfig exclude
DAreRodz Jun 19, 2023
872e827
Add client-side navigation meta tag code
DAreRodz Jun 19, 2023
ad7d14a
Remove unneeded blocks.php file
DAreRodz Jun 19, 2023
b7488bb
Fix store tag id
DAreRodz Jun 20, 2023
7ae8b81
Register Interactivity API runtime script
DAreRodz Jun 20, 2023
b27eedf
Fix Interactivity API runtime registering
DAreRodz Jun 20, 2023
98c6bf4
Add Simple Price Filter block
DAreRodz Jun 20, 2023
f08b670
Remove all files related to directive processing in PHP
DAreRodz Jun 21, 2023
f1b79aa
Merge branch 'update/interactivity-api' into try/simple-price-filter-…
DAreRodz Jun 21, 2023
5b1eea0
Use values directly for SimplePriceFilter SSR
DAreRodz Jun 21, 2023
0e468fe
Merge branch 'trunk' into try/simple-price-filter-block-demo
luisherranz Aug 15, 2023
70636c9
Reset pages to 0 when changing filter
luisherranz Aug 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions assets/js/blocks/simple-price-filter/block.json
Original file line number Diff line number Diff line change
@@ -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
}
}
75 changes: 75 additions & 0 deletions assets/js/blocks/simple-price-filter/frontend.js
Original file line number Diff line number Diff line change
@@ -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;
},
},
},
} );
9 changes: 9 additions & 0 deletions assets/js/blocks/simple-price-filter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';

registerBlockType( 'woocommerce/simple-price-filter', {
edit: () => <div>Simple price filter</div>,
save: () => null,
} );
67 changes: 67 additions & 0 deletions assets/js/blocks/simple-price-filter/style.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 0 additions & 1 deletion assets/js/interactivity/index.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
1 change: 1 addition & 0 deletions bin/webpack-entries.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const blocks = {
'reviews-by-product': {
customDir: 'reviews/reviews-by-product',
},
'simple-price-filter': {},
'single-product': {},
'stock-filter': {},
'product-collection': {
Expand Down
107 changes: 107 additions & 0 deletions src/BlockTypes/SimplePriceFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
* SimplePriceFilter class.
*/
class SimplePriceFilter extends AbstractBlock {

/**
* Block name.
*
* @var string
*/
protected $block_name = 'simple-price-filter';
const MIN_PRICE_QUERY_VAR = 'min_price';
const MAX_PRICE_QUERY_VAR = 'max_price';

/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
public function render( $attributes = [], $content = '', $block = null ) {
$wrapper_attributes = get_block_wrapper_attributes();
$max_range = 90; // This value should come from DB.
$min_price = get_query_var( self::MIN_PRICE_QUERY_VAR, 0 );
$max_price = get_query_var( self::MAX_PRICE_QUERY_VAR, $max_range );

// CSS variables for the range bar style.
$__low = 100 * $min_price / $max_range;
$__high = 100 * $max_price / $max_range;
$range_style = "--low: $__low%; --high: $__high%";

wc_store(
array(
'state' => array(
'filters' => array(
'minPrice' => $min_price,
'maxPrice' => $max_price,
'maxRange' => $max_range,
'rangeStyle' => $range_style,
'isMinActive' => true,
'isMaxActive' => false,
),
),
)
);

return <<<HTML
<div $wrapper_attributes>
<h3>Filter by price</h3>
<div
class='range'
style='$range_style'
data-wc-bind--style='state.filters.rangeStyle'
data-wc-on--mousemove='actions.filters.updateActiveHandle'
>
<div class='range-bar'></div>
<input
type='range'
min='0'
max='$max_range'
value='$min_price'
class='active'
data-wc-bind--max='state.filters.maxRange'
data-wc-bind--value='state.filters.minPrice'
data-wc-class--active='state.filters.isMinActive'
data-wc-on--input='actions.filters.setMinPrice'
data-wc-on--change='actions.filters.updateProducts'
>
<input
type='range'
min='0'
max='$max_range'
value='$max_price'
data-wc-bind--max='state.filters.maxRange'
data-wc-bind--value='state.filters.maxPrice'
data-wc-class--active='state.filters.isMaxActive'
data-wc-on--input='actions.filters.setMaxPrice'
data-wc-on--change='actions.filters.updateProducts'
>
</div>
<div class='text'>
<input
type='text'
value='$min_price'
data-wc-bind--value='state.filters.minPrice'
data-wc-on--input='actions.filters.setMinPrice'
data-wc-on--change='actions.filters.updateProducts'
>
<input
type='text'
value='$max_price'
data-wc-bind--value='state.filters.maxPrice'
data-wc-on--input='actions.filters.setMaxPrice'
data-wc-on--change='actions.filters.updateProducts'
>
</div>
<button data-wc-on--click='actions.filters.reset'>Reset</button>
</div>
HTML;
}
}
1 change: 1 addition & 0 deletions src/BlockTypesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ protected function get_block_types() {
'ReviewsByProduct',
'RelatedProducts',
'ProductDetails',
'SimplePriceFilter',
'SingleProduct',
'StockFilter',
];
Expand Down
9 changes: 9 additions & 0 deletions src/Interactivity/client-side-navigation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

/**
* Print client-side navigation meta tag (hard-coded for now).
*/
function woocommerce_interactivity_add_client_side_navigation_meta_tag() {
echo '<meta itemprop="wc-client-side-navigation" content="active">';
}
add_action( 'wp_head', 'woocommerce_interactivity_add_client_side_navigation_meta_tag' );
3 changes: 3 additions & 0 deletions woocommerce-gutenberg-products-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Loading