PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.3.2
Block Animations, Motion & Scroll Effects – Ghost Kit v3.3.2
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
ghostkit / gutenberg / components / icon-picker / index.js

index.js in Block Animations, Motion & Scroll Effects – Ghost Kit 3.3.2, at gutenberg/components/icon-picker/index.js

510 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import 'react-virtualized/styles.css';
2
3 import classnames from 'classnames/dedupe';
4 import {
5 AutoSizer,
6 CellMeasurer,
7 CellMeasurerCache,
8 List,
9 } from 'react-virtualized';
10
11 import dashCaseToTitle from '../../utils/dash-case-to-title';
12 import { maybeDecode, maybeEncode } from '../../utils/encode-decode';
13
14 const { GHOSTKIT } = window;
15
16 import {
17 BaseControl,
18 Button,
19 Dropdown,
20 G,
21 Path,
22 SVG,
23 TextareaControl,
24 TextControl,
25 Tooltip,
26 } from '@wordpress/components';
27 import { Fragment, useEffect, useRef, useState } from '@wordpress/element';
28 import { __ } from '@wordpress/i18n';
29
30 /**
31 * Go over each icon.
32 *
33 * @param {Function} callback callback.
34 */
35 function eachIcons(callback) {
36 const { icons, settings } = GHOSTKIT;
37
38 Object.keys(icons).forEach((key) => {
39 const allow =
40 typeof settings[`icon_pack_${key}`] === 'undefined' ||
41 settings[`icon_pack_${key}`];
42
43 if (allow) {
44 callback(icons[key]);
45 }
46 });
47 }
48
49 /**
50 * Get Icon Tip.
51 *
52 * @param {Object} iconData icon data.
53 *
54 * @return {string} tip
55 */
56 function getIconTip(iconData) {
57 let result = '';
58
59 if (iconData.keys) {
60 result = dashCaseToTitle(iconData.keys.split(',')[0]);
61 }
62
63 return result;
64 }
65
66 // we need this lazy loading component to prevent a huge lags while first loading SVG icons
67 function Icon(props) {
68 const { iconData, onClick, active } = props;
69
70 return (
71 <IconPicker.Preview
72 className={classnames(
73 'ghostkit-component-icon-picker-button',
74 active ? 'ghostkit-component-icon-picker-button-active' : ''
75 )}
76 onClick={onClick}
77 data={iconData}
78 />
79 );
80 }
81
82 const hiddenIconCategories = {};
83
84 /**
85 * Dropdown
86 *
87 * @param props
88 */
89 function IconPickerDropdown(props) {
90 const cellMCache = useRef();
91
92 const { onChange, value, label, className, renderToggle, insideInspector } =
93 props;
94
95 const [search, setSearch] = useState('');
96 const [hiddenCategories, setHiddenCategories] =
97 useState(hiddenIconCategories);
98 const [customizeIcon, setCustomizeIcon] = useState(false);
99
100 // Mounted.
101 useEffect(() => {
102 cellMCache.current = new CellMeasurerCache({
103 defaultHeight: 65,
104 fixedWidth: true,
105 });
106 }, []);
107
108 // Mounted and updated.
109 useEffect(() => {
110 if (cellMCache.current) {
111 cellMCache.current.clearAll();
112 }
113 });
114
115 function getDropdownContent() {
116 const rows = [
117 {
118 key: 'form',
119 render: (
120 <Fragment key="form">
121 <TextControl
122 label={__('Search Icon', 'ghostkit')}
123 value={maybeDecode(search)}
124 onChange={(searchVal) =>
125 setSearch(maybeEncode(searchVal))
126 }
127 placeholder={__('Type to Search…', 'ghostkit')}
128 autoComplete="off"
129 // eslint-disable-next-line jsx-a11y/no-autofocus
130 autoFocus
131 />
132 <div className="ghostkit-component-icon-picker-input-output">
133 {customizeIcon ? (
134 <TextareaControl
135 label={__('Icon Output', 'ghostkit')}
136 value={value}
137 onChange={(newData) => {
138 onChange(newData);
139 }}
140 placeholder={__('Icon Output', 'ghostkit')}
141 autoComplete="off"
142 />
143 ) : (
144 <Button
145 isSmall
146 isSecondary
147 onClick={() => setCustomizeIcon(true)}
148 style={{
149 marginRight: 5,
150 }}
151 >
152 {__('Customize Icon', 'ghostkit')}
153 </Button>
154 )}
155 <Button
156 isSmall
157 isSecondary
158 disabled={!value}
159 onClick={() => onChange('')}
160 >
161 {__('Reset', 'ghostkit')}
162 </Button>
163 </div>
164 </Fragment>
165 ),
166 },
167 ];
168
169 eachIcons((iconsData) => {
170 const showCategory =
171 typeof hiddenCategories[iconsData.name] !== 'undefined'
172 ? hiddenCategories[iconsData.name]
173 : true;
174 const searchString = search.toLowerCase();
175
176 // prepare all icons.
177 const allIcons = iconsData.icons
178 .filter((iconData) => {
179 if (
180 !searchString ||
181 (searchString &&
182 iconData.keys.indexOf(searchString.toLowerCase()) >
183 -1)
184 ) {
185 return true;
186 }
187
188 return false;
189 })
190 .map((iconData) => {
191 const iconTip = getIconTip(iconData);
192
193 const result = (
194 <Icon
195 active={
196 maybeDecode(iconData.svg) === maybeDecode(value)
197 }
198 iconData={iconData}
199 onClick={() => {
200 onChange(maybeDecode(iconData.svg));
201 }}
202 />
203 );
204
205 if (iconTip) {
206 return (
207 <Tooltip
208 key={maybeDecode(iconData.svg)}
209 text={iconTip}
210 >
211 {/* We need this <div> just because Tooltip don't work without it */}
212 <div>{result}</div>
213 </Tooltip>
214 );
215 }
216
217 return result;
218 });
219
220 if (!allIcons.length) {
221 return;
222 }
223
224 // prepare icons category toggle title.
225 rows.push({
226 key: `title-${iconsData.name}`,
227 render: (
228 // Used PanelBody https://github.com/WordPress/gutenberg/blob/master/packages/components/src/panel/body.js.
229 <div
230 className={classnames(
231 'components-panel__body ghostkit-component-icon-picker-list-panel-toggle',
232 showCategory ? 'is-opened' : ''
233 )}
234 >
235 <h2 className="components-panel__body-title">
236 <Button
237 className="components-panel__body-toggle"
238 onClick={() => {
239 setHiddenCategories({
240 ...hiddenCategories,
241 [iconsData.name]: !showCategory,
242 });
243 }}
244 aria-expanded={showCategory}
245 >
246 {/*
247 Firefox + NVDA don't announce aria-expanded because the browser
248 repaints the whole element, so this wrapping span hides that.
249 */}
250 <span aria-hidden="true">
251 {showCategory ? (
252 <SVG
253 className="components-panel__arrow"
254 width="24px"
255 height="24px"
256 viewBox="0 0 24 24"
257 xmlns="http://www.w3.org/2000/svg"
258 >
259 <G>
260 <Path
261 fill="none"
262 d="M0,0h24v24H0V0z"
263 />
264 </G>
265 <G>
266 <Path d="M12,8l-6,6l1.41,1.41L12,10.83l4.59,4.58L18,14L12,8z" />
267 </G>
268 </SVG>
269 ) : (
270 <SVG
271 className="components-panel__arrow"
272 width="24px"
273 height="24px"
274 viewBox="0 0 24 24"
275 xmlns="http://www.w3.org/2000/svg"
276 >
277 <G>
278 <Path
279 fill="none"
280 d="M0,0h24v24H0V0z"
281 />
282 </G>
283 <G>
284 <Path d="M7.41,8.59L12,13.17l4.59-4.58L18,10l-6,6l-6-6L7.41,8.59z" />
285 </G>
286 </SVG>
287 )}
288 </span>
289 {iconsData.name}
290 </Button>
291 </h2>
292 </div>
293 ),
294 });
295
296 if (!showCategory) {
297 return;
298 }
299
300 let currentIcons = [];
301
302 allIcons.forEach((icon, i) => {
303 currentIcons.push(icon);
304
305 if (currentIcons.length === 3 || allIcons.length === i + 1) {
306 rows.push({
307 key: 'icons',
308 render: (
309 <div className="ghostkit-component-icon-picker-list">
310 {currentIcons}
311 </div>
312 ),
313 });
314
315 currentIcons = [];
316 }
317 });
318 });
319
320 // No icons.
321 if (rows.length === 1) {
322 rows.push({
323 key: 'icons',
324 render: __('No icons found.', 'ghostkit'),
325 });
326 }
327
328 const result = (
329 <AutoSizer key={`autosizer-${customizeIcon ? '1' : '0'}`}>
330 {({ width, height }) => (
331 <List
332 className="ghostkit-component-icon-picker-list-wrap"
333 width={width}
334 height={height}
335 rowCount={rows.length}
336 rowHeight={cellMCache?.current?.rowHeight}
337 rowRenderer={(data) => {
338 const { index, style, parent, key } = data;
339
340 return (
341 <CellMeasurer
342 cache={cellMCache?.current}
343 columnIndex={0}
344 key={key}
345 rowIndex={index}
346 parent={parent}
347 >
348 {() => (
349 <div style={style}>
350 {rows[index].render || ''}
351 </div>
352 )}
353 </CellMeasurer>
354 );
355 }}
356 />
357 )}
358 </AutoSizer>
359 );
360
361 return (
362 <>
363 <div className="ghostkit-component-icon-picker-sizer" />
364 <div className="ghostkit-component-icon-picker">{result}</div>
365 </>
366 );
367 }
368
369 const dropdown = (
370 <Dropdown
371 className={className}
372 contentClassName="ghostkit-component-icon-picker-content"
373 renderToggle={renderToggle}
374 focusOnMount={false}
375 renderContent={() => getDropdownContent()}
376 {...(insideInspector
377 ? {
378 popoverProps: {
379 placement: 'left-start',
380 offset: 36,
381 shift: true,
382 },
383 }
384 : {})}
385 />
386 );
387
388 return label ? (
389 <BaseControl id={label} label={label} className={className}>
390 {dropdown}
391 </BaseControl>
392 ) : (
393 dropdown
394 );
395 }
396
397 export default function IconPicker(props) {
398 const { value, label, onChange, insideInspector } = props;
399
400 return (
401 <BaseControl className="ghostkit-component-icon-picker-wrapper">
402 <IconPicker.Dropdown
403 onChange={(val) => onChange(maybeEncode(val))}
404 value={maybeDecode(value)}
405 insideInspector={insideInspector}
406 renderToggle={({ isOpen, onToggle }) => (
407 <Button
408 className={classnames(
409 'ghostkit-component-icon-picker-toggle',
410 isOpen
411 ? 'ghostkit-component-icon-picker-toggle-active'
412 : ''
413 )}
414 onClick={onToggle}
415 >
416 <IconPicker.Preview
417 className="ghostkit-component-icon-picker-toggle-indicator"
418 name={maybeDecode(value)}
419 alwaysRender
420 />
421 <span className="ghostkit-component-icon-picker-toggle-label">
422 {label}
423 </span>
424 </Button>
425 )}
426 />
427 </BaseControl>
428 );
429 }
430
431 // dropdown
432 IconPicker.Dropdown = IconPickerDropdown;
433
434 // preview icon.
435 IconPicker.Preview = function (props) {
436 const { onClick, className, alwaysRender = false } = props;
437
438 let { data, name } = props;
439
440 if (!data && name) {
441 eachIcons((iconsData) => {
442 iconsData.icons.forEach((iconData) => {
443 if (
444 !data &&
445 iconData.svg &&
446 (iconData.svg === name ||
447 (iconData.fallback &&
448 iconData.fallback === name &&
449 iconData.svg))
450 ) {
451 if (iconData.svg) {
452 data = iconData;
453 } else {
454 name = iconData.class;
455 }
456 }
457 });
458 });
459 }
460
461 let result = '';
462
463 if (data && data.svg) {
464 result = data.svg;
465 } else if (name || (data && data.class)) {
466 result = name || data.class;
467 }
468
469 return result || alwaysRender ? (
470 <IconPicker.Render
471 name={maybeDecode(result)}
472 tag="span"
473 className={classnames(
474 className,
475 'ghostkit-component-icon-picker-preview',
476 onClick
477 ? 'ghostkit-component-icon-picker-preview-clickable'
478 : ''
479 )}
480 onClick={onClick}
481 onKeyPress={() => {}}
482 role="button"
483 tabIndex={0}
484 alwaysRender={alwaysRender}
485 />
486 ) : null;
487 };
488
489 // render icon.
490 IconPicker.Render = function (props) {
491 const { tag = 'span', name, alwaysRender = false, ...restProps } = props;
492
493 const Tag = tag;
494 let result = '';
495
496 const decodedName = maybeDecode(name);
497
498 if (decodedName && /^</g.test(decodedName)) {
499 result = decodedName;
500 } else if (decodedName) {
501 result = `<span class="${decodedName}"></span>`;
502 }
503
504 if (!result && !alwaysRender) {
505 return null;
506 }
507
508 return <Tag dangerouslySetInnerHTML={{ __html: result }} {...restProps} />;
509 };
510