PluginProbe
ElasticPress / 4.2.1
ElasticPress v4.2.1
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / instant-results / components / common / checkbox-list.js

checkbox-list.js in ElasticPress 4.2.1, at assets/js/instant-results/components/common/checkbox-list.js

241 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies.
3 */
4 import { Fragment, useRef, useState, WPElement } from '@wordpress/element';
5 import { __, _n, sprintf } from '@wordpress/i18n';
6
7 /**
8 * Internal dependencies.
9 */
10 import Checkbox from './checkbox';
11 import SmallButton from './small-button';
12
13 /**
14 * Checkbox list component.
15 *
16 * @typedef {object} Option
17 * @property {number} count Number associated with option.
18 * @property {string} id Option ID.
19 * @property {string} label Option label.
20 * @property {number} order Option order.
21 * @property {string} parent Parent option value.
22 * @property {any} value Option value.
23 *
24 * @param {object} props Component props.
25 * @param {boolean} props.disabled Whether the checkboxes should be disabled.
26 * @param {boolean} props.label List label.
27 * @param {Function} props.onChange Checkbox change event callback function.
28 * @param {Option[]} props.options Checkbox options.
29 * @param {string} props.selected Selected values.
30 * @param {string} props.sortBy How to sort options.
31 * @returns {WPElement} A React element.
32 */
33 export default ({ disabled, label, options, onChange, selected, sortBy }) => {
34 /**
35 * Outermost list element.
36 */
37 const listEl = useRef(null);
38
39 /**
40 * Whether all items are displayed and a setter.
41 */
42 const [showAll, setShowAll] = useState(false);
43
44 /**
45 * Reducer to group options by parent.
46 *
47 * @param {object} items Options grouped by parent.
48 * @param {Option} option Option details.
49 * @param {string} option.parent Option parent value.
50 * @returns {object} Options grouped by parent.
51 */
52 const reduceOptionsByParent = (items, { parent, ...option }) => {
53 // eslint-disable-next-line eqeqeq
54 if (parent != false) {
55 items[parent] = items[parent] || [];
56 items[parent].push(option);
57 }
58
59 return items;
60 };
61
62 /**
63 * Options grouped by parent value.
64 */
65 const childOptions = options.reduce(reduceOptionsByParent, {});
66
67 /**
68 * Reducer to group top level options. Top level options are options
69 * with a parent of '0' or without a parent.
70 *
71 * @param {Array} items Options without a parent.
72 * @param {Option} option Option details.
73 * @param {string} option.parent Option parent value.
74 * @returns {object} Options without a parent.
75 */
76 const reduceTopLevelOptions = (items, { parent, ...option }) => {
77 // eslint-disable-next-line eqeqeq
78 if (parent == false || parent === '0') {
79 items.push(option);
80 }
81
82 return items;
83 };
84
85 /**
86 * Top level options.
87 */
88 const topLevelOptions = options.reduce(reduceTopLevelOptions, []);
89
90 /**
91 * How many options should be displayed by default.
92 */
93 const optionsLimit = options.length > 8 ? 5 : 8;
94
95 /**
96 * How many options have been displayed.
97 *
98 * Incremented each time an item is displayed. Used to limit the number of
99 * items displayed by default until the show more button is pressed.
100 */
101 let optionsShown = 0;
102
103 /**
104 * Handle checkbox change event.
105 *
106 * @param {Event} event Change event.
107 */
108 const onCheck = (event) => {
109 const { checked, value } = event.target;
110
111 let values = checked ? [...selected, value] : selected.filter((v) => v !== value);
112
113 /* Only send selected values that are in the available options. */
114 values = values.filter((v) => options.some((o) => o.value === v));
115
116 onChange(values);
117 };
118
119 /**
120 * Render an option.
121 *
122 * @param {Option} option Option.
123 * @returns {WPElement} Render function.
124 */
125 const displayOption = ({ count, id, label, value }) => {
126 const children = childOptions[value];
127
128 if (!showAll && optionsShown >= optionsLimit) {
129 return <Fragment key={value} />;
130 }
131
132 const option = (
133 <li className="ep-search-options-list__item" key={value}>
134 <Checkbox
135 checked={selected.includes(value)}
136 count={count}
137 disabled={disabled}
138 id={id}
139 label={label}
140 onChange={onCheck}
141 value={value}
142 />
143
144 {children && (showAll || optionsShown < optionsLimit) && (
145 <ul className="ep-search-options-list ep-search-options-list__sub-menu">
146 {
147 /* eslint-disable-next-line no-use-before-define */
148 displayOptions(children)
149 }
150 </ul>
151 )}
152 </li>
153 );
154
155 optionsShown++;
156
157 return option;
158 };
159
160 /**
161 * Sort option callback.
162 *
163 * @param {Option} a First option to compare.
164 * @param {Option} b second option to compare.
165 * @returns {number} Comparison number.
166 */
167 const sortOptions = (a, b) => {
168 let comparison = 0;
169
170 if (sortBy === 'count') {
171 comparison = b.count - a.count;
172 }
173
174 if (sortBy === 'name' || comparison === 0) {
175 comparison = a.label.localeCompare(b.label);
176 }
177
178 return comparison;
179 };
180
181 /**
182 * Render a list of options.
183 *
184 * @param {Option[]} options Options to display.
185 * @returns {WPElement[]} Array of elements.
186 */
187 const displayOptions = (options) => {
188 return options.splice(0).sort(sortOptions).map(displayOption);
189 };
190
191 /**
192 * Handle clicking the show more/fewer button.
193 *
194 * @returns {void}
195 */
196 const onToggleShowAll = () => {
197 setShowAll(!showAll);
198
199 listEl.current.focus();
200 };
201
202 return (
203 <>
204 {options.length > 0 && (
205 <ul
206 aria-label={label}
207 className="ep-search-options-list"
208 ref={listEl}
209 tabIndex="-1"
210 >
211 {
212 /* Display top level options and their children. */
213 displayOptions(topLevelOptions)
214 }
215 {
216 /* Display remaining orphaned options. */
217 Object.values(childOptions).map(displayOptions)
218 }
219 </ul>
220 )}
221
222 {options.length > optionsLimit && (
223 <SmallButton aria-expanded={showAll} disabled={disabled} onClick={onToggleShowAll}>
224 {showAll
225 ? __('Show fewer options', 'elasticpress')
226 : sprintf(
227 /* translators: %d: Number of additional options available. */
228 _n(
229 'Show %d more option',
230 'Show %d more options',
231 options.length - optionsLimit,
232 'elasticpress',
233 ),
234 options.length - optionsLimit,
235 )}
236 </SmallButton>
237 )}
238 </>
239 );
240 };
241