diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx index de4e4f4206fe3..65e244f604f7d 100644 --- a/packages/components/src/color-palette/index.tsx +++ b/packages/components/src/color-palette/index.tsx @@ -1,7 +1,12 @@ /** * External dependencies */ -import type { ForwardedRef } from 'react'; +import type { + ForwardedRef, + MouseEvent, + ChangeEvent, + KeyboardEvent, +} from 'react'; import { colord, extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; import a11yPlugin from 'colord/plugins/a11y'; @@ -12,7 +17,14 @@ import clsx from 'clsx'; */ import { useInstanceId } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; -import { useCallback, useMemo, useState, forwardRef } from '@wordpress/element'; +import { + useCallback, + useMemo, + useState, + forwardRef, + useEffect, + useRef, +} from '@wordpress/element'; /** * Internal dependencies @@ -31,6 +43,7 @@ import type { MultiplePalettesProps, PaletteObject, SinglePaletteProps, + CustomColorValueInputProps, } from './types'; import type { WordPressComponentProps } from '../context'; import type { DropdownProps } from '../dropdown/types'; @@ -42,6 +55,77 @@ import { extend( [ namesPlugin, a11yPlugin ] ); +function CustomColorValueInput( { + value, + onChange, + isHex, +}: CustomColorValueInputProps ) { + const [ isEditing, setIsEditing ] = useState( false ); + const [ inputValue, setInputValue ] = useState( value ); + const inputRef = useRef< HTMLInputElement >( null ); + + useEffect( () => { + if ( isEditing && inputRef.current ) { + inputRef.current.focus(); + } + }, [ isEditing ] ); + + const handleClick = ( e: MouseEvent< HTMLDivElement > ) => { + e.preventDefault(); + if ( isHex ) { + setIsEditing( true ); + } + }; + + const handleChange = ( e: ChangeEvent< HTMLInputElement > ) => { + setInputValue( e.target.value ); + }; + + const handleBlur = () => { + setIsEditing( false ); + if ( isHex && /^#[0-9A-Fa-f]{6}$/.test( inputValue || '' ) ) { + onChange( inputValue ); + } else { + setInputValue( value ); + } + }; + + const handleKeyDown = ( e: KeyboardEvent< HTMLInputElement > ) => { + if ( e.key === 'Enter' ) { + ( e.target as HTMLInputElement ).blur(); + } else if ( e.key === 'Escape' ) { + setInputValue( value ); + setIsEditing( false ); + } + }; + + if ( isEditing && isHex ) { + return ( + + ); + } + + return ( + + { value } + + ); +} + function SinglePalette( { className, clearColor, @@ -220,7 +304,7 @@ function UnforwardedColorPalette( /> ); - const isHex = value?.startsWith( '#' ); + const isHex = value?.startsWith( '#' ) ?? false; // Leave hex values as-is. Remove the `var()` wrapper from CSS vars. const displayValue = value?.replace( /^var\((.+)\)$/, '$1' ); @@ -311,22 +395,11 @@ function UnforwardedColorPalette( ? buttonLabelName : __( 'No color selected' ) } - { /* - This `Truncate` is always rendered, even if - there is no `displayValue`, to ensure the layout - does not shift - */ } - - { displayValue } - + ) } diff --git a/packages/components/src/color-palette/types.ts b/packages/components/src/color-palette/types.ts index b2a7a884fec57..c6f4446542e13 100644 --- a/packages/components/src/color-palette/types.ts +++ b/packages/components/src/color-palette/types.ts @@ -122,3 +122,9 @@ export type ColorPaletteProps = Pick< PaletteProps, 'onChange' > & { 'aria-label'?: never; } ); + +export type CustomColorValueInputProps = { + value?: string; + onChange: ( newColor?: string ) => void; + isHex: boolean; +};