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.
29 lines
2.0 KiB
TypeScript
29 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import type { PlaitElement } from "@plait/core";
|
|
import { getTopicEntries, getTopicTitle } from "@/lib/flow-board/topic-details";
|
|
|
|
export function XmindPresentation({ elements, title, onClose }: { elements: PlaitElement[]; title: string; onClose: () => void }) {
|
|
const slides = getTopicEntries(elements).filter(({ element, depth }) => depth === 0 || element.children?.length);
|
|
const [index,setIndex] = useState(0);
|
|
const entry = slides[Math.min(index,slides.length - 1)];
|
|
const next = () => setIndex(value => Math.min(slides.length - 1,value + 1));
|
|
const previous = () => setIndex(value => Math.max(0,value - 1));
|
|
useEffect(() => {
|
|
const onKey = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") { event.preventDefault(); onClose(); }
|
|
if (["ArrowRight","ArrowDown"," ","PageDown"].includes(event.key)) { event.preventDefault(); next(); }
|
|
if (["ArrowLeft","ArrowUp","PageUp"].includes(event.key)) { event.preventDefault(); previous(); }
|
|
};
|
|
window.addEventListener("keydown",onKey);
|
|
return () => window.removeEventListener("keydown",onKey);
|
|
}, [onClose,slides.length]);
|
|
return <section className="xmind-presentation" aria-label="演示视图" data-flow-board-shell-control="true">
|
|
<header><span>{title}</span><button type="button" aria-label="退出演示" onClick={onClose}><X size={16} />退出演示</button></header>
|
|
<div className="xmind-presentation-slide"><h1>{entry?.title || title}</h1><ul>{(entry?.element.children || []).map(child => <li key={child.id}>{getTopicTitle(child)}</li>)}</ul></div>
|
|
<footer><button type="button" aria-label="上一页" disabled={index === 0} onClick={previous}><ChevronLeft size={18} /></button><span>{Math.min(index + 1,slides.length)} / {slides.length}</span><button type="button" aria-label="下一页" disabled={index >= slides.length - 1} onClick={next}><ChevronRight size={18} /></button></footer>
|
|
</section>;
|
|
}
|