@woocommerce/components
Version:
UI components for WooCommerce.
269 lines (268 loc) • 13.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* External dependencies
*/
const i18n_1 = require("@wordpress/i18n");
const components_1 = require("@wordpress/components");
const element_1 = require("@wordpress/element");
const lodash_1 = require("lodash");
const prop_types_1 = __importDefault(require("prop-types"));
const add_outline_1 = __importDefault(require("gridicons/dist/add-outline"));
const navigation_1 = require("@woocommerce/navigation");
/**
* Internal dependencies
*/
const link_1 = __importDefault(require("../link"));
const item_1 = __importDefault(require("./item"));
const experimental_1 = require("../experimental");
const utils_1 = require("./utils");
const matches = [
{ value: 'all', label: (0, i18n_1.__)('All', 'woocommerce') },
{ value: 'any', label: (0, i18n_1.__)('Any', 'woocommerce') },
];
/**
* Displays a configurable set of filters which can modify query parameters.
*/
class AdvancedFilters extends element_1.Component {
constructor({ query, config }) {
super(...arguments);
this.instanceCounts = {};
const filtersFromQuery = (0, navigation_1.getActiveFiltersFromQuery)(query, config.filters);
// @todo: This causes rerenders when instance numbers don't match (from adding/remove before updating query string).
const activeFilters = filtersFromQuery.map((filter) => {
if (config.filters[filter.key].allowMultiple) {
filter.instance = this.getInstanceNumber(filter.key);
}
return filter;
});
this.state = {
match: query.match || 'all',
activeFilters,
};
this.filterListRef = (0, element_1.createRef)();
this.onMatchChange = this.onMatchChange.bind(this);
this.onFilterChange = this.onFilterChange.bind(this);
this.getAvailableFilters = this.getAvailableFilters.bind(this);
this.addFilter = this.addFilter.bind(this);
this.removeFilter = this.removeFilter.bind(this);
this.clearFilters = this.clearFilters.bind(this);
this.getUpdateHref = this.getUpdateHref.bind(this);
this.onFilter = this.onFilter.bind(this);
}
componentDidUpdate(prevProps) {
const { config, query } = this.props;
const { query: prevQuery } = prevProps;
if (!(0, lodash_1.isEqual)(prevQuery, query)) {
const filtersFromQuery = (0, navigation_1.getActiveFiltersFromQuery)(query, config.filters);
// Update all multiple instance counts.
this.instanceCounts = {};
// @todo: This causes rerenders when instance numbers don't match (from adding/remove before updating query string).
const activeFilters = filtersFromQuery.map((filter) => {
if (config.filters[filter.key].allowMultiple) {
filter.instance = this.getInstanceNumber(filter.key);
}
return filter;
});
/* eslint-disable react/no-did-update-set-state */
this.setState({ activeFilters });
/* eslint-enable react/no-did-update-set-state */
}
}
getInstanceNumber(key) {
if (!this.instanceCounts.hasOwnProperty(key)) {
this.instanceCounts[key] = 1;
}
return this.instanceCounts[key]++;
}
onMatchChange(match) {
const { onAdvancedFilterAction } = this.props;
this.setState({ match });
onAdvancedFilterAction('match', { match });
}
onFilterChange(index, { property, value, shouldResetValue = false }) {
const newActiveFilters = [...this.state.activeFilters];
newActiveFilters[index] = {
...newActiveFilters[index],
[property]: value,
...(shouldResetValue === true ? { value: null } : {}),
};
this.setState({ activeFilters: newActiveFilters });
}
removeFilter(index) {
const { onAdvancedFilterAction } = this.props;
const activeFilters = [...this.state.activeFilters];
onAdvancedFilterAction('remove', activeFilters[index]);
activeFilters.splice(index, 1);
this.setState({ activeFilters });
if (activeFilters.length === 0) {
const history = (0, navigation_1.getHistory)();
history.push(this.getUpdateHref([]));
}
}
getTitle() {
const { match } = this.state;
const { config } = this.props;
return (0, utils_1.backwardsCompatibleCreateInterpolateElement)(config.title, {
select: ((0, element_1.createElement)(components_1.SelectControl, { className: "woocommerce-filters-advanced__title-select", options: matches, value: match, onChange: this.onMatchChange, "aria-label": (0, i18n_1.__)('Choose to apply any or all filters', 'woocommerce') })),
});
}
getAvailableFilters() {
const { config } = this.props;
const activeFilterKeys = this.state.activeFilters.map((f) => f.key);
// Get filter objects with keys.
const allFilters = Object.entries(config.filters).map(([key, value]) => ({ key, ...value }));
// Available filters are those that allow multiple instances or are not already active.
const availableFilters = allFilters.filter((filter) => {
return (filter.allowMultiple ||
!activeFilterKeys.includes(filter.key));
});
// Sort filters by their add label.
availableFilters.sort((a, b) => a.labels.add.localeCompare(b.labels.add));
return availableFilters;
}
addFilter(key, onClose) {
const { onAdvancedFilterAction, config } = this.props;
const filterConfig = config.filters[key];
const newFilter = { key };
if (Array.isArray(filterConfig.rules) &&
filterConfig.rules.length) {
newFilter.rule = filterConfig.rules[0].value;
}
if (filterConfig.input && filterConfig.input.options) {
newFilter.value = (0, navigation_1.getDefaultOptionValue)(filterConfig, filterConfig.input.options);
}
if (filterConfig.input && filterConfig.input.component === 'Search') {
newFilter.value = '';
}
if (filterConfig.allowMultiple) {
newFilter.instance = this.getInstanceNumber(key);
}
this.setState((state) => {
return {
activeFilters: [...state.activeFilters, newFilter],
};
});
onAdvancedFilterAction('add', 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();
});
}
clearFilters() {
const { onAdvancedFilterAction } = this.props;
onAdvancedFilterAction('clear_all');
this.setState({
activeFilters: [],
match: 'all',
});
}
getUpdateHref(activeFilters, matchValue) {
const { path, query, config } = this.props;
const updatedQuery = (0, navigation_1.getQueryFromActiveFilters)(activeFilters, query, config.filters);
const match = matchValue === 'all' ? undefined : matchValue;
return (0, navigation_1.getNewPath)({ ...updatedQuery, match }, path, query);
}
isEnglish() {
return /en[-|_]/.test(this.props.siteLocale);
}
onFilter() {
const { onAdvancedFilterAction, query, config } = this.props;
const { activeFilters, match } = this.state;
const updatedQuery = (0, navigation_1.getQueryFromActiveFilters)(activeFilters, query, config.filters);
onAdvancedFilterAction('filter', { ...updatedQuery, match });
}
orderFilters(a, b) {
const qs = window.location.search;
const aPos = qs.indexOf(a.key);
const bPos = qs.indexOf(b.key);
// If either isn't in the url, it means its just been added, so leave it as is.
if (aPos === -1 || bPos === -1) {
return 0;
}
// Otherwise use the url to determine order in which filter was added.
return aPos - bPos;
}
render() {
const { config, query, currency } = this.props;
const { activeFilters, match } = this.state;
const availableFilters = this.getAvailableFilters();
const updateHref = this.getUpdateHref(activeFilters, match);
const updateDisabled = 'admin.php' + window.location.search === updateHref ||
activeFilters.length === 0;
const isEnglish = this.isEnglish();
return ((0, element_1.createElement)(components_1.Card, { className: "woocommerce-filters-advanced", size: "small" },
(0, element_1.createElement)(components_1.CardHeader, { justify: "flex-start" },
(0, element_1.createElement)(experimental_1.Text, { variant: "subtitle.small", as: "div", weight: "600", size: "14", lineHeight: "20px", isBlock: "false" }, this.getTitle())),
!!activeFilters.length && ((0, element_1.createElement)(components_1.CardBody, { size: null },
(0, element_1.createElement)("ul", { className: "woocommerce-filters-advanced__list", ref: this.filterListRef }, activeFilters
.sort(this.orderFilters)
.map((filter, idx) => {
const { instance, key } = filter;
return ((0, element_1.createElement)(item_1.default, { key: key + (instance || ''), config: config, currency: currency, filter: filter, isEnglish: isEnglish, onFilterChange: (0, lodash_1.partial)(this.onFilterChange, idx), query: query, removeFilter: () => this.removeFilter(idx) }));
})))),
availableFilters.length > 0 && ((0, element_1.createElement)(components_1.CardBody, null,
(0, element_1.createElement)("div", { className: "woocommerce-filters-advanced__add-filter" },
(0, element_1.createElement)(components_1.Dropdown, { className: "woocommerce-filters-advanced__add-filter-dropdown", popoverProps: {
placement: 'bottom',
}, renderToggle: ({ isOpen, onToggle }) => ((0, element_1.createElement)(components_1.Button, { className: "woocommerce-filters-advanced__add-button", onClick: onToggle, "aria-expanded": isOpen },
(0, element_1.createElement)(add_outline_1.default, null),
(0, i18n_1.__)('Add a filter', 'woocommerce'))), renderContent: ({ onClose }) => ((0, element_1.createElement)("ul", { className: "woocommerce-filters-advanced__add-dropdown" }, availableFilters.map((filter) => ((0, element_1.createElement)("li", { key: filter.key },
(0, element_1.createElement)(components_1.Button, { onClick: (0, lodash_1.partial)(this.addFilter, filter.key, onClose) }, filter.labels.add)))))) })))),
(0, element_1.createElement)(components_1.CardFooter, { align: "center" },
(0, element_1.createElement)("div", { className: "woocommerce-filters-advanced__controls" },
updateDisabled && ((0, element_1.createElement)(components_1.Button, { isPrimary: true, disabled: true }, (0, i18n_1.__)('Filter', 'woocommerce'))),
!updateDisabled && ((0, element_1.createElement)(link_1.default, { className: "components-button is-primary is-button", type: "wc-admin", href: updateHref, onClick: this.onFilter }, (0, i18n_1.__)('Filter', 'woocommerce'))),
activeFilters.length > 0 && ((0, element_1.createElement)(link_1.default, { type: "wc-admin", href: this.getUpdateHref([]), onClick: this.clearFilters }, (0, i18n_1.__)('Clear all filters', 'woocommerce')))))));
}
}
AdvancedFilters.propTypes = {
/**
* The configuration object required to render filters.
*/
config: prop_types_1.default.shape({
title: prop_types_1.default.string,
filters: prop_types_1.default.objectOf(prop_types_1.default.shape({
labels: prop_types_1.default.shape({
add: prop_types_1.default.string,
remove: prop_types_1.default.string,
rule: prop_types_1.default.string,
title: prop_types_1.default.string,
filter: prop_types_1.default.string,
}),
rules: prop_types_1.default.arrayOf(prop_types_1.default.object),
input: prop_types_1.default.object,
})),
}).isRequired,
/**
* Name of this filter, used in translations.
*/
path: prop_types_1.default.string.isRequired,
/**
* The query string represented in object form.
*/
query: prop_types_1.default.object,
/**
* Function to be called after an advanced filter action has been taken.
*/
onAdvancedFilterAction: prop_types_1.default.func,
/**
* The locale for the site.
*/
siteLocale: prop_types_1.default.string,
/**
* The currency formatting instance for the site.
*/
currency: prop_types_1.default.object.isRequired,
};
AdvancedFilters.defaultProps = {
query: {},
onAdvancedFilterAction: () => { },
siteLocale: 'en_US',
};
exports.default = AdvancedFilters;