Files
SnapOtter/apps/web/src/components/editor/editor-toolbar.tsx
T
SnapOtter e22ffa4ec9 fix: editor native layout, UI polish, meme templates auth, pipeline export, install queuing
- Remove editor from AppLayout so it owns the full viewport with no sidebar/padding
- Add back-arrow in menu bar for navigation, restyle menu bar to match native app feel
- Slim left toolbar from 48px to 36px with smaller icons and subtler dividers
- Fix options bar overlapping canvas via shrink-0 and overflow-hidden
- Fix opacity/blend controls clipping in right panel with min-w-0 on flex children
- Add global range slider CSS for consistent thin-track + round-thumb styling
- Make meme template routes public so img tags can load without Bearer auth
- Show pipeline export buttons in default compact view, not just expanded hover
- Queue individual AI feature installs instead of erroring when one is active
2026-05-08 23:30:27 +08:00

138 lines
3.8 KiB
TypeScript

// apps/web/src/components/editor/editor-toolbar.tsx
//
// Toolbar layout, icons, and shortcuts follow the standard Photoshop
// convention so users coming from other editors feel at home.
import {
Blend,
BoxSelect,
Crop,
Droplet,
Droplets,
Eraser,
Fingerprint,
Flame,
Hand,
Hexagon,
Lasso,
Maximize2,
MousePointer2,
PaintBucket,
Paintbrush,
Pencil,
Pipette,
Stamp,
Sun,
Triangle,
Type,
Wand2,
ZoomIn,
} from "lucide-react";
import { useEditorStore } from "@/stores/editor-store";
import type { ToolType } from "@/types/editor";
import { IconButton } from "./common/icon-button";
interface ToolGroup {
tools: {
tool: ToolType;
icon: typeof MousePointer2;
label: string;
shortcut: string;
}[];
}
const TOOL_GROUPS: ToolGroup[] = [
{
tools: [
{ tool: "move", icon: MousePointer2, label: "Move", shortcut: "V" },
{ tool: "transform", icon: Maximize2, label: "Free Transform", shortcut: "Ctrl+T" },
],
},
{
tools: [
{ tool: "marquee-rect", icon: BoxSelect, label: "Marquee", shortcut: "M" },
{ tool: "lasso-free", icon: Lasso, label: "Lasso", shortcut: "L" },
{ tool: "magic-wand", icon: Wand2, label: "Magic Wand", shortcut: "W" },
],
},
{
tools: [
{ tool: "crop", icon: Crop, label: "Crop", shortcut: "C" },
{ tool: "eyedropper", icon: Pipette, label: "Eyedropper", shortcut: "I" },
],
},
{
tools: [
{ tool: "brush", icon: Paintbrush, label: "Brush", shortcut: "B" },
{ tool: "pencil", icon: Pencil, label: "Pencil", shortcut: "N" },
],
},
{
tools: [{ tool: "clone-stamp", icon: Stamp, label: "Clone Stamp", shortcut: "S" }],
},
{
tools: [{ tool: "eraser", icon: Eraser, label: "Eraser", shortcut: "E" }],
},
{
tools: [
{ tool: "fill", icon: PaintBucket, label: "Paint Bucket", shortcut: "G" },
{ tool: "gradient", icon: Blend, label: "Gradient", shortcut: "Shift+G" },
],
},
{
tools: [
{ tool: "blur-brush", icon: Droplet, label: "Blur", shortcut: "" },
{ tool: "sharpen-brush", icon: Triangle, label: "Sharpen", shortcut: "" },
{ tool: "smudge", icon: Fingerprint, label: "Smudge", shortcut: "" },
],
},
{
tools: [
{ tool: "dodge", icon: Sun, label: "Dodge", shortcut: "O" },
{ tool: "burn", icon: Flame, label: "Burn", shortcut: "Shift+O" },
{ tool: "sponge", icon: Droplets, label: "Sponge", shortcut: "Shift+O" },
],
},
{
tools: [{ tool: "shape-rect", icon: Hexagon, label: "Shape", shortcut: "U" }],
},
{
tools: [{ tool: "text", icon: Type, label: "Text", shortcut: "T" }],
},
{
tools: [
{ tool: "hand", icon: Hand, label: "Hand", shortcut: "H" },
{ tool: "zoom", icon: ZoomIn, label: "Zoom", shortcut: "Z" },
],
},
];
export function EditorToolbar() {
const activeTool = useEditorStore((s) => s.activeTool);
const setTool = useEditorStore((s) => s.setTool);
const sourceImageUrl = useEditorStore((s) => s.sourceImageUrl);
return (
<div className="flex flex-col items-center w-9 bg-card border-r border-border py-1.5 gap-0 overflow-y-auto">
{TOOL_GROUPS.map((group, gi) => (
<div key={group.tools[0].tool}>
{gi > 0 && <div className="w-4 h-px bg-border/50 mx-auto my-0.5" />}
{group.tools.map((t) => (
<IconButton
key={t.tool}
icon={t.icon}
label={t.label}
shortcut={t.shortcut}
active={activeTool === t.tool}
disabled={!sourceImageUrl && t.tool !== "hand" && t.tool !== "zoom"}
onClick={() => setTool(t.tool)}
data-testid={`tool-${t.tool}`}
data-tool={t.tool}
data-tool-active={String(activeTool === t.tool)}
/>
))}
</div>
))}
</div>
);
}