Compare commits

...
Author SHA1 Message Date
Tanish Manku 12bc70bac7 Add Background button and menu to toolbar 2025-02-25 18:22:29 -08:00
5 changed files with 229 additions and 2 deletions
@@ -5,6 +5,7 @@ import {
faEraser,
faFilePlus,
faImage,
faImages,
faLocationArrow,
faPaintbrush,
faShapes,
@@ -27,6 +28,8 @@ import { paintColor } from "~/signals/uiEvents/toolbarMain/paintMode";
import { ColorPicker } from "~/components/ui/TextEditor/ColorPicker";
import { PaintModeMenu } from "~/components/ui/ToolbarMain/PaintModeMenu";
import { EraseModeMenu } from "~/components/ui/ToolbarMain/EraseModeMenu";
import { BackgroundMenu } from "~/components/ui/ToolbarMain/BackgroundMenu";
import { bgColor } from "~/signals/uiEvents/toolbarMain/backgroundMenu";
export const ToolbarMain = ({
disabled = false,
@@ -226,6 +229,31 @@ export const ToolbarMain = ({
tooltip="Save"
/> */}
</div>
<hr className="w-full border-t border-white/15" />
<div className="flex flex-col items-center gap-2">
<div className="relative">
<BackgroundMenu
color={bgColor.value}
onChange={dispatchUiEvents.toolbarMain.setBgColor}
faIcon={faImage}
borderStyle="bg-transparent hover:bg-white/15 text-white"
showBar={false}
staticIconColor="white"
streamChanges
defaultOpen={false}
anchor="right"
anchorGap={12}
closeOnMouseLeave={true}
/>
<div
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
style={{ backgroundColor: bgColor.value }}
/>
</div>
</div>
</div>
</>
);
@@ -15,4 +15,5 @@ export enum ToolbarMainButtonNames {
SAVE = "SAVE",
PREVIEW = "PREVIEW",
DOWNLOAD = "DOWNLOAD",
BACKGROUND = "BACKGROUND",
}
@@ -0,0 +1,180 @@
import {
faFont,
faHashtag,
IconDefinition,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { ChangeEvent, useEffect, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "~/components/styles";
import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } from "~/signals/uiEvents/toolbarMain/paintMode";
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
export const BackgroundMenu = ({
color: prevColor,
onChange,
faIcon = faFont,
borderStyle,
showBar = true,
anchor = "bottom",
anchorGap = 0,
fillBg = false,
staticIconColor,
streamChanges = false,
defaultOpen = false,
closeOnMouseLeave = false,
}: {
color: string;
onChange: (newColor: string) => void;
faIcon?: IconDefinition;
borderStyle?: string;
showBar?: boolean;
anchor?: "top" | "right" | "bottom" | "left";
anchorGap?: number;
fillBg?: boolean;
staticIconColor?: string;
streamChanges?: boolean;
defaultOpen?: boolean;
closeOnMouseLeave?: boolean;
}) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
const [{ currColor, textInput }, setStates] = useState<{
currColor: string;
textInput: string;
}>({
currColor: prevColor,
textInput: prevColor.substring(1),
});
useEffect(() => {
setIsOpen(defaultOpen);
}, [defaultOpen]);
const handleMouseLeave = (close: () => void) => {
if (closeOnMouseLeave) {
onChange(currColor);
setIsOpen(false);
close();
}
};
const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {
if (isHexColorCode(e.target.value)) {
setStates({
currColor: `#${e.target.value}`,
textInput: e.target.value,
});
} else {
setStates((curr) => ({
...curr,
textInput: e.target.value,
}));
}
};
const isHexColorCode = (color: string) => {
const hexColorRegex = /^(?:[0-9a-fA-F]{3}){1,2}$/;
return hexColorRegex.test(color);
};
// Rerender the component when these signals change:
useSignals();
return (
<Popover className="relative">
{({ open, close }) => (
<>
<PopoverButton
className={twMerge(
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2",
borderStyle,
showBar ? "flex-col" : "justify-center",
)}
style={fillBg ? { backgroundColor: prevColor } : {}}
onMouseEnter={() => defaultOpen && setIsOpen(true)}
>
<FontAwesomeIcon
icon={faIcon}
color={staticIconColor ?? (fillBg ? undefined : prevColor)}
/>
{showBar && (
<span
className="h-1 w-full"
style={{ backgroundColor: prevColor }}
/>
)}
</PopoverButton>
{(open || isOpen) && (
<div onMouseLeave={() => handleMouseLeave(close)}>
<PopoverPanel
anchor={anchor}
className={twMerge(
paperWrapperStyles,
"flex flex-col items-center gap-2 overflow-hidden",
)}
style={
{
"--anchor-gap": `${anchorGap}px`,
} as React.CSSProperties
}
static
>
<HexColorPicker
className="overflow-hidden"
color={currColor}
onChange={(color) => {
setStates({
currColor: color,
textInput: color.substring(1),
});
if (streamChanges) {
onChange(color);
}
}}
/>
<Input
style={{ width: "198px" }}
icon={faHashtag}
value={textInput}
onChange={handleTextInput}
/>
{!streamChanges && (
<div className="flex w-full justify-center gap-2">
<Button
variant="secondary"
onClick={() => {
setStates({
currColor: prevColor,
textInput: prevColor.substring(1),
});
setIsOpen(false);
close();
}}
>
Cancel
</Button>
<Button
onClick={() => {
onChange(currColor);
setIsOpen(false);
close();
}}
>
OK
</Button>
</div>
)}
</PopoverPanel>
</div>
)}
</>
)}
</Popover>
);
};
@@ -0,0 +1,15 @@
import { signal, effect } from "@preact/signals-react";
export const DEFAULT_BG_COLOR: string = "#ffffff";
export const bgColor = signal<string>(DEFAULT_BG_COLOR);
export const setBgColor = (data: string) => {
bgColor.value = data;
};
export const onBgColorChanged = (callback: (data: string) => void) => {
effect(() => {
if (bgColor.value) {
callback(bgColor.value);
}
});
};
@@ -5,16 +5,19 @@ import {
} from "./loadingBar";
import { onEraseBrushSizeChanged, setEraseBrushSize } from "./eraseMode";
import { onPaintColorChanged, setPaintBrushSize, setPaintColor } from "./paintMode";
import { onBgColorChanged, setBgColor } from "./backgroundMenu";
export const dispatchers = {
...buttonDispatchers,
loadingBarRetry: loadingBarRetryDispatch,
setPaintColor,
setPaintBrushSize,
setEraseBrushSize
setEraseBrushSize,
setBgColor
};
export const eventsHandlers = {
...buttonEventsHandlers,
loadingBarRetry: loadingBarRetryEventHandler,
onPaintColorChanged,
onEraseBrushSizeChanged
onEraseBrushSizeChanged,
onBgColorChanged
};