Skip to content

Commit

Permalink
fix: prevent #NaN color picker values
Browse files Browse the repository at this point in the history
  • Loading branch information
brian-smith-tcril committed Dec 19, 2023
1 parent f37c7ec commit 7305955
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 18 deletions.
26 changes: 24 additions & 2 deletions src/ColorPicker/ColorPicker.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,35 @@ describe('picker works as expected', () => {
const color = 'wassap';
const setColor = jest.fn();
it('validates hex color', async () => {
const { rerender } = render(<ColorPicker color={color} setColor={setColor} />);
render(<ColorPicker color={color} setColor={setColor} />);

await act(async () => {
await userEvent.click(screen.getByRole('button'));
});
expect(screen.queryByTestId('hex-input').value).toEqual('#wassap');
expect(screen.queryByText('Colors must be in hexadecimal format.')).toBeInTheDocument();

rerender(<ColorPicker color="#32116c" setColor={setColor} />);
await act(async () => {
await userEvent.clear(screen.getByTestId('hex-input'));
await userEvent.paste(screen.getByTestId('hex-input'), '32116c');
});
expect(screen.queryByTestId('hex-input').value).toEqual('#32116c');
expect(screen.queryByText('Colors must be in hexadecimal format.')).not.toBeInTheDocument();

await act(async () => {
await userEvent.clear(screen.getByTestId('hex-input'));
await userEvent.paste(screen.getByTestId('hex-input'), 'yuk');
});

expect(screen.queryByTestId('hex-input').value).toEqual('#yuk');
expect(screen.queryByText('Colors must be in hexadecimal format.')).toBeInTheDocument();

await act(async () => {
await userEvent.clear(screen.getByTestId('hex-input'));
await userEvent.paste(screen.getByTestId('hex-input'), '#fad');
});

expect(screen.queryByTestId('hex-input').value).toEqual('#fad');
expect(screen.queryByText('Colors must be in hexadecimal format.')).not.toBeInTheDocument();
});
});
72 changes: 56 additions & 16 deletions src/ColorPicker/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { HexColorPicker } from 'react-colorful';
Expand All @@ -15,24 +15,63 @@ function ColorPicker({
}) {
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = React.useState(null);
const [hexValid, setHexValid] = React.useState(true);

const validateHex = useCallback((input) => {
const colorIsValid = (colorToValidate) => {
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
if (input.length > 1 && !input.startsWith('#')) {
setColor(`#${input}`);
} else {
setColor(input);
return hexRegex.test(colorToValidate);
};

const formatHexColorString = (colorString) => {
if (!colorString.startsWith('#')) {
return `#${colorString}`.slice(0, 7);
}

return colorString.slice(0, 7);
};

const [hexValid, setHexValid] = React.useState(() => (color === '' || colorIsValid(formatHexColorString(color))));

const [hexColorString, setHexColorString] = React.useState(() => {
if (color === '') {
return '';
}

return formatHexColorString(color);
});
const [colorToDisplay, setColorToDisplay] = React.useState(() => {
const formattedColor = formatHexColorString(color);
if (colorIsValid(formattedColor)) {
return formattedColor;
}
if (input === '' || hexRegex.test(input) === true) {

return '#fff';
});

const setValidatedColor = (newColor) => {
if (newColor === '') {
setHexValid(true);
} else {
setHexValid(false);
setColor('');
setHexColorString('');
setColorToDisplay('#fff');
return;
}
}, [setColor]);

// this is needed for when a user changes the color through the sliders
useEffect(() => validateHex(color), [validateHex, color]);
const formattedColor = formatHexColorString(newColor);

if (colorIsValid(formattedColor)) {
setHexValid(true);
setColor(formattedColor);
setHexColorString(formattedColor);
setColorToDisplay(formattedColor);
return;
}

setHexValid(false);
setHexColorString(formattedColor);

// ensure the picker value stays in sync with the textbox
setColor(formattedColor);
};

return (
<>
Expand Down Expand Up @@ -65,16 +104,17 @@ function ColorPicker({
className="pgn__color-modal rounded shadow"
style={{ textAlign: 'start' }}
>
<HexColorPicker color={color || ''} onChange={setColor} />
<HexColorPicker color={colorToDisplay} onChange={setValidatedColor} />
<Form.Group className="pgn__hex-form" size="sm">
<div>
<Form.Label className="pgn__hex-label">Hex</Form.Label>
<Form.Control
className="pgn__hex-field"
isInvalid={!hexValid}
value={color}
onChange={(e) => validateHex(e.target.value)}
value={hexColorString}
onChange={(e) => setValidatedColor(e.target.value)}
data-testid="hex-input"
spellCheck="false"
/>
</div>
{!hexValid && (
Expand Down

0 comments on commit 7305955

Please sign in to comment.