refactor: unify floating toolbar sizing and show graph title

This commit is contained in:
2026-11-06 00:59:51 +08:00
parent d4b45c5051
commit 4ad8655a88
11 changed files with 106 additions and 33 deletions
+22
View File
@@ -0,0 +1,22 @@
"use client";
import { createContext, useContext, useMemo, useState, type Dispatch, type ReactNode, type SetStateAction } from "react";
import type { Board } from "@/lib/workspace";
type ActiveBoard = Pick<Board, "id" | "workspace_id" | "title"> | null;
const ActiveBoardContext = createContext<{
board: ActiveBoard;
setBoard: Dispatch<SetStateAction<ActiveBoard>>;
} | null>(null);
export function ActiveBoardProvider({ children }: { children: ReactNode }) {
const [board, setBoard] = useState<ActiveBoard>(null);
const value = useMemo(() => ({ board, setBoard }), [board]);
return <ActiveBoardContext.Provider value={value}>{children}</ActiveBoardContext.Provider>;
}
export function useActiveBoard() {
const context = useContext(ActiveBoardContext);
if (!context) throw new Error("useActiveBoard requires ActiveBoardProvider");
return context;
}
+9
View File
@@ -6,11 +6,13 @@ import { api, errorText } from "@/lib/api";
import { readDemoBoards } from "@/lib/demo-workspace";
import type { Board } from "@/lib/workspace";
import styles from "./workspace.module.css";
import { useActiveBoard } from "./active-board";
import GraphCanvas from "@/components/canvas/graph-canvas";
export default function GraphPage({ workspaceId, boardId }: { workspaceId: string; boardId: string }) {
const demo = useSearchParams().get("demo") === "1";
const router = useRouter();
const { setBoard: setActiveBoard } = useActiveBoard();
const [board, setBoard] = useState<Board | null>(null);
const [error, setError] = useState("");
@@ -45,6 +47,13 @@ export default function GraphPage({ workspaceId, boardId }: { workspaceId: strin
return () => { active = false; controller.abort(); };
}, [workspaceId, boardId, demo, router]);
useEffect(() => {
if (board?.id === boardId && board.workspace_id === workspaceId) {
setActiveBoard({ id: board.id, workspace_id: board.workspace_id, title: board.title });
}
return () => setActiveBoard(null);
}, [board, boardId, workspaceId, setActiveBoard]);
return (
<main className={styles.graph} aria-label={board ? board.title + " · 无限图谱" : "无限图谱"} aria-busy={!board && !error}>
{board ? <GraphCanvas key={workspaceId + ":" + boardId} board={board} /> : <div className={styles.graphSurface} aria-label="正在读取画布" />}
@@ -94,7 +94,7 @@
.newWorkspace { color: #2383e2; }
.logout { color: #78746d; font-size: 12px; }
.separator { height: 1px; margin: 6px 7px; background: #eeece8; }
.floating { position: fixed; left: 12px; top: 12px; z-index: 40; display: flex; gap: 6px; padding: 6px; background: #fffefa; border: 1px solid #e7e2db; border-radius: 8px; box-shadow: 0 3px 14px #3c312b0d; color: #85817b; }
.floating { position: fixed; left: 12px; top: 12px; max-width: calc(100vw - 24px); z-index: 40; }
.floatingError { position: absolute; left: 0; top: 42px; width: 240px; padding: 10px; background: white; border: 1px solid #e7e2db; border-radius: 8px; color: #b14b3c; font-size: 12px; }
.settingsPanel { color: #37352f; border-color: #e6e2dc; border-radius: 12px; }
.settingsForm { display: flex; flex-direction: column; gap: 16px; padding-top: 4px; }
@@ -112,11 +112,15 @@
@media (max-width: 600px) { .shell { --sidebar-width: 240px; } }
@media (prefers-reduced-motion: reduce) { .accountPopup:popover-open { animation: none; } }
.logoLink { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 28px; height: 24px; padding: 4px 6px; border-radius: 5px; }
.logoLink img { display: block; width: 16px; height: 16px; }
.logoLink { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; box-sizing: border-box; width: var(--toolbar-control-width, 28px); height: var(--toolbar-control-height, 24px); padding: 4px 6px; border-radius: 5px; }
.logoLink img { display: block; width: var(--toolbar-icon-size, 16px); height: var(--toolbar-icon-size, 16px); }
.logoLink:hover { background: #e9e7e3; }
.logoLink:focus-visible { outline: 2px solid #9d7d66; outline-offset: 2px; }
.floatingAvatarButton { display: grid; place-items: center; width: 28px; height: 24px; border-radius: 5px; }
.floatingAvatarButton { display: grid; place-items: center; flex: 0 0 auto; box-sizing: border-box; width: var(--toolbar-control-width, 28px); height: var(--toolbar-control-height, 24px); border-radius: 5px; }
.floatingAvatarButton:hover { background: #e9e7e3; }
.accountPopup[data-floating="true"] { inset: 56px auto auto 12px; max-height: calc(100dvh - 70px); transform-origin: top left; }
.floatingAvatarButton .avatar { flex-basis: var(--toolbar-icon-size, 16px); width: var(--toolbar-icon-size, 16px); height: var(--toolbar-icon-size, 16px); font-size: 9px; }
.floatingTitle { flex: 1 1 auto; min-width: 0; max-width: 240px; margin: 0 4px; color: #37352f; font-size: 12px; line-height: 24px; font-weight: 500; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
@media (max-width: 700px) { .floatingTitle { max-width: 180px; } }