import { classnames, React } from '@gravityforms/libraries';
import { usePhoneInputFormatUtilsContext as usePhoneInputFormatUtils } from '@gravityforms/react-utils';
import { spacerClasses } from '@gravityforms/utils';
import Box from '../../elements/Box';
import HelpText from '../../elements/HelpText';
import Input from '../../elements/Input';
import Dropdown from '../Dropdown';
import Loader from '../Loaders/RingLoader';
const { forwardRef, lazy, Suspense } = React;
const PhoneComponent = lazy( () => import( './PhoneComponent' ) );
const PhoneComponentPlaceholder = ( {
countries,
customAttributes = {},
customClasses = [],
dropdownAttributes = {},
dropdownClasses = [],
inputAttributes = {},
inputClasses = [],
label = '',
labelAttributes = {},
labelClasses = [],
language = 'en',
required = false,
requiredLabelAttributes = {},
requiredLabelClasses = [],
size = 'r',
spacing = '',
width = 300,
} ) => {
const disabled = true;
const componentProps = {
className: classnames( {
'gform-phone': true,
'gform-phone--placeholder': true,
'wp-exclude-emoji': true,
[ `gform-phone--size-${ size }` ]: true,
'gform-phone--disabled': disabled,
...spacerClasses( spacing ),
}, customClasses ),
style: {
width: width ? `${ width }px` : undefined,
},
...customAttributes,
};
const labelProps = {
className: classnames( [
'gform-phone__label',
'gform-text',
'gform-text--color-port',
'gform-typography--size-text-sm',
'gform-typography--weight-medium',
], labelClasses ),
...labelAttributes,
};
const requiredLabelProps = {
size: 'text-sm',
weight: 'medium',
...requiredLabelAttributes,
customClasses: classnames( [ 'gform-phone__required' ], requiredLabelClasses ),
};
const dropdownProps = {
countries,
hasSearch: true,
popoverMaxHeight: 300,
showCallingCode: true,
...dropdownAttributes,
customClasses: classnames( [
'gform-phone__dropdown',
], dropdownClasses ),
disabled,
language,
searchAttributes: {
wrapperClasses: [ 'gform-phone__dropdown-search-wrapper' ],
...( dropdownAttributes?.searchAttributes || {} ),
},
searchClasses: [ 'gform-phone__dropdown-search' ],
size,
triggerClasses: [ 'gform-phone__dropdown-trigger' ],
};
const inputProps = {
...inputAttributes,
customClasses: classnames( [
'gform-phone__input',
], inputClasses ),
directControlled: true,
disabled,
required,
size: `size-${ size }`,
type: 'tel',
wrapperClasses: classnames( {
'gform-phone__input-wrapper': true,
}, inputAttributes?.wrapperClasses || [] ),
};
return (
<div { ...componentProps }>
{ label && <div { ...labelProps }>{ label }{ required && <HelpText { ...requiredLabelProps } /> }</div> }
<div className="gform-phone__wrapper">
<Dropdown { ...dropdownProps } />
<Input { ...inputProps } />
</div>
</div>
);
};
/**
* @module Phone
* @description A phone input component with country code selection.
*
* @since 5.5.0
*
* @param {object} props Component props
* @param {Array} props.countries Array of country codes.
* @param {object} props.customAttributes Custom attributes for the component.
* @param {string|Array|object} props.customClasses Custom classes for the component.
* @param {boolean} props.disabled Whether the input is disabled.
* @param {object} props.dropdownAttributes Custom attributes for the dropdown.
* @param {string|Array|object} props.dropdownClasses Custom classes for the dropdown.
* @param {object} props.helpTextAttributes Custom attributes for the help text.
* @param {string|Array|object} props.helpTextClasses Custom classes for the help text.
* @param {object} props.i18n i18n object for the country dropdown component.
* @param {string} props.id Optional id (auto-generated if not provided).
* @param {string} props.inputAttributes Custom attributes for the input.
* @param {string|Array|object} props.inputClasses Custom classes for the input.
* @param {boolean} props.international Whether the phone number is international format.
* @param {object} props.label Label text.
* @param {object} props.labelAttributes Custom attributes for the label.
* @param {string|Array|object} props.labelClasses Custom classes for the label.
* @param {string} props.language Language code for the country dropdown component.
* @param {Function} props.onChange Change handler function, fires both on input and dropdown change. Receives { country, number, isValid }.
* @param {Array} props.preferredCountries Array of preferred country codes.
* @param {boolean} props.required Whether the field is required.
* @param {object} props.requiredLabelAttributes Custom attributes for the required label.
* @param {string|Array|object} props.requiredLabelClasses Custom classes for the required label.
* @param {string} props.size Component size: 'r', 'l', or 'xl'.
* @param {string|number|Array|object} props.spacing Component spacing.
* @param {boolean} props.usePlaceholder Whether to use a placeholder.
* @param {boolean} props.useValidation Whether to use validation for the phone input.
* @param {number} props.width Width of the component in pixels.
* @param {object} ref Forwarded ref.
*
* @return {JSX.Element} Phone component
*
* @example
* import Phone from '@gravityforms/components/react/admin/modules/Phone';
*
* return <Phone
* countries={ [ 'US', 'CA', 'GB', 'MX', 'DE', 'JP' ] }
* helpTextAttributes={ { content: 'The phone number you entered is not valid.' } }
* i18n={ { allCountries: 'All countries' } }
* id="contact-phone"
* international={ true }
* label="Phone Number"
* onChange={ ( value, event ) => {
* console.log( 'country: ', value.country );
* console.log( 'number: ', value.number );
* console.log( 'isValid: ', value.isValid );
* console.log( 'event: ', event );
* } }
* preferredCountries={ [ 'US', 'CA', 'GB' ] }
* required={ true }
* requiredLabelAttributes={ { content: '(Required)' } }
* size="xl"
* usePlaceholder={ true }
* useValidation={ true }
* width={ 500 }
* />;
*
*/
const Phone = forwardRef( ( props, ref ) => {
const { width = 300 } = props;
const { isLoading } = usePhoneInputFormatUtils();
const fallback = (
<Box customClasses={ [ 'gform-phone__fallback' ] } x={ width }>
<PhoneComponentPlaceholder { ...props } />
<Loader foreground="#9092b2" />
</Box>
);
if ( isLoading ) {
return fallback;
}
return (
<Suspense fallback={ fallback }>
<PhoneComponent { ...props } ref={ ref } />
</Suspense>
);
} );
export default Phone;