ColorPickerInput: Enable input clearing (#53587)

* ColorPickerInput: Enable removing selected color

* ColorPickerInput: Add tests

* ColorInput: Add isClearable prop
This commit is contained in:
Alex Khomenko 2022-08-12 13:07:24 +03:00 committed by GitHub
parent d4f382892d
commit 7944f3a692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 38 deletions

View File

@ -10,51 +10,60 @@ import { Input, Props as InputProps } from '../Input/Input';
import { ColorPickerProps } from './ColorPickerPopover';
interface ColorInputProps extends ColorPickerProps, Omit<InputProps, 'color' | 'onChange'> {}
interface ColorInputProps extends ColorPickerProps, Omit<InputProps, 'color' | 'onChange'> {
isClearable?: boolean;
}
const ColorInput = forwardRef<HTMLInputElement, ColorInputProps>(({ 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<HTMLInputElement, ColorInputProps>(
({ 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<HTMLInputElement>) => {
const newColor = tinycolor(event.currentTarget.value);
const onChangeColor = (event: React.SyntheticEvent<HTMLInputElement>) => {
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 (
<Input
{...inputProps}
value={value}
onChange={onChangeColor}
onBlur={onBlur}
addonBefore={<ColorPreview color={color} />}
ref={ref}
/>
);
});
return (
<Input
{...inputProps}
value={value}
onChange={onChangeColor}
onBlur={onBlur}
addonBefore={<ColorPreview color={color} />}
ref={ref}
/>
);
}
);
ColorInput.displayName = 'ColorInput';

View File

@ -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(<ColorPickerInput onChange={noop} />);
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(<ColorPickerInput onChange={mockOnChange} />);
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(<ColorPickerInput onChange={mockOnChange} />);
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(<ColorPickerInput onChange={mockOnChange} value={'rgb(0,0,0)'} />);
// 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('');
});
});

View File

@ -53,13 +53,14 @@ export const ColorPickerInput = forwardRef<HTMLInputElement, ColorPickerInputPro
<div className={styles.wrapper}>
{isOpen && (
<RgbaStringColorPicker
data-testid={'color-popover'}
color={currentColor}
onChange={setColor}
className={cx(paletteStyles.root, styles.picker)}
/>
)}
<div onClick={() => setIsOpen(true)}>
<ColorInput {...inputProps} theme={theme} color={currentColor} onChange={setColor} ref={ref} />
<ColorInput {...inputProps} theme={theme} color={currentColor} onChange={setColor} ref={ref} isClearable />
</div>
</div>
</ClickOutsideWrapper>