* 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:
Kelly Dwan 2018-12-19 11:56:44 -05:00 committed by GitHub
parent d3c8998430
commit aa9f543834
6 changed files with 639 additions and 478 deletions

View File

@ -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>
/>
);
}

View File

@ -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%;
}
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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;
}
}
}