All files / components/SearchField SearchField.jsx

100% Statements 14/14
100% Branches 4/4
100% Functions 0/0
100% Lines 14/14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178                        134x   134x               134x                                                                                                                                                     268x                                                 25x   25x   25x                     25x         25x       25x   25x 25x 25x                     25x                                
import _ from 'lodash';
import React, { createElement } from 'react';
import PropTypes from 'prop-types';
import { lucidClassNames } from '../../util/style-helpers';
import { createClass, getFirst, omitProps } from '../../util/component-types';
import { buildHybridComponent } from '../../util/state-management';
 
import TextField from '../TextField/TextField';
import SearchIcon from '../Icon/SearchIcon/SearchIcon';
 
import reducers from './SearchField.reducers';
 
const cx = lucidClassNames.bind('&-SearchField');
 
const { bool, func, node, number, oneOfType, string } = PropTypes;
 
/**
* {"categories": ["controls", "text"], "madeFrom": ["TextField", "SearchIcon"]}
*
* This is a wrapper around TextField that styles it for a search use-case. The
* icon and TextField are customizable through child components.
*/
const SearchField = createClass({
	displayName: 'SearchField',
 
	components: {
		/**
		 * Allows for full control over the TextField that's used under the hood.
		 */
		TextField,
		/**
		 * Icon this is displayed on the right side of the SearchField. Any of the
		 * lucid `*Icon` components should work.
		 */
		Icon: createClass({
			displayName: 'SearchField.Icon',
			propName: 'Icon',
		}),
	},
 
	reducers,
 
	propTypes: {
		/**
		 * Fires an event every time the user types text into the TextField.
		 *
		 * Signature: `(value, { event, props }) => {}`
		 */
		onChange: func,
		/**
		 * Fires an event, debounced by `debounceLevel`, when the user types text
		 * into the TextField.
		 *
		 * Signature: `(value, { event, props }) => {}`
		 */
		onChangeDebounced: func,
		/**
		 * Number of milliseconds to debounce the `onChangeDebounced` callback.
		 * Only useful if you provide an `onChangeDebounced` handler.
		 */
		debounceLevel: number,
		/**
		 * Fires an event when the user hits "enter" from the SearchField.
		 *
		 * Signature: `(value, { event, props }) => {}`
		 */
		onSubmit: func,
		/**
		 * Set the value of the input.
		 */
		value: oneOfType([number, string]),
		/**
		 * Controls the highlighting of the search icon. Should be passed `true` when
		 * the search text is valid, e.g. contains enough characters to perform a search.
		 */
		isValid: bool,
		/**
		 * Disables the SearchField by greying it out.
		 */
		isDisabled: bool,
		/**
		 * placeholder value
		 */
		placeholder: string,
		/**
		* Appended to the component-specific class names set on the root
		* element.
		*/
		className: string,
		/**
		 * Icon this is displayed on the right side of the SearchField. Any of the
		 * lucid `*Icon` components should work.
		 */
		Icon: node,
	},
 
	getDefaultProps() {
		return {
			isDisabled: false,
			onChange: _.noop,
			onChangeDebounced: _.noop,
			debounceLevel: 500,
			onSubmit: _.noop,
			value: '',
		};
	},
 
	render() {
		const {
			props,
			props: {
				className,
				isDisabled,
				isValid,
				onChange,
				onChangeDebounced,
				debounceLevel,
				onSubmit,
				placeholder,
				value,
				...passThroughs
			},
		} = this;
 
		const { Icon, TextField } = SearchField;
 
		const textFieldProps = {
			isDisabled,
			onChange,
			onChangeDebounced,
			debounceLevel,
			onSubmit,
			placeholder,
			isMultiLine: false,
			value,
		};
 
		const textFieldElement = getFirst(
			props,
			TextField,
			<TextField {...textFieldProps} />
		);
		const isIconActive = _.isUndefined(isValid)
			? !_.isEmpty(_.get(textFieldElement, 'props.value'))
			: isValid;
		const defaultIcon = (
			<SearchIcon className={cx('&-Icon', { '&-Icon-active': isIconActive })} />
		);
		const iconElement = getFirst(props, Icon);
		const iconChildren = _.get(iconElement, 'props.children');
		const icon = iconChildren
			? createElement(iconChildren.type, {
					...iconChildren.props,
					className: cx(
						'&-Icon',
						{ '&-Icon-active': isIconActive },
						iconChildren.props.className
					),
				})
			: defaultIcon;
 
		return (
			<div
				{...omitProps(passThroughs, SearchField)}
				className={cx('&', className)}
			>
				{textFieldElement}
				<div className={cx('&-Icon-container')}>
					{icon}
				</div>
			</div>
		);
	},
});
 
export default buildHybridComponent(SearchField);
export { SearchField as SearchFieldDumb };