Add native-style menus, search and presentation controls, typography resources, and Xmind import/export styling support. Bundle canvas styles for preview and production, show marquee borders and selection fills, and align editor density with the project UI. Include interaction regression coverage and client comparison notes.
130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { Trash2, X } from "lucide-react";
|
|
import type { AnnotatedElement } from "@/lib/flow-board/mind-annotations";
|
|
|
|
export function XmindAnnotationPanel({
|
|
element,
|
|
onChange,
|
|
onDelete,
|
|
onClose,
|
|
}: {
|
|
element: AnnotatedElement;
|
|
onChange: (patch: Record<string, unknown>) => void;
|
|
onDelete: () => void;
|
|
onClose: () => void;
|
|
}) {
|
|
const relationship = element.mindAnnotation.kind === "relationship";
|
|
const label = (element.texts?.[0]?.text?.children || [])
|
|
.map((leaf: { text?: string }) => leaf.text || "")
|
|
.join("");
|
|
return (
|
|
<aside
|
|
className="xmind-aux-panel absolute right-0 top-[94px] bottom-9 z-30 w-full max-w-[304px] overflow-y-auto border-l border-[#dfe2e7] bg-white text-[#343b48]"
|
|
data-flow-board-shell-control="true"
|
|
>
|
|
<div className="flex h-12 items-center justify-between border-b border-[#eceef1] px-4">
|
|
<h2 className="text-sm font-semibold">
|
|
{relationship ? "联系格式" : "外框格式"}
|
|
</h2>
|
|
<button aria-label="关闭格式面板" onClick={onClose} type="button">
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
<div className="space-y-5 p-4 text-xs">
|
|
{relationship ? (
|
|
<label className="block space-y-2">
|
|
<span>联系文字</span>
|
|
<input
|
|
className="h-9 w-full rounded border border-[#dfe2e7] px-2 text-sm"
|
|
value={label}
|
|
onChange={(event) =>
|
|
onChange({
|
|
texts: event.target.value
|
|
? [
|
|
{
|
|
position: 0.5,
|
|
text: {
|
|
type: "paragraph",
|
|
children: [{ text: event.target.value }],
|
|
},
|
|
},
|
|
]
|
|
: [],
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
) : (
|
|
<label className="flex items-center justify-between">
|
|
<span>外框留白</span>
|
|
<select
|
|
aria-label="外框留白"
|
|
className="h-8 rounded border border-[#dfe2e7] px-2"
|
|
value={
|
|
element.mindAnnotation.kind === "boundary"
|
|
? element.mindAnnotation.padding
|
|
: 18
|
|
}
|
|
onChange={(event) =>
|
|
onChange({
|
|
mindAnnotation: {
|
|
...element.mindAnnotation,
|
|
padding: Number(event.target.value),
|
|
},
|
|
})
|
|
}
|
|
>
|
|
{[12, 18, 24, 32].map((value) => (
|
|
<option key={value} value={value}>
|
|
{value} px
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
)}
|
|
<label className="flex items-center justify-between">
|
|
<span>线条颜色</span>
|
|
<input
|
|
aria-label="联系或外框颜色"
|
|
className="h-7 w-12 cursor-pointer"
|
|
type="color"
|
|
value={element.strokeColor || "#64748b"}
|
|
onChange={(event) => onChange({ strokeColor: event.target.value })}
|
|
/>
|
|
</label>
|
|
<label className="flex items-center justify-between">
|
|
<span>线条粗细</span>
|
|
<select
|
|
aria-label="联系或外框粗细"
|
|
className="h-8 rounded border border-[#dfe2e7] px-2"
|
|
value={element.strokeWidth || 2}
|
|
onChange={(event) =>
|
|
onChange({ strokeWidth: Number(event.target.value) })
|
|
}
|
|
>
|
|
{[1, 2, 3, 4].map((value) => (
|
|
<option key={value} value={value}>
|
|
{value} pt
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<p className="leading-5 text-[#8b919c]">
|
|
{relationship
|
|
? "联系线会跟随主题移动。双击线上的文字也可以编辑。"
|
|
: "外框包含所选主题及其展开的子主题,随分支布局自动调整。"}
|
|
</p>
|
|
<button
|
|
className="flex h-9 w-full items-center justify-center gap-2 rounded border border-[#e5e7eb] text-[#b44343] hover:bg-red-50"
|
|
onClick={onDelete}
|
|
type="button"
|
|
>
|
|
<Trash2 size={14} />
|
|
删除{relationship ? "联系" : "外框"}
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|