lucid-ui
Version:
A UI component library from Xandr.
228 lines • 8.05 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { lucidClassNames } from '../../util/style-helpers';
import reducers from './TextField.reducers';
import * as KEYCODE from '../../constants/key-code';
const cx = lucidClassNames.bind('&-TextField');
const { bool, string, func, number, object, oneOfType } = PropTypes;
/** TODO: Remove the nonPassThroughs when the component is converted to a functional component */
const nonPassThroughs = [
'style',
'isMultiLine',
'isDisabled',
'rows',
'className',
'onChange',
'onBlur',
'onChangeDebounced',
'onKeyDown',
'onSubmit',
'value',
'debounceLevel',
'lazyLevel',
'initialState',
'callbackId',
'children',
];
class TextField extends React.Component {
constructor() {
super(...arguments);
this.state = {
value: this.props.value,
isHolding: false,
isMounted: false,
};
this.textareaElement = React.createRef();
this.inputElement = React.createRef();
this.nativeElement = this.props.isMultiLine
? this.textareaElement
: this.inputElement;
this.handleChangeDebounced = _.debounce((value, { event, props, }) => {
this.props.onChangeDebounced &&
this.props.onChangeDebounced(value, { event, props });
}, this.props.debounceLevel);
this.releaseHold = _.debounce(() => {
if (!this.state.isMounted) {
return;
}
this.setState({ isHolding: false });
}, this.props.lazyLevel);
this.updateWhenReady = _.debounce((newValue) => {
if (!this.state.isMounted) {
return;
}
if (this.state.isHolding) {
this.updateWhenReady(newValue);
}
else if (newValue !== this.state.value) {
this.setState({ value: newValue });
}
}, this.props.lazyLevel);
this.handleChange = (event) => {
const { onChange, onChangeDebounced } = this.props;
const value = _.get(event, 'target.value', '');
this.setState({ value, isHolding: true });
this.releaseHold();
onChange && onChange(value, { event, props: this.props });
// Also call the debounced handler in case the user wants debounced change
// events.
if (onChangeDebounced !== _.noop) {
event.persist(); // https://facebook.github.io/react/docs/events.html#event-pooling
this.handleChangeDebounced(value, { event, props: this.props });
}
};
this.handleBlur = (event) => {
const { onBlur, onChangeDebounced } = this.props;
const value = _.get(event, 'target.value', '');
if (onChangeDebounced !== _.noop) {
this.handleChangeDebounced.flush();
}
onBlur && onBlur(value, { event, props: this.props });
};
this.handleKeyDown = (event) => {
const { props, props: { onSubmit, onKeyDown, onChangeDebounced }, } = this;
const value = _.get(event, 'target.value', '');
// If the consumer passed an onKeyDown, we call it
if (onKeyDown) {
onKeyDown({ event, props });
}
if (event.keyCode === KEYCODE.Enter) {
if (onChangeDebounced !== _.noop) {
this.handleChangeDebounced.flush();
}
onSubmit && onSubmit(value, { event, props: this.props });
}
};
this.focus = (options) => {
/* istanbul ignore next */
this.nativeElement.current.focus(options);
};
}
UNSAFE_componentWillMount() {
this.setState({ isMounted: true });
}
componentWillUnmount() {
this.setState({ isMounted: false });
}
UNSAFE_componentWillReceiveProps(nextProps) {
// Allow consumer to optionally control state
if (_.has(nextProps, 'value')) {
if (this.state.isHolding) {
this.updateWhenReady(nextProps.value);
}
else {
this.setState({ value: nextProps.value });
}
}
}
render() {
const { className, isDisabled, isMultiLine, rows, style, ...passThroughs } = this.props;
const { value } = this.state;
const finalProps = {
..._.omit(passThroughs, nonPassThroughs),
className: cx('&', {
'&-is-disabled': isDisabled,
'&-is-multi-line': isMultiLine,
'&-is-single-line': !isMultiLine,
}, className),
disabled: isDisabled,
onChange: this.handleChange,
onBlur: this.handleBlur,
onKeyDown: this.handleKeyDown,
style,
rows,
value,
};
return isMultiLine ? (React.createElement("textarea", { ref: this.textareaElement, ...finalProps })) : (React.createElement("input", { type: 'text', ref: this.inputElement, ...finalProps }));
}
}
TextField.displayName = 'TextField';
TextField.peek = {
description: `\`TextField\` should cover all your text input needs. It is able to handle single- and multi-line inputs.`,
categories: ['controls', 'text'],
};
TextField.propTypes = {
/**
Styles that are passed through to native control.
*/
style: object,
/**
Set the TextField to multi line mode. Under the hood this will use a
\`textarea\` instead of an \`input\` if set to \`true\`.
*/
isMultiLine: bool,
/**
Disables the TextField by greying it out.
*/
isDisabled: bool,
/**
Initial number of rows a multi line TextField should have. Ignored when
not in multi-line mode.
*/
rows: number,
/**
Class names that are appended to the defaults.
*/
className: string,
/**
Fires an event every time the user types text into the TextField.
Signature: \`(value, { event, props }) => {}\`
*/
onChange: func,
/**
Fires an on the \`input\`'s onBlur. Signature:
\`(currentValue, { event, props }) => {}\`
*/
onBlur: func,
/**
Fires an event, debounced by \`debounceLevel\`, when the user types text
into the TextField. Signature: \`(value, { event, props }) => {}\`
*/
onChangeDebounced: func,
/**
Fires an event on every keydown Signature: \`({ event, props }) => {}\`
*/
onKeyDown: func,
/**
Fires an event when the user hits "enter" from the TextField. You
shouldn't use it if you're using \`isMultiLine\`. Signature:
\`(value, { event, props }) => {}\`
*/
onSubmit: func,
/**
Set the value of the input.
*/
value: oneOfType([number, string]),
/**
Number of milliseconds to debounce the \`onChangeDebounced\` callback.
Only useful if you provide an \`onChangeDebounced\` handler.
*/
debounceLevel: number,
/**
Set the holding time, in milliseconds, that the component will wait if
the user is typing and the component gets a new \`value\` prop. Any time
the user hits a key, it starts a timer that prevents state changes from
flowing in to the component until the timer has elapsed. This was
heavily inspired by the
[lazy-input](https:/docs.npmjs.com/package/lazy-input) component.
*/
lazyLevel: number,
};
TextField.defaultProps = {
style: undefined,
isDisabled: false,
isMultiLine: false,
onBlur: _.noop,
onChange: _.noop,
onChangeDebounced: _.noop,
onSubmit: _.noop,
onKeyDown: _.noop,
rows: 5,
debounceLevel: 500,
lazyLevel: 1000,
value: '',
};
TextField.reducers = reducers;
export default TextField;
//# sourceMappingURL=TextField.js.map