Merge pull request woocommerce/woocommerce-admin#276 from woocommerce/add/advanced-filters-card
Add/advanced filters card
This commit is contained in:
commit
8683726d06
|
@ -0,0 +1,89 @@
|
||||||
|
/** @format */
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
export const filters = [
|
||||||
|
{ label: __( 'All Orders', 'wc-admin' ), value: 'all' },
|
||||||
|
{
|
||||||
|
label: __( 'Single Order', 'wc-admin' ),
|
||||||
|
value: 'single',
|
||||||
|
subFilters: [
|
||||||
|
{
|
||||||
|
label: __( 'Single Order', 'wc-admin' ),
|
||||||
|
component: 'Search',
|
||||||
|
value: 'single_order',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ label: __( 'Top Orders by Items Sold', 'wc-admin' ), value: 'top_items' },
|
||||||
|
{ label: __( 'Top Orders by Gross Sales', 'wc-admin' ), value: 'top_sales' },
|
||||||
|
{ label: __( 'Advanced Filters', 'wc-admin' ), value: 'advanced' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const filterPaths = {
|
||||||
|
all: [],
|
||||||
|
single: [],
|
||||||
|
single_order: [ 'single' ],
|
||||||
|
top_items: [],
|
||||||
|
top_sales: [],
|
||||||
|
advanced: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const advancedFilterConfig = {
|
||||||
|
status: {
|
||||||
|
label: __( 'Order Status', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Order Status', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||||
|
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
input: {
|
||||||
|
component: 'SelectControl',
|
||||||
|
options: [
|
||||||
|
{ value: 'pending', label: __( 'Pending', 'wc-admin' ) },
|
||||||
|
{ value: 'processing', label: __( 'Processing', 'wc-admin' ) },
|
||||||
|
{ value: 'on-hold', label: __( 'On Hold', 'wc-admin' ) },
|
||||||
|
{ value: 'completed', label: __( 'Completed', 'wc-admin' ) },
|
||||||
|
{ value: 'refunded', label: __( 'Refunded', 'wc-admin' ) },
|
||||||
|
{ value: 'cancelled', label: __( 'Cancelled', 'wc-admin' ) },
|
||||||
|
{ value: 'failed', label: __( 'Failed', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
label: __( 'Product', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Products', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
|
||||||
|
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
|
||||||
|
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||||
|
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
input: {
|
||||||
|
component: 'FormTokenField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coupon: {
|
||||||
|
label: __( 'Coupon Code', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Coupon Codes', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
|
||||||
|
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
|
||||||
|
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||||
|
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
input: {
|
||||||
|
component: 'FormTokenField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
label: __( 'Customer is', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Customer Type', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'new', label: __( 'New', 'wc-admin' ) },
|
||||||
|
{ value: 'returning', label: __( 'Returning', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
|
@ -15,6 +15,11 @@ import { partial } from 'lodash';
|
||||||
*/
|
*/
|
||||||
import Header from 'layout/header/index';
|
import Header from 'layout/header/index';
|
||||||
import Card from 'components/card';
|
import Card from 'components/card';
|
||||||
|
import DatePicker from 'components/date-picker';
|
||||||
|
import FilterPicker, { FILTER_PARAM } from 'components/filter-picker';
|
||||||
|
import AdvancedFilters from 'components/advanced-filters';
|
||||||
|
import { filters, filterPaths, advancedFilterConfig } from './constants';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
class OrdersReport extends Component {
|
class OrdersReport extends Component {
|
||||||
constructor( props ) {
|
constructor( props ) {
|
||||||
|
@ -32,7 +37,7 @@ class OrdersReport extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { orders, orderIds } = this.props;
|
const { orders, orderIds, query, path } = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Header
|
<Header
|
||||||
|
@ -41,6 +46,22 @@ class OrdersReport extends Component {
|
||||||
__( 'Orders', 'wc-admin' ),
|
__( 'Orders', 'wc-admin' ),
|
||||||
] }
|
] }
|
||||||
/>
|
/>
|
||||||
|
<div className="woocommerce-orders__pickers">
|
||||||
|
<DatePicker query={ query } path={ path } key={ JSON.stringify( query ) } />
|
||||||
|
<FilterPicker
|
||||||
|
query={ query }
|
||||||
|
path={ path }
|
||||||
|
filters={ filters }
|
||||||
|
filterPaths={ filterPaths }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ 'advanced' === query[ FILTER_PARAM ] && (
|
||||||
|
<AdvancedFilters
|
||||||
|
config={ advancedFilterConfig }
|
||||||
|
filterTitle={ __( 'Orders', 'wc-admin' ) }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
<p>Below is a temporary example</p>
|
||||||
<Card title="Orders">
|
<Card title="Orders">
|
||||||
<table style={ { width: '100%' } }>
|
<table style={ { width: '100%' } }>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/** @format */
|
||||||
|
|
||||||
|
.woocommerce-orders__pickers {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-right: $gap-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include breakpoint( '<1100px' ) {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,17 +15,6 @@ import { filters, filterPaths } from './constants';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export default class extends Component {
|
export default class extends Component {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.getQueryParamValue = this.getQueryParamValue.bind( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
getQueryParamValue() {
|
|
||||||
const { query } = this.props;
|
|
||||||
return query.product || 'all';
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { query, path } = this.props;
|
const { query, path } = this.props;
|
||||||
|
|
||||||
|
@ -44,8 +33,6 @@ export default class extends Component {
|
||||||
path={ path }
|
path={ path }
|
||||||
filters={ filters }
|
filters={ filters }
|
||||||
filterPaths={ filterPaths }
|
filterPaths={ filterPaths }
|
||||||
queryParam="product"
|
|
||||||
getQueryParamValue={ this.getQueryParamValue }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
margin-right: 24px;
|
margin-right: $gap-large;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint( '<1100px' ) {
|
@include breakpoint( '<1100px' ) {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
Advanced Filters
|
||||||
|
============
|
||||||
|
|
||||||
|
Displays a configurable set of filters which can modify query parameters.
|
||||||
|
|
||||||
|
## How to use:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import AdvancedFilters from 'components/advanced-filters';
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
status: {
|
||||||
|
label: __( 'Order Status', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Order Status', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||||
|
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
input: {
|
||||||
|
component: 'SelectControl',
|
||||||
|
options: [
|
||||||
|
{ value: 'pending', label: __( 'Pending', 'wc-admin' ) },
|
||||||
|
{ value: 'processing', label: __( 'Processing', 'wc-admin' ) },
|
||||||
|
{ value: 'on-hold', label: __( 'On Hold', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
label: __( 'Product', 'wc-admin' ),
|
||||||
|
addLabel: __( 'Products', 'wc-admin' ),
|
||||||
|
rules: [
|
||||||
|
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
|
||||||
|
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
|
||||||
|
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||||
|
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||||
|
],
|
||||||
|
input: {
|
||||||
|
component: 'FormTokenField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<AdvancedFilters config={ filters } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## AdvancedFilters Props
|
||||||
|
|
||||||
|
* `config` (required): The configuration object required to render filters
|
||||||
|
|
||||||
|
## config object jsDoc
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @type filterConfig {{
|
||||||
|
* key: {
|
||||||
|
* label: {string},
|
||||||
|
* addLabel: {string},
|
||||||
|
* rules: [{{ value:{string}, label:{string} }}],
|
||||||
|
* input: {
|
||||||
|
* component: {string},
|
||||||
|
* [options]: [*]
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
```
|
|
@ -0,0 +1,238 @@
|
||||||
|
/** @format */
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { Component, Fragment, createRef } from '@wordpress/element';
|
||||||
|
import { SelectControl, Button, FormTokenField, Dropdown, IconButton } from '@wordpress/components';
|
||||||
|
import { partial, findIndex, find, difference } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Gridicon from 'gridicons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import Card from 'components/card';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
const matches = [
|
||||||
|
{ value: 'all', label: __( 'All', 'wc-admin' ) },
|
||||||
|
{ value: 'any', label: __( 'Any', 'wc-admin' ) },
|
||||||
|
];
|
||||||
|
|
||||||
|
class AdvancedFilters extends Component {
|
||||||
|
constructor( props ) {
|
||||||
|
super( props );
|
||||||
|
this.state = {
|
||||||
|
match: matches[ 0 ],
|
||||||
|
activeFilters: [
|
||||||
|
/**
|
||||||
|
* Example activeFilter
|
||||||
|
* { key: ‘product’, rule: ‘includes’, value: [ ‘one’, ‘two’ ] }
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.filterListRef = createRef();
|
||||||
|
|
||||||
|
this.onMatchChange = this.onMatchChange.bind( this );
|
||||||
|
this.onFilterChange = this.onFilterChange.bind( this );
|
||||||
|
this.getSelector = this.getSelector.bind( this );
|
||||||
|
this.getAvailableFilterKeys = this.getAvailableFilterKeys.bind( this );
|
||||||
|
this.addFilter = this.addFilter.bind( this );
|
||||||
|
this.removeFilter = this.removeFilter.bind( this );
|
||||||
|
this.clearAllFilters = this.clearAllFilters.bind( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
onMatchChange( value ) {
|
||||||
|
this.setState( {
|
||||||
|
match: find( matches, match => value === match.value ),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilterChange( key, property, value ) {
|
||||||
|
const activeFilters = this.state.activeFilters.map( activeFilter => {
|
||||||
|
if ( key === activeFilter.key ) {
|
||||||
|
return Object.assign( {}, activeFilter, { [ property ]: value } );
|
||||||
|
}
|
||||||
|
return activeFilter;
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.setState( { activeFilters } );
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFilter( key ) {
|
||||||
|
const activeFilters = [ ...this.state.activeFilters ];
|
||||||
|
const index = findIndex( activeFilters, filter => filter.key === key );
|
||||||
|
activeFilters.splice( index, 1 );
|
||||||
|
this.setState( { activeFilters } );
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle() {
|
||||||
|
const { match } = this.state;
|
||||||
|
const { filterTitle } = this.props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<span>{ sprintf( __( '%s Match', 'wc-admin' ), filterTitle ) }</span>
|
||||||
|
<SelectControl
|
||||||
|
className="woocommerce-advanced-filters__title-select"
|
||||||
|
options={ matches }
|
||||||
|
value={ match.value }
|
||||||
|
onChange={ this.onMatchChange }
|
||||||
|
aria-label={ __( 'Match any or all filters', 'wc-admin' ) }
|
||||||
|
/>
|
||||||
|
<span>{ __( 'Filters', 'wc-admin' ) }</span>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelector( filter ) {
|
||||||
|
const filterConfig = this.props.config[ filter.key ];
|
||||||
|
const { input } = filterConfig;
|
||||||
|
if ( ! input ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( 'SelectControl' === input.component ) {
|
||||||
|
return (
|
||||||
|
<SelectControl
|
||||||
|
className="woocommerce-advanced-filters__list-select"
|
||||||
|
options={ input.options }
|
||||||
|
value={ filter.value }
|
||||||
|
onChange={ partial( this.onFilterChange, filter.key, 'value' ) }
|
||||||
|
aria-label={ sprintf( __( 'Select %s', 'wc-admin' ), filterConfig.label ) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( 'FormTokenField' === input.component ) {
|
||||||
|
return (
|
||||||
|
<FormTokenField
|
||||||
|
value={ filter.value }
|
||||||
|
onChange={ partial( this.onFilterChange, filter.key, 'value' ) }
|
||||||
|
placeholder={ sprintf( __( 'Add %s', 'wc-admin' ), filterConfig.label ) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableFilterKeys() {
|
||||||
|
const { config } = this.props;
|
||||||
|
const activeFilterKeys = this.state.activeFilters.map( f => f.key );
|
||||||
|
return difference( Object.keys( config ), activeFilterKeys );
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter( key, onClose ) {
|
||||||
|
const filterConfig = this.props.config[ key ];
|
||||||
|
const newFilter = { key, rule: filterConfig.rules[ 0 ] };
|
||||||
|
if ( filterConfig.input && filterConfig.input.options ) {
|
||||||
|
newFilter.value = filterConfig.input.options[ 0 ];
|
||||||
|
}
|
||||||
|
this.setState( state => {
|
||||||
|
return {
|
||||||
|
activeFilters: [ ...state.activeFilters, newFilter ],
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
// after render, focus the newly added filter's first focusable element
|
||||||
|
setTimeout( () => {
|
||||||
|
const addedFilter = this.filterListRef.current.querySelector( 'li:last-of-type fieldset' );
|
||||||
|
addedFilter.focus();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllFilters() {
|
||||||
|
this.setState( {
|
||||||
|
activeFilters: [],
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { config } = this.props;
|
||||||
|
const availableFilterKeys = this.getAvailableFilterKeys();
|
||||||
|
return (
|
||||||
|
<Card className="woocommerce-advanced-filters" title={ this.getTitle() }>
|
||||||
|
<ul className="woocommerce-advanced-filters__list" ref={ this.filterListRef }>
|
||||||
|
{ this.state.activeFilters.map( filter => {
|
||||||
|
const { key, rule } = filter;
|
||||||
|
const filterConfig = config[ key ];
|
||||||
|
return (
|
||||||
|
<li className="woocommerce-advanced-filters__list-item" key={ key }>
|
||||||
|
{ /*eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex*/ }
|
||||||
|
<fieldset tabIndex="0">
|
||||||
|
{ /*eslint-enable-next-line jsx-a11y/no-noninteractive-tabindex*/ }
|
||||||
|
<legend className="screen-reader-text">{ filterConfig.label }</legend>
|
||||||
|
<div className="woocommerce-advanced-filters__fieldset">
|
||||||
|
<div className="woocommerce-advanced-filters__fieldset-legend">
|
||||||
|
{ filterConfig.label }
|
||||||
|
</div>
|
||||||
|
<SelectControl
|
||||||
|
className="woocommerce-advanced-filters__list-specifier"
|
||||||
|
options={ filterConfig.rules }
|
||||||
|
value={ rule }
|
||||||
|
onChange={ partial( this.onFilterChange, key, 'rule' ) }
|
||||||
|
aria-label={ sprintf(
|
||||||
|
__( 'Select a %s filter match', 'wc-admin' ),
|
||||||
|
filterConfig.addLabel
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
<div className="woocommerce-advanced-filters__list-selector">
|
||||||
|
{ this.getSelector( filter ) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<IconButton
|
||||||
|
className="woocommerce-advanced-filters__remove"
|
||||||
|
label={ sprintf( __( 'Remove %s filter', 'wc-admin' ), filterConfig.label ) }
|
||||||
|
onClick={ partial( this.removeFilter, key ) }
|
||||||
|
icon={ <Gridicon icon="cross-small" /> }
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} ) }
|
||||||
|
</ul>
|
||||||
|
{ availableFilterKeys.length > 0 && (
|
||||||
|
<div className="woocommerce-advanced-filters__add-filter">
|
||||||
|
<Dropdown
|
||||||
|
position="bottom center"
|
||||||
|
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||||
|
<IconButton
|
||||||
|
className="woocommerce-advanced-filters__add-btn"
|
||||||
|
icon={ <Gridicon icon="add-outline" /> }
|
||||||
|
onClick={ onToggle }
|
||||||
|
aria-expanded={ isOpen }
|
||||||
|
>
|
||||||
|
{ __( 'Add a Filter', 'wc-admin' ) }
|
||||||
|
</IconButton>
|
||||||
|
) }
|
||||||
|
renderContent={ ( { onClose } ) => (
|
||||||
|
<ul className="woocommerce-advanced-filters__add-dropdown">
|
||||||
|
{ availableFilterKeys.map( key => (
|
||||||
|
<li key={ key }>
|
||||||
|
<Button onClick={ partial( this.addFilter, key, onClose ) }>
|
||||||
|
{ config[ key ].addLabel }
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
) ) }
|
||||||
|
</ul>
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
|
||||||
|
<div className="woocommerce-advanced-filters__controls">
|
||||||
|
<Button isPrimary>{ __( 'Filter', 'wc-admin' ) }</Button>
|
||||||
|
<Button isLink onClick={ this.clearAllFilters }>
|
||||||
|
{ __( 'Clear all filters', 'wc-admin' ) }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedFilters.propTypes = {
|
||||||
|
config: PropTypes.object.isRequired,
|
||||||
|
filterTitle: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvancedFilters;
|
|
@ -0,0 +1,158 @@
|
||||||
|
/** @format */
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters {
|
||||||
|
margin: $gap-large 0;
|
||||||
|
|
||||||
|
.woocommerce-card__header {
|
||||||
|
padding: $gap-smaller $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-card__body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-select-control__input {
|
||||||
|
height: 38px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__title-select {
|
||||||
|
width: 70px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 $gap-smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__list {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__list-item {
|
||||||
|
padding: $gap-smaller $gap;
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 40px;
|
||||||
|
background-color: $core-grey-light-100;
|
||||||
|
border-bottom: 1px solid $core-grey-light-700;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $core-grey-light-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-base-control {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__remove {
|
||||||
|
width: 40px;
|
||||||
|
height: 38px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-form-token-field {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__add-filter {
|
||||||
|
padding: $gap-small;
|
||||||
|
margin: 0;
|
||||||
|
color: $woocommerce;
|
||||||
|
display: block;
|
||||||
|
background-color: $core-grey-light-100;
|
||||||
|
border-bottom: 1px solid $core-grey-light-700;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $core-grey-light-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
div div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-popover:not(.is-mobile) .components-popover__content {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__fieldset {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 150px auto;
|
||||||
|
|
||||||
|
@include breakpoint( '<782px' ) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__fieldset-legend {
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
@include breakpoint( '<782px' ) {
|
||||||
|
align-self: initial;
|
||||||
|
padding: $gap-smallest 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__add-btn {
|
||||||
|
color: inherit;
|
||||||
|
padding: $gap-smaller;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.components-icon-button:not(:disabled):not([aria-disabled='true']):not(.is-default):hover {
|
||||||
|
color: $woocommerce-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):not([aria-disabled='true']):focus {
|
||||||
|
color: $woocommerce;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__controls {
|
||||||
|
padding: $gap-smaller $gap;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
margin-right: $gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__add-dropdown {
|
||||||
|
padding: $gap-smaller 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: $gap-smaller;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $core-grey-light-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):not([aria-disabled='true']):focus {
|
||||||
|
background-color: $core-grey-light-300;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__list-selector {
|
||||||
|
padding: 0 0 0 $gap-smaller;
|
||||||
|
|
||||||
|
@include breakpoint( '<782px' ) {
|
||||||
|
padding: $gap-smallest 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-advanced-filters__list-specifier {
|
||||||
|
@include breakpoint( '<782px' ) {
|
||||||
|
padding: $gap-smallest 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ Card.propTypes = {
|
||||||
menu: PropTypes.shape( {
|
menu: PropTypes.shape( {
|
||||||
type: PropTypes.oneOf( [ EllipsisMenu ] ),
|
type: PropTypes.oneOf( [ EllipsisMenu ] ),
|
||||||
} ),
|
} ),
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Card;
|
export default Card;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Filter Picker
|
Filter Picker
|
||||||
===
|
===
|
||||||
|
|
||||||
Modify a url query parameter via a dropdown selection of configurable options
|
Modify a url query parameter via a dropdown selection of configurable options. This component manipulates the `filter` query parameter.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -36,21 +36,12 @@ const renderFilterPicker = ( { path, query } ) {
|
||||||
other_fish: [ 'lunch', 'fish' ],
|
other_fish: [ 'lunch', 'fish' ],
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryParam = 'meal';
|
|
||||||
|
|
||||||
const getQueryParamValue = () => {
|
|
||||||
const { query } = this.props;
|
|
||||||
return return query[ queryParam ] || 'breakfast';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterPicker
|
<FilterPicker
|
||||||
query={ query }
|
query={ query }
|
||||||
path={ path }
|
path={ path }
|
||||||
filters={ filters }
|
filters={ filters }
|
||||||
filterPaths={ filterPaths }
|
filterPaths={ filterPaths }
|
||||||
queryParam={ queryParam }
|
|
||||||
getQueryParamValue={ getQueryParamValue }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,5 +53,3 @@ const renderFilterPicker = ( { path, query } ) {
|
||||||
* `path` (required): Parameter supplied by React-Router
|
* `path` (required): Parameter supplied by React-Router
|
||||||
* `filters` (required): An array of filters and subFilters to construct the menu
|
* `filters` (required): An array of filters and subFilters to construct the menu
|
||||||
* `filterPaths` (required): A map of representing the structure of the tree. Required for faster lookups than searches
|
* `filterPaths` (required): A map of representing the structure of the tree. Required for faster lookups than searches
|
||||||
* `queryParam` (required): The query parameter to update
|
|
||||||
* `getQueryParamValue` (required): A function used to obtain the current value represented in the url
|
|
||||||
|
|
|
@ -18,13 +18,16 @@ import AnimationSlider from 'components/animation-slider';
|
||||||
import Link from 'components/link';
|
import Link from 'components/link';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
|
export const FILTER_PARAM = 'filter';
|
||||||
|
export const DEFAULT_FILTER_PARAM = 'all';
|
||||||
|
|
||||||
class FilterPicker extends Component {
|
class FilterPicker extends Component {
|
||||||
constructor( props ) {
|
constructor( props ) {
|
||||||
super( props );
|
super( props );
|
||||||
|
|
||||||
const { filterPaths, getQueryParamValue } = props;
|
const { filterPaths, query } = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
nav: filterPaths[ getQueryParamValue() ],
|
nav: filterPaths[ this.getQueryParamValue( query ) ],
|
||||||
animate: null,
|
animate: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,24 +39,27 @@ class FilterPicker extends Component {
|
||||||
this.goBack = this.goBack.bind( this );
|
this.goBack = this.goBack.bind( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getQueryParamValue( query ) {
|
||||||
|
return query[ FILTER_PARAM ] || DEFAULT_FILTER_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
getOtherQueries( query ) {
|
getOtherQueries( query ) {
|
||||||
const { queryParam } = this.props;
|
return omit( query, FILTER_PARAM );
|
||||||
return omit( query, queryParam );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectionPath( filter ) {
|
getSelectionPath( filter ) {
|
||||||
const { path, query, queryParam } = this.props;
|
const { path, query } = this.props;
|
||||||
const otherQueries = this.getOtherQueries( query );
|
const otherQueries = this.getOtherQueries( query );
|
||||||
const data = {
|
const data = {
|
||||||
[ queryParam ]: filter.value,
|
[ FILTER_PARAM ]: filter.value,
|
||||||
};
|
};
|
||||||
const queryString = stringifyQueryObject( Object.assign( otherQueries, data ) );
|
const queryString = stringifyQueryObject( Object.assign( otherQueries, data ) );
|
||||||
return `${ path }?${ queryString }`;
|
return `${ path }?${ queryString }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedFilter() {
|
getSelectedFilter() {
|
||||||
const { filters, getQueryParamValue, filterPaths } = this.props;
|
const { filters, filterPaths, query } = this.props;
|
||||||
const value = getQueryParamValue();
|
const value = this.getQueryParamValue( query );
|
||||||
const filterPath = filterPaths[ value ];
|
const filterPath = filterPaths[ value ];
|
||||||
const visibleFilters = this.getVisibleFilters( filters, [ ...filterPath ] );
|
const visibleFilters = this.getVisibleFilters( filters, [ ...filterPath ] );
|
||||||
return find( visibleFilters, filter => filter.value === value );
|
return find( visibleFilters, filter => filter.value === value );
|
||||||
|
@ -176,8 +182,6 @@ FilterPicker.propTypes = {
|
||||||
query: PropTypes.object.isRequired,
|
query: PropTypes.object.isRequired,
|
||||||
filters: PropTypes.array.isRequired,
|
filters: PropTypes.array.isRequired,
|
||||||
filterPaths: PropTypes.object.isRequired,
|
filterPaths: PropTypes.object.isRequired,
|
||||||
queryParam: PropTypes.string.isRequired,
|
|
||||||
getQueryParamValue: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilterPicker;
|
export default FilterPicker;
|
||||||
|
|
Loading…
Reference in New Issue