import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import { useStateWithDep, ConditionalWrapper } from '@gravityforms/react-utils';
import Button from '../Button';
import Label from '../Label';
import HelpText from '../HelpText';
import Icon from '../Icon';
const { useState, forwardRef } = React;
/**
* @module Input
* @description A multi type input component that supports standard text, email, tel.
*
* @since 1.1.15
*
* @param {object} props Component props.
* @param {string} props.borderStyle The border style, one of `default`, `error`, or `correct`.
* @param {boolean} props.clearable If input is clearable.
* @param {object} props.clearableButtonAttributes Custom attributes for the clearable button.
* @param {boolean} props.controlled Whether the input is controlled or not.
* @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 input is disabled.
* @param {object} props.helpTextAttributes Custom attribute for the help text.
* @param {string} props.helpTextPosition The position of the help text.
* @param {string} props.iconAttributes Custom attributes for the icon.
* @param {string} props.id Optional id. Auto generated if not passed.
* @param {object} props.labelAttributes Any custom attributes for the label.
* @param {string} props.name The name attribute for the input.
* @param {Function} props.onBlur On blur function handler.
* @param {Function} props.onChange On change function handler.
* @param {Function} props.onClear On clear function handler. When clearable true, this function is called when it occurs.
* @param {Function} props.onFocus On focus function handler.
* @param {string} props.placeholder The optional placeholder attribute for the input.
* @param {boolean} props.required Whether the field is required or not.
* @param {object} props.requiredLabel Required label properties.
* @param {string} props.size The input size: regular (`size-r`), large (`size-l`), or extra large (`size-xl`).
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {string} props.textSecurity The text security of the input, one of `circle`, `disc`, `square`, or `none`.
* @param {string} props.theme The theme of the input.
* @param {string} props.type The input type.
* @param {string} props.value The input value.
* @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 input wrapper. Defaults to `div`.
* @param {object|null} ref Ref to the component.
*
* @return {JSX.Element} The input component.
*
* @example
* import Input from '@gravityforms/components/react/admin/elements/Input';
*
* return (
* <Input
* onChange={ () => {} }
* placeholder="I am a placeholder"
* />
* );
*
*/
const Input = forwardRef( ( {
actionButtonAttributes = {
label: '',
},
borderStyle = 'default',
clearable = false,
clearableButtonAttributes = {},
controlled = false,
customAttributes = {},
customClasses = [],
disabled = false,
helpTextAttributes = {
size: 'text-xs',
weight: 'regular',
},
helpTextPosition = 'below',
iconAttributes = {},
id = '',
labelAttributes = {
size: 'text-sm',
weight: 'medium',
},
name = '',
onBlur = () => {},
onChange = () => {},
onClear = () => {},
onKeyDown = () => {},
onFocus = () => {},
placeholder = '',
required = false,
requiredLabel = {
size: 'text-sm',
weight: 'medium',
},
size = 'size-r',
spacing = '',
textSecurity = 'none',
theme = 'cosmos',
type = 'text',
value = '',
wrapperAttributes = {},
wrapperClasses = [],
wrapperTagName = 'div',
}, ref ) => {
const useStateFxn = controlled ? useStateWithDep : useState;
const [ inputValue, setInputValue ] = useStateFxn( value );
const inputId = id || uniqueId( 'input' );
const helpTextId = `${ inputId }-help-text`;
const withIcon = !! ( iconAttributes.icon || iconAttributes.preset );
const withAction = actionButtonAttributes.label && actionButtonAttributes.label.length > 0;
const wrapperProps = {
...wrapperAttributes,
className: classnames( {
'gform-input-wrapper': true,
[ `gform-input-wrapper--theme-${ theme }` ]: true,
'gform-input-wrapper--input': true,
'gform-input-wrapper--with-action': withAction,
'gform-input-wrapper--clearable': clearable,
'gform-input-wrapper--disabled': disabled,
...spacerClasses( spacing ),
'gform-input-wrapper--required': required,
[ `gform-input-wrapper--border-${ borderStyle }` ]: true,
'gform-input-wrapper--with-icon': withIcon,
[ `gform-input-wrapper--text-security-${ textSecurity }` ]: type !== 'password' && textSecurity !== 'none',
}, wrapperClasses ),
ref,
};
const inputProps = {
...customAttributes,
className: classnames( {
'gform-input': true,
'gform-typography--size-text-sm': 'cosmos' === theme,
[ `gform-input--${ size }` ]: true,
[ `gform-input--${ type }` ]: true,
}, customClasses ),
disabled: disabled || labelAttributes?.locked === true,
id: inputId,
name,
onBlur,
onKeyDown,
onChange: ( e ) => {
const { value: newInputValue } = e.target;
setInputValue( newInputValue );
onChange( newInputValue, e );
},
onFocus,
type,
value: inputValue,
};
if ( placeholder ) {
inputProps.placeholder = placeholder;
}
if ( helpTextAttributes.content ) {
inputProps[ 'aria-describedby' ] = helpTextId;
}
if ( required ) {
inputProps.required = true;
}
const labelProps = {
...labelAttributes,
htmlFor: inputId,
};
const helpTextProps = {
...helpTextAttributes,
id: helpTextId,
};
const requiredLabelProps = {
customClasses: classnames(
[ 'gform-input-help-text--required' ],
),
id: helpTextId,
...requiredLabel,
};
const iconProps = {
...iconAttributes,
customClasses: classnames(
[ 'gform-input__icon' ],
iconAttributes.customClasses || [],
),
};
const clearableButtonProps = {
icon: 'circle-close',
type: 'unstyled',
...clearableButtonAttributes,
customClasses: classnames(
[ 'gform-input__clearable-button' ],
clearableButtonAttributes.customClasses || [],
),
};
const actionButtonProps = {
iconPosition: 'leading',
type: 'white',
...actionButtonAttributes,
customClasses: classnames(
[ 'gform-input__action-button' ],
actionButtonAttributes.customClasses || [],
),
onClick: ( e ) => {
if ( actionButtonAttributes.onClick ) {
actionButtonAttributes.onClick( e, inputValue );
}
},
};
const Container = wrapperTagName;
return (
<Container { ...wrapperProps }>
<Label { ...labelProps } />{ required && <HelpText { ...requiredLabelProps } /> }
{ helpTextPosition === 'above' && <HelpText { ...helpTextProps } /> }
<ConditionalWrapper
condition={ withAction }
wrapper={ ( ch ) => <div className="gform-input__action-wrapper">{ ch }{ <Button { ...actionButtonProps } /> }</div> }
>
<ConditionalWrapper
condition={ withIcon }
wrapper={ ( ch ) => <div className="gform-input__wrapper">{ ch }</div> }
>
<input { ...inputProps } />
{ withIcon && <Icon { ...iconProps } /> }
{ clearable && inputValue && (
<Button
{ ...clearableButtonProps }
onClick={ () => {
setInputValue( '' );
onClear();
} }
/>
) }
</ConditionalWrapper>
</ConditionalWrapper>
{ helpTextPosition === 'below' && <HelpText { ...helpTextProps } /> }
</Container>
);
} );
Input.propTypes = {
borderStyle: PropTypes.string,
clearable: PropTypes.bool,
clearableButtonAttributes: PropTypes.object,
controlled: PropTypes.bool,
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
disabled: PropTypes.bool,
helpTextAttributes: PropTypes.object,
helpTextPosition: PropTypes.string,
iconAttributes: PropTypes.object,
id: PropTypes.string,
labelAttributes: PropTypes.object,
name: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onClear: PropTypes.func,
onKeyDown: PropTypes.func,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
required: PropTypes.bool,
requiredLabel: PropTypes.object,
size: PropTypes.string,
spacing: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.object,
] ),
textSecurity: PropTypes.string,
theme: PropTypes.string,
type: PropTypes.string,
value: PropTypes.string,
wrapperAttributes: PropTypes.object,
wrapperClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
wrapperTagName: PropTypes.string,
};
Input.displayName = 'Input';
export default Input;