UNPKG

@react-ui-org/react-ui

Version:

React UI is a themeable UI library for React apps.

204 lines (196 loc) 6.4 kB
import PropTypes from 'prop-types'; import React, { useContext } from 'react'; import { withGlobalProps } from '../../providers/globalProps'; import { classNames } from '../../helpers/classNames/classNames'; import { transferProps } from '../../helpers/transferProps'; import { getRootSizeClassName } from '../_helpers/getRootSizeClassName'; import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName'; import { resolveContextOrProp } from '../_helpers/resolveContextOrProp'; import { FormLayoutContext } from '../FormLayout'; import { InputGroupContext } from '../InputGroup/InputGroupContext'; import styles from './TextField.module.scss'; const SMALL_INPUT_SIZE = 10; export const TextField = React.forwardRef((props, ref) => { const { disabled, fullWidth, helpText, id, inputSize, isLabelVisible, label, layout, required, size, type, validationState, validationText, variant, ...restProps } = props; const formLayoutContext = useContext(FormLayoutContext); const inputGroupContext = useContext(InputGroupContext); const hasSmallInput = (inputSize !== undefined) && (inputSize <= SMALL_INPUT_SIZE); return ( <label className={classNames( styles.root, fullWidth && styles.isRootFullWidth, hasSmallInput && styles.hasRootSmallInput, inputSize && styles.hasRootCustomInputSize, formLayoutContext && styles.isRootInFormLayout, resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled) && styles.isRootDisabled, resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal' ? styles.isRootLayoutHorizontal : styles.isRootLayoutVertical, inputGroupContext && styles.isRootGrouped, required && styles.isRootRequired, getRootSizeClassName( resolveContextOrProp(inputGroupContext && inputGroupContext.size, size), styles, ), getRootValidationStateClassName(validationState, styles), variant === 'filled' ? styles.isRootVariantFilled : styles.isRootVariantOutline, )} htmlFor={id} id={id && `${id}__label`} {...(inputSize ? { style: { '--rui-custom-input-size': inputSize } } : {})} > <div className={classNames( styles.label, (!isLabelVisible || inputGroupContext) && styles.isLabelHidden, )} id={id && `${id}__labelText`} > {label} </div> <div className={styles.field}> <div className={styles.inputContainer}> <input {...transferProps(restProps)} className={styles.input} disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)} id={id} ref={ref} required={required} size={type !== 'number' ? inputSize : null} type={type} /> {variant === 'filled' && ( <div className={styles.bottomLine} /> )} </div> {(helpText && !inputGroupContext) && ( <div className={styles.helpText} id={id && `${id}__helpText`} > {helpText} </div> )} {(validationText && !inputGroupContext) && ( <div className={styles.validationText} id={id && `${id}__validationText`} > {validationText} </div> )} </div> </label> ); }); TextField.defaultProps = { disabled: false, fullWidth: false, helpText: undefined, id: undefined, inputSize: undefined, isLabelVisible: true, layout: 'vertical', required: false, size: 'medium', type: 'text', validationState: undefined, validationText: undefined, variant: 'outline', }; TextField.propTypes = { /** * If `true`, the input will be disabled. */ disabled: PropTypes.bool, /** * If `true`, the field will span the full width of its parent. */ fullWidth: PropTypes.bool, /** * Optional help text. * * Help text is never rendered when the component is placed into `InputGroup`. * If a help text is needed, it must be defined on the `InputGroup` component instead. */ helpText: PropTypes.node, /** * ID of the input HTML element. It also serves as a prefix for nested elements: * * `<ID>__label` * * `<ID>__labelText` * * `<ID>__helpText` * * `<ID>__validationText` */ id: PropTypes.string, /** * Width of the input field. Translated as `size` attribute for input types other than `number`. */ inputSize: PropTypes.number, /** * If `false`, the label will be visually hidden (but remains accessible by assistive * technologies). * * Automatically set to `false` when the component is rendered within `InputGroup` component. */ isLabelVisible: PropTypes.bool, /** * Text field label. */ label: PropTypes.node.isRequired, /** * Layout of the field. * * Ignored if the component is rendered within `FormLayout` component * as the value is inherited in such case. */ layout: PropTypes.oneOf(['horizontal', 'vertical']), /** * If `true`, the input will be required. */ required: PropTypes.bool, /** * Size of the field. * * Ignored if the component is rendered within `InputGroup` component as the value is inherited in such case. */ size: PropTypes.oneOf(['small', 'medium', 'large']), /** * HTML input type, translated as `type` attribute of the input. */ type: PropTypes.oneOf(['email', 'number', 'password', 'tel', 'text']), /** * Alter the field to provide feedback based on validation result. */ validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']), /** * Validation message to be displayed. * * Validation text is never rendered when the component is placed into `InputGroup`. * If a validation text is needed, it must be defined on the `InputGroup` component instead. */ validationText: PropTypes.node, /** * Design variant of the field, further customizable with CSS custom properties. */ variant: PropTypes.oneOf(['filled', 'outline']), }; export const TextFieldWithGlobalProps = withGlobalProps(TextField, 'TextField'); export default TextFieldWithGlobalProps;