elements_Checkbox_index.js

import { React, classnames, PropTypes } from '@gravityforms/libraries';
import { useStateWithDep } from '@gravityforms/react-utils';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import Label from '../Label';
import HelpText from '../HelpText';

const { forwardRef, useEffect, useRef, useState } = React;

/**
 * @module Checkbox
 * @description A checkbox input component.
 *
 * @since 1.1.15
 *
 * @param {object}                     props                    Component props.
 * @param {object}                     props.customAttributes   Custom attributes for the component.
 * @param {string|Array|object}        props.customClasses      Custom classes for the component.
 * @param {boolean}                    props.disabled           If checkbox is disabled.
 * @param {boolean}                    props.externalChecked    If checkbox is checked or not, can change current checked state if changed.
 * @param {boolean}                    props.externalControl    If checkbox is controlled externally.
 * @param {object}                     props.helpTextAttributes Custom attribute for the help text.
 * @param {string}                     props.id                 Optional id. Auto generated if not passed.
 * @param {boolean}                    props.indeterminate      If checkbox is indeterminate, only works if externally controlled.
 * @param {boolean}                    props.initialChecked     Whether it is checked on initial render.
 * @param {object}                     props.labelAttributes    Custom attributes for the label.
 * @param {string}                     props.name               The name attribute for the checkbox.
 * @param {Function}                   props.onBlur             On blur function handler.
 * @param {Function}                   props.onChange           On change function handler.
 * @param {Function}                   props.onFocus            On focus function handler.
 * @param {string}                     props.size               The checkbox size, small (`size-sm`) or medium (`size-md`).
 * @param {string|number|Array|object} props.spacing            The spacing for the component, as a string, number, array, or object.
 * @param {string}                     props.theme              The theme of the checkbox.
 * @param {string}                     props.value              The checkbox value.
 * @param {object}                     props.wrapperAttributes  Custom attributes for the wrapper element.
 * @param {string|Array|object}        props.wrapperClasses     Custom classes for the wrapper element.
 * @param {string}                     props.wrapperTagName     Tag to use for the checkbox wrapper. Defaults to `div`.
 * @param {object|null}                ref                      Ref to the component.
 *
 * @return {JSX.Element} The checkbox component.
 *
 * @example
 * import Checkbox from '@gravityforms/components/react/admin/elements/Checkbox';
 *
 * return (
 *     <Checkbox />
 *         checked={ true }
 *         labelAttributes={ {
 *             label: 'Check me!',
 *         } }
 *         id="checkbox"
 *         onChange={ () => {} }
 *     />
 * );
 *
 */
const Checkbox = forwardRef( ( {
	customAttributes = {},
	customClasses = [],
	disabled = false,
	externalChecked = false,
	externalControl = false,
	helpTextAttributes = {},
	id = '',
	indeterminate = false,
	initialChecked = false,
	labelAttributes = {},
	name = '',
	onBlur = () => {},
	onChange = () => {},
	onFocus = () => {},
	size = 'size-sm',
	spacing = '',
	theme = 'cosmos',
	value = '',
	wrapperAttributes = {},
	wrapperClasses = [],
	wrapperTagName = 'div',
}, ref ) => {
	const [ inputChecked, setInputChecked ] = useState( initialChecked );
	const [ controlChecked, setControlChecked ] = useStateWithDep( externalChecked ); // eslint-disable-line no-unused-vars
	const inputRef = useRef();
	const inputId = id || uniqueId( 'checkbox' );
	const helpTextId = `${ inputId }-help-text`;

	useEffect( () => {
		if ( ! inputRef.current ) {
			return;
		}

		// Do not set indeterminate prop if not controlled externally or if the checkbox is checked.
		if ( ! externalControl || controlChecked ) {
			return;
		}

		inputRef.current.indeterminate = indeterminate;
	}, [ indeterminate, inputRef, controlChecked ] );

	const wrapperProps = {
		...wrapperAttributes,
		className: classnames( {
			'gform-input-wrapper': true,
			[ `gform-input-wrapper--theme-${ theme }` ]: true,
			'gform-input-wrapper--checkbox': true,
			'gform-input-wrapper--disabled': disabled,
			...spacerClasses( spacing ),
		}, wrapperClasses ),
		ref,
	};

	const inputProps = {
		...customAttributes,
		checked: externalControl ? controlChecked : inputChecked,
		className: classnames( {
			'gform-input--checkbox': true,
			[ `gform-input--${ size }` ]: true,
		}, customClasses ),
		disabled: disabled || labelAttributes?.locked === true,
		id: inputId,
		name,
		onBlur,
		onChange: ( e ) => {
			const { checked: inputCheckedState } = e.target;
			if ( ! externalControl ) {
				setInputChecked( inputCheckedState );
			}
			onChange( inputCheckedState, e );
		},
		onFocus,
		ref: inputRef,
		type: 'checkbox',
		value,
	};

	if ( helpTextAttributes.content ) {
		inputProps[ 'aria-describedby' ] = helpTextId;
	}

	const labelProps = {
		...labelAttributes,
		htmlFor: inputId,
	};

	const helpTextProps = {
		...helpTextAttributes,
		id: helpTextId,
	};

	const Container = wrapperTagName;

	return (
		<Container { ...wrapperProps }>
			<input { ...inputProps } />
			<Label { ...labelProps } />
			<HelpText { ...helpTextProps } />
		</Container>
	);
} );

Checkbox.propTypes = {
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	disabled: PropTypes.bool,
	externalChecked: PropTypes.bool,
	externalControl: PropTypes.bool,
	helpTextAttributes: PropTypes.object,
	id: PropTypes.string,
	indeterminate: PropTypes.bool,
	initialChecked: PropTypes.bool,
	labelAttributes: PropTypes.object,
	name: PropTypes.string,
	onBlur: PropTypes.func,
	onChange: PropTypes.func,
	onFocus: PropTypes.func,
	size: PropTypes.string,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	theme: PropTypes.string,
	value: PropTypes.string,
	wrapperAttributes: PropTypes.object,
	wrapperClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	wrapperTagName: PropTypes.string,
};

Checkbox.displayName = 'Checkbox';

export default Checkbox;