Add new SearchListItem component (https://github.com/woocommerce/woocommerce-blocks/pull/258)
* Add new SearchListItem component Extract the custom functionality from product categories, for re-use in product attributes * Fix error when no breadcrumbs are set * Update snapshot * Remove unnecessary key
This commit is contained in:
parent
d3c8998430
commit
aa9f543834
|
@ -5,16 +5,15 @@ import { __, _n, sprintf } from '@wordpress/i18n';
|
|||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { find, first, last } from 'lodash';
|
||||
import { MenuItem, SelectControl } from '@wordpress/components';
|
||||
import { find } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import { IconChecked, IconUnchecked } from '../icons';
|
||||
import SearchListControl from '../search-list-control';
|
||||
import SearchListItem from '../search-list-control/item';
|
||||
|
||||
class ProductCategoryControl extends Component {
|
||||
constructor() {
|
||||
|
@ -38,29 +37,10 @@ class ProductCategoryControl extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
getBreadcrumbsForDisplay( breadcrumbs ) {
|
||||
if ( breadcrumbs.length === 1 ) {
|
||||
return first( breadcrumbs );
|
||||
}
|
||||
if ( breadcrumbs.length === 2 ) {
|
||||
return first( breadcrumbs ) + ' › ' + last( breadcrumbs );
|
||||
}
|
||||
|
||||
return first( breadcrumbs ) + ' … ' + last( breadcrumbs );
|
||||
}
|
||||
|
||||
renderItem( {
|
||||
getHighlightedName,
|
||||
isSelected,
|
||||
item,
|
||||
onSelect,
|
||||
search,
|
||||
depth = 0,
|
||||
} ) {
|
||||
renderItem( args ) {
|
||||
const { item, search, depth = 0 } = args;
|
||||
const classes = [
|
||||
'woocommerce-search-list__item',
|
||||
'woocommerce-product-categories__item',
|
||||
`depth-${ depth }`,
|
||||
];
|
||||
if ( search.length ) {
|
||||
classes.push( 'is-searching' );
|
||||
|
@ -74,12 +54,10 @@ class ProductCategoryControl extends Component {
|
|||
`${ item.breadcrumbs.join( ', ' ) }, ${ item.name }`;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={ item.id }
|
||||
role="menuitemcheckbox"
|
||||
<SearchListItem
|
||||
className={ classes.join( ' ' ) }
|
||||
onClick={ onSelect( item ) }
|
||||
isSelected={ isSelected }
|
||||
{ ...args }
|
||||
showCount
|
||||
aria-label={ sprintf(
|
||||
_n(
|
||||
'%s, has %d product',
|
||||
|
@ -90,27 +68,7 @@ class ProductCategoryControl extends Component {
|
|||
accessibleName,
|
||||
item.count
|
||||
) }
|
||||
>
|
||||
<span className="woocommerce-search-list__item-state">
|
||||
{ isSelected ? <IconChecked /> : <IconUnchecked /> }
|
||||
</span>
|
||||
<span className="woocommerce-product-categories__item-label">
|
||||
{ !! item.breadcrumbs.length && (
|
||||
<span className="woocommerce-product-categories__item-prefix">
|
||||
{ this.getBreadcrumbsForDisplay( item.breadcrumbs ) }
|
||||
</span>
|
||||
) }
|
||||
<span
|
||||
className="woocommerce-product-categories__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
<span className="woocommerce-product-categories__item-count">
|
||||
{ item.count }
|
||||
</span>
|
||||
</MenuItem>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
.woocommerce-product-categories {
|
||||
.woocommerce-product-categories__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
// Anything deeper than 5 levels will use this fallback depth
|
||||
[class*="depth-"] & {
|
||||
padding-left: $gap-small * 5;
|
||||
}
|
||||
|
||||
.depth-0 & {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.depth-1 & {
|
||||
padding-left: $gap-small;
|
||||
}
|
||||
|
||||
.depth-2 & {
|
||||
padding-left: $gap-small * 2;
|
||||
}
|
||||
|
||||
.depth-3 & {
|
||||
padding-left: $gap-small * 3;
|
||||
}
|
||||
|
||||
.depth-4 & {
|
||||
padding-left: $gap-small * 4;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item {
|
||||
.woocommerce-product-categories__item-name {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item-prefix {
|
||||
display: none;
|
||||
color: $core-grey-dark-300;
|
||||
}
|
||||
|
||||
&.is-searching, &.is-skip-level {
|
||||
.woocommerce-product-categories__item-prefix {
|
||||
display: inline-block;
|
||||
margin-right: $gap-smallest;
|
||||
|
||||
&:after {
|
||||
content: ' ›';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-searching {
|
||||
.woocommerce-product-categories__item-name {
|
||||
color: $core-grey-dark-900;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item-count {
|
||||
flex: 0;
|
||||
padding: $gap-smallest/2 $gap-smaller;
|
||||
border: 1px solid $core-grey-light-500;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.4;
|
||||
color: $core-grey-dark-300;
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__operator {
|
||||
margin-bottom: $gap;
|
||||
text-align: left;
|
||||
|
||||
.components-base-control__label,
|
||||
.components-select-control__input {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.components-select-control__input {
|
||||
margin-left: $gap;
|
||||
width: auto;
|
||||
min-width: 15em;
|
||||
|
||||
@include breakpoint( '<480px' ) {
|
||||
margin-left: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
Button,
|
||||
MenuItem,
|
||||
MenuGroup,
|
||||
Spinner,
|
||||
TextControl,
|
||||
|
@ -22,7 +21,7 @@ import { Tag } from '@woocommerce/components';
|
|||
*/
|
||||
import './style.scss';
|
||||
import { buildTermsTree } from './hierarchy';
|
||||
import { IconChecked, IconUnchecked } from '../icons';
|
||||
import SearchListItem from './item';
|
||||
|
||||
const defaultMessages = {
|
||||
clear: __( 'Clear all selected items', 'woo-gutenberg-products-block' ),
|
||||
|
@ -91,45 +90,9 @@ export class SearchListControl extends Component {
|
|||
return isHierarchical ? buildTermsTree( filteredList, list ) : filteredList;
|
||||
}
|
||||
|
||||
getHighlightedName( name, search ) {
|
||||
if ( ! search ) {
|
||||
return name;
|
||||
}
|
||||
const re = new RegExp( escapeRegExp( search ), 'ig' );
|
||||
return name.replace( re, '<strong>$&</strong>' );
|
||||
}
|
||||
|
||||
defaultRenderItem( {
|
||||
depth = 0,
|
||||
getHighlightedName,
|
||||
item,
|
||||
isSelected,
|
||||
onSelect,
|
||||
search = '',
|
||||
} ) {
|
||||
const classes = [ 'woocommerce-search-list__item' ];
|
||||
if ( this.props.isHierarchical ) {
|
||||
classes.push( `depth-${ depth }` );
|
||||
}
|
||||
|
||||
defaultRenderItem( args ) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ item.id }
|
||||
role="menuitemcheckbox"
|
||||
className={ classes.join( ' ' ) }
|
||||
onClick={ onSelect( item ) }
|
||||
isSelected={ isSelected }
|
||||
>
|
||||
<span className="woocommerce-search-list__item-state">
|
||||
{ isSelected ? <IconChecked /> : <IconUnchecked /> }
|
||||
</span>
|
||||
<span
|
||||
className="woocommerce-search-list__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
</MenuItem>
|
||||
<SearchListItem { ...args } />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -142,7 +105,6 @@ export class SearchListControl extends Component {
|
|||
return list.map( ( item ) => (
|
||||
<Fragment key={ item.id }>
|
||||
{ renderItem( {
|
||||
getHighlightedName: this.getHighlightedName,
|
||||
item,
|
||||
isSelected: this.isSelected( item ),
|
||||
onSelect: this.onSelect,
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { escapeRegExp, first, last } from 'lodash';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { IconChecked, IconUnchecked } from '../icons';
|
||||
|
||||
function getHighlightedName( name, search ) {
|
||||
if ( ! search ) {
|
||||
return name;
|
||||
}
|
||||
const re = new RegExp( escapeRegExp( search ), 'ig' );
|
||||
return name.replace( re, '<strong>$&</strong>' );
|
||||
}
|
||||
|
||||
function getBreadcrumbsForDisplay( breadcrumbs ) {
|
||||
if ( breadcrumbs.length === 1 ) {
|
||||
return first( breadcrumbs );
|
||||
}
|
||||
if ( breadcrumbs.length === 2 ) {
|
||||
return first( breadcrumbs ) + ' › ' + last( breadcrumbs );
|
||||
}
|
||||
|
||||
return first( breadcrumbs ) + ' … ' + last( breadcrumbs );
|
||||
}
|
||||
|
||||
const SearchListItem = ( {
|
||||
className,
|
||||
depth = 0,
|
||||
item,
|
||||
isSelected,
|
||||
onSelect,
|
||||
search = '',
|
||||
showCount = false,
|
||||
...props
|
||||
} ) => {
|
||||
const classes = [ className, 'woocommerce-search-list__item' ];
|
||||
classes.push( `depth-${ depth }` );
|
||||
|
||||
const hasBreadcrumbs = item.breadcrumbs && item.breadcrumbs.length;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
role="menuitemcheckbox"
|
||||
className={ classes.join( ' ' ) }
|
||||
onClick={ onSelect( item ) }
|
||||
isSelected={ isSelected }
|
||||
{ ...props }
|
||||
>
|
||||
<span className="woocommerce-search-list__item-state">
|
||||
{ isSelected ? <IconChecked /> : <IconUnchecked /> }
|
||||
</span>
|
||||
|
||||
<span className="woocommerce-search-list__item-label">
|
||||
{ hasBreadcrumbs ? (
|
||||
<span className="woocommerce-search-list__item-prefix">
|
||||
{ getBreadcrumbsForDisplay( item.breadcrumbs ) }
|
||||
</span>
|
||||
) : null }
|
||||
<span
|
||||
className="woocommerce-search-list__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
|
||||
{ !! showCount && (
|
||||
<span className="woocommerce-search-list__item-count">
|
||||
{ item.count }
|
||||
</span>
|
||||
) }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
SearchListItem.propTypes = {
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Depth, non-zero if the list is hierarchical.
|
||||
*/
|
||||
depth: PropTypes.number,
|
||||
/**
|
||||
* Current item to display.
|
||||
*/
|
||||
item: PropTypes.object,
|
||||
/**
|
||||
* Whether this item is selected.
|
||||
*/
|
||||
isSelected: PropTypes.bool,
|
||||
/**
|
||||
* Callback for selecting the item.
|
||||
*/
|
||||
onSelect: PropTypes.func,
|
||||
/**
|
||||
* Search string, used to highlight the substring in the item name.
|
||||
*/
|
||||
search: PropTypes.string,
|
||||
/**
|
||||
* Toggles the "count" bubble on/off.
|
||||
*/
|
||||
showCount: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default SearchListItem;
|
|
@ -91,15 +91,6 @@
|
|||
border-bottom: 1px solid $core-grey-light-500 !important;
|
||||
color: $core-grey-dark-500;
|
||||
|
||||
.woocommerce-search-list__item-state {
|
||||
flex: 0 0 16px;
|
||||
margin-right: $gap-smaller;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@include hover-state {
|
||||
background: $core-grey-light-100;
|
||||
}
|
||||
|
@ -107,5 +98,64 @@
|
|||
&:last-of-type {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-state {
|
||||
flex: 0 0 16px;
|
||||
margin-right: $gap-smaller;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// Anything deeper than 5 levels will use this fallback depth
|
||||
&[class*="depth-"] .woocommerce-search-list__item-label {
|
||||
padding-left: $gap-small * 5;
|
||||
}
|
||||
|
||||
@for $i from 0 to 5 {
|
||||
&.depth-#{$i} .woocommerce-search-list__item-label {
|
||||
padding-left: $gap-small * $i;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-name {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-prefix {
|
||||
display: none;
|
||||
color: $core-grey-dark-300;
|
||||
}
|
||||
|
||||
&.is-searching,
|
||||
&.is-skip-level {
|
||||
.woocommerce-search-list__item-prefix {
|
||||
display: inline-block;
|
||||
margin-right: $gap-smallest;
|
||||
|
||||
&:after {
|
||||
content: " ›";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-searching {
|
||||
.woocommerce-search-list__item-name {
|
||||
color: $core-grey-dark-900;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item-count {
|
||||
flex: 0;
|
||||
padding: $gap-smallest/2 $gap-smaller;
|
||||
border: 1px solid $core-grey-light-500;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.4;
|
||||
color: $core-grey-dark-300;
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue