Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const App: React.FC = () => {
<Input
labelText="rgb"
colorType={colorTypes.rgb}
placeHolder="rgb(255, 0, 0)"
placeHolder="rgb(255 0 0)"
onChange={onInputChange}
incomingColor={incomingColor}
incomingColorType={incomingColorType}
Expand All @@ -62,7 +62,7 @@ const App: React.FC = () => {
<Input
labelText="rgba"
colorType={colorTypes.rgba}
placeHolder="rgba(255, 0, 0, 1)"
placeHolder="rgba(255 0 0 / 1)"
onChange={onInputChange}
incomingColor={incomingColor}
incomingColorType={incomingColorType}
Expand All @@ -71,7 +71,7 @@ const App: React.FC = () => {
<Input
labelText="hsl"
colorType={colorTypes.hsl}
placeHolder="hsl(100, 100%, 50%)"
placeHolder="hsl(100 100% 50%)"
onChange={onInputChange}
incomingColor={incomingColor}
incomingColorType={incomingColorType}
Expand All @@ -80,7 +80,7 @@ const App: React.FC = () => {
<Input
labelText="hsla"
colorType={colorTypes.hsla}
placeHolder="hsla(100, 100%, 50%, 1)"
placeHolder="hsla(100 100% 50% / 1)"
onChange={onInputChange}
incomingColor={incomingColor}
incomingColorType={incomingColorType}
Expand Down
120 changes: 67 additions & 53 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useState, useEffect, ChangeEvent } from "react";

import { colorTypes } from '../utils/colorTypes';
import { colorTypes } from "../utils/colorTypes";
import { isLchOutOfRgbGamut } from "../utils/isLchOutOfRgbGamut";
import { isValidColor } from '../utils/isValidColor';
import { translatedColor } from '../utils/translatedColor';
import { isValidColor } from "../utils/isValidColor";
import { translatedColor } from "../utils/translatedColor";

type InputProps = {
labelText: string;
Expand All @@ -26,28 +26,33 @@ enum inputStates {
outOfFocusOutOfGamut = `outOfFocusOutOfGamut`,
}

const Input: React.FC<InputProps> = props => {
const Input: React.FC<InputProps> = (props) => {
const {
labelText, placeHolder, onChange, colorType, incomingColor, incomingColorType
labelText,
placeHolder,
onChange,
colorType,
incomingColor,
incomingColorType,
} = props;

const initInputState = () => {
// show the gamut warning on load
if (colorType === colorTypes.lch && isLchOutOfRgbGamut(incomingColor)) {
return inputStates.outOfFocusOutOfGamut
return inputStates.outOfFocusOutOfGamut;
} else {
return inputStates.outOfFocus
return inputStates.outOfFocus;
}
}
};

const [value, setValue] = useState(incomingColor);
const [inputState, setInputState] = useState(initInputState());

const localChangeHandler = (e:ChangeEvent<HTMLInputElement>) => {
const localChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
const changedValue = e.currentTarget.value;
setValue(changedValue);

if (isValidColor(changedValue, colorType)) {
if (isValidColor(changedValue, colorType)) {
if (colorType === colorTypes.lch && isLchOutOfRgbGamut(changedValue)) {
setInputState(inputStates.inFocusValidValueOutOfGamut);
} else {
Expand All @@ -57,80 +62,89 @@ const Input: React.FC<InputProps> = props => {
} else {
setInputState(inputStates.inFocusInvalidValue);
}
}
};

const blurHandler = (e:ChangeEvent<HTMLInputElement>) => {
const blurHandler = (e: ChangeEvent<HTMLInputElement>) => {
const changedValue = e.currentTarget.value;
if (isValidColor(changedValue, colorType)) {
setInputState(inputStates.outOfFocus);
} else {
setInputState(inputStates.onBlurInvalidValue);
}
}
};

const translatedIncomingColor = translatedColor(incomingColor, incomingColorType, colorType);
const translatedIncomingColor = translatedColor(
incomingColor,
incomingColorType,
colorType,
);

useEffect(() => {
if (inputState === inputStates.onBlurInvalidValue &&
translatedIncomingColor !== colorTypes.none &&
translatedIncomingColor !== value) {
if (
inputState === inputStates.onBlurInvalidValue &&
translatedIncomingColor !== colorTypes.none &&
translatedIncomingColor !== value
) {
setValue(translatedIncomingColor);
setInputState(inputStates.outOfFocus);
}
// disabling this because we only want to update when
// translatedIncomingColor changes, but not value or inputState
// eslint-disable-next-line react-hooks/exhaustive-deps
// disabling this because we only want to update when
// translatedIncomingColor changes, but not value or inputState
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [translatedIncomingColor]);

if (inputState === inputStates.outOfFocus &&
if (
inputState === inputStates.outOfFocus &&
translatedIncomingColor !== colorTypes.none &&
translatedIncomingColor !== value) {
translatedIncomingColor !== value
) {
setValue(translatedIncomingColor);
}

const showGamutWarning = inputState === inputStates.inFocusValidValueOutOfGamut || inputState === inputStates.outOfFocusOutOfGamut
const showGamutWarning =
inputState === inputStates.inFocusValidValueOutOfGamut ||
inputState === inputStates.outOfFocusOutOfGamut;

if (colorType === colorTypes.picker) {
return (
<div className="input-wrapper">
<label>
<span className="label-text">
{labelText}:
</span>
<div className="color-input-wrapper">
<label>
<span className="label-text">{labelText}:</span>
<div className="color-input-wrapper">
<input
type="color"
placeholder={placeHolder}
onChange={localChangeHandler}
onFocus={() => setInputState(inputStates.inFocus)}
onBlur={blurHandler}
value={value}
name={colorType}
/>
</div>
</label>
</div>
);
} else {
return (
<div className="input-wrapper">
<label>
<span className="label-text">{labelText}:</span>
<input
type="color"
type="text"
placeholder={placeHolder}
onChange={localChangeHandler}
onFocus={() => setInputState(inputStates.inFocus)}
onBlur={blurHandler}
value={value}
name={colorType}
/>
</div>
</label>
</div>
);
} else {
return (
<div className="input-wrapper">
<label>
<span className="label-text">
{labelText}:
</span>
<input
type="text"
placeholder={placeHolder}
onChange={localChangeHandler}
onFocus={() => setInputState(inputStates.inFocus)}
onBlur={blurHandler}
value={value}
name={colorType}
/>
</label>
{ showGamutWarning &&
<small className="gamut-warning">⚠️ This lch value is outside the RGB gamut; translated values are approximated</small>
}
</label>
{showGamutWarning && (
<small className="gamut-warning">
⚠️ This lch value is outside the RGB gamut; translated values are
approximated
</small>
)}
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const DEFAULT_COLOR = `rgba(255, 0, 0, 1)`; // red
const DEFAULT_COLOR = `rgba(255 0 0 / 1)`; // red
const ASSUMED_BACKGROUND_COLOR = [255, 255, 255]; // white

export { DEFAULT_COLOR, ASSUMED_BACKGROUND_COLOR }
8 changes: 4 additions & 4 deletions src/utils/formatColor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { formatColor } from './formatColor';

describe('formatColor()', () => {
it('formatColor returns correct string', () => {
expect(formatColor([255, 0, 0, 1], colorTypes.rgba)).toEqual('rgba(255, 0, 0, 1)');
expect(formatColor([255, 0, 0], colorTypes.rgba)).toEqual('rgba(255, 0, 0, 1)');
expect(formatColor([255, 0, 0], colorTypes.rgb)).toEqual('rgb(255, 0, 0)');
expect(formatColor([120, 60, 40], colorTypes.hsla)).toEqual('hsla(120, 60%, 40%, 1)');
expect(formatColor([255, 0, 0, 1], colorTypes.rgba)).toEqual('rgba(255 0 0 / 1)');
expect(formatColor([255, 0, 0], colorTypes.rgba)).toEqual('rgba(255 0 0 / 1)');
expect(formatColor([255, 0, 0], colorTypes.rgb)).toEqual('rgb(255 0 0)');
expect(formatColor([120, 60, 40], colorTypes.hsla)).toEqual('hsla(120 60% 40% / 1)');
});
});
8 changes: 4 additions & 4 deletions src/utils/formatColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { colorTypes } from "./colorTypes";
const formatColor = (arr: Array<number>, formatColorType: colorTypes): string => {
switch (true) {
case formatColorType === colorTypes.rgb:
return `rgb(${arr[0]}, ${arr[1]}, ${arr[2]})`;
return `rgb(${arr[0]} ${arr[1]} ${arr[2]})`;
case formatColorType === colorTypes.rgba:
return `rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, ${arr[3] ? arr[3] : 1})`;
return `rgba(${arr[0]} ${arr[1]} ${arr[2]} / ${arr[3] ? arr[3] : 1})`;
case formatColorType === colorTypes.hsl:
return `hsl(${arr[0]}, ${arr[1]}%, ${arr[2]}%)`;
return `hsl(${arr[0]} ${arr[1]}% ${arr[2]}%)`;
case formatColorType === colorTypes.hsla:
return `hsla(${arr[0]}, ${arr[1]}%, ${arr[2]}%, ${arr[3] ? arr[3] : 1})`;
return `hsla(${arr[0]} ${arr[1]}% ${arr[2]}% / ${arr[3] ? arr[3] : 1})`;
case formatColorType === colorTypes.lch:
return `lch(${arr[0]}% ${arr[1]} ${arr[2]}${arr[3] && arr[3] !== 100 ? ` / ${arr[3]}%` : ''})`;
default:
Expand Down
22 changes: 22 additions & 0 deletions src/utils/isValidColor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ describe('isValidRgb', () => {
expect(isValidRgb('rgb(255,255,255)')).toBe(true);
});

it('return true on valid modern syntax rgb values', () => {
expect(isValidRgb('rgb(255 255 255)')).toBe(true);
expect(isValidRgb('rgb(0 0 0)')).toBe(true);
expect(isValidRgb('rgb(100% 100% 100%)')).toBe(true);
});

it('return false on invalid rgb values', () => {
// expect(isValidRgb('rgb(200%, 200%, 200%)')).toBe(false);
expect(isValidRgb('rgb(100%, 255, 100%)')).toBe(false);
Expand All @@ -59,6 +65,12 @@ describe('isValidRgba', () => {
expect(isValidRgba('rgba(255,255,255,0.5)')).toBe(true);
});

it('return true on valid modern syntax rgba values', () => {
expect(isValidRgba('rgba(255 255 255 / 0.5)')).toBe(true);
expect(isValidRgba('rgba(0 0 0 / 1)')).toBe(true);
expect(isValidRgba('rgba(100% 100% 100% / 0.5)')).toBe(true);
});

it('return false on invalid rgba values', () => {
// expect(isValidRgba('rgb(200%, 200%, 200%)')).toBe(false);
expect(isValidRgba('rgb(100%, 100%, 100%)')).toBe(false);
Expand All @@ -74,6 +86,11 @@ describe('isValidHsl', () => {
expect(isValidHsl('hsl(100,100%,100%)')).toBe(true);
});

it('return true on valid modern syntax hsl values', () => {
expect(isValidHsl('hsl(100 100% 100%)')).toBe(true);
expect(isValidHsl('hsl(0 0% 0%)')).toBe(true);
});

it('return false on invalid hsl values', () => {
expect(isValidHsl('hsl(100%, 100%, 100%)')).toBe(false);
expect(isValidHsl('hsl(100%, 255, 100%)')).toBe(false);
Expand All @@ -86,6 +103,11 @@ describe('isValidHsla', () => {
expect(isValidHsla('hsla(100,100%,100%, 1)')).toBe(true);
});

it('return true on valid modern syntax hsla values', () => {
expect(isValidHsla('hsla(100 100% 100% / 0.5)')).toBe(true);
expect(isValidHsla('hsla(0 0% 0% / 1)')).toBe(true);
});

it('return false on invalid hsla values', () => {
expect(isValidHsla('hsla(100%, 100%, 100%)')).toBe(false);
expect(isValidHsla('hsla (100%, 255, 100%, 1)')).toBe(false);
Expand Down
Loading
Loading