trezy/firebase-system-update

View on GitHub
src/components/Form/FieldColor.js

Summary

Maintainability
B
6 hrs
Test Coverage
// Module imports
import {
    useCallback,
    useEffect,
    useRef,
    useState,
} from 'react'
import { ChromePicker } from 'react-color'
import classnames from 'classnames'
import PropTypes from 'prop-types'





// Local imports
import { Button } from 'components/Form/Button'
import { getReadableTextColorForBackground } from 'helpers/getReadableTextColorForBackground'
import { useForm } from 'components/Form'





function FieldColor(props) {
    const {
        className,
        id,
        isDisabled,
        isRequired,
    } = props
    const {
        errors: formErrors,
        updateValidity,
        updateValue,
        values,
    } = useForm()
    const [shouldShowColorPicker, setShouldShowColorPicker] = useState(false)
    const buttonRef = useRef(null)
    const dropdownRef = useRef(null)

    const hideColorPicker = useCallback(() => setShouldShowColorPicker(false))
    const toggleColorPicker = useCallback(() => setShouldShowColorPicker(previousState => !previousState))

    const validate = useCallback(async (state, initialProps) => {
        const errors = []

        if (initialProps.isRequired && !state) {
            errors.push('Field is required')
        }

        if (!/^#(?:[0-9a-f]){6}$/i.test(state)) {
            errors.push(`${state} isn't a valid color`)
        }

        if (typeof initialProps.validate === 'function') {
            const customError = await initialProps.validate(state, values)
            if (customError) {
                errors.push(customError)
            }
        }

        updateValidity(id, errors)
    }, [
        updateValidity,
        values,
    ])

    const handleChange = useCallback(value => {
        updateValue(id, value.hex)
        validate(value.hex, props)
    }, [
        id,
        props,
        updateValue,
        validate,
    ])

    useEffect(() => {
        // Run a validity check against a field's initial state if it's non-empty
        if (isRequired && (values[id] !== '')) {
            validate(values[id], props)
        } else {
            updateValidity(id, [])
        }
    }, [])

    useEffect(() => {
        if (shouldShowColorPicker) {
            const handleRemoteClick = ({ target }) => {
                const buttonElement = buttonRef.current
                const dropdownElement = dropdownRef.current

                if (
                    (target !== buttonElement) &&
                    (target !== dropdownElement) &&
                    !dropdownElement.contains(target)
                ) {
                    hideColorPicker()
                }
            }

            document.addEventListener('click', handleRemoteClick)
            return () => document.removeEventListener('click', handleRemoteClick)
        }
    }, [shouldShowColorPicker])

    return (
        <div className={classnames('control', className)}>
            <div
                className={classnames({
                    dropdown: true,
                    'is-active': shouldShowColorPicker,
                    'is-fullwidth': true,
                    'is-up': true,
                })}>
                <Button
                    aria-controls="dropdown-menu7"
                    aria-haspopup="true"
                    className="is-fullwidth is-white"
                    isDisabled={isDisabled}
                    onClick={toggleColorPicker}
                    ref={buttonRef}
                    style={{
                        backgroundColor: values[id],
                        color: getReadableTextColorForBackground(values[id]),
                    }}>
                    {values[id]}
                </Button>

                <div
                    className="dropdown-menu"
                    ref={dropdownRef}>
                    <ChromePicker
                        color={values[id]}
                        onChange={handleChange} />
                </div>
            </div>
        </div>
    )
}

FieldColor.defaultProps = {
    className: null,
    isDisabled: false,
    isRequired: false,
    validate: () => {},
}

FieldColor.propTypes = {
    className: PropTypes.string,
    id: PropTypes.string.isRequired,
    isDisabled: PropTypes.bool,
    isRequired: PropTypes.bool,
    validate: PropTypes.func,
}

export { FieldColor }