modules_InputGroup_index.js

import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { ConditionalWrapper } from '@gravityforms/react-utils';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import Checkbox from '../../elements/Checkbox';
import Radio from '../../elements/Radio';

const { forwardRef, useState } = React;

/**
 * @module InputGroup
 * @description A inputgroup component that wraps checkboxes or radios and handles their state changes as one
 * aggregate.
 *
 * @since 1.1.15
 *
 * @param {object}                     props                   Component props.
 * @param {JSX.Element}                props.children          React element children.
 * @param {boolean}                    props.childrenBelow     Render children below or above group if found?
 * @param {object}                     props.customAttributes  Custom attributes for the component.
 * @param {string|Array|object}        props.customClasses     Custom classes for the component.
 * @param {Array}                      props.data              The data for the inputs.
 * @param {string}                     props.id                The id of the input group.
 * @param {string}                     props.initialValue      The initial value of the input group if input type is radio.
 * @param {string}                     props.inputType         The input type, one of `radio` or `checkbox`.
 * @param {Function}                   props.onChange          On change function handler.
 * @param {string|number|Array|object} props.spacing           The spacing for the component, as a string, number, array, or object.
 * @param {string}                     props.tagName           Tagname for the container.
 * @param {boolean}                    props.useWrapper        Whether to use a wrapper around the inputs or not.
 * @param {object}                     props.wrapperAttributes Custom attributes for the wrapper component.
 * @param {string}                     props.wrapperTagName    Tagname for the wrapper.
 * @param {object|null}                ref                     Ref to the component.
 *
 * @return {JSX.Element} The InputGroup component.
 *
 * @example
 * import InputGroup from '@gravityforms/components/react/admin/modules/InputGroup';
 *
 * return (
 *      <InputGroup
 *          id="setup-wizard-form-types"
 *          data={ [ { ...input1Data }, { ...input2Data } ] }
 *          onChange={ () => { handleInputChange(); } }
 *          useWrapper={ true }
 *      />
 * );
 *
 */
const InputGroup = forwardRef( ( {
	children = null,
	childrenBelow = true,
	customAttributes = {},
	customClasses = [],
	data = [],
	id = '',
	initialValue = '',
	inputType = 'checkbox',
	onChange = () => {},
	spacing = '',
	tagName = 'div',
	useWrapper = false,
	wrapperAttributes = {},
	wrapperTagName = 'div',
}, ref ) => {
	const [ value, setValue ] = useState( initialValue );

	const componentProps = {
		className: classnames( {
			'gform-input-group': true,
			...spacerClasses( spacing ),
		}, customClasses ),
		...customAttributes,
	};

	const inputId = id || uniqueId( 'input-group' );
	const InputContainer = inputType === 'checkbox' ? Checkbox : Radio;

	const handleOnChange = ( checked, e ) => {
		if ( inputType === 'radio' ) {
			setValue( e.target.value );
		}
		onChange( checked, e );
	};

	const getWrapper = ( ch ) => {
		const Wrapper = wrapperTagName;
		const {
			customClasses: wrapperCustomClasses,
			...restWrapperAttributes
		} = wrapperAttributes;
		const wrapperProps = {
			...restWrapperAttributes,
			className: classnames(
				[ 'gform-input-group__wrapper' ],
				wrapperCustomClasses || [],
			),
		};
		return <Wrapper { ...wrapperProps }>{ ch }</Wrapper>;
	};

	const Container = tagName;

	return (
		<Container { ...componentProps } ref={ ref }>
			{ ! childrenBelow && children }
			{
				data.map( ( props, index ) => {
					const inputProps = {
						...props,
						onChange: handleOnChange,
					};

					if ( inputType === 'radio' ) {
						inputProps.externalControl = true;
						inputProps.externalChecked = props.value === value;
					}

					return (
						<ConditionalWrapper
							condition={ useWrapper }
							key={ `${ inputId }-${ index }` }
							wrapper={ getWrapper }
						>
							<InputContainer { ...inputProps } />
						</ConditionalWrapper>
					);
				} )
			}
			{ childrenBelow && children }
		</Container>
	);
} );

InputGroup.propTypes = {
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ),
	childrenBelow: PropTypes.bool,
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	data: PropTypes.array,
	id: PropTypes.string,
	inputType: PropTypes.string,
	onChange: PropTypes.func,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	tagName: PropTypes.string,
	useWrapper: PropTypes.bool,
	wrapperAttributes: PropTypes.object,
	wrapperTagName: PropTypes.string,
};

InputGroup.displayName = 'InputGroup';

export default InputGroup;