UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

255 lines (226 loc) 6.91 kB
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;