This repository has been archived on 2026-07-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cloud-reader/frontend/src/components/reader/TableOfContents.tsx
T
2026-06-04 06:42:27 -05:00

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>
</>
);
}