This is an automated email from the ASF dual-hosted git repository.
guoqqqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new cd935f62f43 fix(docs): improve sidebar hierarchy and version picker
(#2142)
cd935f62f43 is described below
commit cd935f62f4369f831ef328e79c357a05cce12f82
Author: Zhi Yuan Ju <[email protected]>
AuthorDate: Fri Oct 23 09:18:06 2026 +0800
fix(docs): improve sidebar hierarchy and version picker (#2142)
---
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: '✓'; }