import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { ConditionalWrapper, useStateWithDep } from '@gravityforms/react-utils';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import HelpText from '../HelpText';
import Label from '../Label';
const { useState, forwardRef } = React;
/**
* @module Toggle
* @description A toggle component to use wherever boolean based form fields are needed.
*
* @since 1.1.15
*
* @param {object} props Component props.
* @param {string} props.ariaLabel The aria-label text for the toggle.
* @param {JSX.Element} props.children React element children.
* @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 toggle is disabled.
* @param {boolean} props.externalChecked If toggle is checked or not, can change current checked state if changed.
* @param {boolean} props.externalControl If toggle can be controlled externally.
* @param {object} props.helpTextAttributes Attributes for the help text component.
* @param {string} props.helpTextWidth Width of the help text, one of `auto` or `full`.
* @param {boolean} props.icons Do we use the checkmark and x icons for this instance.
* @param {string} props.iconPrefix The icon prefix to use for the icons.
* @param {string} props.iconBefore The icon to use when the toggle is unchecked.
* @param {string} props.iconAfter The icon to use when the toggle is checked.
* @param {string} props.id Id for the toggle, auto generated if not passed.
* @param {boolean} props.initialChecked Is it checked on render?
* @param {object} props.labelAttributes Attributes for the label component.
* @param {string} props.labelPosition Position of the label, one of `left` or `right`.
* @param {string} props.name Name for the input.
* @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 Size for the toggle. Small (`size-s`), medium (`size-m`), or large (`size-l`).
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {string} props.theme Theme for the toggle, one of `primary` or `cosmos`.
* @param {string} props.width Width for the toggle, one of `auto` or `full`.
* @param {object} props.wrapperAttributes Custom attributes for the wrapper element.
* @param {string|Array|object} props.wrapperClasses Custom classes for the wrapper element.
* @param {object} props.wrapperTagName Tag to use for the toggle wrapper. Defaults to `div`.
* @param {object|null} ref Ref to the component.
*
* @return {JSX.Element} The toggle component.
*
* @example
* import Toggle from '@gravityforms/components/react/admin/elements/Toggle';
*
* return <Toggle disabled={ true } initialChecked={ true } onChange={ () => {} } />;
*
*/
const Toggle = forwardRef( ( {
ariaLabel = '',
children = null,
customAttributes = {},
customClasses = [],
disabled = false,
externalChecked = false,
externalControl = false,
helpTextAttributes = {},
helpTextWidth = 'auto',
icons = true,
id = '',
initialChecked = false,
labelAttributes = {},
labelPosition = 'right',
name = '',
onBlur = () => {},
onChange = () => {},
onFocus = () => {},
size = 'size-s',
spacing = '',
theme = 'cosmos',
width = 'auto',
wrapperAttributes = {},
wrapperClasses = [],
wrapperTagName = 'div',
}, ref ) => {
const [ checked, setChecked ] = useState( initialChecked );
const [ controlChecked, setControlChecked ] = useStateWithDep( externalChecked ); // eslint-disable-line no-unused-vars
const inputId = id || uniqueId( 'toggle' );
const helpTextId = `${ inputId }-help-text`;
const wrapperProps = {
...wrapperAttributes,
className: classnames( {
'gform-toggle': true,
[ `gform-toggle--theme-${ theme }` ]: true,
[ `gform-toggle--${ size }` ]: true,
[ `gform-toggle--label-${ labelPosition }` ]: true,
[ `gform-toggle--width-${ width }` ]: true,
[ `gform-toggle--help-text-width-${ helpTextWidth }` ]: helpTextAttributes.content,
'gform-toggle--disabled': disabled,
...spacerClasses( spacing ),
}, wrapperClasses ),
ref,
};
const inputProps = {
...customAttributes,
checked: externalControl ? controlChecked : checked,
className: classnames( {
'gform-toggle__toggle': true,
'gform-toggle__toggle--has-icons': icons,
}, customClasses ),
disabled: disabled || labelAttributes?.locked === true,
id: inputId,
name,
onBlur,
onChange: ( event ) => {
const { checked: toggleChecked } = event.target;
if ( ! externalControl ) {
setChecked( toggleChecked );
}
setTimeout( () => {
onChange( toggleChecked );
}, 150 );
},
onFocus,
type: 'checkbox',
};
if ( ariaLabel ) {
inputProps[ 'aria-label' ] = ariaLabel;
}
if ( helpTextAttributes.content ) {
inputProps[ 'aria-describedby' ] = helpTextId;
}
if ( ! helpTextAttributes.spacing ) {
helpTextAttributes.spacing = [ 4, 0, 0, 0 ];
}
const labelProps = {
...labelAttributes,
customClasses: classnames( [
'gform-toggle__label',
], labelAttributes.customClasses ),
htmlFor: inputId,
};
const helpTextProps = {
...helpTextAttributes,
id: helpTextId,
};
const Container = wrapperTagName;
return (
<Container { ...wrapperProps }>
<input { ...inputProps } />
<ConditionalWrapper
condition={ helpTextAttributes.content && labelAttributes.label }
wrapper={ ( ch ) => <div className="gform-toggle__label-wrapper">{ ch }</div> }
>
<Label { ...labelProps } />
{ helpTextAttributes.content && <HelpText { ...helpTextProps } /> }
{ children }
</ConditionalWrapper>
</Container>
);
} );
Toggle.propTypes = {
ariaLabel: PropTypes.string,
children: PropTypes.oneOfType( [
PropTypes.arrayOf( PropTypes.node ),
PropTypes.node,
] ),
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
disabled: PropTypes.bool,
externalChecked: PropTypes.bool,
externalControl: PropTypes.bool,
helpTextAttributes: PropTypes.object,
helpTextWidth: PropTypes.oneOf( [ 'auto', 'full' ] ),
icons: PropTypes.bool,
id: PropTypes.string,
initialChecked: PropTypes.bool,
labelAttributes: PropTypes.object,
labelPosition: PropTypes.string,
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,
width: PropTypes.oneOf( [ 'auto', 'full' ] ),
wrapperAttributes: PropTypes.object,
wrapperClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
wrapperTagName: PropTypes.string,
};
Toggle.displayName = 'Toggle';
export default Toggle;