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

Commit 86420f7

Browse files
authored
Filter all products block by attribute (#1127)
* Block setup * Working filtering and intersection of arrays * Implement block settings and no attribute placeholder * Correctly toggle counts * Implement filtering * Fix price slider constraints * Fix price slider constraints * Edit mode * Rename ProductAttributeControl to ProductAttributeTermControl * Attribute ID saving * fix incorrect test fixtures * fix incorrect regex for parsing model (or resource names) from the route. * Fix query classes for some endpoints * Style improvements * Update inline comments * use previous tests * Show attribute control in sidebar * Remove displayStyle option * Sort attributes by name * Show more/less toggle * Use renderFrontend * Only sort when adding values * Rename memo placeholder * More specific CSS for pointer * Update assets/js/base/hooks/use-query-state.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Remove always true taxonomy check * Update assets/js/blocks/attribute-filter/block.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Remove lodash join * native js for string casting * Move internal deps * hyphenate attributes * Correct data set names * Remove unwanted dependency * Moving imports * Missing deps * replace yoda conditonal * Missing deps * Missing deps * Check value exists * Remove undefined filter * renderedOptions usememo * Set defaults in checkbox list * Show more/less refactor * Use getAdminLink * Fix object length check * Correct AND query handling for counts * useQueryStateByContext * Add store rest endpoints * Update assets/js/base/components/checkbox-list/index.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Update assets/js/base/components/checkbox-list/index.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Update assets/js/base/components/checkbox-list/index.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Update assets/js/blocks/attribute-filter/block.js Co-Authored-By: Albert Juhé Lluveras <[email protected]> * Feedback * feedback * API readme * Fix API relation queries for multiple attributes * Prevent all options flashing visible during loads * null check * Improve loading state * Remove null options change - it's no longer needed
1 parent 38a5893 commit 86420f7

File tree

35 files changed

+1679
-92
lines changed

35 files changed

+1679
-92
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { __, sprintf } from '@wordpress/i18n';
5+
import PropTypes from 'prop-types';
6+
import {
7+
Fragment,
8+
useCallback,
9+
useMemo,
10+
useState,
11+
useEffect,
12+
} from '@wordpress/element';
13+
import classNames from 'classnames';
14+
15+
/**
16+
* Internal dependencies
17+
*/
18+
import './style.scss';
19+
20+
/**
21+
* Component used to show a list of checkboxes in a group.
22+
*/
23+
const CheckboxList = ( {
24+
className,
25+
onChange = () => {},
26+
options = [],
27+
isLoading = false,
28+
limit = 10,
29+
} ) => {
30+
// Holds all checked options.
31+
const [ checked, setChecked ] = useState( [] );
32+
const [ showExpanded, setShowExpanded ] = useState( false );
33+
34+
useEffect( () => {
35+
onChange( checked );
36+
}, [ checked ] );
37+
38+
const placeholder = useMemo( () => {
39+
return [ ...Array( 5 ) ].map( ( x, i ) => (
40+
<li
41+
key={ i }
42+
style={ {
43+
/* stylelint-disable */
44+
width: Math.floor( Math.random() * 75 ) + 25 + '%',
45+
} }
46+
/>
47+
) );
48+
}, [] );
49+
50+
const onCheckboxChange = useCallback(
51+
( event ) => {
52+
const isChecked = event.target.checked;
53+
const checkedValue = event.target.value;
54+
const newChecked = checked.filter(
55+
( value ) => value !== checkedValue
56+
);
57+
58+
if ( isChecked ) {
59+
newChecked.push( checkedValue );
60+
newChecked.sort();
61+
}
62+
63+
setChecked( newChecked );
64+
},
65+
[ checked ]
66+
);
67+
68+
const renderedShowMore = useMemo( () => {
69+
const optionCount = options.length;
70+
return (
71+
! showExpanded && (
72+
<li key="show-more" className="show-more">
73+
<button
74+
onClick={ () => {
75+
setShowExpanded( true );
76+
} }
77+
aria-expanded={ false }
78+
aria-label={ sprintf(
79+
__(
80+
'Show %s more options',
81+
'woo-gutenberg-products-block'
82+
),
83+
optionCount - limit
84+
) }
85+
>
86+
{ // translators: %s number of options to reveal.
87+
sprintf(
88+
__(
89+
'Show %s more',
90+
'woo-gutenberg-products-block'
91+
),
92+
optionCount - limit
93+
) }
94+
</button>
95+
</li>
96+
)
97+
);
98+
}, [ options, limit, showExpanded ] );
99+
100+
const renderedShowLess = useMemo( () => {
101+
return (
102+
showExpanded && (
103+
<li key="show-less" className="show-less">
104+
<button
105+
onClick={ () => {
106+
setShowExpanded( false );
107+
} }
108+
aria-expanded={ true }
109+
aria-label={ __(
110+
'Show less options',
111+
'woo-gutenberg-products-block'
112+
) }
113+
>
114+
{ __( 'Show less', 'woo-gutenberg-products-block' ) }
115+
</button>
116+
</li>
117+
)
118+
);
119+
}, [ showExpanded ] );
120+
121+
const renderedOptions = useMemo( () => {
122+
// Truncate options if > the limit + 5.
123+
const optionCount = options.length;
124+
const shouldTruncateOptions = optionCount > limit + 5;
125+
return (
126+
<Fragment>
127+
{ options.map( ( option, index ) => (
128+
<Fragment key={ option.key }>
129+
<li
130+
{ ...shouldTruncateOptions &&
131+
! showExpanded &&
132+
index >= limit && { hidden: true } }
133+
>
134+
<input
135+
type="checkbox"
136+
id={ option.key }
137+
value={ option.key }
138+
onChange={ onCheckboxChange }
139+
checked={ checked.includes( option.key ) }
140+
/>
141+
<label htmlFor={ option.key }>
142+
{ option.label }
143+
</label>
144+
</li>
145+
{ shouldTruncateOptions &&
146+
index === limit - 1 &&
147+
renderedShowMore }
148+
</Fragment>
149+
) ) }
150+
{ shouldTruncateOptions && renderedShowLess }
151+
</Fragment>
152+
);
153+
}, [
154+
options,
155+
checked,
156+
showExpanded,
157+
limit,
158+
onCheckboxChange,
159+
renderedShowLess,
160+
renderedShowMore,
161+
] );
162+
163+
const classes = classNames(
164+
'wc-block-checkbox-list',
165+
{
166+
'is-loading': isLoading,
167+
},
168+
className
169+
);
170+
171+
return (
172+
<ul className={ classes }>
173+
{ isLoading ? placeholder : renderedOptions }
174+
</ul>
175+
);
176+
};
177+
178+
CheckboxList.propTypes = {
179+
onChange: PropTypes.func,
180+
options: PropTypes.arrayOf(
181+
PropTypes.shape( {
182+
key: PropTypes.string.isRequired,
183+
label: PropTypes.node.isRequired,
184+
} )
185+
),
186+
className: PropTypes.string,
187+
isLoading: PropTypes.bool,
188+
limit: PropTypes.number,
189+
};
190+
191+
export default CheckboxList;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.editor-styles-wrapper .wc-block-checkbox-list,
2+
.wc-block-checkbox-list {
3+
margin: 0;
4+
padding: 0;
5+
list-style: none outside;
6+
7+
li {
8+
margin: 0 0 $gap-smallest;
9+
padding: 0;
10+
list-style: none outside;
11+
}
12+
13+
li.show-more,
14+
li.show-less {
15+
button {
16+
background: none;
17+
border: none;
18+
padding: 0;
19+
text-decoration: underline;
20+
cursor: pointer;
21+
}
22+
}
23+
24+
&.is-loading {
25+
li {
26+
@include placeholder();
27+
}
28+
}
29+
}

assets/js/base/hooks/use-collection-header.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ import { useShallowEqual } from './use-shallow-equal';
4141
* loading (true) or not.
4242
*/
4343
export const useCollectionHeader = ( headerKey, options ) => {
44-
const { namespace, resourceName, resourceValues, query } = options;
44+
const {
45+
namespace,
46+
resourceName,
47+
resourceValues = [],
48+
query = {},
49+
} = options;
4550
if ( ! namespace || ! resourceName ) {
4651
throw new Error(
4752
'The options object must have valid values for the namespace and ' +
@@ -61,7 +66,7 @@ export const useCollectionHeader = ( headerKey, options ) => {
6166
resourceName,
6267
currentQuery,
6368
currentResourceValues,
64-
].filter( ( item ) => typeof item !== 'undefined' );
69+
];
6570
return {
6671
value: store.getCollectionHeader( ...args ),
6772
isLoading: store.hasFinishedResolution(

assets/js/base/hooks/use-collection.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ import { useShallowEqual } from './use-shallow-equal';
3636
* loading (true) or not.
3737
*/
3838
export const useCollection = ( options ) => {
39-
const { namespace, resourceName, resourceValues, query } = options;
39+
const {
40+
namespace,
41+
resourceName,
42+
resourceValues = [],
43+
query = {},
44+
} = options;
4045
if ( ! namespace || ! resourceName ) {
4146
throw new Error(
4247
'The options object must have valid values for the namespace and ' +
@@ -55,7 +60,7 @@ export const useCollection = ( options ) => {
5560
resourceName,
5661
currentQuery,
5762
currentResourceValues,
58-
].filter( ( item ) => typeof item !== 'undefined' );
63+
];
5964
return {
6065
results: store.getCollection( ...args ),
6166
isLoading: ! store.hasFinishedResolution(

assets/js/base/hooks/use-query-state.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,20 @@ export const useQueryStateByContext = ( context ) => {
4444
*
4545
* @param {string} context What context to retrieve the query state for.
4646
* @param {*} queryKey The specific query key to retrieve the value for.
47+
* @param {*} defaultValue Default value if query does not exist.
4748
*
4849
* @return {*} Whatever value is set at the query state index using the
4950
* provided context and query key.
5051
*/
51-
export const useQueryStateByKey = ( context, queryKey ) => {
52+
export const useQueryStateByKey = (
53+
context,
54+
queryKey,
55+
defaultValue
56+
) => {
5257
const queryValue = useSelect(
5358
( select ) => {
5459
const store = select( storeKey );
55-
return store.getValueForQueryKey( context, queryKey, undefined );
60+
return store.getValueForQueryKey( context, queryKey, defaultValue );
5661
},
5762
[ context, queryKey ]
5863
);

0 commit comments

Comments
 (0)