import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses } from '@gravityforms/utils';
import Icon from '../../elements/Icon';
import Label from '../../elements/Label';
import ColorPicker from '../ColorPicker';
import { invertColor } from '../../utils/colors';
const { useState, useEffect, useRef, forwardRef } = React;
/**
* @module Swatch
* @description Allows users to select from a pallete of swatches, or add their own using a color picker
*
* @since 1.1.15
*
* @param {object} props Component props.
* @param {boolean} props.allowNew Whether to display an icon to add new swatches to the palette.
* @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.id The ID for the component.
* @param {object} props.labelAttributes Custom attributes to apply to the label for each swatch.
* @param {string} props.name The name of the component.
* @param {Array} props.palette An array of hex color values to display as default palette options.
* @param {Array} props.paletteCustom An array of hex color values to display as custom/editable palette options.
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {string} props.value The initial hex value to select.
* @param {object|null} ref Ref to the component.
*
* @return {JSX.Element} The Swatch component.
*
* @example
* import Swatch from '@gravityforms/components/react/admin/modules/Swatch';
*
* return (
* <Swatch customClasses={ [ 'example-class' ] } palette={ [ '#000', '#111' ] } />
* );
*
*/
const Swatch = forwardRef( ( {
allowNew = true,
customAttributes = {},
customClasses = [],
i18n = {},
id = '',
labelAttributes = {
size: 'text-sm',
weight: 'medium',
},
name = '',
palette = [],
paletteCustom = [],
spacing = '',
value = '',
}, ref ) => {
const [ showPicker, setShowPicker ] = useState( false );
const [ selectedColor, setSelectedColor ] = useState( value );
const [ color, setColor ] = useState( value );
const [ colorBeingModified, setColorBeingModified ] = useState( palette.length + 1 );
const [ customPaletteOptions, setCustomPaletteOptions ] = useState( paletteCustom );
const [ currentRef, setCurrentRef ] = useState( null );
const allInputRef = useRef( null );
const addNewRef = useRef( document.querySelector( `[data-js-setting-name="${ name }"] .gform-input--swatch__option--new` ) );
const swatchRefs = useRef( [] );
useEffect( () => {
const newPaletteString = JSON.stringify( customPaletteOptions );
allInputRef.current.value = newPaletteString;
if ( customPaletteOptions.length === 0 ) {
setSelectedColor( value );
}
}, [ customPaletteOptions, value ] );
useEffect( () => {
if ( ! currentRef || ! currentRef.current ) {
setCurrentRef( addNewRef );
}
}, [ setCurrentRef, currentRef ] );
/**
* @function handlePickerCancel
* @description Handler for the cancel event on the swatch.
*
* @since 1.1.15
*
* @return {void}
*/
const handlePickerCancel = () => {
setShowPicker( false );
};
/**
* @function handlePickerDelete
* @description Handler for the delete event on the swatch.
*
* @since 1.1.15
*
* @param {number} index Index of the swatch palette.
*
* @return {void}
*/
const handlePickerDelete = ( index ) => {
setCustomPaletteOptions( ( prevPalette ) => prevPalette.filter( ( item, thisIndex ) => thisIndex !== index ) );
handlePickerCancel();
};
/**
* @function handlePickerSave
* @description Handler for the save event on the swatch.
*
* @since 1.1.15
*
* @param {string} swatch The swatch value.
*
* @return {void}
*/
const handlePickerSave = ( swatch ) => {
setColor( swatch );
if ( ! customPaletteOptions.includes( swatch ) ) {
setCustomPaletteOptions( ( prevPalette ) => {
const newPalette = prevPalette;
newPalette[ colorBeingModified ] = swatch;
return newPalette;
} );
}
setSelectedColor( swatch );
setShowPicker( false );
};
/**
* @function handleColorChange
* @description Handler for the color change event.
*
* @since 1.1.15
*
* @param {object} event Event object.
*
* @return {void}
*/
const handleColorChange = ( event ) => {
setSelectedColor( event.target.value );
};
/**
* @function renderSwatchOption
* @description Render the swatch option.
*
* @since 1.1.15
*
* @param {string} swatch The swatch value.
* @param {number} index The index of the swatch palette.
* @param {boolean} isCustom Whether the swatch is custom or not.
*
* @return {JSX.Element} The swatch option.
*/
const renderSwatchOption = ( swatch, index, isCustom = false ) => {
const liProps = {
className: classnames( {
'gform-input--swatch__option': true,
} ),
key: index,
};
const labelProps = {
htmlFor: `${ name }_${ swatch }_${ index }`,
label: i18n?.swatch || '',
isVisible: false,
...labelAttributes,
};
const swatchInputProps = {
onChange: handleColorChange,
type: 'radio',
name,
value: swatch,
id: `${ name }_${ swatch }_${ index }`,
checked: swatch === selectedColor,
};
if ( isCustom ) {
swatchInputProps.onClick = () => {
setColor( swatch );
setCurrentRef( { current: swatchRefs.current[ index ] } );
setColorBeingModified( index );
setShowPicker( true );
};
}
const swatchSpanProps = {
className: classnames( {
'gform-input--swatch__option-preview': true,
} ),
style: {
backgroundColor: swatch,
},
onClick: ( e ) => {
if ( e.target.classList.contains( 'gform-input--swatch-delete' ) ) {
handlePickerDelete( index );
return;
}
document.getElementById( `${ name }_${ swatch }_${ index }` ).click();
},
ref: isCustom ? ( el ) => ( swatchRefs.current[ index ] = el ) : null,
};
const iconProps = {
icon: 'check',
customClasses: classnames( {
'gform-input--swatch-selected': true,
} ),
customAttributes: {
style: {
color: invertColor( swatch ),
},
},
};
const deleteIconProps = {
icon: 'delete',
customClasses: classnames( {
'gform-input--swatch-delete': true,
} ),
};
return (
<li { ...liProps }>
<Label { ...labelProps } />
<input { ...swatchInputProps } />
<span { ...swatchSpanProps } >
{ swatch === selectedColor && <Icon { ...iconProps } /> }
{ isCustom && <Icon { ...deleteIconProps } /> }
</span>
</li>
);
};
/**
* @function renderAddNewSwatchOption
* @description Render the add new swatch option.
*
* @since 1.1.15
*
* @return {JSX.Element} The add new swatch option.
*/
const renderAddNewSwatchOption = () => {
const liProps = {
className: classnames( {
'gform-input--swatch__option': true,
'gform-input--swatch__option--new': true,
} ),
key: 'add-new',
};
const swatchSpanProps = {
className: classnames( {
'gform-input--swatch__option-preview': true,
'gform-input--swatch__option-preview--new': true,
} ),
onClick: () => {
setCurrentRef( addNewRef );
setColorBeingModified( customPaletteOptions.length + 1 );
setShowPicker( true );
},
ref: addNewRef,
};
const iconProps = {
icon: 'plus-regular',
};
return (
<li { ...liProps }>
<span { ...swatchSpanProps } >
<Icon { ...iconProps } />
</span>
</li>
);
};
const componentProps = {
className: classnames( {
'gform-input--swatch': true,
...spacerClasses( spacing ),
}, customClasses ),
id,
'data-js-setting-name': name,
...customAttributes,
};
const swatchOptionsProps = {
className: classnames( {
'gform-input--swatch-options': true,
} ),
};
const allSwatchesInputProps = {
name: `${ name }-all-swatches`,
defaultValue: JSON.stringify( customPaletteOptions ),
id: `${ name }-all-swatches`,
type: 'hidden',
ref: allInputRef,
};
const pickerProps = {
value: color || '#ffffff',
onSave: handlePickerSave,
onCancel: handlePickerCancel,
triggerRef: currentRef,
i18n: i18n?.colorPicker || {},
};
return (
<div { ...componentProps } ref={ ref }>
<div style={ { height: '0' } } />
<ul { ...swatchOptionsProps }>
{ palette.map( ( swatch, index ) => renderSwatchOption( swatch, index ) ) }
{ customPaletteOptions.map( ( swatch, index ) => renderSwatchOption( swatch, index, true ) ) }
{ allowNew && renderAddNewSwatchOption() }
</ul>
{ showPicker && <ColorPicker { ...pickerProps } /> }
<input { ...allSwatchesInputProps } />
</div>
);
} );
Swatch.propTypes = {
allowNew: PropTypes.bool,
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
i18n: PropTypes.object,
id: PropTypes.string,
labelAttributes: PropTypes.object,
name: PropTypes.string,
palette: PropTypes.array,
paletteCustom: PropTypes.array,
spacing: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.object,
] ),
value: PropTypes.string,
};
Swatch.displayName = 'Swatch';
export default Swatch;