]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Update docs sidebars (#42011)
authorMark Otto <markd.otto@gmail.com>
Tue, 20 Jan 2026 03:53:54 +0000 (19:53 -0800)
committerGitHub <noreply@github.com>
Tue, 20 Jan 2026 03:53:54 +0000 (19:53 -0800)
* Add some CSS vars to nav component, use nav in sidebar more, update toc and ads

* more iterations on ToC and ads layout

scss/_nav.scss
site/src/components/DocsSidebar.astro
site/src/components/TableOfContents.astro
site/src/components/header/Navigation.astro
site/src/components/home/MastHead.astro
site/src/layouts/DocsLayout.astro
site/src/scss/_ads.scss
site/src/scss/_navbar.scss
site/src/scss/_sidebar.scss
site/src/scss/_toc.scss
site/src/scss/_variables.scss

index 743010296f5b121e655e2c3c938b6adfe747cd21..773c21dcd436f763dd9c7624f32d28bf1587662e 100644 (file)
@@ -8,6 +8,8 @@
 // scss-docs-start nav-variables
 $nav-gap:                           .125rem !default;
 $nav-link-gap:                      .5rem !default;
+$nav-link-align:                    center !default;
+$nav-link-justify:                  center !default;
 $nav-link-padding-y:                .5rem !default;
 $nav-link-padding-x:                1rem !default;
 $nav-link-color:                    var(--fg-2) !default;
@@ -44,6 +46,8 @@ $nav-underline-link-active-color:   var(--fg-color) !default;
     // scss-docs-start nav-css-vars
     --nav-gap: #{$nav-gap};
     --nav-link-gap: #{$nav-link-gap};
+    --nav-link-align: #{$nav-link-align};
+    --nav-link-justify: #{$nav-link-justify};
     --nav-link-padding-x: #{$nav-link-padding-x};
     --nav-link-padding-y: #{$nav-link-padding-y};
     --nav-link-color: #{$nav-link-color};
@@ -70,8 +74,8 @@ $nav-underline-link-active-color:   var(--fg-color) !default;
   .nav-link {
     display: flex;
     gap: var(--nav-link-gap);
-    align-items: center;
-    justify-content: center;
+    align-items: var(--nav-link-align);
+    justify-content: var(--nav-link-justify);
     padding: var(--nav-link-padding-y) var(--nav-link-padding-x);
     font-weight: var(--nav-link-font-weight);
     color: var(--nav-link-color);
index 1a9fdade33cf7648963a914fe5663b60b67bfb14..3ce0be57a049a675c89eb9b42f1e804d1f24d654 100644 (file)
@@ -7,120 +7,114 @@ import { getSlug } from '@libs/utils'
 const sidebar = getData('sidebar')
 ---
 
-<nav class="bd-links w-100" id="bd-docs-nav" aria-label="Docs navigation">
-  <ul class="bd-links-nav list-unstyled mb-0 pb-3 pb-md-2 pe-lg-2">
-    {
-      sidebar.map((group) => {
-        const groupSlug = getSlug(group.title)
+<nav class="nav vstack bd-links w-100 pb-3 pb-md-2 pe-lg-2" id="bd-docs-nav" aria-label="Docs navigation">
+  {
+    sidebar.map((group) => {
+      const groupSlug = getSlug(group.title)
 
-        if (group.pages) {
-          return (
-            <li class="bd-links-group py-2">
-              <strong class="bd-links-heading d-flex w-100 align-items-center fw-semibold">
-                {group.icon && (
-                  <svg
-                    class="bi me-2"
-                    style={group.icon_color && `color: light-dark(var(--bs-${group.icon_color}-500), var(--bs-${group.icon_color}-400));`}
-                    aria-hidden="true"
-                  >
-                    <use href={`#${group.icon}`} />
-                  </svg>
-                )}
-                {group.title}
-              </strong>
-              <ul class="list-unstyled fw-normal pb-2 small">
-                {group.pages?.map((item: any) => {
-                  // Handle sub-groups
-                  if (item.group && item.pages) {
-                    return (
-                      <li class="mb-2">
-                        <div class="bd-links-subgroup fw-semibold mt-3">
-                          {item.group}
-                        </div>
-                        <ul class="list-unstyled">
-                          {item.pages.map((page: any) => {
-                            const docSlug = getSlug(page.title)
-                            const unversionedPageSlug = `${groupSlug}/${docSlug}`
-
-                            const url = `/docs/${getConfig().docs_version}/${unversionedPageSlug}`
-                            const active = Astro.params.slug === unversionedPageSlug
+      if (group.pages) {
+        return (
+          <div class="vstack bd-links-group py-2">
+            <strong class="bd-links-heading d-flex w-100 align-items-center fw-semibold">
+              {group.icon && (
+                <svg
+                  class="bi me-2"
+                  style={group.icon_color && `color: light-dark(var(--bs-${group.icon_color}-500), var(--bs-${group.icon_color}-400));`}
+                  aria-hidden="true"
+                >
+                  <use href={`#${group.icon}`} />
+                </svg>
+              )}
+              {group.title}
+            </strong>
+            <ul class="nav flex-column bd-links-nav">
+              {group.pages?.map((item: any) => {
+                // Handle sub-groups
+                if (item.group && item.pages) {
+                  return (
+                    <>
+                      <li class="bd-links-subgroup fw-semibold mt-3">
+                        {item.group}
+                      </li>
+                      {item.pages.map((page: any) => {
+                        const docSlug = getSlug(page.title)
+                        const unversionedPageSlug = `${groupSlug}/${docSlug}`
 
-                            const generatedPage = docsPages.find((page) => page.slug === unversionedPageSlug)
+                        const url = `/docs/${getConfig().docs_version}/${unversionedPageSlug}`
+                        const active = Astro.params.slug === unversionedPageSlug
 
-                            // This test should not be necessary, see comments for `getSlug()` in `src/libs/utils.ts`.
-                            if (!generatedPage) {
-                              throw new Error(
-                                `The page '${page.title}' referenced in 'site/data/sidebar.yml' does not exist at '${url}'.`
-                              )
-                            }
+                        const generatedPage = docsPages.find((page) => page.slug === unversionedPageSlug)
 
-                            return (
-                              <li>
-                                <a
-                                  href={url}
-                                  class:list={['bd-links-link', { active }]}
-                                  aria-current={active ? 'page' : undefined}
-                                >
-                                  {page.title}
-                                </a>
-                              </li>
-                            )
-                          })}
-                        </ul>
-                      </li>
-                    )
-                  }
+                        // This test should not be necessary, see comments for `getSlug()` in `src/libs/utils.ts`.
+                        if (!generatedPage) {
+                          throw new Error(
+                            `The page '${page.title}' referenced in 'site/data/sidebar.yml' does not exist at '${url}'.`
+                          )
+                        }
 
-                  // Handle regular pages
-                  const docSlug = getSlug(item.title)
-                  const unversionedPageSlug = `${groupSlug}/${docSlug}`
+                        return (
+                          <li>
+                            <a
+                              href={url}
+                              class:list={['nav-link bd-links-link', { active }]}
+                              aria-current={active ? 'page' : undefined}
+                            >
+                              {page.title}
+                            </a>
+                          </li>
+                        )
+                      })}
+                    </>
+                  )
+                }
 
-                  const url = `/docs/${getConfig().docs_version}/${unversionedPageSlug}`
-                  const active = Astro.params.slug === unversionedPageSlug
+                // Handle regular pages
+                const docSlug = getSlug(item.title)
+                const unversionedPageSlug = `${groupSlug}/${docSlug}`
 
-                  const generatedPage = docsPages.find((page) => page.slug === unversionedPageSlug)
+                const url = `/docs/${getConfig().docs_version}/${unversionedPageSlug}`
+                const active = Astro.params.slug === unversionedPageSlug
 
-                  // This test should not be necessary, see comments for `getSlug()` in `src/libs/utils.ts`.
-                  if (!generatedPage) {
-                    throw new Error(
-                      `The page '${item.title}' referenced in 'site/data/sidebar.yml' does not exist at '${url}'.`
-                    )
-                  }
+                const generatedPage = docsPages.find((page) => page.slug === unversionedPageSlug)
 
-                  return (
-                    <li>
-                      <a
-                        href={url}
-                        class:list={['bd-links-link', { active }]}
-                        aria-current={active ? 'page' : undefined}
-                      >
-                        {item.title}
-                      </a>
-                    </li>
+                // This test should not be necessary, see comments for `getSlug()` in `src/libs/utils.ts`.
+                if (!generatedPage) {
+                  throw new Error(
+                    `The page '${item.title}' referenced in 'site/data/sidebar.yml' does not exist at '${url}'.`
                   )
-                })}
-              </ul>
-            </li>
-          )
-        } else {
-          const active = Astro.params.slug === groupSlug
+                }
 
-          return (
-            <>
-              <li class="bd-links-span-all mt-1 mb-3 mx-4 border-top" />
-              <li class="bd-links-span-all">
-                <a
-                  href={`/docs/${getConfig().docs_version}/${groupSlug}/`}
-                  class:list={['bd-links-link', { active }]}
-                  aria-current={active ? 'page' : undefined}
-                >
-                  {group.title}
-                </a>
-              </li>
-            </>
-          )
-        }
-      })
-    }
-  </ul>
+                return (
+                  <li>
+                    <a
+                      href={url}
+                      class:list={['nav-link bd-links-link', { active }]}
+                      aria-current={active ? 'page' : undefined}
+                    >
+                      {item.title}
+                    </a>
+                  </li>
+                )
+              })}
+            </ul>
+          </div>
+        )
+      } else {
+        const active = Astro.params.slug === groupSlug
+
+        return (
+          <>
+            <div class="bd-links-span-all mt-1 mb-3 mx-4 border-top" />
+            <a
+              href={`/docs/${getConfig().docs_version}/${groupSlug}/`}
+              class:list={['bd-links-link bd-links-span-all', { active }]}
+              aria-current={active ? 'page' : undefined}
+            >
+              {group.title}
+            </a>
+          </>
+        )
+      }
+    })
+  }
 </nav>
index 7f3f3557a4757b771328e1fdb16c06e55af36700..734aa363b195177c1ad688ffa41e762f4615acc9 100644 (file)
@@ -12,12 +12,12 @@ const { entries, headings } = Astro.props
 const toc = entries ? entries : generateToc(headings ?? [])
 ---
 
-<ul>
+<ul class="nav flex-column">
   {
     toc.map(({ children, slug, text }) => {
       return (
         <li>
-          <a href={`#${slug}`}>{text}</a>
+          <a class="nav-link" href={`#${slug}`}>{text}</a>
           {children.length > 0 && <Astro.self entries={children} />}
         </li>
       )
index b7dda6307034b5dada59d3f875ddcb62687f32b9..aec219bca5ce6ebf6167890a3dfb631500a8bce9 100644 (file)
@@ -21,7 +21,7 @@ interface Props {
 const { addedIn, layout, title } = Astro.props
 ---
 
-<header class="navbar navbar-expand-lg bg-body bd-navbar border-bottom border-subtle sticky-top bd-sticky-navbar">
+<header class="navbar navbar-expand-lg bg-body bd-navbar border-bottom sticky-top bd-sticky-navbar">
   <nav class="container-2xl bd-gutter flex-wrap flex-lg-nowrap" aria-label="Main navigation">
     {
       layout === 'docs' && (
index 0d4bd5f125336a3b7d66d0d1469ae10e3b4c7454..f2483cbcc5cb0c6f1f443de28df9056f6c0846cf 100644 (file)
@@ -10,12 +10,12 @@ import ResponsiveImage from '@layouts/partials/ResponsiveImage.astro'
   <div class="container-2xl bd-gutter">
     <div class="col-md-8 mx-auto text-center">
       <a
-        class="d-flex flex-column flex-lg-row justify-content-center align-items-center mb-4 text-dark lh-sm text-decoration-none"
+        class="d-flex flex-column flex-lg-row justify-content-center align-items-center mb-4 lh-sm text-decoration-none"
         href="https://www.herodevs.com/support/nes-bootstrap?utm_source=Bootstrap_site&utm_medium=Banner&utm_campaign=v3and4_eol"
         rel="noopener"
         target="_blank"
       >
-        <span class="d-sm-inline-flex align-items-center gap-1 py-2 px-3 me-2 mb-2 mb-lg-0 rounded-5 masthead-notice">
+        <span class="d-sm-inline-flex align-items-center gap-1 py-2 px-3 me-2 mb-2 mb-lg-0 rounded-5 masthead-notice theme-subtle theme-warning ">
           Get Security Updates for Bootstrap 3 &amp; 4
           <svg class="bi" style="width: 20px; height: 20px; margin-block: -2px;" aria-hidden="true"
             ><use href="#arrow-right-short"></use></svg
@@ -39,7 +39,7 @@ import ResponsiveImage from '@layouts/partials/ResponsiveImage.astro'
         <CodeCopy code={`npm i bootstrap@${getConfig().current_version}`} />
         <a
           href={getVersionedDocsPath('getting-started/install')}
-          class="btn btn-lg bd-btn-lg btn-bd-primary d-flex align-items-center justify-content-center fw-semibold"
+          class="btn-solid theme-accent btn-lg justify-content-center"
         >
           <svg class="bi me-2" aria-hidden="true"><use href="#book-half"></use></svg>
           Read the docs
index c80bdecd91fc7bb4be3c95adde53b0dda2c761d5..a4322eeb83dc0dc643d90111ec05694abca17d0c 100644 (file)
@@ -165,11 +165,12 @@ if (currentPageIndex < allPages.length - 1) {
       </div>
 
 
-      <div class="bd-toc mt-3 mb-5 my-lg-0 mb-lg-5 px-sm-1 text-body-secondary">
+      <div class="vstack bd-toc mt-3 mb-5 my-lg-0 mb-lg-5 px-sm-1 fg-3">
+        <div class="overflow-y-auto">
         {
           frontmatter.toc && headings && (
             <button
-              class="btn btn-link p-md-0 mb-2 mb-md-0 text-decoration-none bd-toc-toggle d-flex align-items-center d-md-none"
+              class="btn btn-link p-lg-0 mb-2 mb-lg-0 text-decoration-none bd-toc-toggle d-flex align-items-center d-lg-none"
               type="button"
               data-bs-toggle="collapse"
               data-bs-target="#tocContents"
@@ -177,12 +178,10 @@ if (currentPageIndex < allPages.length - 1) {
               aria-controls="tocContents"
             >
               On this page
-              <svg class="bi d-md-none ms-2" aria-hidden="true">
+              <svg class="bi d-lg-none ms-2" aria-hidden="true">
                 <use href="#chevron-expand" />
               </svg>
             </button>
-            <div class="d-none d-md-block h6 my-2 ms-3" id="docs-tocs">On this page</div>
-            <hr class="d-none d-md-block my-2 ms-3" />
             <div class="collapse bd-toc-collapse" id="tocContents">
               <nav id="TableOfContents" aria-labelledby="docs-tocs">
                 <TableOfContents headings={headings} />
@@ -190,6 +189,7 @@ if (currentPageIndex < allPages.length - 1) {
             </div>
           )
         }
+        </div>
         <Ads />
       </div>
 
index 5ffed8e3e702bb01b5b82e43431ce70ab15f611b..dc19871d9566a3b90ba0205a2eca1affddf95b9f 100644 (file)
   #carbonads {
     position: static;
     max-width: 400px;
-    padding: 15px 15px 15px 160px;
+    padding: .75rem .75rem .75rem 160px;
     margin: 1rem 0;
     overflow: hidden;
-    font-size: .8125rem;
+    font-size: var(--bs-font-size-sm);
     line-height: 1.4;
     text-align: start;
     background-color: var(--bs-bg-1);
+    @include border-radius(var(--bs-border-radius-lg));
 
     a {
       color: var(--bs-body-color);
       text-decoration: none;
     }
-
-    @include media-breakpoint-up(sm) {
-      @include border-radius(.5rem);
-    }
   }
 
   .carbon-img {
     float: inline-start;
     margin-inline-start: -145px;
+
+    > img {
+      @include border-radius(var(--bs-border-radius-sm));
+    }
   }
 
   @container (max-width: 240px) {
     #carbonads {
-      padding-inline-start: 15px;
+      position: sticky;
+      bottom: 0;
+      flex-shrink: 0;
+      padding-block: 1.25rem;
+      padding-inline: 0;
+      background-color: var(--bs-bg-body);
+      border-top: 1px solid var(--bs-border-color);
+      @include border-radius(0);
     }
 
     .carbon-img {
+      position: relative;
       display: block;
       float: none;
+      width: fit-content;
       margin-inline-start: 0;
+
+      &::after {
+        position: absolute;
+        inset: 0;
+        display: block;
+        content: "";
+        border: 1px solid var(--bs-border-color-translucent);
+        @include border-radius(var(--bs-border-radius-sm));
+      }
     }
 
     .carbon-wrap {
       flex-direction: column;
       gap: .5rem;
     }
-
-    .carbon-img > img {
-      width: 100%;
-      max-width: 100% !important;
-      height: auto;
-      @include border-radius(var(--bs-border-radius-sm));
-    }
   }
 
   .carbon-poweredby {
     display: block;
-    margin-top: .75rem;
+    margin-top: .5rem;
     color: var(--bs-fg-3) !important;
   }
 }
index baec248caaaecf8fc180555593251646538348cf..01ff85f23537bb8870859a1f9bc39fed38b95549 100644 (file)
   }
 
   .bd-navbar {
+    --bs-border-color: color-mix(in oklch, var(--bs-fg-body) 12%, transparent);
     padding: .75rem 0;
-    // background-color: transparent;
-    // box-shadow: 0 .5rem 1rem rgba($black, .15), inset 0 -1px 0 rgba($white, .15);
-
-    // @media (forced-colors) {
-    //   background-color: Canvas;
-    // }
-
-    // &::after {
-    //   position: absolute;
-    //   inset: 0;
-    //   z-index: -1;
-    //   display: block;
-    //   content: "";
-    //   background-image: linear-gradient(var(--bd-violet), color-mix(in srgb, var(--bd-violet), transparent 5%));
-    //   // background-image: linear-gradient(color-mix(in srgb, var(--bs-gray-900), $black 10%), color-mix(in srgb, var(--bs-gray-900), $black 15%));
-    // }
+    margin-bottom: -1px;
+    background-clip: padding-box;
 
     .bd-navbar-toggle {
       @include media-breakpoint-down(lg) {
index 168367c02a466c9eaeab93074b83dc5c3d1d26f2..6366d8924dc3b2c6759ea4d87a4947a7314313db 100644 (file)
     }
   }
 
-  .bd-links-heading {
-    gap: .25rem;
-    color: var(--bs-emphasis-color);
-  }
-  .bd-links-heading .bi {
-    width: 16px;
-    height: 16px;
-  }
+  .bd-links {
+    .nav {
+      --bs-nav-gap: 2px;
+      --bs-nav-link-justify: flex-start;
+      --bs-nav-link-font-size: #{$font-size-sm};
+      --bs-nav-link-padding-x: .75rem;
+      --bs-nav-link-padding-y: .25rem;
+      margin-top: .25rem;
+    }
 
-  .bd-links-subgroup {
-    margin-inline-start: 1.75rem;
-    color: var(--bs-fg-3);
-  }
+    .nav-link {
+      margin-inline-start: 1rem;
+    }
 
-  .bd-links-nav {
     @include media-breakpoint-between(xs, lg) {
       column-count: 2;
       column-gap: 1.5rem;
     }
   }
 
-  .bd-links-link {
-    display: block;
-    padding: .25rem .75rem;
-    margin-inline-start: 1rem;
-    margin-top: .125rem;
-    font-size: .875rem;
-    color: var(--bs-body-color);
-    text-decoration: none;
-    @include border-radius(var(--bs-border-radius));
-
-    &:hover {
-      background-color: var(--bs-bg-1);
-    }
-
-    &:focus-visible {
-      @include focus-ring();
-    }
+  .bd-links-heading {
+    gap: .25rem;
+    color: var(--bs-emphasis-color);
+  }
+  .bd-links-heading .bi {
+    width: 16px;
+    height: 16px;
+  }
 
-    &.active {
-      font-weight: 500;
-      color: var(--bs-emphasis-color);
-      background-color: var(--bs-bg-2);
-    }
+  .bd-links-subgroup {
+    margin-inline-start: 1.75rem;
+    color: var(--bs-fg-body);
   }
 }
index 0a4e49c981b2a946fa5069c01fd0e9edef4f1130..760f6407013c2e4afd231fb043a496e77eee004b 100644 (file)
@@ -1,8 +1,6 @@
 @use "../../../scss/layout/breakpoints" as *;
 @use "../../../scss/mixins/border-radius" as *;
 
-// stylelint-disable selector-max-type, selector-no-qualifying-type
-
 @layer custom {
   .bd-toc {
     container-type: inline-size;
       top: 5rem;
       right: 0;
       z-index: 2;
-      height: calc(100vh - 7rem);
-      overflow-y: auto;
+      height: calc(100vh - 6em);
+      // overflow-y: auto;
     }
 
-    nav {
-      font-size: .875rem;
-      // @include font-size(.875rem);
-
-      ul {
-        padding-inline-start: 0;
-        margin-bottom: 0;
-        list-style: none;
-
-        ul {
-          padding-inline-start: 1rem;
-        }
+    .nav {
+      --bs-nav-link-justify: flex-start;
+      --bs-nav-link-padding-x: .75rem;
+      --bs-nav-link-padding-y: .125rem;
+      --bs-nav-link-font-size: .875rem;
+      --bs-nav-link-hover-bg: transparent;
+      --bs-nav-link-active-bg: transparent;
+      --bs-nav-link-active-color: var(--bs-fg-body);
+      --bs-border-radius: 0;
+
+      .nav .nav-link{
+        padding-inline-start: 1.75rem;
       }
+    }
 
-      a {
-        display: block;
-        padding: .125rem 0 .125rem .75rem;
-        color: inherit;
-        text-decoration: none;
-        border-inline-start: .125rem solid transparent;
-
-        &:hover,
-        &.active {
-          color: var(--bd-toc-color);
-          border-inline-start-color: var(--bd-toc-color);
-        }
+    .nav-link {
+      border-inline-start: .125rem solid transparent;
 
-        &.active {
-          font-weight: 500;
-        }
+      &.active {
+        font-weight: 500;
+        border-inline-start-color: var(--bs-fg-body);
+      }
 
-        code {
-          font: inherit;
-        }
+      code {
+        font: inherit;
       }
     }
   }
 
+
   .bd-toc-toggle {
     display: flex;
     align-items: center;
 
-    @include media-breakpoint-down(sm) {
+    @include media-breakpoint-down(md) {
       justify-content: space-between;
       width: 100%;
     }
 
-    @include media-breakpoint-down(md) {
+    @include media-breakpoint-down(lg) {
       color: var(--bs-color-body);
       border: 1px solid var(--bs-border-color);
       @include border-radius(var(--bs-border-radius));
@@ -85,7 +75,7 @@
   }
 
   .bd-toc-collapse {
-    @include media-breakpoint-down(md) {
+    @include media-breakpoint-down(lg) {
       nav {
         padding: 1.25rem 1.25rem 1.25rem 1rem;
         background-color: var(--bs-bg-subtle);
@@ -94,7 +84,7 @@
       }
     }
 
-    @include media-breakpoint-up(md) {
+    @include media-breakpoint-up(lg) {
       display: block !important; // stylelint-disable-line declaration-no-important
     }
   }
index 354db6aa0027933d72cc960236e3943568f396da..3b01914d45613ff7ccad9ee1ba7b279f11cf719b 100644 (file)
@@ -19,7 +19,7 @@ $bd-callout-variants: info, warning, danger !default;
     --bd-violet: #{$bd-violet};
     --bd-accent: #{$bd-accent};
     --bd-violet-bg: var(--bd-violet);
-    --bd-toc-color: light-dark(var(--bd-violet), var(--bs-indigo-300));
+    --bd-toc-color: var(--bs-fg-body);
     --bd-sidebar-link-bg: light-dark(color-mix(in srgb, var(--bd-violet), transparent 90%), color-mix(in srgb, var(--bd-violet), transparent 70%));
     --bd-callout-link: var(--blue-600);
     --bd-callout-code-color: light-dark(var(--bs-pink-600), var(--bs-pink-300));