"use client"; import { AnimatePresence, motion } from "motion/react"; import { FrameMode, Palette } from "../lib/tokens"; import { IconBtn, Segmented, TidyButton, TidyState } from "./ui"; import { ShareButton } from "./ShareMenu"; import { Icon } from "./M3Node"; import { Popover } from "./Menus"; import { t, useLang } from "../lib/i18n"; export type Mode = "select" | "hand"; const REPO_URL = "https://github.com/lnkiai/m3e-canvas"; export function GitHubLink({ p, size = 40 }: { p: Palette; size?: number }) { return ( ); } function Pill({ p, children }: { p: Palette; children: React.ReactNode }) { return (
{children}
); } export function Toolbar({ p, mode, onMode, frame, zoom, onZoom, onFit, canUndo, canRedo, onUndo, onRedo, onClear, onAddFrame, onPreview, rightInset, mobile, onPrompt, onSettings, onLangSheet, tidy, onTidy, note, onSaveProject, onOpenProject, onShare, shareState = "idle", onDraftKeep, onDraftUndo, onDraftSave, quickUndo, }: { p: Palette; mode: Mode; onMode: (m: Mode) => void; frame: FrameMode; onFrame: (f: FrameMode) => void; zoom: number; onZoom: (z: number) => void; onFit: () => void; canUndo: boolean; canRedo: boolean; onUndo: () => void; onRedo: () => void; onClear: () => void; onAddFrame: () => void; onPreview: () => void; /** width of the open right panel, so the zoom pill slides out of its way */ rightInset: number; mobile?: boolean; onPrompt?: () => void; onSettings?: () => void; /** phone: open the language sheet instead of the menu */ onLangSheet?: () => void; /** the tidy button for the screen being worked on; absent when no screen is in play */ tidy?: TidyState; onTidy?: () => void; /** a short message shown beside the tidy button for a moment */ note?: { text: string; icon: string } | null; onSaveProject?: () => void; onOpenProject?: () => void; /** opens the "ask an AI" dialog from the left end of the zoom row */ onShare?: () => void; /** busy while a model drafts; review while the draft waits to be kept or undone */ shareState?: "idle" | "busy" | "review"; onDraftKeep?: () => void; onDraftUndo?: () => void; /** saves the arrived design as a project file, the same as the header's save */ onDraftSave?: () => void; /** after a kept draft: the header's undo, shown beside the opener so the previous design stays a tap away */ quickUndo?: boolean; }) { const lang = useLang(); if (mobile) { const S = 42; return (
); } return ( <>
{note && ( {note.text} )}
{tidy && onTidy && ( )} onZoom(zoom / 1.2)} title={t("zoomOut", lang)} size={40} /> onZoom(zoom * 1.2)} title={t("zoomIn", lang)} size={40} />
{onShare && (
{shareState === "review" && onDraftUndo && onDraftKeep ? ( /* the draft waits for a verdict: undo sits quietly, keep is the primary action */ [ { icon: "undo", title: t("draftUndo", lang), onClick: onDraftUndo, primary: false }, { icon: "check", title: t("draftKeep", lang), onClick: onDraftKeep, primary: true }, ...(onDraftSave ? [{ icon: "download", title: t("saveProject", lang), onClick: onDraftSave, primary: false }] : []), ].map((b) => ( )) ) : ( <> {quickUndo && canUndo && } )}
)}
options={[ { key: "select", icon: "arrow_selector_tool", title: t("select", lang), }, { key: "hand", icon: "pan_tool", title: t("hand", lang) }, ]} value={mode} onChange={onMode} p={p} height={40} grow={false} /> {frame === "phone" && ( )} {onSaveProject && onOpenProject && ( {(close) => (
{ close(); onSaveProject(); }} /> { close(); onOpenProject(); }} />
)}
)}
); }