@react-ui-org/react-ui
Version:
React UI is a themeable UI library for React apps.
200 lines (191 loc) • 5.43 kB
JSX
import PropTypes from 'prop-types';
import React, {
useContext,
useMemo,
} 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 { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
import { FormLayoutContext } from '../FormLayout';
import { Text } from '../Text';
import { InputGroupContext } from './InputGroupContext';
import styles from './InputGroup.module.scss';
export const InputGroup = ({
children,
disabled,
helpTexts,
id,
isLabelVisible,
label,
layout,
required,
size,
validationTexts,
...restProps
}) => {
const formLayoutContext = useContext(FormLayoutContext);
const inputGroupContextValue = useMemo(() => ({
disabled,
layout,
size,
}), [disabled, layout, size]);
if (isChildrenEmpty(children)) {
return null;
}
const validationState = children.reduce(
(state, child) => {
if (state === 'invalid' || (state === 'warning' && child.props.validationState === 'valid')) {
return state;
}
return child.props.validationState ?? state;
},
null,
);
return (
<fieldset
{...transferProps(restProps)}
className={classNames(
styles.root,
formLayoutContext && styles.isRootInFormLayout,
resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
? styles.isRootLayoutHorizontal
: styles.isRootLayoutVertical,
disabled && styles.isRootDisabled,
required && styles.isRootRequired,
getRootSizeClassName(size, styles),
getRootValidationStateClassName(validationState, styles),
)}
disabled={disabled}
id={id}
>
<legend
className={styles.legend}
id={id && `${id}__label`}
>
{label}
</legend>
{isLabelVisible && (
<div
aria-hidden
className={styles.label}
id={id && `${id}__displayLabel`}
>
{label}
</div>
)}
<div className={styles.field}>
<div
className={styles.inputGroup}
id={id && `${id}__group`}
>
<InputGroupContext.Provider value={inputGroupContextValue}>
{children}
</InputGroupContext.Provider>
</div>
{helpTexts && helpTexts.length > 0 && (
<ul
className={styles.helpText}
id={id && `${id}__helpTexts`}
>
{helpTexts.map((helpText) => (
<li key={helpText}>
<Text blockLevel>
{helpText}
</Text>
</li>
))}
</ul>
)}
{validationTexts && validationTexts.length > 0 && (
<ul
className={styles.validationText}
id={id && `${id}__validationTexts`}
>
{validationTexts.map((validationText) => (
<li key={validationText}>
<Text blockLevel>
{validationText}
</Text>
</li>
))}
</ul>
)}
</div>
</fieldset>
);
};
InputGroup.defaultProps = {
children: undefined,
disabled: false,
helpTexts: undefined,
id: undefined,
isLabelVisible: true,
layout: 'vertical',
required: false,
size: 'medium',
validationTexts: undefined,
};
InputGroup.propTypes = {
/**
* Supported elements to be grouped:
* * `Button`
* * `SelectField`
* * `TextField`
*
* If none are provided nothing is rendered.
*/
children: PropTypes.node,
/**
* If `true`, the whole input group with all nested inputs and buttons will be disabled.
*/
disabled: PropTypes.bool,
/**
* An array of help texts to be displayed.
*/
helpTexts: PropTypes.arrayOf(PropTypes.node),
/**
* ID of the root HTML element.
*
* Also serves as base for ids of nested elements:
* * `<ID>__label`
* * `<ID>__displayLabel`
* * `<ID>__group`
* * `<ID>__validationTexts`
*/
id: PropTypes.string,
/**
* If `false`, the label will be visually hidden (but remains accessible by assistive
* technologies).
*/
isLabelVisible: PropTypes.bool,
/**
* Input group label.
*/
label: PropTypes.node.isRequired,
/**
* Layout of the group.
*
* 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 `InputGroup`'s label appears as required. Underlying `<fieldset>`
* element does not take `required` attribute so there is no functional effect.
*/
required: PropTypes.bool,
/**
* Size of the `children` elements.
*/
size: PropTypes.oneOf(['small', 'medium', 'large']),
/**
* An array of validation messages to be displayed.
*/
validationTexts: PropTypes.arrayOf(PropTypes.node),
};
export const InputGroupWithGlobalProps = withGlobalProps(InputGroup, 'InputGroup');
export default InputGroupWithGlobalProps;