feat: 统一图表悬浮展开入口

This commit is contained in:
tianma-if committed 2026-09-06 17:06:18 +08:00
1 parent 67d561a408
commit cd4e0df1a3
4 files changed
+67 -15

No files matched your search

@@ -115,6 +115,7 @@ describe("diagram editor canvas surface", () => {
test("organizes architecture resources in a searchable category library", () => {
expect(source).toContain("ARCHITECTURE_LIBRARY_CATEGORIES");
expect(source).toContain('t("diagram.componentLibrary")');
expect(source).toContain('onPointerEnter={() => setOpen(true)}');
expect(source).toContain('t("diagram.componentSearch")');
expect(source).toContain('labelKey: "diagram.componentCategoryExperience"');
expect(source).toContain('labelKey: "diagram.componentCategoryServices"');
@@ -141,6 +142,16 @@ describe("diagram editor canvas surface", () => {
expect(source).not.toContain('className="line-clamp-2"');
});
test("opens every diagram insertion library immediately on pointer hover", () => {
expect(source).toContain("const DiagramInsertMenu = ({");
expect(source.match(/onPointerEnter=\{\(\) => setOpen\(true\)\}/g)?.length).toBe(2);
expect(source).toContain('label={t("diagram.addTopic")}');
expect(source).toContain('label: t("diagram.addSiblingTopic")');
expect(source).toContain('label={t("diagram.addStep")}');
expect(source).toContain('label: t("diagram.addDecision")');
expect(source).toContain('label: t("diagram.addTerminator")');
});
test("shows connection handles only on selected flowchart nodes", () => {
expect(globalStyles).toContain('.x6-node.edgeever-flow-node-active .edgeever-flow-port-dot');
expect(globalStyles).toContain('.x6-port-body:hover .edgeever-flow-port-dot');
+54 -15
View File
@@ -322,7 +322,9 @@ const ArchitectureComponentLibrary = ({
if (!nextOpen) setQuery("");
}}>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline"><Boxes className="h-4 w-4" />{t("diagram.componentLibrary")}</Button>
<Button size="sm" variant="outline" onPointerEnter={() => setOpen(true)}>
<Boxes className="h-4 w-4" />{t("diagram.componentLibrary")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="max-h-[min(36rem,calc(100vh-8rem))] w-[min(30rem,calc(100vw-2rem))] overflow-y-auto p-0">
<div className="sticky top-0 z-10 border-b border-slate-200 bg-white p-2.5">
@@ -385,6 +387,40 @@ const ArchitectureComponentLibrary = ({
);
};
const DiagramInsertMenu = ({
icon: TriggerIcon,
items,
label,
}: {
icon: LucideIcon;
items: Array<{ icon: LucideIcon; label: string; onSelect: () => void }>;
label: string;
}) => {
const [open, setOpen] = useState(false);
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="soft" onPointerEnter={() => setOpen(true)}>
<TriggerIcon className="h-4 w-4" />
{label}
<ChevronDown className="h-3.5 w-3.5 text-slate-400" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
{items.map((item) => {
const Icon = item.icon;
return (
<DropdownMenuItem key={item.label} onSelect={item.onSelect}>
<Icon className="h-4 w-4" />
{item.label}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
};
const FLOW_ACTIVE_NODE_CLASS = "edgeever-flow-node-active";
const FLOW_PORT_HIT_RADIUS = 14;
const FLOW_PORT_DOT_RADIUS = 7;
@@ -2074,12 +2110,14 @@ export const DiagramEditorPane = ({
<div className="flex shrink-0 flex-wrap items-center gap-1 border-b border-slate-200 bg-white px-3 py-2">
{!readOnly && (
document.kind === "mind-map" ? (
<Tooltip>
<TooltipTrigger asChild>
<Button size="sm" variant="soft" onClick={() => addNode("topic")}><GitBranch className="h-4 w-4" />{t("diagram.addTopic")}</Button>
</TooltipTrigger>
<TooltipContent>{t("diagram.mindMapShortcuts")}</TooltipContent>
</Tooltip>
<DiagramInsertMenu
icon={GitBranch}
label={t("diagram.addTopic")}
items={[
{ icon: GitBranch, label: t("diagram.addTopic"), onSelect: () => addNode("topic", { relation: "child" }) },
{ icon: ListTree, label: t("diagram.addSiblingTopic"), onSelect: () => addNode("topic", { relation: "sibling" }) },
]}
/>
) : document.kind === "architecture" ? (
<>
<ArchitectureComponentLibrary
@@ -2093,14 +2131,15 @@ export const DiagramEditorPane = ({
</>
) : (
<>
<Tooltip>
<TooltipTrigger asChild>
<Button size="sm" variant="soft" onClick={() => addNode("process")}><Box className="h-4 w-4" />{t("diagram.addStep")}</Button>
</TooltipTrigger>
<TooltipContent>{t("diagram.flowchartShortcuts")}</TooltipContent>
</Tooltip>
<Tooltip><TooltipTrigger asChild><Button size="sm" variant="ghost" aria-label={t("diagram.addDecision")} onClick={() => addNode("decision")}><Diamond className="h-4 w-4" /></Button></TooltipTrigger><TooltipContent>{t("diagram.addDecision")}</TooltipContent></Tooltip>
<Tooltip><TooltipTrigger asChild><Button size="sm" variant="ghost" aria-label={t("diagram.addTerminator")} onClick={() => addNode("terminator")}><Circle className="h-4 w-4" /></Button></TooltipTrigger><TooltipContent>{t("diagram.addTerminator")}</TooltipContent></Tooltip>
<DiagramInsertMenu
icon={Box}
label={t("diagram.addStep")}
items={[
{ icon: Box, label: t("diagram.addStep"), onSelect: () => addNode("process") },
{ icon: Diamond, label: t("diagram.addDecision"), onSelect: () => addNode("decision") },
{ icon: Circle, label: t("diagram.addTerminator"), onSelect: () => addNode("terminator") },
]}
/>
<span className="hidden items-center gap-1.5 px-2 text-xs text-slate-500 xl:flex"><Link2 className="h-3.5 w-3.5" />{t("diagram.connectHint")}</span>
</>
)
+1
View File
@@ -250,6 +250,7 @@ export const enUS = {
title: "Diagram title",
canvas: "{{type}} canvas",
addTopic: "Add child topic",
addSiblingTopic: "Add sibling topic",
mindMapShortcuts: "Tab adds a child; Enter adds a sibling",
addStep: "Add step",
flowchartShortcuts: "Tab connects the next node; Enter edits; Cmd/Ctrl+D duplicates; arrow keys nudge",
+1
View File
@@ -250,6 +250,7 @@ export const zhCN = {
title: "图表标题",
canvas: "{{type}}画布",
addTopic: "添加子主题",
addSiblingTopic: "添加同级主题",
mindMapShortcuts: "Tab 添加子主题,Enter 添加同级主题",
addStep: "添加步骤",
flowchartShortcuts: "Tab 连接后续节点,Enter 编辑,⌘/Ctrl+D 复制,方向键微调",