"use client"; import React, { memo, ReactNode, useCallback, useEffect, useState, } from "react"; import { useStableCallback } from "@noya-app/react-utils"; import { validHex } from "../utils/validate"; // Escapes all non-hexadecimal characters including "#" const escape = (hex: string) => hex.replace(/([^0-9A-F]+)/gi, "").substr(0, 6); interface ComponentProps { color: string; onChange: (newColor: string) => void; } type InputProps = Omit< React.InputHTMLAttributes, "onChange" | "value" >; export default memo(function HexColorInput( props: Partial ): ReactNode { const { color = "", onChange, onBlur, ...rest } = props; const [value, setValue] = useState(() => escape(color)); const onChangeCallback = useStableCallback(onChange); const onBlurCallback = useStableCallback(onBlur); // Trigger `onChange` handler only if the input value is a valid HEX-color const handleChange = useCallback( (e: React.ChangeEvent) => { const inputValue = escape(e.target.value); setValue(inputValue); if (validHex(inputValue)) onChangeCallback("#" + inputValue); }, [onChangeCallback] ); // Take the color from props if the last typed color (in local state) is not valid const handleBlur = useCallback( (e: React.FocusEvent) => { if (!validHex(e.target.value)) setValue(escape(color)); onBlurCallback(e); }, [color, onBlurCallback] ); // Update the local state when `color` property value is changed useEffect(() => { setValue(escape(color)); }, [color]); return ( ); });