]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Docs design updates (#41918)
authorMark Otto <markd.otto@gmail.com>
Thu, 11 Dec 2025 22:10:51 +0000 (14:10 -0800)
committerGitHub <noreply@github.com>
Thu, 11 Dec 2025 22:10:51 +0000 (14:10 -0800)
* Refactor docs site styling and Shiki integration

Code Highlighting:
- Enhance Code.astro with Shiki transformers and Bootstrap theme
- Add tab support for multi-language code examples
- Add toolbar with language labels and copy button
- Support diff highlighting via @shikijs/transformers

Components:
- Update Example.astro styling and structure
- Refine DocsSidebar.astro layout
- Update Navigation, Versions, and GetStarted components
- Update ThemeToggler.astro
- Minor updates to ReferenceTable, Swatch components

Site SCSS:
- Restyle code snippets and examples (_component-examples.scss)
- Update syntax highlighting styles (_syntax.scss)
- Refine sidebar styling (_sidebar.scss)
- Update search component styles (_search.scss)
- Update navbar styles (_navbar.scss)
- Refresh callouts, clipboard, content styles

Config & Assets:
- Update astro.config.ts for Shiki
- Add sticky.js partial
- Update application.js
- Update guide screenshots (parcel, vite, webpack)

* fixes

* update images

* update permissions

* Optimised images with calibre/image-actions

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
35 files changed:
.github/workflows/calibreapp-image-actions.yml
package-lock.json
package.json
site/astro.config.ts
site/src/assets/application.js
site/src/assets/partials/sticky.js [new file with mode: 0644]
site/src/components/DocsSidebar.astro
site/src/components/ReferenceTable.astro
site/src/components/UtilityReferenceTable.astro
site/src/components/head/Head.astro
site/src/components/header/Navigation.astro
site/src/components/header/Versions.astro
site/src/components/home/GetStarted.astro
site/src/components/shortcodes/Code.astro
site/src/components/shortcodes/Example.astro
site/src/components/shortcodes/Swatch.astro
site/src/layouts/DocsLayout.astro
site/src/layouts/partials/ThemeToggler.astro
site/src/scss/_callouts.scss
site/src/scss/_clipboard-js.scss
site/src/scss/_component-examples.scss
site/src/scss/_content.scss
site/src/scss/_navbar.scss
site/src/scss/_search.scss
site/src/scss/_sidebar.scss
site/src/scss/_syntax.scss
site/src/scss/_variables.scss
site/static/docs/[version]/assets/img/guides/bootstrap-npm.png [new file with mode: 0644]
site/static/docs/[version]/assets/img/guides/bootstrap-npm@2x.png [new file with mode: 0644]
site/static/docs/[version]/assets/img/guides/bootstrap-parcel.png
site/static/docs/[version]/assets/img/guides/bootstrap-parcel@2x.png
site/static/docs/[version]/assets/img/guides/bootstrap-vite.png
site/static/docs/[version]/assets/img/guides/bootstrap-vite@2x.png
site/static/docs/[version]/assets/img/guides/bootstrap-webpack.png
site/static/docs/[version]/assets/img/guides/bootstrap-webpack@2x.png

index 09d038e761875fee994042bb8dbcc23b85c78cef..cf21837170954695b2bb15cbbfa29b544d035e71 100644 (file)
@@ -18,7 +18,8 @@ jobs:
     name: calibreapp/image-actions
     runs-on: ubuntu-latest
     permissions:
-      # allow calibreapp/image-actions to update PRs
+      # allow calibreapp/image-actions to update PRs and commit compressed images
+      contents: write
       pull-requests: write
     steps:
       - name: Clone repository
index b84bb316230f4f3a1ed7e6d90a530617a556f740..bbcc8d32ae920fd06078583e1325d328bcaf7ecc 100644 (file)
@@ -33,6 +33,7 @@
         "@rollup/plugin-commonjs": "^29.0.0",
         "@rollup/plugin-node-resolve": "^16.0.3",
         "@rollup/plugin-replace": "^6.0.3",
+        "@shikijs/transformers": "^3.7.0",
         "@stackblitz/sdk": "^1.11.0",
         "@types/js-yaml": "^4.0.9",
         "@types/mime": "^4.0.0",
@@ -40,6 +41,7 @@
         "astro": "^5.15.6",
         "astro-auto-import": "^0.4.5",
         "autoprefixer": "^10.4.22",
+        "bootstrap-vscode-theme": "^0.0.9",
         "bundlewatch": "^0.4.1",
         "clean-css-cli": "^5.6.3",
         "clipboard": "^2.0.11",
         "@shikijs/types": "3.15.0"
       }
     },
