Your IP : 216.73.216.138


Current Path : /home/ereika83/www/wp-content/plugins/edit-flow/modules/calendar/lib/react/combobox/
Upload File :
Current File : /home/ereika83/www/wp-content/plugins/edit-flow/modules/calendar/lib/react/combobox/index.js

/* global EF_CALENDAR */

/**
 * External dependencies
 */
import React from 'react';
import Downshift from 'downshift';
import matchSorter from 'match-sorter';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { BaseControl, Button, IconButton } from '@wordpress/components';

// Get rid of this eventually
const ACTIVE_ICON_BUTTON = parseFloat( EF_CALENDAR.WP_VERSION ) >= 5.3 ? Button : IconButton;

/**
 * Internal dependencies
 */
import './style.react.scss';

/**
 * Filters items based on simple name text match
 *
 * @param {string} filter a string to filter items by
 * @param {Item[]} items a list of items to be filtered
 * @return {string[]} array of strings that match
 */
function getItems( filter, items ) {
	return filter
		? matchSorter( items, filter, {
			keys: [ 'name' ],
		} )
		: items;
}

/**
 * Find an item by Id
 * @param {Item[]} items a list of items
 * @param {*} id an id to find
 * @return {Item} an item with the id
 */
function getItem( items, id ) {
	return items.find( item => item.value === id );
}

/**
 * An item that can be supplied to <Combobox>
 * @typedef {Object} Item
 * @property {string} name - The name of the item, used for filtering results.
 * @property {string|number} id - The unique identifier for the item
 * @property {string|number} [parent] - An optional identifier for a parent
 * @property {number} [level] - An optional identifier designating nesting level
 */

// A combobox supporting <select> with search
const ComboBox = ( {
	className,
	placeholder,
	inputLabel,
	buttonOpenLabel,
	buttonCloseLabel,
	buttonClearLabel,
	items,
	noMatchText = 'No items match',
	onInputBlur,
	...comboboxPropsRest
} ) => {
	return (
		<div className={classnames( 'ef-combobox', className )}>
			<Downshift {...comboboxPropsRest}>
				{( {
					getInputProps,
					getToggleButtonProps,
					getMenuProps,
					getItemProps,
					isOpen,
					openMenu,
					clearSelection,
					selectedItem,
					inputValue,
					highlightedIndex,
				} ) => {
					let foundItems = [];
					let filteredItems = [];

					if ( isOpen ) {
						filteredItems = getItems( inputValue, items );
						foundItems = filteredItems.map( ( item, index ) => {
							return (
								<li
									aria-label={item.name}
									className={classnames( {
										'is-active': highlightedIndex === index,
									} )}
									key={item.value}
									{...getItemProps( {
										item: item,
										index,
									} )}
								>
									{item.level && ! inputValue
										? new Array( item.level ).fill( '\xa0' ).join( '' )
										: null}
									{item.parent && inputValue ? (
										<span className="ef-combobox-item-parent">
											{getItem( items, item.parent ).name}
										</span>
									) : null}
									{item.parent && inputValue ? '\xa0' : null}{item.name}
								</li>
							);
						} );
					}

					if ( isOpen && foundItems.length < 1 ) {
						foundItems = [
							<li
								aria-label={noMatchText}
								className="disabled"
								key="no-items-match"
								{...getItemProps( {
									item: noMatchText,
									disabled: true,
								} )}
							>
								{noMatchText}
							</li>,
						];
					}

					return (
						<div>
							<div className="ef-combobox-input-wrapper">
								<BaseControl
									label={inputLabel}
								>
									<input
										className={classnames(
											{ 'is-open': isOpen },
											'ef-combobox-input components-text-control__input'
										)}
										{...getInputProps( {
											onBlur: () => {
												onInputBlur && onInputBlur( filteredItems, inputValue );
											},
											onFocus: openMenu,
											type: 'text',
											placeholder: placeholder,
										} )}
									/>
								</BaseControl>
								{selectedItem ? (
									<ACTIVE_ICON_BUTTON
										{...getToggleButtonProps( {
											'aria-label': buttonClearLabel,
										} )}
										onClick={clearSelection}
										key="no-alt"
										className="ef-combobox-input-button"
										icon="no-alt"
									/>
								) : (
									<ACTIVE_ICON_BUTTON
										{...getToggleButtonProps( {
											'aria-label': isOpen ? buttonCloseLabel : buttonOpenLabel,
										} )}
										className="ef-combobox-input-button"
										icon={isOpen ? 'arrow-up-alt2' : 'arrow-down-alt2'}
									/>
								)}
							</div>
							<ul
								className={classnames( 'ef-combobox-menu-wrapper', {
									'ef-combobox-menu-wrapper-hidden': ! isOpen,
								} )}
								{...getMenuProps()}
							>
								{isOpen ? foundItems : null}
							</ul>
						</div>
					);
				}}
			</Downshift>
		</div>
	);
};

ComboBox.propTypes = {
	className: PropTypes.string,
	placeholder: PropTypes.string,
	inputLabel: PropTypes.string,
	buttonOpenLabel: PropTypes.string,
	buttonCloseLabel: PropTypes.string,
	buttonClearLabel: PropTypes.string,
	label: PropTypes.string,
	items: PropTypes.arrayOf( PropTypes.shape( {
		name: PropTypes.string.isRequired,
		id: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
		parent: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
		level: PropTypes.number,
	} ) ),
	noMatchText: PropTypes.string, // What to display in the menu dropdown if no items match
	onInputBlur: PropTypes.func, // Arguments to function: (filtered items at the time of blur, the value in the input)
};

export { ComboBox };