This is an automated email from the ASF dual-hosted git repository. juzhiyuan pushed a commit to branch fix/docs-sidebar-navigation in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit 621ff89f2e3264e4fd1d9edaf6b1d2c5756f8cd7 Author: juzhiyuan <[email protected]> AuthorDate: Thu Oct 22 22:37:18 2026 +0800 fix(docs): improve sidebar hierarchy and version picker --- next/src/components/SidebarNodes.astro | 12 ++++-- next/src/layouts/DocPage.astro | 20 ++++++++- next/src/styles/global.css | 74 ++++++++++++++++++---------------- 3 files changed, 66 insertions(+), 40 deletions(-) diff --git a/next/src/components/SidebarNodes.astro b/next/src/components/SidebarNodes.astro index 230d74616fe..0b8c038b7e2 100644 --- a/next/src/components/SidebarNodes.astro +++ b/next/src/components/SidebarNodes.astro @@ -20,17 +20,23 @@ const renderable = (n: SidebarNode): boolean => const hasChildren = (n: SidebarNode): boolean => Array.isArray(n.items) && n.items.some(renderable); + +const containsActive = (n: SidebarNode): boolean => + Boolean(active && (n.id === active || n.items?.some(containsActive))); --- <ul> {nodes.filter(renderable).map((node) => ( <li class={hasChildren(node) ? 'cat' : ''}> {hasChildren(node) ? ( - <details class="sidebar-category"> - <summary>{node.label ?? titleById?.get(node.id ?? '') ?? node.id}</summary> + <details class="sidebar-category" open={containsActive(node)}> + <summary> + <span>{node.label ?? titleById?.get(node.id ?? '') ?? node.id}</span> + <svg class="sidebar-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="m9 6 6 6-6 6" /></svg> + </summary> <Astro.self nodes={node.items!} active={active} titleById={titleById} urlById={urlById} /> </details> ) : node.id ? ( - <a href={urlById(node.id)} class={node.id === active ? 'active' : ''}>{titleById?.get(node.id) ?? node.id}</a> + <a href={urlById(node.id)} class={node.id === active ? 'active' : ''} aria-current={node.id === active ? 'page' : undefined}>{titleById?.get(node.id) ?? node.id}</a> ) : ( <span>{node.label}</span> )} diff --git a/next/src/layouts/DocPage.astro b/next/src/layouts/DocPage.astro index b5aa76bd079..6aa5f84dac9 100644 --- a/next/src/layouts/DocPage.astro +++ b/next/src/layouts/DocPage.astro @@ -70,7 +70,10 @@ const hasAlternate = entry.hasTranslation && !canonicalOverride; versions.length > 1 ? ( <details class="version-picker"> - <summary>{versionLabel}</summary> + <summary> + <span>{versionLabel}</span> + <svg class="version-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg> + </summary> <ul> {versions.map((v) => ( <li><a href={v.href} aria-current={v.current ? 'true' : undefined}>{v.label}</a></li> @@ -80,7 +83,9 @@ const hasAlternate = entry.hasTranslation && !canonicalOverride; ) : <p style="font-weight:700;padding:.2rem .5rem;margin:0">{versionLabel}</p> )} - <SidebarNodes nodes={sidebar} active={active} titleById={titleById} urlById={urlById!} /> + <div class="docs-sidebar-tree"> + <SidebarNodes nodes={sidebar} active={active} titleById={titleById} urlById={urlById!} /> + </div> </nav> )} <article class="docs-content prose"> @@ -93,6 +98,17 @@ const hasAlternate = entry.hasTranslation && !canonicalOverride; </article> </div> <script> + document.querySelectorAll<HTMLDetailsElement>('.docs-sidebar .version-picker').forEach((picker) => { + document.addEventListener('click', (event) => { + if (picker.open && event.target instanceof Node && !picker.contains(event.target)) picker.open = false; + }); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && picker.open) { + picker.open = false; + picker.querySelector('summary')?.focus(); + } + }); + }); document.querySelectorAll<HTMLElement>('.table-shell').forEach((shell) => { const scroller = shell.querySelector<HTMLElement>('.table-scroll'); const table = scroller?.querySelector('table'); diff --git a/next/src/styles/global.css b/next/src/styles/global.css index f04787654aa..39268dac36e 100644 --- a/next/src/styles/global.css +++ b/next/src/styles/global.css @@ -339,35 +339,31 @@ a.tag:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2 .docs-sidebar { position: sticky; top: calc(var(--header-height) + 1rem); + display: flex; flex-direction: column; gap: 1rem; min-height: 0; max-height: calc(100vh - var(--header-height) - 2rem); - max-height: calc(100dvh - var(--header-height) - 2rem); overflow-y: auto; - font-size: .88rem; padding-right: .5rem; -} -.docs-sidebar ul { list-style: none; padding-left: .85rem; margin: .2rem 0; } -.docs-sidebar > ul { padding-left: 0; } -.docs-sidebar li { margin: .1rem 0; } -.docs-sidebar a { display: block; color: var(--color-text-soft); padding: .22rem .5rem; border-radius: 6px; } -.docs-sidebar a:hover { color: var(--color-primary); background: var(--color-surface-alt); text-decoration: none; } -.docs-sidebar a.active { color: var(--color-primary); font-weight: 600; background: #fdeeee; } + max-height: calc(100dvh - var(--header-height) - 2rem); + border-right: 1px solid var(--color-border); padding-right: 1.25rem; + font-size: .875rem; line-height: 1.5; +} +.docs-sidebar-tree { min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #d7dce2 transparent; } +.docs-sidebar ul { list-style: none; margin: 0; padding: 0; } +.docs-sidebar-tree ul ul { margin: .2rem 0 .35rem .8rem; padding-left: .65rem; border-left: 1px solid var(--color-border); } +.docs-sidebar li { margin: .125rem 0; } +.docs-sidebar-tree > ul > .cat + .cat { margin-top: .5rem; } +.docs-sidebar a { display: block; color: var(--color-text-soft); padding: .45rem .65rem; border-radius: 6px; overflow-wrap: anywhere; } +.docs-sidebar a:hover { color: var(--color-text); background: var(--color-surface-alt); text-decoration: none; } +.docs-sidebar a.active { color: var(--color-primary-dark); font-weight: 600; background: #fff1f0; box-shadow: inset 2px 0 var(--color-primary); } .docs-sidebar .sidebar-category > summary { - display: flex; - align-items: center; - gap: .35rem; - padding: .5rem .5rem .2rem; - color: var(--color-text); - font-weight: 700; - cursor: pointer; - list-style: none; + display: flex; align-items: center; justify-content: space-between; gap: .5rem; + padding: .5rem .65rem; border-radius: 6px; + color: var(--color-text); font-weight: 600; cursor: pointer; list-style: none; } +.docs-sidebar .sidebar-category .sidebar-category > summary { font-weight: 500; } .docs-sidebar .sidebar-category > summary::-webkit-details-marker { display: none; } -.docs-sidebar .sidebar-category > summary::before { - content: '▸'; - color: var(--color-text-soft); - font-size: .8em; - transition: transform var(--dur) var(--ease); -} -.docs-sidebar .sidebar-category[open] > summary::before { transform: rotate(90deg); } -.docs-sidebar .sidebar-category > summary:hover { color: var(--color-primary); } +.docs-sidebar .sidebar-category > summary:hover { background: var(--color-surface-alt); } +.sidebar-chevron { flex: none; color: #8b949f; transition: transform var(--dur) var(--ease); } +.sidebar-category[open] > summary .sidebar-chevron { transform: rotate(90deg); } +.docs-sidebar a:focus-visible, .docs-sidebar summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; } /* stylelint-disable no-descending-specificity */ .docs-content { @@ -578,7 +574,7 @@ a.tag:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2 there. Capping the box at 30vh keeps the article's own heading on the first screen. minmax(0,1fr) keeps long code lines from inflating the column. */ .docs-layout { grid-template-columns: minmax(0, 1fr); } - .docs-sidebar { position: static; max-height: 30vh; max-height: 30dvh; overflow-y: auto; border: 1px solid var(--color-border); border-radius: 8px; padding: .75rem; } + .docs-sidebar { position: static; max-height: 30vh; max-height: 30dvh; border: 1px solid var(--color-border); border-radius: 8px; padding: .75rem; } } /* ---------- homepage extras ---------- */ @@ -696,15 +692,23 @@ h2:hover .anchor-link, h3:hover .anchor-link, .anchor-link:focus-visible { opaci @media (max-width: 996px) { .docsearch-slot { display: none; } } /* ---------- docs version picker ---------- */ -.version-picker { margin: 0 0 .5rem; } +.version-picker { position: relative; flex: none; } .version-picker > summary { - cursor: pointer; font-weight: 700; padding: .35rem .5rem; border-radius: 6px; - list-style: none; display: flex; align-items: center; gap: .35rem; + display: flex; align-items: center; justify-content: space-between; gap: .5rem; + padding: .65rem .75rem; border: 1px solid #dde2e8; border-radius: 8px; + background: #f8fafc; color: var(--color-text); font-weight: 600; cursor: pointer; list-style: none; } .version-picker > summary::-webkit-details-marker { display: none; } -.version-picker > summary::after { content: '▾'; font-size: .8em; color: var(--color-text-soft); } -.version-picker > summary:hover { background: var(--color-surface-alt); } -.version-picker ul { list-style: none; margin: .25rem 0 0; padding: 0 0 0 .5rem; } -.version-picker li a { display: block; padding: .3rem .5rem; border-radius: 6px; color: var(--color-text-soft); font-size: .9rem; } -.version-picker li a:hover { background: var(--color-surface-alt); color: var(--color-primary); text-decoration: none; } -.version-picker li a[aria-current] { color: var(--color-primary); font-weight: 600; } +.version-picker > summary:hover { background: #f1f5f9; } +.version-chevron { flex: none; color: #7b8794; transition: transform var(--dur) var(--ease); } +.version-picker[open] .version-chevron { transform: rotate(180deg); } +.version-picker[open] > summary { border-color: #c5ced8; } +.version-picker > ul { + position: absolute; z-index: 110; top: calc(100% + .5rem); left: 0; right: 0; + max-height: min(24rem, 50dvh); overflow-y: auto; padding: .4rem; + border: 1px solid var(--color-border); border-radius: 10px; + background: #fff; box-shadow: 0 12px 32px rgb(15 23 42 / 12%); +} +.version-picker li a { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .5rem .65rem; font-size: .875rem; } +.version-picker li a[aria-current] { color: var(--color-primary-dark); background: #fff1f0; font-weight: 600; } +.version-picker li a[aria-current]::after { content: '✓'; }
