@react-ui-org/react-ui
Version:
React UI is a themeable UI library for React apps.
201 lines (193 loc) • 6.18 kB
JSX
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 !== null) && (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 && (
<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: null,
id: undefined,
inputSize: null,
isLabelVisible: true,
layout: 'vertical',
required: false,
size: 'medium',
type: 'text',
validationState: null,
validationText: null,
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.
*/
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`. Instead, the `InputGroup`
* component itself renders all validation texts of its nested components.
*/
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;