UNPKG

@woocommerce/components

Version:
269 lines (268 loc) 13.5 kB
"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;