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;