diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx index 354ee19bf9b..2a3554701c0 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx @@ -10,51 +10,60 @@ import { Input, Props as InputProps } from '../Input/Input'; import { ColorPickerProps } from './ColorPickerPopover'; -interface ColorInputProps extends ColorPickerProps, Omit {} +interface ColorInputProps extends ColorPickerProps, Omit { + isClearable?: boolean; +} -const ColorInput = forwardRef(({ color, onChange, ...inputProps }, ref) => { - const [value, setValue] = useState(color); - const [previousColor, setPreviousColor] = useState(color); - // eslint-disable-next-line react-hooks/exhaustive-deps - const updateColor = useMemo(() => debounce(onChange, 100), []); +const ColorInput = forwardRef( + ({ color, onChange, isClearable = false, ...inputProps }, ref) => { + const [value, setValue] = useState(color); + const [previousColor, setPreviousColor] = useState(color); + // eslint-disable-next-line react-hooks/exhaustive-deps + const updateColor = useMemo(() => debounce(onChange, 100), []); - useEffect(() => { - const newColor = tinycolor(color); - if (newColor.isValid() && color !== previousColor) { - setValue(newColor.toString()); - setPreviousColor(color); - } - }, [color, previousColor]); + useEffect(() => { + const newColor = tinycolor(color); + if (newColor.isValid() && color !== previousColor) { + setValue(newColor.toString()); + setPreviousColor(color); + } + }, [color, previousColor]); - const onChangeColor = (event: React.SyntheticEvent) => { - const newColor = tinycolor(event.currentTarget.value); + const onChangeColor = (event: React.SyntheticEvent) => { + const { value: colorValue } = event.currentTarget; - setValue(event.currentTarget.value); + setValue(colorValue); + if (colorValue === '' && isClearable) { + updateColor(colorValue); + return; + } + const newColor = tinycolor(colorValue); - if (newColor.isValid()) { - updateColor(newColor.toString()); - } - }; + if (newColor.isValid()) { + updateColor(newColor.toString()); + } + }; - const onBlur = () => { - const newColor = tinycolor(value); + const onBlur = () => { + const newColor = tinycolor(value); - if (!newColor.isValid()) { - setValue(color); - } - }; + if (!newColor.isValid()) { + setValue(color); + } + }; - return ( - } - ref={ref} - /> - ); -}); + return ( + } + ref={ref} + /> + ); + } +); ColorInput.displayName = 'ColorInput'; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx new file mode 100644 index 00000000000..eeab0e71112 --- /dev/null +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx @@ -0,0 +1,41 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { ColorPickerInput } from './ColorPickerInput'; + +const noop = () => {}; +describe('ColorPickerInput', () => { + it('should show color popover on focus', async () => { + render(); + expect(screen.queryByTestId('color-popover')).not.toBeInTheDocument(); + await userEvent.click(screen.getByRole('textbox')); + expect(screen.getByTestId('color-popover')).toBeInTheDocument(); + }); + + it('should pass correct color to onChange callback', async () => { + const mockOnChange = jest.fn(); + render(); + await userEvent.type(screen.getByRole('textbox'), 'rgb(255,255,255)'); + await waitFor(() => expect(mockOnChange).toHaveBeenCalledWith('rgb(255, 255, 255)')); + }); + + it('should not pass invalid color value to onChange callback', async () => { + const mockOnChange = jest.fn(); + render(); + await userEvent.type(screen.getByRole('textbox'), 'some text'); + screen.getByRole('textbox').blur(); + await waitFor(() => expect(mockOnChange).not.toHaveBeenCalled()); + expect(screen.getByRole('textbox')).toHaveValue(''); + }); + + it('should be able to reset selected value', async () => { + const mockOnChange = jest.fn(); + render(); + // Should show the value in the input + expect(screen.getByDisplayValue('rgb(0,0,0)')).toBeInTheDocument(); + await userEvent.clear(screen.getByRole('textbox')); + await waitFor(() => expect(mockOnChange).toHaveBeenCalledWith('')); + expect(screen.getByRole('textbox')).toHaveValue(''); + }); +}); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx index 437d7d03982..6e87286251c 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx @@ -53,13 +53,14 @@ export const ColorPickerInput = forwardRef {isOpen && ( )}
setIsOpen(true)}> - +