+    "node_modules/@shikijs/transformers": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.19.0.tgz",
+      "integrity": "sha512-e6vwrsyw+wx4OkcrDbL+FVCxwx8jgKiCoXzakVur++mIWVcgpzIi8vxf4/b4dVTYrV/nUx5RjinMf4tq8YV8Fw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@shikijs/core": "3.19.0",
+        "@shikijs/types": "3.19.0"
+      }
+    },
+    "node_modules/@shikijs/transformers/node_modules/@shikijs/core": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.19.0.tgz",
+      "integrity": "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@shikijs/types": "3.19.0",
+        "@shikijs/vscode-textmate": "^10.0.2",
+        "@types/hast": "^3.0.4",
+        "hast-util-to-html": "^9.0.5"
+      }
+    },
+    "node_modules/@shikijs/transformers/node_modules/@shikijs/types": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.19.0.tgz",
+      "integrity": "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@shikijs/vscode-textmate": "^10.0.2",
+        "@types/hast": "^3.0.4"
+      }
+    },
     "node_modules/@shikijs/types": {
       "version": "3.15.0",
       "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz",
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/bootstrap-vscode-theme": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/bootstrap-vscode-theme/-/bootstrap-vscode-theme-0.0.9.tgz",
+      "integrity": "sha512-++aMildSGVaDS7qD59FEh4Fh6bJol4Gpo7u3rbEPqcuReRY7zOvLn77sBoK9zhVe+YT7bkPiDut47jErweChdw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "vscode": "^1.0.0"
+      }
+    },
     "node_modules/boxen": {
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
index 019aa97c41c3335f1c407e073d3d3bbfd5007418..a9c11aabaa8162bafb8fd706553bec14d80fec76 100644 (file)
     "@rollup/plugin-commonjs": "^29.0.0",
     "@rollup/plugin-node-resolve": "^16.0.3",
     "@rollup/plugin-replace": "^6.0.3",
+    "@shikijs/transformers": "^3.7.0",
     "@stackblitz/sdk": "^1.11.0",
     "@types/js-yaml": "^4.0.9",
     "@types/mime": "^4.0.0",
     "astro": "^5.15.6",
     "astro-auto-import": "^0.4.5",
     "autoprefixer": "^10.4.22",
+    "bootstrap-vscode-theme": "^0.0.9",
     "bundlewatch": "^0.4.1",
     "clean-css-cli": "^5.6.3",
     "clipboard": "^2.0.11",
index de9a1433bb30a75914578c662510ef732f45f407..d5f3cbdc91f5122e96571a550613d8af31821754 100644 (file)
@@ -1,4 +1,7 @@
 import { defineConfig } from 'astro/config'
+import bootstrapLight from 'bootstrap-vscode-theme/themes/bootstrap-light.json'
+import bootstrapDark from 'bootstrap-vscode-theme/themes/bootstrap-dark.json'
+import { transformerNotationDiff } from '@shikijs/transformers'
 
 import { bootstrap } from './src/libs/astro'
 import { getConfig } from './src/libs/config'
@@ -27,9 +30,22 @@ export default defineConfig({
     syntaxHighlight: 'shiki',
     shikiConfig: {
       themes: {
-        light: 'github-light',
-        dark: 'github-dark'
-      }
+        light: bootstrapLight,
+        dark: bootstrapDark
+      },
+      transformers: [
+        transformerNotationDiff(),
+        {
+          name: 'add-language-attribute',
+          pre(node) {
+            // Add data-language attribute to pre tag so Code component can access it
+            const lang = this.options.lang
+            if (lang) {
+              node.properties['dataLanguage'] = lang
+            }
+          }
+        }
+      ]
     }
   },
   site,
index 62f5f88b344f24b608542e0fb15c9955627b6f42..c9fce7fad64f9c01a795c226aeeaef75e773d0fc 100644 (file)
@@ -11,6 +11,8 @@
 
 import sidebarScroll from './partials/sidebar.js'
 import snippets from './partials/snippets.js'
+import stickyNav from './partials/sticky.js'
 
 sidebarScroll()
 snippets()
+stickyNav()
diff --git a/site/src/assets/partials/sticky.js b/site/src/assets/partials/sticky.js
new file mode 100644 (file)
index 0000000..8d803fb
--- /dev/null
@@ -0,0 +1,28 @@
+// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
+// IT'S ALL JUST JUNK FOR OUR DOCS!
+// ++++++++++++++++++++++++++++++++++++++++++
+
+/*
+ * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
+ * Copyright 2011-2025 The Bootstrap Authors
+ * Licensed under the Creative Commons Attribution 3.0 Unported License.
+ * For details, see https://creativecommons.org/licenses/by/3.0/.
+ */
+
+export default () => {
+  const navbar = document.querySelector('.bd-sticky-navbar')
+
+  if (!navbar) {
+    return
+  }
+
+  const handleScroll = () => {
+    navbar.classList.toggle('is-stuck', window.scrollY > 0)
+  }
+
+  // Check initial state
+  handleScroll()
+
+  // Update on scroll
+  window.addEventListener('scroll', handleScroll, { passive: true })
+}
index 2bc4d3f4118e9b78dd621b153659a39b6360991c..cfb6c336ff63b4b2040a8e6d912b050e4115c620 100644 (file)
@@ -91,7 +91,7 @@ const sidebar = getData('sidebar')
                     <li>
                       <a
                         href={url}
-                        class:list={['bd-links-link d-inline-block rounded', { active }]}
+                        class:list={['bd-links-link', { active }]}
                         aria-current={active ? 'page' : undefined}
                       >
                         {item.title}
@@ -111,7 +111,7 @@ const sidebar = getData('sidebar')
               <li class="bd-links-span-all">
                 <a
                   href={`/docs/${getConfig().docs_version}/${groupSlug}/`}
-                  class:list={['bd-links-link d-inline-block rounded small', { active }]}
+                  class:list={['bd-links-link', { active }]}
                   aria-current={active ? 'page' : undefined}
                 >
                   {group.title}
index c98d1528ad665a5268bf040f1e05130a176ff516..ff9b33838b3456c0911d3f5c067395851728cddc 100644 (file)
@@ -74,7 +74,7 @@ function addVarComments(cssValue: string): string {
   cssValue.replace(/var\((--[a-z0-9-]+)\)/gi, (match, varName) => {
     const resolvedValue = cssVarValues[varName];
     if (resolvedValue) {
-      comments.push(`<span class="color-3">/* ${resolvedValue} */</span>`);
+      comments.push(`<span class="fg-3">/* ${resolvedValue} */</span>`);
     }
     return match;
   });
index b41902aeaf6d851023b87b564d2f059b568ff708..14ab1717b914e47124601c4709d27bb6ed630222 100644 (file)
@@ -128,7 +128,7 @@ function addVarComments(cssValue: string): string {
   cssValue.replace(/var\((--[a-z0-9-]+)\)/gi, (match, varName) => {
     const resolvedValue = cssVarValues[varName];
     if (resolvedValue) {
-      comments.push(`<span class="color-3">/* ${resolvedValue} */</span>`);
+      comments.push(`<span class="fg-3">/* ${resolvedValue} */</span>`);
     }
     return match;
   });
index 11bf4fedcd6f8c092fbe0767346b6fac9a820188..a5e4a2d8f58d2b4a274352a34868e9ea07a7af2f 100644 (file)
@@ -44,12 +44,24 @@ const ScssProd = import.meta.env.PROD ? await import('@components/head/ScssProd.
 
 <link rel="preconnect" href=`https://${getConfig().algolia.app_id}-dsn.algolia.net` crossorigin />
 
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap" rel="stylesheet">
+
 <title>{pageTitle}</title>
 
 {robots && <meta name="robots" content={robots} />}
 
 <script is:inline src={getVersionedDocsPath('assets/js/color-modes.js')}></script>
 
+<!-- CSS Anchor Positioning polyfill for Firefox/Safari -->
+<script is:inline type="module">
+  if (!CSS.supports('anchor-name', '--test')) {
+    import('https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js')
+      .then(mod => mod.default())
+  }
+</script>
+
 {import.meta.env.PROD && ScssProd && (
   <Stylesheet direction={direction} layout={layout} />
   <ScssProd.default />
index 43d8fb7521bd71eb695fdafe2b9bb03814cb97a3..6c6df6b19a574905e8f508d27e977b2929e69695 100644 (file)
@@ -21,7 +21,7 @@ interface Props {
 const { addedIn, layout, title } = Astro.props
 ---
 
-<header class="navbar navbar-expand-lg bd-navbar sticky-top">
+<header class="navbar navbar-expand-lg bg-body bd-navbar border-bottom border-subtle sticky-top bd-sticky-navbar">
   <nav class="container-2xl bd-gutter flex-wrap flex-lg-nowrap" aria-label="Main navigation">
     {
       layout === 'docs' && (
@@ -42,7 +42,7 @@ const { addedIn, layout, title } = Astro.props
     }
     {layout !== 'docs' && <div class="d-lg-none" style="width: 4.25rem;" />}
 
-    <a class="navbar-brand p-0 me-0 me-lg-2" href="/" aria-label="Bootstrap">
+    <a class="navbar-brand p-0 me-0 me-lg-2" href="/" aria-label="Bootstrap" style="color: var(--bs-indigo-500);">
       <BootstrapWhiteFillIcon class="d-block my-1" height={32} width={40} />
     </a>
 
@@ -109,15 +109,15 @@ const { addedIn, layout, title } = Astro.props
             <small class="d-lg-none ms-2">Open Collective</small>
           </LinkItem>
           <li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
-            <div class="vr d-none d-lg-flex h-100 mx-lg-2 text-white"></div>
-            <hr class="d-lg-none my-2 text-white-50" />
+            <div class="vr d-none d-lg-flex mx-lg-2 my-auto"></div>
+            <hr class="d-lg-none my-2" />
           </li>
 
           <Versions layout={layout} addedIn={addedIn} />
 
           <li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
-            <div class="vr d-none d-lg-flex h-100 mx-lg-2 text-white"></div>
-            <hr class="d-lg-none my-2 text-white-50" />
+            <div class="vr d-none d-lg-flex mx-lg-2 my-auto"></div>
+            <hr class="d-lg-none my-2" />
           </li>
 
           <li class="nav-item dropdown">
index a1119e0a322d57f1a298f13c4ef4ddf09f2a8b6f..df06eaa0a243d3df138028fd87497f58c74becc4 100644 (file)
@@ -32,15 +32,16 @@ const addedIn53 = addedIn?.version === '5.3'
     type="button"
     class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle"
     data-bs-toggle="dropdown"
+    data-bs-target="#bd-versions-menu"
+    data-bs-placement="bottom-end"
     aria-expanded="false"
-    data-bs-display="static"
   >
     <span class="d-lg-none" aria-hidden="true">Bootstrap</span><span class="visually-hidden">Bootstrap&nbsp;</span> v{
       getConfig().docs_version
     }
     <span class="visually-hidden">(switch to other versions)</span>
   </button>
-  <ul class="dropdown-menu dropdown-menu-end">
+  <ul class="dropdown-menu dropdown-menu-end" id="bd-versions-menu">
     <li><h6 class="dropdown-header">v5 releases</h6></li>
     <li>
       <a
index 817d72969882d00ece0044aaf854bde08df9e75f..9ad30dfba8736c678e6cc11bfbd0c0fd21526998 100644 (file)
@@ -22,7 +22,7 @@ import Code from '@shortcodes/Code.astro'
 
 <section class="row g-3 g-md-5 mb-5 pb-5 justify-content-center">
   <div class="col-lg-6 py-lg-4 pe-lg-5">
-    <svg class="bi mb-2 fs-2 text-body-secondary" aria-hidden="true"><use xlink:href="#box-seam"></use></svg>
+    <svg class="bi mb-2 fs-2 fg-3" aria-hidden="true"><use xlink:href="#box-seam"></use></svg>
     <h3 class="fw-semibold">Install via package manager</h3>
     <p class="pe-lg-5">
       Install Bootstrap’s source Sass and JavaScript files via npm, RubyGems, Composer, or Meteor. Package-managed
@@ -38,7 +38,7 @@ import Code from '@shortcodes/Code.astro'
     </p>
   </div>
   <div class="col-lg-6 py-lg-4 ps-lg-5 border-lg-start">
-    <svg class="bi mb-2 fs-2 text-body-secondary" aria-hidden="true"><use xlink:href="#globe2"></use></svg>
+    <svg class="bi mb-2 fs-2 fg-3" aria-hidden="true"><use xlink:href="#globe2"></use></svg>
     <h3 class="fw-semibold">Include via CDN</h3>
     <p class="pe-lg-5">
       When you only need to include Bootstrap’s compiled CSS or JS, you can use <a
@@ -68,6 +68,20 @@ import Code from '@shortcodes/Code.astro'
     <h4 class="fw-semibold">Read our getting started guides</h4>
     <p>Get a jump on including Bootstrap's source files in a new project with our official guides.</p>
     <div class="d-flex flex-wrap align-items-center justify-content-center gap-4 mt-4">
+      <a
+        class="d-flex flex-column align-items-center text-decoration-none animate-img"
+        href={getVersionedDocsPath('guides/npm/')}
+      >
+        <img
+          class="d-block mb-2"
+          src={getVersionedDocsPath('/assets/img/npm.svg')}
+          alt=""
+          width="72"
+          height="72"
+          loading="lazy"
+        />
+        <span class="fg-3">npm</span>
+      </a>
       <a
         class="d-flex flex-column align-items-center text-decoration-none animate-img"
         href={getVersionedDocsPath('guides/webpack/')}
@@ -80,7 +94,7 @@ import Code from '@shortcodes/Code.astro'
           height="72"
           loading="lazy"
         />
-        <span class="text-body-secondary">Webpack</span>
+        <span class="fg-3">Webpack</span>
       </a>
       <a
         class="d-flex flex-column align-items-center text-decoration-none animate-img"
@@ -94,7 +108,7 @@ import Code from '@shortcodes/Code.astro'
           height="72"
           loading="lazy"
         />
-        <span class="text-body-secondary">Parcel</span>
+        <span class="fg-3">Parcel</span>
       </a>
       <a
         class="d-flex flex-column align-items-center text-decoration-none animate-img"
@@ -108,7 +122,7 @@ import Code from '@shortcodes/Code.astro'
           height="72"
           loading="lazy"
         />
-        <span class="text-body-secondary">Vite</span>
+        <span class="fg-3">Vite</span>
       </a>
     </div>
   </div>
index 6377aaeaeb6f75f938de60776dbd2a6652ac7cff..83152e766ea5c331d8b367674fbc8212ab36b35c 100644 (file)
@@ -2,8 +2,23 @@
 import fs from 'node:fs'
 import path from 'node:path'
 import { codeToHtml } from 'shiki'
+import { transformerNotationDiff } from '@shikijs/transformers'
+import bootstrapLight from 'bootstrap-vscode-theme/themes/bootstrap-light.json'
+import bootstrapDark from 'bootstrap-vscode-theme/themes/bootstrap-dark.json'
+import { replaceConfigInText } from '@libs/remark'
+
+interface Tab {
+  label: string
+  code: string
+  lang?: string
+}
 
 interface Props {
+  /**
+   * If extra JS snippet should be added to StackBlitz or not.
+   * @default false
+   */
+  addStackblitzJs?: boolean
   /**
    * The CSS class(es) to be added to the `pre` HTML element when rendering code blocks in Markdown.
    * Note that this prop is not used when the component is invoked directly.
@@ -18,6 +33,10 @@ interface Props {
    * The CSS class(es) to be added to the `div` wrapper HTML element.
    */
   containerClass?: string
+  /**
+   * The language attribute from Shiki/Astro markdown processing
+   */
+  'data-language'?: string
   /**
    * The language to use for highlighting.
    * @see https://prismjs.com/#supported-languages
@@ -38,9 +57,53 @@ interface Props {
    * @default false
    */
   nestedInExample?: boolean
+  /**
+   * If true, hides the highlight toolbar (language label and copy button).
+   * @default false
+   */
+  noToolbar?: boolean
+  /**
+   * Array of tabs to display a tabbed interface.
+   * When provided, this will render tabs instead of a single language label.
+   */
+  tabs?: Tab[]
 }
 
-const { class: className, code, containerClass, fileMatch, filePath, lang, nestedInExample = false } = Astro.props
+const {
+  addStackblitzJs = false,
+  class: className,
+  code,
+  containerClass,
+  'data-language': dataLanguage,
+  fileMatch,
+  filePath,
+  lang,
+  nestedInExample = false,
+  noToolbar = false,
+  tabs
+} = Astro.props
+
+// Extract language from multiple possible sources (for markdown code blocks)
+// Priority: lang prop > data-language attribute > className pattern
+let detectedLang = lang || dataLanguage
+if (!detectedLang && className) {
+  const langMatch = className.match(/language-(\w+)/)
+  if (langMatch) {
+    detectedLang = langMatch[1]
+  }
+}
+
+// Format language for display (e.g., "html" -> "HTML", "javascript" -> "JavaScript")
+const displayLang = detectedLang
+  ? detectedLang === 'js' ? 'JavaScript'
+  : detectedLang === 'ts' ? 'TypeScript'
+  : detectedLang === 'html' ? 'HTML'
+  : detectedLang === 'css' ? 'CSS'
+  : detectedLang === 'scss' ? 'SCSS'
+  : detectedLang === 'bash' || detectedLang === 'sh' ? 'Shell'
+  : detectedLang === 'powershell' ? 'PowerShell'
+  : detectedLang.toUpperCase()
+  : ''
 
 let codeToDisplay = filePath
   ? fs.readFileSync(path.join(process.cwd(), filePath), 'utf8')
@@ -59,38 +122,112 @@ if (filePath && fileMatch && codeToDisplay) {
 }
 
 // Add line wrapper for shell languages to support shell prompts
-const shouldWrapLines = lang && ['bash', 'sh', 'powershell'].includes(lang)
+const shouldWrapLines = detectedLang && ['bash', 'sh', 'powershell'].includes(detectedLang)
+
+// Transformer to ensure class name is always 'astro-code' instead of 'shiki'
+const classTransformer = {
+  name: 'class-name-transformer',
+  pre(node: any) {
+    // Force replace all 'shiki' classes with 'astro-code'
+    const existingClasses = node.properties?.className || []
+    const newClasses = existingClasses.map((cls: any) => {
+      if (typeof cls === 'string') {
+        return cls.replace(/shiki/g, 'astro-code')
+      }
+      return cls
+    })
+    node.properties.className = newClasses
+  }
+}
+
+const lineWrapperTransformer = {
+  name: 'line-wrapper',
+  line(node: any) {
+    // Wrap non-comment lines in a span with .line class for shell prompt styling
+    const hasOnlyComments = node.children.every((child: any) =>
+      child.type === 'element' &&
+      child.properties?.class &&
+      Array.isArray(child.properties.class) &&
+      child.properties.class.some((cls: any) => typeof cls === 'string' && cls.includes('comment'))
+    )
+
+    if (!hasOnlyComments) {
+      node.properties = node.properties || {}
+      node.properties.class = node.properties.class
+        ? `${node.properties.class} line`
+        : 'line'
+    }
+  }
+}
+
+const transformers: any[] = [
+  transformerNotationDiff(), // Run diff transformer first, supports // [!code ++] and // [!code --] notation
+  classTransformer
+]
+if (shouldWrapLines) {
+  transformers.push(lineWrapperTransformer)
+}
+
+// Process tabs if provided
+let highlightedTabs: Array<{ label: string; code: string }> | null = null
+if (tabs && tabs.length > 0) {
+  highlightedTabs = await Promise.all(
+    tabs.map(async (tab) => {
+      // Replace config placeholders in the code
+      const processedCode = replaceConfigInText(tab.code)
+
+      const tabLang = tab.lang || detectedLang || 'bash'
+      const shouldWrapTabLines = ['bash', 'sh', 'powershell'].includes(tabLang)
+
+      const tabTransformers: any[] = [
+        transformerNotationDiff(),
+        classTransformer
+      ]
+      if (shouldWrapTabLines) {
+        tabTransformers.push(lineWrapperTransformer)
+      }
 
-const highlightedCode = codeToDisplay && lang
+      let tabHighlighted = await codeToHtml(processedCode, {
+        lang: tabLang,
+        themes: {
+          light: bootstrapLight,
+          dark: bootstrapDark
+        },
+        transformers: tabTransformers
+      })
+
+      // Replace 'shiki' with 'astro-code' in the generated HTML
+      tabHighlighted = tabHighlighted.replace(/class=(["'])shiki(\s+)/g, 'class=$1astro-code$2')
+      tabHighlighted = tabHighlighted.replace(/class=(["'])shiki(["'])/g, 'class=$1astro-code$2')
+      tabHighlighted = tabHighlighted.replace(/shiki-themes/g, 'astro-code-themes')
+
+      return {
+        label: tab.label,
+        code: tabHighlighted
+      }
+    })
+  )
+}
+
+let highlightedCode = codeToDisplay && detectedLang
   ? await codeToHtml(codeToDisplay, {
-      lang,
+      lang: detectedLang,
       themes: {
-        light: 'github-light',
-        dark: 'github-dark'
+        light: bootstrapLight,
+        dark: bootstrapDark
       },
-      transformers: shouldWrapLines ? [
-        {
-          name: 'line-wrapper',
-          line(node) {
-            // Wrap non-comment lines in a span with .line class for shell prompt styling
-            const hasOnlyComments = node.children.every(child =>
-              child.type === 'element' &&
-              child.properties?.class &&
-              Array.isArray(child.properties.class) &&
-              child.properties.class.some((cls) => typeof cls === 'string' && cls.includes('comment'))
-            )
-
-            if (!hasOnlyComments) {
-              node.properties = node.properties || {}
-              node.properties.class = node.properties.class
-                ? `${node.properties.class} line`
-                : 'line'
-            }
-          }
-        }
-      ] : []
+      transformers
     })
   : null
+
+// Replace 'shiki' with 'astro-code' in the generated HTML
+if (highlightedCode) {
+  // Replace class="shiki" or class='shiki' (preserving other classes like has-diff)
+  highlightedCode = highlightedCode.replace(/class=(["'])shiki(\s+)/g, 'class=$1astro-code$2')
+  highlightedCode = highlightedCode.replace(/class=(["'])shiki(["'])/g, 'class=$1astro-code$2')
+  // Replace shiki-themes if it exists
+  highlightedCode = highlightedCode.replace(/shiki-themes/g, 'astro-code-themes')
+}
 ---
 
 <script>
@@ -108,13 +245,42 @@ const highlightedCode = codeToDisplay && lang
   snippetButtonTooltip('.btn-clipboard', btnTitle)
   snippetButtonTooltip('.btn-edit', btnEdit)
 
+  // Handle tab switching
+  document.querySelectorAll('.code-tabs').forEach((tabContainer) => {
+    const buttons = tabContainer.querySelectorAll('.code-tab-btn')
+    // Find the parent container that holds both tabs and content
+    // Could be .bd-code-snippet or .bd-example-snippet
+    const parentContainer = tabContainer.closest('.bd-code-snippet') ||
+                           tabContainer.closest('.bd-example-snippet') ||
+                           tabContainer.parentElement?.parentElement
+    const codeBlocks = parentContainer?.querySelectorAll('.code-tab-content')
+
+    buttons.forEach((button, index) => {
+      button.addEventListener('click', () => {
+        // Remove active class from all buttons and hide all code blocks
+        buttons.forEach((btn) => btn.classList.remove('active'))
+        codeBlocks?.forEach((block) => block.classList.remove('active'))
+
+        // Add active class to clicked button and show corresponding code block
+        button.classList.add('active')
+        codeBlocks?.[index]?.classList.add('active')
+      })
+    })
+  })
+
   const clipboard = new ClipboardJS('.btn-clipboard', {
     target: (trigger) => trigger.closest('.bd-code-snippet')?.querySelector('.highlight')!,
     text: (trigger) => {
+      // For tabbed code, find the active tab's code
+      const snippet = trigger.closest('.bd-code-snippet')
+      const activeTab = snippet?.querySelector('.code-tab-content.active .astro-code')
+      if (activeTab) {
+        return activeTab.textContent?.trim()!
+      }
       // Trim text to workaround a Firefox issue where the structure of the DOM (uncontrolled) is relevant for the
       // copied text.
       // https://github.com/zenorocha/clipboard.js/issues/439#issuecomment-312344621
-      return trigger.closest('.bd-code-snippet')?.querySelector('.highlight')!.textContent?.trim()!
+      return snippet?.querySelector('.astro-code')!.textContent?.trim()!
     }
   })
 
@@ -169,31 +335,94 @@ const highlightedCode = codeToDisplay && lang
   })
 </script>
 
-<div class:list={[{ 'bd-code-snippet': !nestedInExample }, containerClass]}>
-  {
-    nestedInExample
-      ? (<></>)
-      : Astro.slots.has('pre')
-        ? (
-          <slot name="pre" />
-        )
-        : (
-          <div class="bd-clipboard">
-            <button type="button" class="btn-clipboard">
-              <svg class="bi" role="img" aria-label="Copy">
-                <use xlink:href="#clipboard" />
+{nestedInExample ? (
+  <>
+    {!noToolbar && (
+      <div class="hstack highlight-toolbar">
+        {highlightedTabs ? (
+          <div class="code-tabs">
+            {highlightedTabs.map((tab, index) => (
+              <button
+                type="button"
+                class:list={['code-tab-btn', { active: index === 0 }]}
+              >
+                {tab.label}
+              </button>
+            ))}
+          </div>
+        ) : (
+          <div class="font-monospace text-uppercase fs-xs fg-3">{displayLang}</div>
+        )}
+        <div class="d-flex ms-auto">
+          {addStackblitzJs && (
+            <button type="button" class="btn-edit text-nowrap" title="Try it on StackBlitz">
+              <svg class="bi" aria-hidden="true">
+                <use xlink:href="#lightning-charge-fill" />
               </svg>
             </button>
+          )}
+          <button type="button" class="btn-clipboard mt-0 me-0" title="Copy to clipboard">
+            <svg class="bi" aria-hidden="true">
+              <use xlink:href="#clipboard" />
+            </svg>
+          </button>
+        </div>
+      </div>
+    )}
+    {highlightedTabs ? (
+      <>
+        {highlightedTabs.map((tab, index) => (
+          <div class:list={['code-tab-content', { active: index === 0 }]}>
+            <Fragment set:html={tab.code} />
           </div>
-        )
-  }
-  <div class="highlight">
-    {
-      highlightedCode ? (
-        <Fragment set:html={highlightedCode} />
-      ) : (
-        /* prettier-ignore */ <pre class={className}><slot /></pre>
-      )
-    }
+        ))}
+      </>
+    ) : highlightedCode ? (
+      <Fragment set:html={highlightedCode} />
+    ) : (
+      /* prettier-ignore */ <pre class={className}><slot /></pre>
+    )}
+  </>
+) : (
+  <div class="bd-code-snippet">
+    {!noToolbar && (
+      <div class="hstack highlight-toolbar">
+        {highlightedTabs ? (
+          <div class="code-tabs">
+            {highlightedTabs.map((tab, index) => (
+              <button
+                type="button"
+                class:list={['code-tab-btn', { active: index === 0 }]}
+              >
+                {tab.label}
+              </button>
+            ))}
+          </div>
+        ) : (
+          <div class="font-monospace text-uppercase fs-xs fg-3">{displayLang}</div>
+        )}
+        <div class="d-flex ms-auto">
+          <button type="button" class="btn-clipboard mt-0 me-0" title="Copy to clipboard">
+            <svg class="bi" aria-hidden="true">
+              <use xlink:href="#clipboard" />
+            </svg>
+          </button>
+        </div>
+      </div>
+    )}
+
+    {highlightedTabs ? (
+      <>
+        {highlightedTabs.map((tab, index) => (
+          <div class:list={['code-tab-content', { active: index === 0 }]}>
+            <Fragment set:html={tab.code} />
+          </div>
+        ))}
+      </>
+    ) : highlightedCode ? (
+      <Fragment set:html={highlightedCode} />
+    ) : (
+      /* prettier-ignore */ <pre class={className}><slot /></pre>
+    )}
   </div>
-</div>
+)}
index a85f10337d366967c0f102945afa3accfde8cff9..3bba4705b38b89f34071540bc47050f79c2ef551 100644 (file)
@@ -74,41 +74,12 @@ const simplifiedMarkup = sourceMarkup
 ---
 
 <div class="bd-example-snippet bd-code-snippet">
-  {
-    showPreview && (
-      <div id={id} class:list={['bd-example m-0 border-0', className]}>
-        <Fragment set:html={markup} />
-      </div>
-    )
-  }
-
-  {
-    showMarkup && (
-      <>
-        {showPreview && (
-          <div class="d-flex align-items-center highlight-toolbar ps-3 pe-2 py-1 border-0 border-top border-bottom">
-            <small class="font-monospace text-body-secondary text-uppercase">{lang}</small>
-            <div class="d-flex ms-auto">
-              <button
-                type="button"
-                class="btn-edit text-nowrap"
-                title="Try it on StackBlitz"
-                data-sb-js-snippet={addStackblitzJs ? true : undefined}
-              >
-                <svg class="bi" aria-hidden="true">
-                  <use xlink:href="#lightning-charge-fill" />
-                </svg>
-              </button>
-              <button type="button" class="btn-clipboard mt-0 me-0" title="Copy to clipboard">
-                <svg class="bi" aria-hidden="true">
-                  <use xlink:href="#clipboard" />
-                </svg>
-              </button>
-            </div>
-          </div>
-        )}
-        <Code code={simplifiedMarkup} lang={lang} nestedInExample={true} />
-      </>
-    )
-  }
+  {showPreview && (
+    <div id={id} class:list={['bd-example', className]}>
+      <Fragment set:html={markup} />
+    </div>
+  )}
+  {showMarkup && (
+    <Code code={simplifiedMarkup} lang={lang} nestedInExample={true} addStackblitzJs={addStackblitzJs} />
+  )}
 </div>
index 8a5be5c48a1f5bfc705b2b32f719fa493219fed8..9b68ddef0aeda9d9ba2796c27e8a34f7795f009a 100644 (file)
@@ -80,7 +80,7 @@ const displayCssVar = bg
   /* CSS variable display styles */
   .css-var {
     margin-left: 0.25rem;
-    color: var(--bs-color-3);
+    color: var(--bs-fg-3);
   }
 
   .css-var-light {
index 122105857de7d246ae304e9d1f46f91d7f55810e..2f806c5d78750036a2df7ef0a4deecaad1765c30 100644 (file)
@@ -113,7 +113,7 @@ if (currentPageIndex < allPages.length - 1) {
         <div class="bd-subtitle">
           {frontmatter.description && <Fragment set:html={processMarkdownToHtml(frontmatter.description)} />}
         </div>
-        <div class="mb-3 mb-md-0 d-flex text-nowrap">
+        <div class="mb-3 mb-md-0 d-flex gap-2 text-nowrap">
           {
             frontmatter.added &&
               ((frontmatter.added.show_badge !== undefined && frontmatter.added.show_badge === true) ||
@@ -131,12 +131,12 @@ if (currentPageIndex < allPages.length - 1) {
             rel="noopener"
           >
             <GitHubIcon height={16} width={16} class="bi" />
-            View on GitHub
+            Edit on GitHub
           </a>
           {
             frontmatter.mdn && (
               <a
-                class="btn btn-secondary-text btn-sm"
+                class="btn btn-subtle btn-sm theme-secondary"
                 href={frontmatter.mdn}
                 title="View on MDN"
                 target="_blank"
@@ -193,19 +193,19 @@ if (currentPageIndex < allPages.length - 1) {
         <Ads />
       </div>
 
-      <div class="bd-content ps-lg-2">
+      <div class="bd-content prose ps-lg-2">
         {
           frontmatter.sections && (
             <div class="grid grid-cols-3 mb-5">
               {frontmatter.sections.map((section) => (
                 <a
-                  class="d-block text-decoration-none"
+                  class="d-block text-decoration-none bg-1 hover-bg-2 rounded-3 p-3"
                   href={getVersionedDocsPath(
                     `${parentDirectory ? parentDirectory + '/' : ''}${getSlug(section.title)}/`
                   )}
                 >
-                  <strong class="d-block h5 mb-0">{section.title}</strong>
-                  <span class="text-secondary">{section.description}</span>
+                  <strong class="d-block fs-5 mb-0">{section.title}</strong>
+                  <span class="fs-6 fg-3">{section.description}</span>
                 </a>
               ))}
             </div>
index ab8ca4fcc6f3ebeb4a99b7d7227c8ac5def5404b..bed6a33d92fe09b43f0ed0095b1861dfdf8b1e01 100644 (file)
@@ -14,21 +14,22 @@ const { layout } = Astro.props
   type="button"
   aria-expanded="false"
   data-bs-toggle="dropdown"
-  {...layout !== 'examples' ? { 'data-bs-display': 'static' } : {}}
+  data-bs-target="#bd-theme-menu"
+  data-bs-placement="bottom-end"
   aria-label="Toggle theme (auto)"
 >
   <svg class="bi my-1 theme-icon-active" aria-hidden="true"><use href="#circle-half"></use></svg>
   <span class=`${layout === 'examples' ? 'visually-hidden' : 'd-lg-none ms-2'}` id="bd-theme-text">Toggle theme</span>
 </button>
-<ul class=`dropdown-menu dropdown-menu-end${layout === 'examples' ? ' shadow' : ''}` aria-labelledby="bd-theme-text">
+<ul class="dropdown-menu" id="bd-theme-menu" aria-labelledby="bd-theme-text" style="--bs-dropdown-min-width: 8rem;">
   <li>
     <button
       type="button"
-      class="dropdown-item d-flex align-items-center"
+      class="dropdown-item"
       data-bs-theme-value="light"
       aria-pressed="false"
     >
-      <svg class="bi me-2 opacity-50" aria-hidden="true"><use href="#sun-fill"></use></svg>
+      <svg class="bi opacity-50" aria-hidden="true"><use href="#sun-fill"></use></svg>
       Light
       <svg class="bi ms-auto d-none" aria-hidden="true"><use href="#check2"></use></svg>
     </button>
@@ -36,11 +37,11 @@ const { layout } = Astro.props
   <li>
     <button
       type="button"
-      class="dropdown-item d-flex align-items-center"
+      class="dropdown-item"
       data-bs-theme-value="dark"
       aria-pressed="false"
     >
-      <svg class="bi me-2 opacity-50" aria-hidden="true"><use href="#moon-stars-fill"></use></svg>
+      <svg class="bi opacity-50" aria-hidden="true"><use href="#moon-stars-fill"></use></svg>
       Dark
       <svg class="bi ms-auto d-none" aria-hidden="true"><use href="#check2"></use></svg>
     </button>
@@ -48,11 +49,11 @@ const { layout } = Astro.props
   <li>
     <button
       type="button"
-      class="dropdown-item d-flex align-items-center active"
+      class="dropdown-item active"
       data-bs-theme-value="auto"
       aria-pressed="true"
     >
-      <svg class="bi me-2 opacity-50" aria-hidden="true"><use href="#circle-half"></use></svg>
+      <svg class="bi opacity-50" aria-hidden="true"><use href="#circle-half"></use></svg>
       Auto
       <svg class="bi ms-auto d-none" aria-hidden="true"><use href="#check2"></use></svg>
     </button>
index 20d8076cedc6076add95d8a358e56ff3646e45c3..503e5c3cfda6053ce854dc17aadd6e17edce11c5 100644 (file)
@@ -39,9 +39,9 @@
       margin-top: -.25rem;
     }
 
-    .highlight {
-      background-color: rgba($black, .05);
-    }
+    // .highlight {
+    //   background-color: rgba($black, .05);
+    // }
   }
 
   // Variations
index e89b82edf55c72fc3f5074faa4ebedce5b8ada47..6399887f1cfb9d09080a469b007b759f013edb0e 100644 (file)
@@ -12,9 +12,9 @@
     display: none;
     float: inline-end;
 
-    + .highlight {
-      margin-top: 0;
-    }
+    // + .highlight {
+    //   margin-top: 0;
+    // }
 
     @include media-breakpoint-up(md) {
       display: block;
     display: block;
     padding: .5em;
     line-height: 1;
-    color: var(--bs-body-color);
-    background-color: var(--bd-pre-bg);
+    color: var(--bs-fg-3);
+    background-color: var(--bd-bg-1);
     border: 0;
     @include border-radius(.25rem);
 
     &:hover {
-      color: var(--bs-link-hover-color);
+      color: var(--bs-fg-body);
     }
 
     &:focus {
index 796151aeefb8012d2ffb476a10002db7fd657073..561becf45481279f907636ed4ed1e21cd5b6a5dd 100644 (file)
@@ -3,15 +3,30 @@
 @use "../../../scss/vendor/rfs" as *;
 @use "../../../scss/layout/breakpoints" as *;
 @use "../../../scss/mixins/border-radius" as *;
+@use "../../../scss/mixins/box-shadow" as *;
 @use "variables" as *;
 
 //
 // Docs examples
 //
 
+// .bd-code-snippet
+//   .bd-example
+//   .highlight-toolbar
+//   pre.astro-code
+
+// .bd-code-snippet
+//   .highlight-toolbar
+//   pre.astro-code
+
 @layer custom {
   .bd-code-snippet {
+    --bd-example-padding: 1.25rem;
+    --bd-example-inner-radius: calc(var(--bs-border-radius) - 1px);
+
     margin: 0 ($bd-gutter-x * -.5) 1rem;
+    font-size: var(--#{$prefix}font-size-sm);
+    background-color: var(--bd-pre-bg);
     border: solid var(--bs-border-color);
     border-width: 1px 0;
 
       border-width: 1px;
       @include border-radius(var(--bs-border-radius));
     }
+
+    .bd-example {
+      &:first-child {
+        @include border-top-radius(var(--bd-example-inner-radius));
+      }
+      &:last-child {
+        @include border-bottom-radius(var(--bd-example-inner-radius));
+      }
+    }
   }
 
-  .bd-example {
-    --bd-example-padding: 1rem;
+  .highlight-toolbar {
+    padding-block: .375rem;
+    padding-inline-start: var(--bd-example-padding);
+    padding-inline-end: calc(var(--bd-example-padding) - .5em);
+    background-color: var(--bs-bg-1);
+    border-bottom: 1px solid var(--bs-border-color);
+    // @include border-top-radius(calc(var(--bs-border-radius) - 1px));
+
+    @include media-breakpoint-up(md) {
+      &:first-child {
+        @include border-top-radius(calc(var(--bs-border-radius) - 1px));
+      }
+
+      &:not(:first-child) {
+        border-top: 1px solid var(--bs-border-color);
+      }
+    }
+  }
 
+  .bd-example {
     position: relative;
     display: flow-root;
     padding: var(--bd-example-padding);
-    margin: 0 ($bd-gutter-x * -.5) 1rem;
-    border: solid var(--bs-border-color);
-    border-width: 1px 0;
+    font-size: var(--#{$prefix}font-size-base);
+    // margin: 0 ($bd-gutter-x * -.5) 1rem;
+    background-color: var(--bs-bg-body);
 
     @include media-breakpoint-up(md) {
       --bd-example-padding: 1.5rem;
 
       margin-inline: 0;
-      border-width: 1px;
-      @include border-radius(var(--bs-border-radius));
+      // border-width: 1px;
+      // @include border-radius(var(--bs-border-radius));
     }
 
     + p {
@@ -58,8 +99,9 @@
     }
 
     > .dropdown-menu {
-      position: static;
-      display: block;
+      max-width: 12rem;
+      //   position: static;
+      //   display: block;
     }
 
     > :last-child,
     // Navbars
     .fixed-top,
     .sticky-top {
-      position: static;
+      position: static !important; // stylelint-disable-line
       margin: calc(-1 * var(--bd-example-padding)) calc(-1 * var(--bd-example-padding)) var(--bd-example-padding);
     }
 
     .fixed-bottom,
     .sticky-bottom {
-      position: static;
+      position: static !important; // stylelint-disable-line
       margin: var(--bd-example-padding) calc(-1 * var(--bd-example-padding)) calc(-1 * var(--bd-example-padding));
 
     }
   // Code snippets
   //
 
-  .highlight {
+  .astro-code {
     position: relative;
-    padding: .75rem ($bd-gutter-x * .5);
     background-color: var(--bd-pre-bg);
 
     @include media-breakpoint-up(md) {
-      padding: 1rem;
       @include border-radius(calc(var(--bs-border-radius) - 1px));
     }
-
-    @include media-breakpoint-up(lg) {
-      pre {
-        margin-inline-end: 1.875rem;
-      }
-    }
-
-    pre {
-      // padding: .25rem 0 .875rem;
-      // margin-top: .8125rem;
-      margin-bottom: 0;
-      overflow: overlay;
-      white-space: pre;
-      background-color: transparent;
-      border: 0;
-    }
-
-    pre code {
-      @include font-size(inherit);
-      color: var(--bs-body-color); // Effectively the base text color
-      word-wrap: normal;
-    }
   }
 
   .bd-example-snippet .highlight pre {
     margin-inline-end: 0;
   }
 
-  .highlight-toolbar {
-    background-color: var(--bd-pre-bg);
-
-    + .highlight {
-      @include border-top-radius(0);
-    }
-  }
-
-  .bd-file-ref {
-    .highlight-toolbar {
-      @include media-breakpoint-up(md) {
-        @include border-top-radius(calc(var(--bs-border-radius) - 1px));
-      }
-    }
-  }
-
-  .bd-content .bd-code-snippet {
-    margin-bottom: 1rem;
-  }
 }
index e367c0c4edaaba1ef7a3f296b646d0998b6eb016..b93876234366aef77c15e666f85636311b41dea6 100644 (file)
@@ -1,5 +1,6 @@
 @use "sass:color";
 @use "../../../scss/colors" as *;
+@use "../../../scss/config" as *;
 @use "../../../scss/variables" as *;
 @use "../../../scss/vendor/rfs" as *;
 @use "../../../scss/layout/breakpoints" as *;
 
 @layer custom {
   .bd-content {
+    @media (width >= 1024px) {
+      font-size: var(--#{$prefix}font-size-md);
+    }
+
+    > p {
+      margin-bottom: 1.25rem;
+    }
+
     > h2,
     > h3,
     > h4 {
@@ -47,9 +56,9 @@
       overflow-y: auto;
       font-size: .75rem;
 
-      thead th {
-        border-block-end-color: currentcolor;
-      }
+      // thead th {
+      //   border-block-end-color: currentcolor;
+      // }
 
       th,
       td {
 
   .bd-title {
     --bs-heading-color: var(--bs-fg);
-    @include font-size(3rem);
+    // @include font-size(3rem);
   }
 
   .bd-subtitle {
index eff17fa2b13814acccef783a01b98e4a911dc737..0d702a275a3ba33dcb5fb1d34ba2be85a0583f1b 100644 (file)
@@ -6,25 +6,34 @@
 @use "../../../scss/layout/breakpoints" as *;
 @use "../../../scss/vendor/rfs" as *;
 
+:root {
+  --bs-font-sans-serif: "Geist", sans-serif;
+  --bs-font-monospace: "Geist Mono", monospace;
+}
+
 @layer custom {
+  .bd-sticky-navbar.is-stuck {
+    box-shadow: 0 .125rem .5rem rgba($black, .05);
+  }
+
   .bd-navbar {
     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%));
-    }
+    // 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%));
+    // }
 
     .bd-navbar-toggle {
       @include media-breakpoint-down(lg) {
@@ -52,7 +61,7 @@
     }
 
     .navbar-brand {
-      color: $white;
+      // color: $white;
       @include transition(transform .2s ease-in-out);
 
       &:hover {
     .navbar-toggler,
     .nav-link {
       padding-inline: $spacer * .25;
-      color: rgba($white, .85);
+      // color: rgba($white, .85);
 
-      &:hover,
-      &:focus {
-        color: $white;
-      }
+      // &:hover,
+      // &:focus {
+      //   color: $white;
+      // }
 
       &.active {
         font-weight: 600;
-        color: $white;
+        // color: $white;
       }
     }
 
       --bs-dropdown-padding-y: .25rem;
       --bs-dropdown-link-hover-bg: color-mix(in srgb, var(--bd-violet), transparent 90%);
       --bs-dropdown-link-active-bg: var(--bd-violet);
-      @include rfs(.875rem, --bs-dropdown-font-size);
-      @include font-size(.875rem);
-      @include border-radius(.5rem);
+      // @include rfs(.875rem, --bs-dropdown-font-size);
+      // @include font-size(.875rem);
+      // @include border-radius(.5rem);
       box-shadow: var(--#{$prefix}dropdown-box-shadow);
 
       li + li {
index be3db084ca03afabe524fb6a712611438f6cad45..a7959b9467400a56839e5fad7f04a10591521f95 100644 (file)
@@ -3,24 +3,25 @@
 @use "../../../scss/layout/breakpoints" as *;
 @use "../../../scss/mixins/color-mode" as *;
 @use "../../../scss/mixins/border-radius" as *;
+@use "../../../scss/mixins/focus-ring" as *;
 
 // stylelint-disable selector-class-pattern
 
 :root {
-  --docsearch-primary-color: var(--bd-violet);
-  --docsearch-logo-color: var(--bd-violet);
+  --docsearch-primary-color: var(--bs-indigo-500);
+  --docsearch-logo-color: var(--bs-indigo-500);
 }
 
 @include color-mode(dark, true) {
   // From here, the values are copied from https://cdn.jsdelivr.net/npm/@docsearch/css@3
   // in html[data-theme="dark"] selector
   // and are slightly modified for formatting purpose
-  --docsearch-text-color: #f5f6f7;
+  // --docsearch-text-color: #f5f6f7;
   --docsearch-container-background: rgba(9, 10, 17, .8);
   --docsearch-modal-background: #15172a;
   --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;
-  --docsearch-searchbox-background: #090a11;
-  --docsearch-searchbox-focus-background: #000;
+  // --docsearch-searchbox-background: #090a11;
+  // --docsearch-searchbox-focus-background: #000;
   --docsearch-hit-color: #bec3c9;
   --docsearch-hit-shadow: none;
   --docsearch-hit-background: #090a11;
   --docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, .3);
   --docsearch-footer-background: #1e2136;
   --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, .5), 0 -4px 8px 0 rgba(0, 0, 0, .2);
-  --docsearch-muted-color: #7f8497;
+  // --docsearch-muted-color: #7f8497;
 }
 
-@layer custom {
-  .bd-search {
-    position: relative;
+.bd-search {
+  position: relative;
 
-    @include media-breakpoint-up(lg) {
-      position: absolute;
-      top: .875rem;
-      left: 50%;
-      width: 200px;
-      margin-inline-start: -100px;
-    }
-
-    @include media-breakpoint-up(xl) {
-      width: 280px;
-      margin-inline-start: -140px;
-    }
+  @include media-breakpoint-up(lg) {
+    position: absolute;
+    top: .875rem;
+    left: 50%;
+    width: 200px;
+    margin-inline-start: -100px;
+  }
 
+  @include media-breakpoint-up(xl) {
+    width: 280px;
+    margin-inline-start: -140px;
   }
 
-  .DocSearch-Container {
-    --docsearch-muted-color: var(--bs-secondary-color);
-    --docsearch-hit-shadow: none;
+}
 
-    position: fixed;
-    z-index: 2000; // Make sure to be over all components showcased in the documentation
-    cursor: auto; // Needed because of [role="button"] in Algolia search modal. Remove once https://github.com/algolia/docsearch/issues/1370 is tackled.
+.DocSearch-Container {
+  --docsearch-muted-color: var(--bs-fg-3);
+  --docsearch-hit-shadow: none;
 
-    @include media-breakpoint-up(lg) {
-      padding-block-start: 4rem;
-    }
+  position: fixed;
+  z-index: 2000; // Make sure to be over all components showcased in the documentation
+  cursor: auto; // Needed because of [role="button"] in Algolia search modal. Remove once https://github.com/algolia/docsearch/issues/1370 is tackled.
+
+  @include media-breakpoint-up(lg) {
+    padding-block-start: 4rem;
+  }
+}
+
+.DocSearch-Button {
+  --docsearch-searchbox-background: var(--bs-bg-2);
+  --docsearch-searchbox-color: var(--bs-color-body);
+  --docsearch-searchbox-focus-background: var(--bs-bg-1);
+  --docsearch-searchbox-shadow: none;
+  --docsearch-text-color: var(--bs-color-body);
+  --docsearch-muted-color: var(--bs-fg-4);
+
+  width: 100%;
+  height: 36px;
+  padding-inline: 10px;
+  margin-left: 0;
+  border: 1px solid var(--bs-border-subtle);
+  @include border-radius(var(--bs-border-radius));
+
+  .DocSearch-Search-Icon {
+    width: 16px;
+    height: 16px;
+    opacity: .5;
   }
 
-  .DocSearch-Button {
-    --docsearch-searchbox-background: #{rgba($black, .1)};
-    --docsearch-searchbox-color: #{$white};
-    --docsearch-searchbox-focus-background: #{rgba($black, .25)};
-    --docsearch-searchbox-shadow: #{0 0 0 .25rem rgba($bd-accent, .4)};
-    --docsearch-text-color: #{$white};
-    --docsearch-muted-color: #{rgba($white, .65)};
+  &:active,
+  &:focus,
+  &:hover {
+    // @include focus-ring(true);
+    // --bs-focus-ring-offset: -1px;
 
-    width: 100%;
-    height: 38px; // Match Bootstrap inputs
-    margin: 0;
-    border: 1px solid rgba($white, .4);
-    @include border-radius(.375rem);
+    // border-color: rgba($bd-accent, 1);
 
     .DocSearch-Search-Icon {
-      opacity: .65;
+      opacity: 1;
     }
+  }
 
-    &:active,
-    &:focus,
-    &:hover {
-      border-color: rgba($bd-accent, 1);
-
-      .DocSearch-Search-Icon {
-        opacity: 1;
-      }
-    }
+  // @include media-breakpoint-down(lg) {
+  //   &,
+  //   &:hover,
+  //   &:focus {
+  //     background: transparent;
+  //     border: 0;
+  //     box-shadow: none;
+  //   }
+  //   &:focus {
+  //     box-shadow: var(--docsearch-searchbox-shadow);
+  //   }
+  // }
+}
 
-    @include media-breakpoint-down(lg) {
-      &,
-      &:hover,
-      &:focus {
-        background: transparent;
-        border: 0;
-        box-shadow: none;
-      }
-      &:focus {
-        box-shadow: var(--docsearch-searchbox-shadow);
-      }
-    }
+.DocSearch-Button-Keys,
+.DocSearch-Button-Placeholder {
+  @include media-breakpoint-down(lg) {
+    display: none;
   }
+}
 
-  .DocSearch-Button-Keys,
-  .DocSearch-Button-Placeholder {
-    @include media-breakpoint-down(lg) {
-      display: none;
-    }
-  }
+.DocSearch-Button-Placeholder {
+  font-size: 14px;
+}
 
-  .DocSearch-Button-Keys {
-    min-width: 0;
-    padding: .125rem .25rem;
-    background: rgba($black, .25);
-    @include border-radius(.25rem);
-  }
+.DocSearch-Button-Keys {
+  display: flex;
+  gap: 2px;
+  align-items: center;
+  min-width: 0;
+  margin-top: .25rem;
+}
 
-  .DocSearch-Button-Key {
-    top: 0;
-    width: auto;
-    height: 1.25rem;
-    padding-inline: .125rem;
-    margin-inline-end: 0;
-    font-size: .875rem;
-    background: none;
-    box-shadow: none;
-  }
+.DocSearch-Button-Key {
+  position: static;
+  top: 0;
+  width: auto;
+  margin: 0;
+  font-size: var(--bs-font-size-sm);
+  background: none;
+  border-radius: 0; // stylelint-disable-line
+  box-shadow: none;
+}
 
-  .DocSearch-Commands-Key {
-    padding-inline-start: 1px;
-    font-size: .875rem;
-    background-color: rgba($black, .1);
-    background-image: none;
-    box-shadow: none;
-  }
+.DocSearch-Commands-Key {
+  background-image: none;
+  box-shadow: none;
+}
 
-  .DocSearch-Form {
-    @include border-radius(var(--bs-border-radius));
-  }
+.DocSearch-Form {
+  @include border-radius(var(--bs-border-radius));
+}
 
-  .DocSearch-Hits {
-    mark {
-      padding: 0;
-    }
+.DocSearch-Hits {
+  mark {
+    padding: 0;
   }
+}
 
-  .DocSearch-Hit {
-    padding-block-end: 0;
-    @include border-radius(0);
-
-    a {
-      @include border-radius(0);
-      border: solid var(--bs-border-color);
-      border-width: 0 1px 1px;
-    }
+.DocSearch-Hit {
+  padding-block-end: 0;
+  @include border-radius(0);
 
-    &:first-child a {
-      @include border-top-radius(var(--bs-border-radius));
-      border-top-width: 1px;
-    }
-    &:last-child a {
-      @include border-bottom-radius(var(--bs-border-radius));
-    }
+  a {
+    @include border-radius(0);
+    border: solid var(--bs-border-color);
+    border-width: 0 1px 1px;
   }
 
-  .DocSearch-Hit-icon {
-    display: flex;
-    align-items: center;
+  &:first-child a {
+    @include border-top-radius(var(--bs-border-radius));
+    border-top-width: 1px;
   }
-
-  // Fix --docsearch-logo-color that doesn't do anything
-  .DocSearch-Logo svg .cls-1,
-  .DocSearch-Logo svg .cls-2 {
-    fill: var(--docsearch-logo-color);
+  &:last-child a {
+    @include border-bottom-radius(var(--bs-border-radius));
   }
 }
+
+.DocSearch-Hit-icon {
+  display: flex;
+  align-items: center;
+}
+
+// Fix --docsearch-logo-color that doesn't do anything
+.DocSearch-Logo svg .cls-1,
+.DocSearch-Logo svg .cls-2 {
+  fill: var(--docsearch-logo-color);
+}
index 63ad033a89fa3c7c8180a0697f5f4cf124150a75..6c352a7eab0275f4ae0951c4b53350a1d12e0f38 100644 (file)
@@ -1,6 +1,7 @@
 @use "../../../scss/variables" as *;
 @use "../../../scss/layout/breakpoints" as *;
 @use "../../../scss/mixins/border-radius" as *;
+@use "../../../scss/mixins/focus-ring" as *;
 
 @layer custom {
   .bd-sidebar {
@@ -26,6 +27,7 @@
   }
 
   .bd-links-heading {
+    gap: .25rem;
     color: var(--bs-emphasis-color);
   }
   .bd-links-heading .bi {
   }
 
   .bd-links-subgroup {
-    margin-inline-start: 1.5rem;
+    margin-inline-start: 1.75rem;
     color: var(--bs-emphasis-color);
   }
 
   .bd-links-nav {
-    // @include media-breakpoint-down(lg) {
-    //   font-size: .875rem;
-    // }
-
     @include media-breakpoint-between(xs, lg) {
       column-count: 2;
       column-gap: 1.5rem;
   }
 
   .bd-links-link {
-    padding: .1875rem .5rem;
+    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,
-    &:focus,
-    &.active {
-      color: var(--bs-emphasis-color);
-      background-color: var(--bd-sidebar-link-bg);
+    &:hover {
+      background-color: var(--bs-bg-1);
+    }
+
+    &:focus-visible {
+      @include focus-ring();
     }
 
     &.active {
-      font-weight: 600;
+      font-weight: 500;
+      color: var(--bs-emphasis-color);
+      background-color: var(--bs-bg-2);
     }
   }
 }
index cf6b8b6c63caae84c4e96627607bf648a51ae657..e537635b682b16638d713f7ca134060b23071e88 100644 (file)
@@ -2,7 +2,9 @@
 @use "../../../scss/config" as *;
 @use "../../../scss/colors" as *;
 @use "../../../scss/variables" as *;
+@use "../../../scss/mixins/border-radius" as *;
 @use "../../../scss/mixins/color-mode" as *;
+@use "../../../scss/mixins/transition" as *;
 
 // Shell prompt colors for light mode
 :root,
   --shell-prompt-color: #565c64;
 }
 
-// Shell prompt colors for dark mode
-@include color-mode(dark, true) {
-  --shell-prompt-color: #868e96;
+.astro-code {
+  display: flex;
+  padding: var(--bd-example-padding);
+  margin-bottom: 0;
+  line-height: 20px;
+  --bs-font-monospace: "Geist Mono";
+  background-color: var(--bd-pre-bg) !important; // stylelint-disable-line declaration-no-important
 }
 
-.shiki {
-  background-color: transparent !important; // stylelint-disable-line declaration-no-important
-}
+// Dark mode theming for Shiki
+// When using dual themes, Shiki generates inline styles with CSS variables
+// We need to switch from the light theme vars to dark theme vars
+[data-bs-theme="dark"] {
+  --shell-prompt-color: #868e96;
 
-// Shiki dual-theme support
-// Light mode: use default inline styles (no override needed)
-// Dark mode: override to use the dark theme CSS variables
-@include color-mode(dark, true) {
-  .shiki,
-  .shiki span {
+  .astro-code span,
+  .astro-code-themes span {
+    // Override Shiki's inline color with the dark theme color
     color: var(--shiki-dark) !important; // stylelint-disable-line declaration-no-important
   }
 }
     user-select: none;
   }
 }
+
+// Code tabs
+.code-tabs {
+  display: flex;
+  gap: .75rem;
+}
+
+.code-tab-btn {
+  position: relative;
+  padding: .25rem 0;
+  font-size: var(--bs-font-size-xs);
+  color: var(--bs-fg-3);
+  background: transparent;
+  border: 0;
+  @include border-radius(var(--bs-border-radius-sm));
+  @include transition(color .15s ease-in-out, background-color .15s ease-in-out);
+
+  &:hover {
+    color: var(--bs-fg-2);
+  }
+
+  &.active {
+    font-weight: 500;
+    color: var(--bs-fg-body);
+
+    &::after {
+      position: absolute;
+      bottom: -.5rem;
+      left: 0;
+      width: 100%;
+      height: 2px;
+      content: "";
+      background-color: var(--bs-border-emphasized);
+    }
+  }
+}
+
+.code-tab-content {
+  display: none;
+
+  &.active {
+    display: block;
+  }
+}
index 0920a1d6360c67849cd26ea1450e824fde7cff36..4a0289ebd7e5f055059cd92e0956efa7def4cd6a 100644 (file)
@@ -27,7 +27,7 @@ $bd-callout-variants: info, warning, danger !default;
     --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(--#{$prefix}blue-600);
     --bd-callout-code-color: light-dark(var(--bs-pink-600), var(--bs-pink-300));
-    --bd-pre-bg: light-dark(var(--bs-gray-025), color-mix(in srgb, var(--bs-gray-900), var(--bs-black) 25%));
+    --bd-pre-bg: var(--bs-bg-body);
     --bd-swatch-shadow: inset 0 0 0 1px light-dark(rgb(0 0 0 / .1), rgb(255 255 255 / .1));
   }
 
diff --git a/site/static/docs/[version]/assets/img/guides/bootstrap-npm.png b/site/static/docs/[version]/assets/img/guides/bootstrap-npm.png
new file mode 100644 (file)
index 0000000..5c11c51
Binary files /dev/null and b/site/static/docs/[version]/assets/img/guides/bootstrap-npm.png differ
diff --git a/site/static/docs/[version]/assets/img/guides/bootstrap-npm@2x.png b/site/static/docs/[version]/assets/img/guides/bootstrap-npm@2x.png
new file mode 100644 (file)
index 0000000..3ebd7e0
Binary files /dev/null and b/site/static/docs/[version]/assets/img/guides/bootstrap-npm@2x.png differ
index 50fc799513bb3c384690014b872dab6cef4403bb..54d03ea1df4afb118e0e94cc732f58190758f26d 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-parcel.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-parcel.png differ
index fe0ec4c638b1c7495c29ede5631409c03c2ff494..82aac90e45e9738f311746fd11009843b29fea8f 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-parcel@2x.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-parcel@2x.png differ
index de4cad7343bff12e1bd07ffa37ecfe5409918df9..c6224c31e719ecab25a02103f22c2d17423b6c1d 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-vite.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-vite.png differ
index f8d4858088c015a4dc9533c694b09f40ad2e9022..74c5674f81ec6c302601d7f757818a0bb9638523 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-vite@2x.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-vite@2x.png differ
index c7be9fc390a6e7e1c2349dc15d9926c2fa93f257..89c9e2ed5939a7229a80582b2382ec7349109cff 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-webpack.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-webpack.png differ
index 58fdb9cab88c686bce6e2c343bf419f36dba4143..bb09a2cb7bd325a6dd85b58ec0ebad115b2665f5 100644 (file)
Binary files a/site/static/docs/[version]/assets/img/guides/bootstrap-webpack@2x.png and b/site/static/docs/[version]/assets/img/guides/bootstrap-webpack@2x.png differ