Files
C.E.L_Slide_test2/samples/src/components/CustomMobileTableOfContents.astro
T

205 lines
5.4 KiB
Plaintext

---
import TableOfContentsList from "./TableOfContentsList.astro";
const parseFlag = (value) => {
if (typeof value === "boolean") return value;
if (typeof value === "string") {
const normalized = value.trim().toLowerCase();
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
}
return false;
};
const route = Astro.locals.starlightRoute ?? {};
const { toc } = route;
const entryData = route.entry?.data ?? {};
const frontmatter = entryData.frontmatter ?? entryData;
const enableNumbering = parseFlag(frontmatter.numberedHeadings);
const filteredItems = (toc?.items ?? []).filter((item) => item.slug !== "_top");
const title = Astro.locals.t("tableOfContents.onThisPage");
---
{
filteredItems.length > 0 && (
<mobile-starlight-toc data-min-h={toc?.minHeadingLevel} data-max-h={toc?.maxHeadingLevel}>
<nav aria-labelledby="starlight__on-this-page--mobile">
<details id="starlight__mobile-toc">
<summary id="starlight__on-this-page--mobile" class="sl-flex">
<div class="toggle sl-flex">
<a href="#_top">{title}</a>
<svg
aria-hidden="true"
class="caret"
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
style="--sl-icon-size: 1rem;"
>
<path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z" />
</svg>
</div>
<span class="display-current" />
</summary>
<div class="dropdown">
<TableOfContentsList toc={filteredItems} isMobile enableNumbering={enableNumbering} />
</div>
</details>
</nav>
</mobile-starlight-toc>
)
}
<style>
@layer starlight.core {
:global(mobile-starlight-toc) {
display: block !important;
visibility: visible !important;
position: relative;
}
nav {
position: fixed;
z-index: var(--sl-z-index-toc);
top: calc(var(--sl-nav-height) - 1px);
inset-inline: 0;
border-top: 1px solid var(--sl-color-gray-5);
background-color: var(--sl-color-bg-nav);
display: block !important;
}
@media (min-width: 50rem) {
nav {
inset-inline-start: var(--sl-content-inline-start, 0);
}
}
:global(.sidebar-collapsed) nav {
inset-inline-start: 11px !important;
inset-inline-end: 2px !important;
}
@media (min-width: 72rem) {
nav {
display: none !important;
}
}
@media (max-width: 87.499rem) {
:global([data-has-toc] mobile-starlight-toc nav) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
}
summary {
gap: 0.5rem;
align-items: center;
height: var(--sl-mobile-toc-height);
border-bottom: 1px solid var(--sl-color-hairline-shade);
padding: 0.5rem 1rem;
font-size: var(--sl-text-xs);
outline-offset: var(--sl-outline-offset-inside);
}
summary::marker,
summary::-webkit-details-marker {
display: none;
}
.toggle {
flex-shrink: 0;
gap: 1rem;
align-items: center;
justify-content: space-between;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
padding-block: 0.5rem;
padding-inline-start: 0.75rem;
padding-inline-end: 0.5rem;
line-height: 1;
background-color: var(--sl-color-black);
user-select: none;
cursor: pointer;
}
.toggle a {
color: inherit;
text-decoration: none;
}
.toggle a:hover,
.toggle a:focus-visible {
text-decoration: underline;
}
details[open] .toggle {
color: var(--sl-color-white);
border-color: var(--sl-color-accent);
}
details .toggle:hover {
color: var(--sl-color-white);
border-color: var(--sl-color-gray-2);
}
:global([dir="rtl"]) .caret {
transform: rotateZ(180deg);
}
details[open] .caret {
transform: rotateZ(90deg);
}
.display-current {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: var(--sl-color-white);
}
.dropdown {
--border-top: 1px;
margin-top: calc(-1 * var(--border-top));
border: var(--border-top) solid var(--sl-color-gray-6);
border-top-color: var(--sl-color-hairline-shade);
max-height: calc(85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height));
overflow-y: auto;
background-color: var(--sl-color-black);
box-shadow: var(--sl-shadow-md);
overscroll-behavior: contain;
}
}
</style>
<script>
import { StarlightTOC } from "./starlight-toc";
class MobileStarlightTOC extends StarlightTOC {
override set current(link) {
super.current = link;
const display = this.querySelector(".display-current");
if (display) display.textContent = link.textContent;
}
constructor() {
super();
const details = this.querySelector("details");
if (!details) return;
const closeToC = () => {
details.open = false;
};
details.querySelectorAll("a").forEach((a) => {
a.addEventListener("click", closeToC);
});
window.addEventListener("click", (e) => {
if (!details.contains(e.target)) closeToC();
});
window.addEventListener("keydown", (e) => {
if (e.key === "Escape" && details.open) {
const hasFocus = details.contains(document.activeElement);
closeToC();
if (hasFocus) {
const summary = details.querySelector("summary");
if (summary) summary.focus();
}
}
});
}
}
customElements.define("mobile-starlight-toc", MobileStarlightTOC);
</script>