Compare commits

...
Author SHA1 Message Date
Tanish Manku 8b824db1e5 Add an eyedrop to all color pickers 2025-02-26 02:34:59 -08:00
4 changed files with 136 additions and 33 deletions
@@ -0,0 +1,87 @@
import { MouseEvent, MouseEventHandler, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { EyeDropper } from "react-eyedrop";
import { Button } from "./Button";
import { faEyedropper } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
type ColorPickerHTMLAttributes = Omit<React.HTMLAttributes<HTMLDivElement>, "color" | "onChange" | "onChangeCapture">;
interface ColorPickerBaseProps<T extends string> extends ColorPickerHTMLAttributes {
color: T;
onChange: (newColor: T) => void;
}
export const HexEyedropPicker = ({
color,
onDropperChange,
dropperClassName,
pickerClassName,
onPickerChange,
Picker
}: {
color: string;
onDropperChange?: (newColor: string) => void;
dropperClassName?: string;
pickerClassName?: string;
onPickerChange: (newColor: string) => void;
Picker: (props: Partial<ColorPickerBaseProps<string>>) => JSX.Element;
}) => {
const handleDropperClick = () => {
if (!('EyeDropper' in window)) {
// NOTE: This shouldn't happen cuz we're using Chromium
console.error("EyeDropper not found in window");
return;
}
const startEyeDrop = async (): Promise<string | null> => {
// @ts-ignore-next-line
const dropper = new window.EyeDropper();
try {
const result = await dropper.open();
console.log(result);
return result.sRGBHex;
} catch (err) {
// User cancelled the picker
return null;
}
}
startEyeDrop().then((newColor) => {
if (newColor) {
onDropperChange?.(newColor);
}
});
}
const [isDragging, setIsDragging] = useState(false);
const handleOnMouseDown = () => {
setIsDragging(true);
}
const handleOnMouseUp = () => {
setIsDragging(false);
}
return (
<div className={"relative"}>
<Picker
className={pickerClassName}
color={color}
onChange={onPickerChange}
onMouseDown={handleOnMouseDown}
onMouseUp={handleOnMouseUp}
/>
<Button
className={twMerge([
"rounded-full m-1 size-8 text-ui-controls bg-transparent hover:bg-black/70 hover:text-white z-20 shadow-none hover:shadow-xl duration-200",
dropperClassName,
"absolute left-0 top-0",
isDragging ? "opacity-0 -z-10" : ""
])}
icon={faEyedropper}
onClick={handleDropperClick}
/>
</div>
)
}
@@ -12,6 +12,7 @@ import { twMerge } from "tailwind-merge";
import { paperWrapperStyles } from "~/components/styles";
import { Button } from "../Button";
import { Input } from "../Input";
import { HexEyedropPicker } from "../HexEyedropPicker";
export const ColorPicker = ({
color: prevColor,
@@ -82,6 +83,16 @@ export const ColorPicker = ({
return hexColorRegex.test(color);
};
const handleColorChange = (color: string) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}
return (
<Popover className="relative">
{({ open, close }) => (
@@ -122,18 +133,12 @@ export const ColorPicker = ({
}
static
>
<HexAlphaColorPicker
className="overflow-hidden"
<HexEyedropPicker
pickerClassName="overflow-hidden"
color={currColor}
onChange={(color) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}}
onPickerChange={handleColorChange}
onDropperChange={handleColorChange}
Picker={HexAlphaColorPicker}
/>
<Input
style={{ width: "198px" }}
@@ -15,6 +15,8 @@ import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } fro
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
import { EyeDropper } from "react-eyedrop";
import { HexEyedropPicker } from "../HexEyedropPicker";
export const BackgroundMenu = ({
color: prevColor,
@@ -83,6 +85,16 @@ export const BackgroundMenu = ({
return hexColorRegex.test(color);
};
const handleColorChange = (color: string) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}
// Rerender the component when these signals change:
useSignals();
@@ -125,18 +137,12 @@ export const BackgroundMenu = ({
}
static
>
<HexColorPicker
className="overflow-hidden"
<HexEyedropPicker
pickerClassName="overflow-hidden"
color={currColor}
onChange={(color) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}}
onPickerChange={handleColorChange}
onDropperChange={handleColorChange}
Picker={HexColorPicker}
/>
<Input
style={{ width: "198px" }}
@@ -15,6 +15,7 @@ import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } fro
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
import { HexEyedropPicker } from "../HexEyedropPicker";
export const PaintModeMenu = ({
color: prevColor,
@@ -83,6 +84,16 @@ export const PaintModeMenu = ({
return hexColorRegex.test(color);
};
const handleColorChange = (color: string) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}
// Rerender the component when these signals change:
useSignals();
const brushSize = paintBrushSize.value;
@@ -126,18 +137,12 @@ export const PaintModeMenu = ({
}
static
>
<HexAlphaColorPicker
className="overflow-hidden"
<HexEyedropPicker
pickerClassName="overflow-hidden"
color={currColor}
onChange={(color) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}}
onPickerChange={handleColorChange}
onDropperChange={handleColorChange}
Picker={HexAlphaColorPicker}
/>
<Input
style={{ width: "198px" }}