- Adjusted button styles across various components for consistency and better UX. - Enhanced layout of action controls to utilize whitespace more effectively. - Updated slider styles for a more modern appearance and improved usability. - Refined input fields and labels for better accessibility and readability. - Introduced new app surface styles for a cohesive design across the application. - Added tests for canvas cursor behavior to ensure correct cursor display during operations.
105 lines
4.6 KiB
TypeScript
105 lines
4.6 KiB
TypeScript
import { Eraser, PaintBrush } from "@phosphor-icons/react";
|
|
import { commandIds } from "@commands/ids";
|
|
import type { AppStore } from "@editor/store";
|
|
import type { MaskViewMode } from "@editor/state";
|
|
import type { BrushSettings, ToolId } from "@editor/tools";
|
|
import { BottomControlColorPicker } from "./ColorPicker";
|
|
import { BottomControlDivider } from "./Divider";
|
|
import { BottomControlSelectMenu, type BottomControlSelectOption } from "./SelectMenu";
|
|
import { BottomControlSlider } from "./Slider";
|
|
import { bottomControlFieldClass, bottomControlIconSlotClass, bottomControlLabelClass, bottomControlMenuClass } from "./styles";
|
|
|
|
export type BrushControlsProps = {
|
|
tool: Extract<ToolId, "brush" | "eraser">;
|
|
settings: BrushSettings;
|
|
editingMask?: boolean;
|
|
maskViewMode?: MaskViewMode;
|
|
dispatch: AppStore["dispatch"];
|
|
};
|
|
|
|
const maskViewModeOptions = [
|
|
{ value: "composite", label: "Image" },
|
|
{ value: "blackWhite", label: "B/W" },
|
|
{ value: "alpha", label: "Alpha" },
|
|
{ value: "overlay", label: "Overlay" },
|
|
] satisfies readonly BottomControlSelectOption<MaskViewMode>[];
|
|
|
|
export function BrushControls({ tool, settings, editingMask = false, maskViewMode = "composite", dispatch }: BrushControlsProps) {
|
|
return (
|
|
<div className={bottomControlMenuClass()}>
|
|
<span className={bottomControlIconSlotClass()} title={editingMask ? `Mask ${tool === "eraser" ? "hide" : "reveal"}` : tool === "eraser" ? "Eraser" : "Brush"}>
|
|
{tool === "eraser" ? <Eraser size={24} weight="regular" /> : <PaintBrush size={24} weight="regular" />}
|
|
</span>
|
|
<BottomControlDivider />
|
|
{editingMask ? (
|
|
<>
|
|
<button type="button" className={maskModeButtonClass(tool === "brush")} aria-pressed={tool === "brush"} onClick={() => dispatch(commandIds.toolSetActive, { tool: "brush" })}>Reveal</button>
|
|
<button type="button" className={maskModeButtonClass(tool === "eraser")} aria-pressed={tool === "eraser"} onClick={() => dispatch(commandIds.toolSetActive, { tool: "eraser" })}>Hide</button>
|
|
<BottomControlDivider />
|
|
</>
|
|
) : null}
|
|
{tool === "brush" && !editingMask ? (
|
|
<label className={bottomControlFieldClass()}>
|
|
<span className={bottomControlLabelClass()}>Color</span>
|
|
<BottomControlColorPicker
|
|
value={settings.color}
|
|
aria-label="Brush color"
|
|
onValueChange={(color) => dispatch(commandIds.toolSetBrushSettings, { color })}
|
|
/>
|
|
</label>
|
|
) : null}
|
|
<label className={bottomControlFieldClass()}>
|
|
<span className={bottomControlLabelClass()}>Size</span>
|
|
<BottomControlSlider
|
|
min={1}
|
|
max={200}
|
|
value={settings.size}
|
|
className="min-w-10 w-[clamp(2.5rem,9vw,7rem)] shrink"
|
|
aria-label={`${tool} size`}
|
|
onValueChange={(size) => dispatch(commandIds.toolSetBrushSettings, { size })}
|
|
/>
|
|
<span className="w-9 text-right text-xs font-medium text-white/85">{Math.round(settings.size)}</span>
|
|
</label>
|
|
<BottomControlDivider />
|
|
<label className={bottomControlFieldClass()}>
|
|
<span className={bottomControlLabelClass()}>Hard</span>
|
|
<BottomControlSlider
|
|
min={0}
|
|
max={100}
|
|
value={settings.hardness}
|
|
className="min-w-10 w-[clamp(2.5rem,9vw,7rem)] shrink"
|
|
aria-label={`${tool} hardness`}
|
|
onValueChange={(hardness) => dispatch(commandIds.toolSetBrushSettings, { hardness })}
|
|
/>
|
|
<span className="w-9 text-right text-xs font-medium text-white/85">{Math.round(settings.hardness)}</span>
|
|
</label>
|
|
{editingMask ? (
|
|
<>
|
|
<BottomControlDivider />
|
|
<label className={bottomControlFieldClass()}>
|
|
<span className={bottomControlLabelClass()}>View</span>
|
|
<BottomControlSelectMenu
|
|
value={maskViewMode}
|
|
options={maskViewModeOptions}
|
|
aria-label="Mask view mode"
|
|
onValueChange={(mode) => dispatch(commandIds.toolSetMaskViewMode, { mode })}
|
|
/>
|
|
</label>
|
|
<BottomControlDivider />
|
|
<button
|
|
type="button"
|
|
className="rounded-md bg-sky-300 px-3 py-1.5 text-xs font-semibold text-slate-950 transition hover:bg-sky-200"
|
|
onClick={() => dispatch(commandIds.toolExitMaskEdit, undefined)}
|
|
>
|
|
Done
|
|
</button>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function maskModeButtonClass(active: boolean) {
|
|
return `rounded-md px-3 py-1.5 text-xs font-medium transition ${active ? "bg-sky-300 text-slate-950" : "bg-white/[0.06] text-white/70 hover:bg-white/10"}`;
|
|
}
|