100 lines
2.8 KiB
React
100 lines
2.8 KiB
React
import { useEffect, useMemo, useState } from "react";
|
|
|
|
function OutlineBody({ groups, complete, count, total, active, showTitle }) {
|
|
return (
|
|
<>
|
|
<p className="outline-progress">
|
|
{count}/{total} Try-its complete
|
|
</p>
|
|
{showTitle ? <p className="outline-heading">In this chapter</p> : null}
|
|
{groups.map((g) => (
|
|
<div key={g.label} className="outline-group">
|
|
<p className="outline-group-label spec">{g.label}</p>
|
|
<ul>
|
|
{g.items.map((item) => {
|
|
const done = item.recipeId ? !!complete[item.recipeId] : false;
|
|
const isActive = active === item.id;
|
|
return (
|
|
<li key={item.id}>
|
|
<a
|
|
href={`#${item.id}`}
|
|
className={isActive ? "is-active" : undefined}
|
|
aria-current={isActive ? "location" : undefined}
|
|
>
|
|
<span>{item.label}</span>
|
|
{done ? (
|
|
<span className="outline-done" aria-label="Try-it complete">
|
|
✓
|
|
</span>
|
|
) : null}
|
|
</a>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function ChapterOutline({
|
|
groups,
|
|
complete = {},
|
|
count = 0,
|
|
total = 0,
|
|
variant = "desktop",
|
|
}) {
|
|
const ids = useMemo(
|
|
() => groups.flatMap((g) => g.items.map((item) => item.id)),
|
|
[groups]
|
|
);
|
|
const [active, setActive] = useState(ids[0] || "");
|
|
|
|
useEffect(() => {
|
|
const els = ids.map((id) => document.getElementById(id)).filter(Boolean);
|
|
if (!els.length || typeof IntersectionObserver === "undefined") return;
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
const visible = entries
|
|
.filter((e) => e.isIntersecting)
|
|
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
|
|
if (visible[0]?.target?.id) setActive(visible[0].target.id);
|
|
},
|
|
{ rootMargin: "-88px 0px -55% 0px", threshold: [0, 0.1, 0.25, 0.5, 1] }
|
|
);
|
|
els.forEach((el) => observer.observe(el));
|
|
return () => observer.disconnect();
|
|
}, [ids]);
|
|
|
|
if (variant === "mobile") {
|
|
return (
|
|
<details className="chapter-outline-mobile">
|
|
<summary>In this chapter</summary>
|
|
<OutlineBody
|
|
groups={groups}
|
|
complete={complete}
|
|
count={count}
|
|
total={total}
|
|
active={active}
|
|
showTitle={false}
|
|
/>
|
|
</details>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<nav className="chapter-outline" aria-label="In this chapter">
|
|
<OutlineBody
|
|
groups={groups}
|
|
complete={complete}
|
|
count={count}
|
|
total={total}
|
|
active={active}
|
|
showTitle
|
|
/>
|
|
</nav>
|
|
);
|
|
}
|