UNPKG

rmwc

Version:

A thin React wrapper for Material Design (Web) Components

276 lines (247 loc) 6.68 kB
// @flow import type { SimpleTagPropsT } from '../Base'; import type { IconPropsT } from '../Icon'; import * as React from 'react'; import { MDCTextField } from '@material/textfield/dist/mdc.textfield'; import { simpleTag, withFoundation, syncFoundationProp, randomId } from '../Base'; import { Icon } from '../Icon'; import { LineRipple } from '../LineRipple'; import { FloatingLabel } from '../FloatingLabel'; export type TextFieldPropsT = { /** Makes a multiline TextField. */ textarea?: boolean, /** Sets the value for controlled TextFields. */ value?: string | number, /** Makes the TextField fullwidth. */ fullwidth?: boolean, /** Makes the TextField have a visual box. */ box?: boolean, /** A ref for the native input. */ inputRef?: React.Ref<any>, /** Disables the input. */ disabled?: boolean, /** Mark the input as required. */ required?: boolean, /** Makes the TextField visually invalid. This is sometimes automatically applied in cases where required or pattern is used. */ invalid?: boolean, /** Makes the TextField dense */ dense?: boolean, /** Outline the TextField */ outlined?: boolean, /** A label for the input. */ label?: React.Node, /** Add a leading icon. */ withLeadingIcon?: React.Node, /** Add a trailing icon. */ withTrailingIcon?: React.Node, /** By default, props spread to the input. These props are for the component's root container. */ rootProps?: Object, /** An ID for the DOM element */ id?: string } & SimpleTagPropsT; export const TextFieldRoot = simpleTag({ displayName: 'TextFieldRoot', classNames: (props: TextFieldPropsT) => [ 'mdc-text-field', 'mdc-text-field--upgraded', { 'mdc-text-field--textarea': props.textarea, 'mdc-text-field--fullwidth': props.fullwidth, 'mdc-text-field--box': props.box, 'mdc-text-field--outlined': props.outlined, 'mdc-text-field--dense': props.dense, 'mdc-text-field--invalid': props.invalid, 'mdc-text-field--disabled': props.disabled, 'mdc-text-field--with-leading-icon': props.withLeadingIcon, 'mdc-text-field--with-trailing-icon': props.withTrailingIcon } ], consumeProps: [ 'textarea', 'box', 'fullwidth', 'outlined', 'dense', 'invalid', 'disabled', 'withLeadingIcon', 'withTrailingIcon' ] }); export const TextFieldInput = simpleTag({ displayName: 'TextFieldInput', tag: 'input', classNames: 'mdc-text-field__input', defaultProps: { type: 'text' } }); export const TextFieldTextarea = simpleTag({ displayName: 'TextFieldTextarea', tag: 'textarea', classNames: 'mdc-text-field__input' }); export const NotchedOutline = ({ children, ...rest }: { children: React.Node }) => ( <div {...rest} className="mdc-notched-outline"> <svg>{children}</svg> </div> ); export const NotchedOutlinePath = ({ ...rest }: {}) => ( <path {...rest} className="mdc-notched-outline__path" /> ); export const NotchedOutlineIdle = ({ ...rest }: {}) => ( <div {...rest} className="mdc-notched-outline__idle" /> ); export type TextFieldHelperTextPropsT = { /** Make the help text always visible */ persistent?: boolean, /** Make the help a validation message style */ validationMsg?: boolean }; /** * A help text component */ export class TextFieldHelperText extends simpleTag({ displayName: 'TextFieldHelperText', tag: 'p', classNames: (props: TextFieldHelperTextPropsT) => [ 'mdc-text-field-helper-text', { 'mdc-text-field-helper-text--persistent': props.persistent, 'mdc-text-field-helper-text--validation-msg': props.validationMsg } ], consumeProps: ['persistent', 'validationMsg'] })<TextFieldHelperTextPropsT> { render() { return super.render(); } } export type TextFieldIconPropsT = { /** The icon to use */ use: React.Node } & IconPropsT; /** * An Icon in a TextField */ export class TextFieldIcon extends simpleTag({ tag: Icon, classNames: 'mdc-text-field__icon' })<TextFieldIconPropsT> { static displayName = 'TextFieldIcon'; render() { return super.render(); } } export class TextField extends withFoundation({ constructor: MDCTextField, adapter: {} })<TextFieldPropsT> { static displayName = 'TextField'; valid: boolean; value: any; disabled: boolean; syncWithProps(nextProps: TextFieldPropsT) { // invalid | valid syncFoundationProp( nextProps.invalid, !this.valid, () => (this.valid = !nextProps.invalid) ); // value syncFoundationProp(nextProps.value, !this.value, () => { this.value = nextProps.value; }); // disabled syncFoundationProp( nextProps.disabled, this.disabled, () => (this.disabled = !!nextProps.disabled) ); } render() { const { label = '', className, inputRef, box, outlined, fullwidth, dense, invalid, disabled, withLeadingIcon, withTrailingIcon, children, textarea, rootProps = {}, apiRef, ...rest } = this.props; const { root_ } = this.foundationRefs; const tagProps = { ...rest, disabled: disabled, elementRef: inputRef, id: rest.id || randomId('text-field') }; const tag = textarea ? ( <TextFieldTextarea {...tagProps} /> ) : ( <TextFieldInput {...tagProps} /> ); // handle leading and trailing icons const renderIcon = iconNode => { if ( (iconNode && typeof iconNode === 'string') || //$FlowFixMe (iconNode.type && iconNode.type.displayName !== TextFieldIcon.displayName) ) { return <TextFieldIcon use={iconNode} />; } return iconNode; }; return ( <TextFieldRoot {...rootProps} invalid={invalid} withLeadingIcon={!!withLeadingIcon} withTrailingIcon={!!withTrailingIcon} textarea={textarea} box={box} dense={dense} disabled={disabled} outlined={outlined} fullwidth={fullwidth} elementRef={root_} className={className} > {!!withLeadingIcon && renderIcon(withLeadingIcon)} {children} {tag} {!!label && ( <FloatingLabel htmlFor={tagProps.id}>{label}</FloatingLabel> )} {!!withTrailingIcon && renderIcon(withTrailingIcon)} {outlined && ( <NotchedOutline> <NotchedOutlinePath /> </NotchedOutline> )} {outlined ? <NotchedOutlineIdle /> : <LineRipple />} </TextFieldRoot> ); } } export default TextField;