From cf3aa0cb88a14a484b2929d75ce93e7081d929eb Mon Sep 17 00:00:00 2001 From: Timur Mashara Date: Fri, 25 Sep 2026 16:05:54 +0200 Subject: [PATCH 1/8] Compact mobile chrome for the docs shell Phone-only (below lg) changes, mirroring the marketing site's docs-mobile-header-prototype branch: - Site header shrinks to 56px, shows a `Metabase | Docs` mark linking to /docs/latest/, and hides on scroll-down / returns on scroll-up. The "Get started" button is 36px with an 8px radius; the burger no longer paints a hover background. - The section bar is a fixed 40px strip spanning the viewport: menu toggle, search as a bare magnifier, and an "On this page" trigger with a chevron that flips when open. - Breadcrumbs leave the bar and render as a plain slash-separated trail above the content; the Home crumb is a house icon in both trails. - "On this page" opens a bottom sheet instead of an in-flow row. - In the left nav, links always navigate; a separate chevron button toggles a branch. Desktop is untouched apart from the hub label after the wordmark and the nav chevron becoming a button. Co-Authored-By: Claude Fable 5.1 --- public/docs/css/inkeep.css | 17 ++- public/docs/js/learn/left-sidebar.js | 30 ++-- .../InkeepSearch/InkeepSearch.astro | 11 ++ src/components/chrome/Breadcrumb.astro | 73 +++++++++- src/components/chrome/DocsNavToggle.astro | 16 +-- src/components/chrome/Header.astro | 27 ++++ src/components/chrome/LeftSidebar.astro | 68 +++++---- src/components/chrome/NavItem.astro | 16 ++- src/components/chrome/PageNav.astro | 113 +++++++-------- src/components/chrome/TocToggle.astro | 55 ++++++++ src/components/chrome/TopBar.astro | 2 + src/layouts/DefaultNewLayout.astro | 5 +- src/layouts/NewDocsLayout.astro | 9 +- src/styles/section-chrome.css | 130 ++++++++++++++++++ 14 files changed, 442 insertions(+), 130 deletions(-) create mode 100644 src/components/chrome/TocToggle.astro create mode 100644 src/styles/section-chrome.css diff --git a/public/docs/css/inkeep.css b/public/docs/css/inkeep.css index ee0f6f6fbe..93cf8990cb 100644 --- a/public/docs/css/inkeep.css +++ b/public/docs/css/inkeep.css @@ -108,16 +108,21 @@ div.ikp-d_flex.ikp-flex_row.ikp-items_center.ikp-pt_3.ikp-pb_1:has(.ikp-search-b padding-bottom: 30px; } -@media screen and (max-width: 992px) { - .ikp-search-bar-trigger__text { +@media (max-width: 991.98px) { + .ikp-search-bar__text, + .ikp-search-bar__kbd-wrapper { display: none; } - .ikp-search-bar-trigger__icon { - margin-right: 0; + .ikp-search-bar__button { + background: none; + border: 0; + justify-content: center; + padding: 0; } - kbd.ikp-search-bar__kbd-wrapper { - display: none; + .ikp-search-bar__icon { + color: #5a6072; + font-size: 18px; } } diff --git a/public/docs/js/learn/left-sidebar.js b/public/docs/js/learn/left-sidebar.js index b3e2ed05aa..d9618ca437 100644 --- a/public/docs/js/learn/left-sidebar.js +++ b/public/docs/js/learn/left-sidebar.js @@ -1,24 +1,20 @@ function initLearnLeftSidebarToggleVisibility() { - const $expandableLinks = document.querySelectorAll( - ".learn #main-navigation-content ul a", - ); + const nav = document.querySelector(".learn #main-navigation-content"); + if (!nav) return; - $expandableLinks.forEach((link) => { - link.addEventListener("click", (e) => { - if (e.target.querySelector("svg")) { - e.preventDefault(); + // Links navigate; only the chevron button toggles a branch. + nav.addEventListener("click", (e) => { + const item = e.target.closest("button")?.closest("li"); + if (!item) return; - $expandableLinks.forEach((linkToContract) => { - const $listItem = linkToContract.closest("li"); - - if (!$listItem || !$listItem.contains(e.target)) { - $listItem.classList.remove("expanded"); - } - }); - - e.target.closest("li").classList.toggle("expanded"); - } + nav.querySelectorAll("li.expanded").forEach((open) => { + if (!open.contains(item)) open.classList.remove("expanded"); }); + + const expanded = item.classList.toggle("expanded"); + item + .querySelector(":scope > button") + ?.setAttribute("aria-expanded", String(expanded)); }); } diff --git a/src/components/InkeepSearch/InkeepSearch.astro b/src/components/InkeepSearch/InkeepSearch.astro index a1dd534be4..7231537db0 100644 --- a/src/components/InkeepSearch/InkeepSearch.astro +++ b/src/components/InkeepSearch/InkeepSearch.astro @@ -57,3 +57,14 @@ import { TOP_BAR_ID } from "@/constants"; }, }); + + diff --git a/src/components/chrome/Breadcrumb.astro b/src/components/chrome/Breadcrumb.astro index 8c5bbc3278..2d9bf30749 100644 --- a/src/components/chrome/Breadcrumb.astro +++ b/src/components/chrome/Breadcrumb.astro @@ -11,9 +11,11 @@ type Props = { has_magic_breadcrumbs?: boolean; show_category_breadcrumb?: boolean; }; + /** The plain slash-separated trail shown above the content on phones. */ + phone?: boolean; }; -const { page } = Astro.props; +const { page, phone = false } = Astro.props; const { site } = baseCtx; const latestOrExplicitVersion = @@ -38,16 +40,39 @@ const categoryUrl = getFallbackCategoryUrl(latestOrExplicitVersion, page.url); --- -
+
+ + diff --git a/src/components/chrome/DocsNavToggle.astro b/src/components/chrome/DocsNavToggle.astro index 3385ebc6cb..e0da4c8dc0 100644 --- a/src/components/chrome/DocsNavToggle.astro +++ b/src/components/chrome/DocsNavToggle.astro @@ -5,13 +5,14 @@ aria-expanded="false" aria-label="Open docs navigation" > -
+ {section && ( + + )}
{!hideMenu && ( @@ -801,6 +811,23 @@ const resourceLinks: NavLinkProps[] = [ ); } + const header = navbar.closest("header.bootstrap"); + if (header?.dataset.section) { + let last = window.scrollY; + window.addEventListener( + "scroll", + () => { + const y = window.scrollY; + document.documentElement.toggleAttribute( + "data-nav-hidden", + y > last && y > header.offsetHeight, + ); + last = y; + }, + { passive: true }, + ); + } + if ( new URLSearchParams(window.location.search).get("hide_nav") === "true" ) { diff --git a/src/components/chrome/LeftSidebar.astro b/src/components/chrome/LeftSidebar.astro index 5e0eee417b..ac37547ca2 100644 --- a/src/components/chrome/LeftSidebar.astro +++ b/src/components/chrome/LeftSidebar.astro @@ -120,6 +120,7 @@ const { url: pageUrl, version = "latest" } = Astro.props; li { margin: 0; padding: 0 0 4px; + position: relative; ul { display: none; @@ -140,11 +141,43 @@ const { url: pageUrl, version = "latest" } = Astro.props; display: block; } - > a svg { + > button svg { transform: rotate(0); } } + li > button { + align-items: center; + appearance: none; + background: none; + border: 0; + display: flex; + height: 29px; + justify-content: center; + padding: 0; + position: absolute; + right: 2px; + top: 0; + width: 30px; + + svg { + display: block; + height: 12px; + transform: rotate(-90deg); + transition: transform 0.3s ease-in-out; + width: 20px; + } + + svg path { + stroke: #c6c9d2; + transition: stroke 0.3s ease-in-out; + } + + &:hover svg path { + stroke: #509ee3; + } + } + a { border-radius: 8px; color: #5a6072; @@ -162,41 +195,16 @@ const { url: pageUrl, version = "latest" } = Astro.props; &.selected { background-color: #eef6fc; color: #509ee3; + } - svg path { - stroke: #509ee3; - } + &:hover + button svg path, + &.selected + button svg path { + stroke: #509ee3; } &.selected { font-weight: 700; } - - > div { - align-items: center; - display: flex; - height: 30px; - justify-content: center; - pointer-events: none; - position: absolute; - right: 2px; - top: 50%; - translate: 0 -50%; - width: 30px; - } - - svg { - display: block; - height: 12px; - transform: rotate(-90deg); - transition: transform 0.3s ease-in-out; - width: 20px; - } - - svg path { - stroke: #c6c9d2; - transition: stroke 0.3s ease-in-out; - } } } diff --git a/src/components/chrome/NavItem.astro b/src/components/chrome/NavItem.astro index 37c27cc31e..2fb9800320 100644 --- a/src/components/chrome/NavItem.astro +++ b/src/components/chrome/NavItem.astro @@ -17,14 +17,18 @@ const expanded = containsUrl(node, pageUrl);
  • {node.name} - - {hasChildren && ( -
    - -
    - )}
    + {hasChildren && ( + + )} + {hasChildren && (
      {node.pages!.map((child) => ( diff --git a/src/components/chrome/PageNav.astro b/src/components/chrome/PageNav.astro index 65b27503d9..9ba006ff9a 100644 --- a/src/components/chrome/PageNav.astro +++ b/src/components/chrome/PageNav.astro @@ -1,22 +1,25 @@ - - + diff --git a/src/components/chrome/PageNav.astro b/src/components/chrome/PageNav.astro index 9ba006ff9a..c478b3306f 100644 --- a/src/components/chrome/PageNav.astro +++ b/src/components/chrome/PageNav.astro @@ -1,18 +1,30 @@ - + - @@ -208,7 +213,6 @@ const { url: pageUrl, version = "latest" } = Astro.props; color: #509ee3; } - &:hover + button svg path, &.selected + button svg path { stroke: #509ee3; } diff --git a/src/components/chrome/PageNav.astro b/src/components/chrome/PageNav.astro index c478b3306f..4fcf9d3fd7 100644 --- a/src/components/chrome/PageNav.astro +++ b/src/components/chrome/PageNav.astro @@ -118,14 +118,17 @@ border-radius: 8px; box-shadow: 0 12px 32px rgba(34, 36, 43, 0.16); left: 16px; - max-height: min(60dvh, calc(100dvh - var(--section-bar-top, 0px) - 72px)); + --dropdown-top: calc( + var(--section-bar-top, 0px) + var(--section-bar-height, 0px) + 8px + ); + max-height: min(60dvh, calc(100dvh - var(--dropdown-top) - 24px)); opacity: 0; overflow-y: auto; overscroll-behavior: contain; padding: 6px 8px 0; position: fixed; right: 16px; - top: calc(var(--section-bar-top, 0px) + 48px); + top: var(--dropdown-top); transition: opacity 0.2s ease, top 0.2s ease, diff --git a/src/components/chrome/TocToggle.astro b/src/components/chrome/TocToggle.astro index a785ddb7fa..5b9b2fa91c 100644 --- a/src/components/chrome/TocToggle.astro +++ b/src/components/chrome/TocToggle.astro @@ -46,7 +46,7 @@ } @media (max-width: 991.98px) { - .toc-toggle { + .toc-toggle:not([hidden]) { align-items: center; display: inline-flex; } diff --git a/src/components/chrome/TopBar.astro b/src/components/chrome/TopBar.astro index accafcd3ca..b25bf6dff9 100644 --- a/src/components/chrome/TopBar.astro +++ b/src/components/chrome/TopBar.astro @@ -27,7 +27,7 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props;
      {showNavToggle && } - {showBreadcrumb && } + {showBreadcrumb && }
      @@ -46,7 +46,7 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; /* The id outranks every `.bootstrap .learn …` rule in the legacy sheet, so nothing here needs doubling. */ #top-bar { align-items: center; - border-bottom: 1px solid #e4ecfb; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); display: flex; height: 57px; justify-content: space-between; @@ -57,7 +57,7 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; width: 100%; z-index: 3; - :global(div) { + > .container > div { align-items: center; display: flex; gap: 12px; @@ -67,6 +67,20 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; > .container { display: contents; } + + /* Bootstrap's clearfix would otherwise become a third, empty flex item. */ + > .container::after { + content: none; + } + + :global(.version__selector) { + display: flex; + margin-top: 0; + } + + :global(.trail) { + margin-left: 64px; + } } .whats-new { @@ -96,10 +110,13 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; /* On phones the bar is fixed under the site header and shares its bootstrap `.container` width steps. */ @media (max-width: 991.98px) { + :global(:root) { + --section-bar-height: 40px; + } + #top-bar { background-color: #fff; - border: 0; - height: 40px; + height: var(--section-bar-height); padding: 0; position: fixed; right: 0; @@ -110,6 +127,7 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; /* 12px plus each control's 8px tap padding puts the glyphs on the header's 20px gutter. */ > .container { + align-items: center; display: flex; gap: 8px; height: 100%; diff --git a/src/icons/chrome/hamburger.svg b/src/icons/chrome/hamburger.svg deleted file mode 100644 index fed40dfaa3..0000000000 --- a/src/icons/chrome/hamburger.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/layouts/NewDocsLayout.astro b/src/layouts/NewDocsLayout.astro index 0fb34be07b..db54696021 100644 --- a/src/layouts/NewDocsLayout.astro +++ b/src/layouts/NewDocsLayout.astro @@ -10,7 +10,6 @@ import { UNIFY_ENABLED_PAGES } from "@/constants"; import { getNavForVersion } from "@/lib/docs/nav"; import { getLiquidRenderer } from "@/lib/liquid/liquidRenderer"; import DefaultNewLayout from "./DefaultNewLayout.astro"; -import "@/styles/section-chrome.css"; type Props = { page: { @@ -28,8 +27,10 @@ const { page, dirname } = Astro.props; const parts = Astro.url.pathname.split("/"); const isHomePage = parts[3] === "index" || !parts[3]; const showBreadcrumb = !isHomePage || parts[2] === "all"; -const navVersion = page.latest ? "latest" : (page.version ?? "latest"); -const showNavToggle = getNavForVersion(navVersion).categories.length > 0; +const { categories } = getNavForVersion( + page.latest ? "latest" : (page.version ?? "latest"), +); +const showNavToggle = categories.length > 0; const lq = getLiquidRenderer({ page, dirname }); @@ -39,7 +40,7 @@ const unify = UNIFY_ENABLED_PAGES.includes(page.url);
      - +
      @@ -100,7 +101,6 @@ const unify = UNIFY_ENABLED_PAGES.includes(page.url); type="text/javascript" src="/docs/js/position-feedback-widget.js" > - diff --git a/src/components/chrome/TocToggle.astro b/src/components/chrome/TocToggle.astro index 5b9b2fa91c..28e00becea 100644 --- a/src/components/chrome/TocToggle.astro +++ b/src/components/chrome/TocToggle.astro @@ -1,14 +1,4 @@ ---- -/** Phone-only "On this page" trigger for `PageNav`'s dropdown; lives in the section bar. */ ---- - - + + + - diff --git a/src/components/chrome/TopBar.astro b/src/components/chrome/TopBar.astro index b25bf6dff9..8e4976b29d 100644 --- a/src/components/chrome/TopBar.astro +++ b/src/components/chrome/TopBar.astro @@ -108,7 +108,6 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; } } - /* On phones the bar is fixed under the site header and shares its bootstrap `.container` width steps. */ @media (max-width: 991.98px) { :global(:root) { --section-bar-height: 40px; diff --git a/src/layouts/NewDocsLayout.astro b/src/layouts/NewDocsLayout.astro index db54696021..2659c767fa 100644 --- a/src/layouts/NewDocsLayout.astro +++ b/src/layouts/NewDocsLayout.astro @@ -115,7 +115,6 @@ const unify = UNIFY_ENABLED_PAGES.includes(page.url); } } - /* On phones the rail dissolves; the TOC becomes a dropdown opened from the section bar. */ @media (max-width: 991.98px) { /* Room for the fixed section bar; outranks the legacy `.bootstrap .learn .learn__article` padding. */ .bootstrap .learn article.learn__post { @@ -124,11 +123,7 @@ const unify = UNIFY_ENABLED_PAGES.includes(page.url); } .learn__right-sidebar { - display: contents; - - > :not(#sub-navigation-content) { - display: none; - } + display: none; } } diff --git a/src/lib/page-toc.ts b/src/lib/page-toc.ts new file mode 100644 index 0000000000..948fbd0271 --- /dev/null +++ b/src/lib/page-toc.ts @@ -0,0 +1,21 @@ +export const pageHeadings = (): HTMLHeadingElement[] => + Array.from(document.querySelectorAll("h2")); + +export function appendHeadingLinks( + container: HTMLElement, + headings: HTMLHeadingElement[], +): Map { + const links = new Map(); + for (const heading of headings) { + const link = document.createElement("a"); + link.href = `#${heading.id}`; + link.innerText = heading.innerText; + container.appendChild(link); + links.set(heading, link); + } + return links; +} + +/** The first section heading still below the top of the viewport. */ +export const currentHeading = (headings: HTMLHeadingElement[]) => + headings.find((heading) => heading.getBoundingClientRect().top > 0); From ca481f22ecc74ca2dadb6d62b0747612a67cf10b Mon Sep 17 00:00:00 2001 From: Timur Mashara Date: Mon, 5 Oct 2026 15:24:02 +0200 Subject: [PATCH 8/8] fix what's new color / return focus to the toggle in the popover fallback for on this page control --- src/components/chrome/TocToggle.astro | 8 +++++++- src/components/chrome/TopBar.astro | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/chrome/TocToggle.astro b/src/components/chrome/TocToggle.astro index 28e00becea..d365d0f45e 100644 --- a/src/components/chrome/TocToggle.astro +++ b/src/components/chrome/TocToggle.astro @@ -60,7 +60,10 @@ }; const hide = supportsPopover ? () => list.hidePopover() - : () => setOpen(false); + : () => { + setOpen(false); + toggle.focus(); + }; if (supportsPopover) { list.addEventListener("toggle", (event) => { @@ -75,6 +78,9 @@ const target = event.target as Node; if (!list.contains(target) && !toggle.contains(target)) setOpen(false); }); + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && list.classList.contains("open")) hide(); + }); } list.addEventListener("scroll", markOverflow, { passive: true }); diff --git a/src/components/chrome/TopBar.astro b/src/components/chrome/TopBar.astro index 8e4976b29d..49a585722f 100644 --- a/src/components/chrome/TopBar.astro +++ b/src/components/chrome/TopBar.astro @@ -83,7 +83,7 @@ const { page, showBreadcrumb, showNavToggle } = Astro.props; } } - .whats-new { + #top-bar .whats-new { align-items: center; background-color: #eef6fc; border: 1px solid #e4ecfb;