diff --git a/src/Form/Input.js b/src/Form/Input.js index 67008ca..5eba758 100644 --- a/src/Form/Input.js +++ b/src/Form/Input.js @@ -52,6 +52,15 @@ const StyledInput = createComponent({ const StyledTextArea = StyledInput.withComponent('textarea'); +const AutogrowShadow = createComponent({ + name: 'AutogrowShadow', + tag: 'textarea', + style: css` + position: absolute; + left: -9999px; + `, +}); + const validateValueProp = (props, propName, componentName) => { if (props.type === 'number' && typeof props[propName] !== 'number') { return new Error(`Invalid prop ${propName} supplied to ${componentName} with type="number", expected Number`); @@ -77,6 +86,7 @@ export default class Input extends Component { rows: PropTypes.number, maxRows: PropTypes.number, rowHeight: PropTypes.number, + lineHeight: PropTypes.number, autogrow: PropTypes.bool, size: PropTypes.string, floating: PropTypes.bool, @@ -89,7 +99,8 @@ export default class Input extends Component { minRows: 2, rows: 3, maxRows: 6, - rowHeight: 14 * 1.5, + rowHeight: 14, + lineHeight: 1.5, autogrow: false, disabled: false, size: 'md', @@ -99,8 +110,16 @@ export default class Input extends Component { floating: false, }; + static getDerivedStateFromProps(props, state) { + if (props.value !== undefined && props.value !== state.value) { + return { + value: props.value, + }; + } + return null; + } + state = { - value: this.props.value || '', focused: false, }; @@ -111,33 +130,27 @@ export default class Input extends Component { } componentDidMount() { - if (this.props.autogrow) { - this.createShadowElement(); - this.autogrow(); - } - - if (this.props.autofocus) { - if (this.ref.current) { - this.ref.current.focus(); - } + if (this.props.autofocus && this.ref.current) { + this.ref.current.focus(); } - } - componentWillReceiveProps(props) { - if (props.value !== this.props.value) { - this.setState({ value: props.value }); + if (this.props.multiline) { + /* eslint-disable-next-line react/no-did-mount-set-state */ + this.setState({ + height: this.props.rows * this.props.rowHeight * this.props.lineHeight, + }); } } - componentDidUpdate(prevProps) { - if (prevProps.value !== this.props.value) { + componentDidUpdate(oldProps, oldState) { + if (oldState.value !== this.state.value) { this.autogrow(); } } componentWillUnmount() { - if (this.shadow) { - this.shadow.remove(); + if (this.autogrowShadowNode && this.autogrowShadowNode.parentNode) { + this.autogrowShadowNode.parentNode.removeChild(this.autogrowShadowNode); } } @@ -163,30 +176,30 @@ export default class Input extends Component { }; onChange = e => { - this.autogrow(); this.setState({ value: e.target.value }); this.props.onChange(e.target.name, e.target.value); }; - createShadowElement() { - this.shadow = document.createElement('textarea'); - this.shadow.style.position = 'absolute'; - this.shadow.style.left = '-9000px'; - document.body.appendChild(this.shadow); - } + handleAutogrowRef = node => { + this.autogrowShadowNode = node; + this.autogrow(); + }; autogrow() { - const { multiline, autogrow, minRows, maxRows, rowHeight } = this.props; - if (!multiline || !autogrow) { + if (!this.props.autogrow || !this.autogrowShadowNode) { return; } - const minHeight = minRows * rowHeight; - const maxHeight = maxRows * rowHeight; + const { minRows, maxRows, rowHeight, lineHeight } = this.props; + + const minHeight = minRows * rowHeight * lineHeight; + const maxHeight = maxRows * rowHeight * lineHeight; + + this.autogrowShadowNode.style.width = `${this.ref.current.clientWidth}px`; + this.autogrowShadowNode.value = this.state.value; + + let height = this.autogrowShadowNode.scrollHeight + rowHeight * lineHeight; - this.shadow.style.width = `${this.ref.current.clientWidth}px`; - this.shadow.value = this.props.value || this.state.value; - let height = this.shadow.scrollHeight + 14; if (height < minHeight) { height = minHeight; } else if (height > maxHeight) { @@ -235,7 +248,7 @@ export default class Input extends Component { onChange: this.onChange, onFocus: this.onFocus, onBlur: this.onBlur, - style: autogrow ? { ...style, height: this.state.height } : style, + style: multiline ? { ...style, height: this.state.height } : style, placeholder, isFloatable: floating, isFloating, @@ -257,6 +270,8 @@ export default class Input extends Component { {multiline ? : } + {autogrow && } + {!this.state.focused && error ? {error} : null} ); diff --git a/src/Form/Input.mdx b/src/Form/Input.mdx index 84ffcb4..97d9d5f 100644 --- a/src/Form/Input.mdx +++ b/src/Form/Input.mdx @@ -24,12 +24,12 @@ Form input component. Different sizes are available. ### Multiline Input - + ### Autogrow Multiline Input - + ### Floating Input