Archived
97 lines
2.2 KiB
TypeScript
97 lines
2.2 KiB
TypeScript
/**
|
|
* TableOfContents — slide-in drawer for epub.js navigation tree.
|
|
*/
|
|
|
|
import type { KeyboardEvent } from "react";
|
|
import { useTranslation } from "react-i18n-lite";
|
|
import { PanelCloseButton } from "./PanelCloseButton";
|
|
|
|
export interface EpubTocItem {
|
|
label: string;
|
|
href: string;
|
|
subitems?: EpubTocItem[];
|
|
}
|
|
|
|
interface TableOfContentsProps {
|
|
items: EpubTocItem[];
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onNavigate: (href: string) => void;
|
|
}
|
|
|
|
function TocEntry({
|
|
item,
|
|
depth,
|
|
onNavigate,
|
|
}: {
|
|
item: EpubTocItem;
|
|
depth: number;
|
|
onNavigate: (href: string) => void;
|
|
}) {
|
|
return (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="toc-item"
|
|
style={{ paddingLeft: `${16 + depth * 14}px` }}
|
|
onClick={() => onNavigate(item.href)}
|
|
>
|
|
<span className="toc-item-title">{item.label}</span>
|
|
</button>
|
|
{item.subitems?.map((sub) => (
|
|
<TocEntry key={sub.href} item={sub} depth={depth + 1} onNavigate={onNavigate} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function TableOfContents({
|
|
items,
|
|
isOpen,
|
|
onClose,
|
|
onNavigate,
|
|
}: TableOfContentsProps) {
|
|
const { t } = useTranslation();
|
|
|
|
const handleNavigate = (href: string) => {
|
|
onNavigate(href);
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{isOpen && (
|
|
<div
|
|
className="toc-overlay"
|
|
onClick={onClose}
|
|
onKeyDown={(e: KeyboardEvent) => {
|
|
if (e.key === "Escape") onClose();
|
|
}}
|
|
role="presentation"
|
|
/>
|
|
)}
|
|
|
|
<aside className={`toc-drawer ${isOpen ? "toc-drawer--open" : ""}`}>
|
|
<div className="toc-header">
|
|
<h2 className="toc-title">{t("reader.tocTitle")}</h2>
|
|
<PanelCloseButton
|
|
className="toc-close-btn"
|
|
onClick={onClose}
|
|
ariaLabel={t("reader.closeTocAria")}
|
|
/>
|
|
</div>
|
|
|
|
<nav className="toc-list">
|
|
{items.length === 0 ? (
|
|
<p className="toc-empty">{t("reader.tocEmpty")}</p>
|
|
) : (
|
|
items.map((item) => (
|
|
<TocEntry key={item.href} item={item} depth={0} onNavigate={handleNavigate} />
|
|
))
|
|
)}
|
|
</nav>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|