import { React, PropTypes, classnames, HexColorPicker, HexColorInput } from '@gravityforms/libraries';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import Label from '../../elements/Label';
import Button from '../../elements/Button';
import { hexToRGB } from '../../utils/colors';
const { useState, useEffect, useRef, useLayoutEffect } = React;
/**
* @module ColorPicker
* @description A color picker with HEX and RBG support.
*
* @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 {object} props.i18n Translated strings for the UI.
* @param {string} props.rgbIdPrefix Custom prefix for the individual input IDs.
* @param {Function} props.onCancel Custom callback to fire when the picker is closed.
* @param {Function} props.onChange Custom callback to fire when the picker is changed.
* @param {Function} props.onSave Custom callback to fire when the picker is saved/applied.
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {object} props.triggerRef Ref object representing the trigger used to display this picker.
* @param {string} props.value The initial value to use when the picker is instantiated.
*
* @return {object} The ColorPicker component.
*
* @example
* import ColorPicker from '@gravityforms/components/react/admin/modules/ColorPicker';
*
* return ( <ColorPicker onSave={ () => { handleSave(); } } value="#000" /> );
*
*/
const ColorPicker = ( {
customAttributes = {},
customClasses = [],
i18n = {},
rgbIdPrefix = '',
onCancel = () => {},
onChange = () => {},
onSave = () => {},
spacing = '',
triggerRef = { current: null },
value = '#315f92',
} ) => {
const [ color, setColor ] = useState( value );
const [ rgbColor, setRgbColor ] = useState( hexToRGB( value ) );
const [ position, setPosition ] = useState( false );
const [ coords, setCoords ] = useState( false );
const rgbInputIdPrefix = rgbIdPrefix || uniqueId( 'rgb-input-' );
const selfRef = useRef( null );
useEffect( () => {
setRgbColor( hexToRGB( color ) );
}, [ color ] );
// Update the position of the color picker when it is rendered/instantiated.
useLayoutEffect( () => {
const getPosition = () => {
if ( ! selfRef.current || ! triggerRef.current ) {
return 'above';
}
const picker = selfRef.current;
const source = triggerRef.current;
return ( source.getBoundingClientRect().top - 20 ) > picker.offsetHeight ? 'above' : 'below';
};
setPosition( getPosition() );
setCoords( {
left: ( triggerRef?.current?.offsetLeft || 0 ) + ( triggerRef?.current?.offsetWidth / 2 || 0 ),
top: getPosition() === 'above'
? ( triggerRef?.current?.offsetTop || 0 ) - 10
: ( triggerRef?.current?.offsetBottom || 0 ) + ( triggerRef?.current?.offsetHeight ) + 10,
} );
}, [ triggerRef ] );
useEffect( () => {
const handleOutsideClick = ( e ) => {
if ( ! selfRef.current ) {
return;
}
if ( selfRef.current.contains( e.target ) ) {
return;
}
if ( triggerRef.current.contains( e.target ) ) {
return;
}
handleClose();
};
document.addEventListener( 'click', handleOutsideClick );
return () => document.removeEventListener( 'click', handleOutsideClick );
} );
/**
* @function handleOnChange
* @description Handler for the hex color picker change event.
*
* @since 1.1.15
*
* @param {string} newValue The new color value.
*
* @return {void}
*/
const handleOnChange = ( newValue ) => {
setColor( newValue );
onChange( newValue );
};
/**
* @function handleClose
* @description Handle the hex color picker close event.
*
* @since 1.1.15
*
* @return {void}
*/
const handleClose = () => {
onCancel();
};
/**
* @function renderRGBInput
* @description Render the RGB input field.
*
* @since 1.1.15
*
* @param {string} colorPart The color value.
* @param {number} index The index of the color.
*
* @return {JSX.Element}
*/
const renderRGBInput = ( colorPart, index ) => {
const labelProps = {
htmlFor: `${ rgbInputIdPrefix }-${ index }`,
};
const inputProps = {
readOnly: true,
value: colorPart,
type: 'text',
id: `${ rgbInputIdPrefix }-${ index }`,
className: classnames( {
'gform-input': true,
} ),
};
const wrapperProps = {
className: classnames( {
'gform-input--picker-input': true,
'gform-input--picker-input--rgb': true,
} ),
key: index,
};
return (
<div { ...wrapperProps }>
<Label { ...labelProps } label={ index } />
<input { ...inputProps } />
</div>
);
};
const componentProps = {
className: classnames( {
'gform-input--picker': true,
[ `gform-input--picker--pos-${ position }` ]: true,
...spacerClasses( spacing ),
}, customClasses ),
style: {
top: coords.top,
left: coords.left,
},
ref: selfRef,
...customAttributes,
};
const hexInputWrapperProps = {
className: classnames( {
'gform-input--picker-input': true,
} ),
};
const hexInputProps = {
color,
onChange: handleOnChange,
className: classnames( {
'gform-input': true,
} ),
id: `${ rgbInputIdPrefix }-hex`,
type: 'text',
};
const hexLabelProps = {
label: i18n?.hex || '',
htmlFor: `${ rgbInputIdPrefix }-hex`,
};
const saveButtonProps = {
type: 'primary-new',
label: i18n?.apply || '',
onClick: () => onSave( color ),
size: 'size-xs',
};
return (
<div { ...componentProps }>
<div className="gform-input__picker-ui">
<HexColorPicker color={ color } onChange={ handleOnChange } />
<div className="gform-input__picker-inputs">
<div { ...hexInputWrapperProps } >
<Label { ...hexLabelProps } />
<HexColorInput { ...hexInputProps } />
</div>
{ Object.keys( rgbColor ).map( ( index ) => renderRGBInput( rgbColor[ index ], index ) ) }
</div>
</div>
<div className="gform-input__picker-controls">
<Button { ...saveButtonProps } />
</div>
</div>
);
};
ColorPicker.propTypes = {
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
i18n: PropTypes.object,
onCancel: PropTypes.func,
onChange: PropTypes.func,
onSave: PropTypes.func,
spacing: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.object,
] ),
triggerRef: PropTypes.object,
value: PropTypes.string,
};
export default ColorPicker;