ColorInput: add throttling and update style to match spec
This patch adds throttling to events emitted by the native color-picker, preventing noticeable lag caused by rapid state updates. It is currently throttled to one event per 100ms, but we may want to play with this value in the future! For not it feels smooth.
This commit is contained in:
46
src/components/ColorInput/ColorInput.css
Normal file
46
src/components/ColorInput/ColorInput.css
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.color-picker {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 112px;
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker span {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.color-input {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 96%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 0 0 0 4px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.color-input::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.color-input::-webkit-color-swatch {
|
||||||
|
border: none;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.color-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
@@ -1,29 +1,37 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
|
import TinyColor from "tinycolor2";
|
||||||
|
|
||||||
import { iconColorAtom } from "../../state/atoms";
|
import { iconColorAtom } from "../../state/atoms";
|
||||||
|
import useThrottled from "../../hooks/useThrottled";
|
||||||
|
import "./ColorInput.css";
|
||||||
|
|
||||||
type ColorInputProps = {};
|
type ColorInputProps = {};
|
||||||
|
|
||||||
const ColorInput: React.FC<ColorInputProps> = () => {
|
const ColorInput: React.FC<ColorInputProps> = () => {
|
||||||
const [color, setColor] = useRecoilState(iconColorAtom);
|
const [color, setColor] = useRecoilState(iconColorAtom);
|
||||||
|
const isDark = TinyColor(color).isDark();
|
||||||
|
|
||||||
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const {
|
const {
|
||||||
target: { value: color },
|
target: { value: color },
|
||||||
} = event;
|
} = event;
|
||||||
if (color[0] === "#") setColor(color);
|
if (color[0] === "#") setColor(color);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const throttledColorChange = useThrottled(handleColorChange, 100, [handleColorChange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="color-picker">
|
||||||
<input
|
<input
|
||||||
id="color-picker"
|
className="color-input"
|
||||||
aria-label="Icon Color"
|
aria-label="Icon Color"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
type="color"
|
type="color"
|
||||||
onChange={handleColorChange}
|
onChange={throttledColorChange}
|
||||||
value={color}
|
value={color}
|
||||||
/>
|
/>
|
||||||
|
<span style={{ color: isDark ? "white" : "black" }}>{color}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
31
src/hooks/useThrottled.ts
Normal file
31
src/hooks/useThrottled.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { useRef, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
type Callback = (...args: any) => void;
|
||||||
|
|
||||||
|
export default (
|
||||||
|
callback: Callback,
|
||||||
|
delay: number,
|
||||||
|
dependencies: any[] = []
|
||||||
|
) => {
|
||||||
|
const throttleRef = useRef<Boolean>(false);
|
||||||
|
const callbackRef = useRef<Callback>(callback);
|
||||||
|
|
||||||
|
// Update the callback to be used, if it ever changes
|
||||||
|
useEffect(() => {
|
||||||
|
callbackRef.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
return useCallback<Callback>(
|
||||||
|
(...args) => {
|
||||||
|
if (throttleRef.current) return;
|
||||||
|
|
||||||
|
throttleRef.current = true;
|
||||||
|
callbackRef.current(...args);
|
||||||
|
setTimeout(() => {
|
||||||
|
throttleRef.current = false;
|
||||||
|
}, delay);
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[delay, ...dependencies]
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user