From c7d1d1481e64fe9bf87ca61efbc5b5eb603dea47 Mon Sep 17 00:00:00 2001 From: Kim Loos Date: Thu, 2 Jul 2026 10:58:56 +0200 Subject: [PATCH 1/8] UCD-180 add feature to collapse sidebars, improve style --- CHANGELOG.md | 7 + .../javascripts/content-width-toggle.js | 156 ++++++ .../assets/javascripts/toc-collapse-toggle.js | 162 ++++++ .../stylesheets/mkdocs-doubleslash-theme.css | 492 +++++++++++++++--- .../overrides/.icons/lucide/chevrons-left.svg | 1 + .../.icons/lucide/chevrons-right.svg | 1 + .../partials/content-width-toggle.html | 16 + .../overrides/partials/header.html | 18 +- .../partials/toc-collapse-toggle.html | 26 + mkdocs_doubleslash_theme/plugin.py | 15 + 10 files changed, 831 insertions(+), 63 deletions(-) create mode 100644 mkdocs_doubleslash_theme/assets/javascripts/content-width-toggle.js create mode 100644 mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js create mode 100644 mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-left.svg create mode 100644 mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-right.svg create mode 100644 mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html create mode 100644 mkdocs_doubleslash_theme/overrides/partials/toc-collapse-toggle.html diff --git a/CHANGELOG.md b/CHANGELOG.md index b86527c..6e40f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v1.x.x (unreleased) + +### Features + +- add content width toggle to collapse sidebars and widen the content area on desktop +- add table of contents collapse toggle with initial tooltip in the top right corner + ## v1.2.1 (2026-05-27) ### Bug Fixes diff --git a/mkdocs_doubleslash_theme/assets/javascripts/content-width-toggle.js b/mkdocs_doubleslash_theme/assets/javascripts/content-width-toggle.js new file mode 100644 index 0000000..97dcee8 --- /dev/null +++ b/mkdocs_doubleslash_theme/assets/javascripts/content-width-toggle.js @@ -0,0 +1,156 @@ +(function () { + "use strict"; + + var STORAGE_KEY = "ds-content-wide"; + var DESKTOP_QUERY = "(min-width: 76.25em)"; + var LABEL_COLLAPSE = "Navigation ausblenden"; + var LABEL_EXPAND = "Navigation einblenden"; + + function isWideMode() { + return document.documentElement.hasAttribute("data-ds-content-wide"); + } + + function setWideMode(enabled) { + if (enabled) { + document.documentElement.setAttribute("data-ds-content-wide", ""); + try { + localStorage.setItem(STORAGE_KEY, "true"); + } catch (e) {} + } else { + document.documentElement.removeAttribute("data-ds-content-wide"); + try { + localStorage.removeItem(STORAGE_KEY); + } catch (e) {} + } + } + + function sidebarIsHidden(sidebar) { + return !sidebar || sidebar.hasAttribute("hidden"); + } + + function hasCollapsibleSidebars() { + var primary = document.querySelector(".md-sidebar--primary"); + return !sidebarIsHidden(primary); + } + + function updateButtonState(button) { + var wide = isWideMode(); + var collapseIcon = button.querySelector(".ds-content-width-toggle__icon--collapse"); + var expandIcon = button.querySelector(".ds-content-width-toggle__icon--expand"); + + button.setAttribute("aria-pressed", wide ? "true" : "false"); + button.setAttribute("aria-label", wide ? LABEL_EXPAND : LABEL_COLLAPSE); + button.setAttribute("title", wide ? LABEL_EXPAND : LABEL_COLLAPSE); + + if (collapseIcon) { + collapseIcon.hidden = wide; + } + if (expandIcon) { + expandIcon.hidden = !wide; + } + } + + function updateButtonVisibility(button, desktopMedia) { + var shouldShow = desktopMedia.matches && hasCollapsibleSidebars(); + button.hidden = !shouldShow; + + if (!shouldShow && isWideMode()) { + setWideMode(false); + updateButtonState(button); + document.dispatchEvent(new CustomEvent("ds-content-wide-change")); + } + } + + function updateButtonPosition(button) { + if (isWideMode() || button.hidden) { + button.style.removeProperty("left"); + return; + } + + var content = document.querySelector(".md-content"); + if (!content) { + button.style.removeProperty("left"); + return; + } + + // Align with .md-content border-left (the divider belongs to content, not the sidebar). + button.style.left = content.getBoundingClientRect().left + "px"; + } + + function scheduleButtonPositionUpdate(button) { + window.requestAnimationFrame(function () { + updateButtonPosition(button); + }); + } + + function init() { + var button = document.querySelector("[data-md-component='content-width-toggle']"); + if (!button) { + return; + } + + var desktopMedia = window.matchMedia(DESKTOP_QUERY); + + try { + if (localStorage.getItem(STORAGE_KEY) === "true") { + setWideMode(true); + } + } catch (e) {} + + function refreshButton(button, desktopMedia) { + updateButtonVisibility(button, desktopMedia); + updateButtonState(button); + updateButtonPosition(button); + } + + refreshButton(button, desktopMedia); + + button.addEventListener("click", function () { + setWideMode(!isWideMode()); + refreshButton(button, desktopMedia); + document.dispatchEvent(new CustomEvent("ds-content-wide-change")); + }); + + desktopMedia.addEventListener("change", function () { + refreshButton(button, desktopMedia); + }); + + window.addEventListener("resize", function () { + refreshButton(button, desktopMedia); + }); + + document.addEventListener("ds-toc-collapse-change", function () { + scheduleButtonPositionUpdate(button); + }); + + if (typeof MutationObserver !== "undefined") { + var layoutObserver = new MutationObserver(function () { + scheduleButtonPositionUpdate(button); + }); + layoutObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-ds-toc-collapsed", "data-ds-content-wide"], + }); + } + + if (typeof ResizeObserver !== "undefined") { + var resizeObserver = new ResizeObserver(function () { + scheduleButtonPositionUpdate(button); + }); + var content = document.querySelector(".md-content"); + var mainInner = document.querySelector(".md-main__inner"); + if (content) { + resizeObserver.observe(content); + } + if (mainInner) { + resizeObserver.observe(mainInner); + } + } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js b/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js new file mode 100644 index 0000000..74c74c9 --- /dev/null +++ b/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js @@ -0,0 +1,162 @@ +(function () { + "use strict"; + + var STORAGE_KEY = "ds-toc-collapsed"; + var TOOLTIP_STORAGE_KEY = "ds-toc-toggle-tooltip-shown"; + var DESKTOP_QUERY = "(min-width: 76.25em)"; + var TOOLTIP_DURATION_MS = 3000; + var LABEL_COLLAPSE = "Inhaltsverzeichnis ausblenden"; + var LABEL_EXPAND = "Inhaltsverzeichnis einblenden"; + + function isTocCollapsed() { + return document.documentElement.hasAttribute("data-ds-toc-collapsed"); + } + + function setTocCollapsed(enabled) { + if (enabled) { + document.documentElement.setAttribute("data-ds-toc-collapsed", ""); + try { + localStorage.setItem(STORAGE_KEY, "true"); + } catch (e) {} + } else { + document.documentElement.removeAttribute("data-ds-toc-collapsed"); + try { + localStorage.removeItem(STORAGE_KEY); + } catch (e) {} + } + } + + function tocSidebarIsHidden() { + var secondary = document.querySelector(".md-sidebar--secondary"); + return !secondary || secondary.hasAttribute("hidden"); + } + + function updateButtonState(button) { + var collapsed = isTocCollapsed(); + var collapseIcon = button.querySelector(".ds-toc-toggle__icon--collapse"); + var expandIcon = button.querySelector(".ds-toc-toggle__icon--expand"); + + button.setAttribute("aria-pressed", collapsed ? "true" : "false"); + button.setAttribute("aria-label", collapsed ? LABEL_EXPAND : LABEL_COLLAPSE); + button.setAttribute("title", collapsed ? LABEL_EXPAND : LABEL_COLLAPSE); + + if (collapseIcon) { + collapseIcon.hidden = collapsed; + } + if (expandIcon) { + expandIcon.hidden = !collapsed; + } + } + + function hideTooltip(tooltip) { + if (!tooltip || tooltip.hidden) { + return; + } + tooltip.classList.remove("ds-toc-toggle__tooltip--visible"); + window.setTimeout(function () { + tooltip.hidden = true; + }, 200); + } + + function updateButtonVisibility(button, tooltip, desktopMedia) { + var wasVisible = !button.hidden; + var shouldShow = desktopMedia.matches && !tocSidebarIsHidden(); + button.hidden = !shouldShow; + + if (!shouldShow) { + if (wasVisible) { + hideTooltip(tooltip); + } + if (isTocCollapsed()) { + setTocCollapsed(false); + updateButtonState(button); + } + return; + } + + maybeShowInitialTooltip(button, tooltip); + } + + function maybeShowInitialTooltip(button, tooltip) { + if (!tooltip || button.hidden) { + return; + } + + try { + if (sessionStorage.getItem(TOOLTIP_STORAGE_KEY) === "true") { + return; + } + } catch (e) { + return; + } + + showInitialTooltip(tooltip, button); + } + + function showInitialTooltip(tooltip, button) { + if (!tooltip || tooltip.classList.contains("ds-toc-toggle__tooltip--visible")) { + return; + } + + try { + sessionStorage.setItem(TOOLTIP_STORAGE_KEY, "true"); + } catch (e) {} + + tooltip.hidden = false; + window.requestAnimationFrame(function () { + tooltip.classList.add("ds-toc-toggle__tooltip--visible"); + }); + + window.setTimeout(function () { + hideTooltip(tooltip); + }, TOOLTIP_DURATION_MS); + + button.addEventListener( + "click", + function () { + hideTooltip(tooltip); + }, + { once: true } + ); + } + + function init() { + var button = document.querySelector("[data-md-component='toc-collapse-toggle']"); + var tooltip = document.querySelector("[data-md-component='toc-collapse-tooltip']"); + if (!button) { + return; + } + + var desktopMedia = window.matchMedia(DESKTOP_QUERY); + + try { + if (localStorage.getItem(STORAGE_KEY) === "true") { + setTocCollapsed(true); + } + } catch (e) {} + + updateButtonState(button); + updateButtonVisibility(button, tooltip, desktopMedia); + + button.addEventListener("click", function () { + setTocCollapsed(!isTocCollapsed()); + updateButtonState(button); + document.dispatchEvent(new CustomEvent("ds-toc-collapse-change")); + }); + + desktopMedia.addEventListener("change", function () { + updateButtonVisibility(button, tooltip, desktopMedia); + updateButtonState(button); + }); + + window.addEventListener("resize", function () { + updateButtonVisibility(button, tooltip, desktopMedia); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 7b4c023..0b9b128 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -1,56 +1,87 @@ -[data-md-color-scheme="default"] { - /* Primary color shades */ - --md-primary-fg-color: #ffffff; /* Foreground color. Mainly used for header */ - --md-primary-bg-color: #00759e; /* Icon and text color (header) */ - --md-primary-bg-color--light: #00759e; /* Search placeholder text color */ - --md-default-fg-color--light: #000000; - - /* Accent color shades */ - --md-accent-fg-color: #00759e; /* Link hover color, search item match color */ - --md-accent-fg-color--transparent: #00a5e120; /* search item hover color */ - --md-accent-bg-color: #ffffff; /* text color for hovered items (back to top, linked tag, ..) */ - - --md-typeset-a-color: #00759e; /* Active link color */ - --md-custom-accent-color: #00759e; - --md-custom-text-color: #ffffff; - - --language-toggle-active-color: #ffffff; - --language-toggle-active-text-color: #00759e; - --language-toggle-inactive-text-color: #ffffff; - - --md-typeset-color: #000000; +/* Design system tokens — source: frontend/DESIGN.md (living-styleguide, ref=main) */ +:root { + --ds-color-primary: #00759e; + --ds-color-on-primary: #ffffff; + --ds-color-brand-cyan: #00a5e1; + --ds-color-canvas: #ffffff; + --ds-color-surface-bright: #f9f9f9; + --ds-color-surface-medium: #efefef; + --ds-color-ink: #000000; + --ds-color-tertiary: #6d6d6d; + --ds-color-border: #6d6d6d; + --ds-color-disabled-shape: #c6c6c6; + --ds-color-shadow-elevation: #0000001a; + --ds-color-shadow-token: #00000029; + --ds-color-dark-clickable: #77ddff; + --ds-color-dark-ink: #d7e9f4; + --ds-color-dark-ink-secondary: #afc1c7; + --ds-color-dark-canvas: #15202b; + --ds-color-dark-container: #22303c; + --ds-color-dark-hover: #2d3740; + --ds-rounded-xs: 4px; + --ds-rounded-md: 10px; + --ds-rounded-pill: 32px; + --ds-spacing-xs: 4px; + --ds-spacing-sm: 6px; + --ds-spacing-md: 8px; + --ds-spacing-control-height: 43px; + --ds-spacing-xl: 16px; + --ds-spacing-xxl: 24px; + --ds-motion-duration: 0.3s; + --ds-motion-transition: var(--ds-motion-duration) ease all; +} - --md-footer-bg-color: #6d6d6d; +[data-md-color-scheme="default"] { + --md-primary-fg-color: var(--ds-color-on-primary); + --md-primary-bg-color: var(--ds-color-primary); + --md-primary-bg-color--light: var(--ds-color-primary); + --md-default-fg-color--light: var(--ds-color-ink); + + --md-accent-fg-color: var(--ds-color-primary); + --md-accent-fg-color--transparent: #00a5e120; + --md-accent-bg-color: var(--ds-color-canvas); + + --md-typeset-a-color: var(--ds-color-primary); + --md-custom-accent-color: var(--ds-color-primary); + --md-custom-text-color: var(--ds-color-on-primary); + + --language-toggle-active-color: var(--ds-color-on-primary); + --language-toggle-active-text-color: var(--ds-color-primary); + --language-toggle-inactive-text-color: var(--ds-color-on-primary); + + --md-typeset-color: var(--ds-color-ink); + --md-footer-bg-color: var(--ds-color-canvas); + --md-footer-bg-color--dark: var(--ds-color-tertiary); + --md-footer-fg-color: var(--ds-color-tertiary); } [data-md-color-scheme="slate"] { --md-hue: 210; - --md-default-fg-color: #d7e9f4; /* Text color */ - --md-default-bg-color: #15202b; /* Background color */ - --md-default-fg-color--light: #d7e9f4; - - /* Primary color shades */ - --md-primary-fg-color: #22303c; /* Foreground color. Mainly used for header */ - --md-primary-bg-color: #77ddff; /* Icon and text color (header) */ - --md-primary-bg-color--light: #77ddff; /* Search placeholder text color */ + --md-default-fg-color: var(--ds-color-dark-ink); + --md-default-bg-color: var(--ds-color-dark-canvas); + --md-default-fg-color--light: var(--ds-color-dark-ink); - /* Accent color shades */ - --md-accent-fg-color: #77ddff; /* Link/card hover color, search item match color */ - --md-accent-fg-color--transparent: #00a5e120; /* search item hover color */ - --md-accent-bg-color: #ffffff; /* text color for hovered items (back to top, linked tag, ..) */ + --md-primary-fg-color: var(--ds-color-dark-container); + --md-primary-bg-color: var(--ds-color-dark-clickable); + --md-primary-bg-color--light: var(--ds-color-dark-clickable); - --md-typeset-a-color: #77ddff; /* Active link color */ - --md-custom-accent-color: #15202b; - --md-custom-text-color: #d7e9f4; + --md-accent-fg-color: var(--ds-color-dark-clickable); + --md-accent-fg-color--transparent: #00a5e120; + --md-accent-bg-color: var(--ds-color-canvas); - --language-toggle-active-color: #22303c; - --language-toggle-active-text-color: #77ddff; - --language-toggle-inactive-text-color: #77ddff; + --md-typeset-a-color: var(--ds-color-dark-clickable); + --md-custom-accent-color: var(--ds-color-dark-canvas); + --md-custom-text-color: var(--ds-color-dark-ink); - --md-typeset-color: #ffffff; + --language-toggle-active-color: var(--ds-color-dark-container); + --language-toggle-active-text-color: var(--ds-color-dark-clickable); + --language-toggle-inactive-text-color: var(--ds-color-dark-clickable); - --md-footer-bg-color--dark: #22303c; + --md-typeset-color: var(--ds-color-dark-ink); + --md-footer-bg-color: var(--ds-color-dark-container); + --md-footer-bg-color--dark: var(--ds-color-dark-container); + --md-footer-fg-color: var(--ds-color-dark-ink-secondary); } @font-face { @@ -68,27 +99,40 @@ } :root { - --md-text-font: Inter; + --md-text-font: Inter, Arial, Helvetica, sans-serif; .md-header--shadow { - box-shadow: 0 0 6px #00000029; + box-shadow: 0 0 10px var(--ds-color-shadow-elevation); } .md-header { - padding-top: 0.25rem; - padding-bottom: 0.25rem; + padding-top: var(--ds-spacing-xs); + padding-bottom: var(--ds-spacing-xs); } .md-search__form { background-color: var(--md-primary-fg-color); border: 1px solid var(--md-primary-bg-color); - border-radius: 8px; + border-radius: var(--ds-rounded-md); + min-height: var(--ds-spacing-control-height); + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__form { + border-radius: var(--ds-rounded-md) var(--ds-rounded-md) 0 0; + } + + .md-search__output { + border: 1px solid var(--md-primary-bg-color); + border-radius: 0 0 var(--ds-rounded-md) var(--ds-rounded-md); } .md-tabs__item { color: var(--md-typeset-color); opacity: 1; - font-weight: bold; + font-family: "Inter SemiBold", Arial, Helvetica, sans-serif; + font-size: 15px; + font-weight: 600; + line-height: 24px; } .md-tabs__item a:hover { @@ -98,7 +142,7 @@ .md-tabs__item--active { color: var(--md-typeset-a-color); opacity: 1; - font-weight: bold; + font-weight: 600; } .md-top:hover { @@ -106,8 +150,8 @@ } .md-header__title { - margin-left: 0.5rem; - margin-right: 0.4rem; + margin-left: var(--ds-spacing-md); + margin-right: var(--ds-spacing-sm); } .md-logo svg { @@ -117,8 +161,8 @@ } .md-header__button.md-logo { - margin: 0.2rem; - padding: 0.2rem; + margin: var(--ds-spacing-xs); + padding: var(--ds-spacing-xs); } .md-status { @@ -129,10 +173,23 @@ border-left: none; } + /* Stretch main area on short pages so the content divider reaches the footer */ + .md-main { + display: flex; + flex-direction: column; + } + + .md-main__inner { + flex: 1; + width: 100%; + align-items: stretch; + } + .md-content { border-left: solid; - border-width: thin; - border-color: #6d6d6d80; + border-width: 1px; + border-color: var(--ds-color-border); + transition: border-color var(--ds-motion-duration) ease; } .md-content .md-typeset h1, @@ -141,38 +198,63 @@ .md-content .md-typeset h4, .md-content .md-typeset h5, .md-content .md-typeset h6 { - font-family: "Inter SemiBold", sans-serif; + font-family: "Inter SemiBold", Arial, Helvetica, sans-serif; font-weight: 600; } + /* Website typography — source: typography.body-website, heading-h*-website */ body { + font-family: Inter, Arial, Helvetica, sans-serif; font-size: 18px; line-height: 27px; } .md-content .md-typeset h1 { font-size: 45px; + line-height: 58.5px; margin-bottom: 30px; } .md-content .md-typeset h2 { - font-size: 25px; + font-size: 40px; + line-height: 52px; margin-bottom: 15px; } .md-content .md-typeset h3 { font-size: 25px; + line-height: 40px; + margin-bottom: 12px; + } + + .md-content .md-typeset h4 { + font-size: 23px; + line-height: 29.9px; + margin-bottom: 12px; + } + + .md-content .md-typeset h5 { + font-size: 20px; + line-height: 26px; + margin-bottom: 12px; + } + + .md-content .md-typeset h6 { + font-size: 18px; + line-height: 24px; margin-bottom: 12px; } + /* link-primary — no underline at rest */ .md-typeset a { - text-decoration: underline; + text-decoration: none; color: var(--md-typeset-a-color); - transition: text-decoration-thickness 0.2s ease; + transition: var(--ds-motion-transition); } .md-typeset a:hover { - text-decoration-thickness: 2px; + text-decoration: underline; + text-decoration-thickness: 1px; } [data-md-color-scheme="default"] .md-typeset ul:not([class]) > li { @@ -180,12 +262,40 @@ } [data-md-color-scheme="slate"] .md-typeset ul:not([class]) > li { - list-style-image: url("data:image/svg+xml;utf8,"); + list-style-image: url("data:image/svg+xml;utf8,"); } + /* table-default */ .md-typeset table:not([class]) { border-collapse: collapse; - border-radius: 10px; + border-radius: var(--ds-rounded-md); + font-size: 15px; + line-height: 24px; + } + + .md-typeset table:not([class]) thead { + font-family: "Inter SemiBold", Arial, Helvetica, sans-serif; + font-weight: 600; + } + + .md-typeset table:not([class]) th { + padding: 15px 15px 7px 15px; + } + + .md-typeset table:not([class]) td { + padding: 15px 30px 15px 15px; + } + + .md-typeset table:not([class]) tbody tr { + border-bottom: 1px solid var(--ds-color-disabled-shape); + } + + .md-typeset table:not([class]) tbody tr:hover { + background-color: var(--ds-color-surface-bright); + } + + [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody tr:hover { + background-color: var(--ds-color-dark-hover); } tr:last-child { @@ -198,6 +308,17 @@ .md-path__item:not(:first-child) { align-items: center; + font-size: 13px; + line-height: 16px; + } + + .md-footer { + font-size: 15px; + line-height: 19px; + } + + .md-footer-meta { + border-top: 1px solid var(--ds-color-disabled-shape); } } @@ -211,3 +332,250 @@ .md-grid { max-width: min(1800px, 100% - 4rem); } + +@media screen and (min-width: 76.25em) { + .md-content { + padding-left: 40px; + } + + .md-sidebar--primary, + .md-sidebar--secondary { + transition: + width var(--ds-motion-duration) ease, + min-width var(--ds-motion-duration) ease, + opacity var(--ds-motion-duration) ease, + visibility 0s linear 0s; + } + + .md-main__inner { + transition: margin var(--ds-motion-duration) ease; + } + + html[data-ds-content-wide] .md-sidebar--primary { + width: 0; + min-width: 0; + opacity: 0; + overflow: hidden; + pointer-events: none; + visibility: hidden; + transition: + width var(--ds-motion-duration) ease, + min-width var(--ds-motion-duration) ease, + opacity var(--ds-motion-duration) ease, + visibility 0s linear var(--ds-motion-duration); + } + + html[data-ds-toc-collapsed] .md-sidebar--secondary { + width: 0; + min-width: 0; + opacity: 0; + overflow: hidden; + pointer-events: none; + visibility: hidden; + transition: + width var(--ds-motion-duration) ease, + min-width var(--ds-motion-duration) ease, + opacity var(--ds-motion-duration) ease, + visibility 0s linear var(--ds-motion-duration); + } + + html[data-ds-content-wide] .md-main__inner { + margin-left: 0; + } + + html[data-ds-toc-collapsed] .md-main__inner { + margin-right: 80px; + } + + html[data-ds-content-wide] .md-content { + border-left-color: transparent; + } + + .ds-content-width-toggle { + --ds-toggle-size: 2rem; + position: fixed; + top: 50%; + margin-top: calc(var(--ds-toggle-size) / -2); + margin-left: calc(var(--ds-toggle-size) / -2); + display: flex; + align-items: center; + justify-content: center; + width: var(--ds-toggle-size); + height: var(--ds-toggle-size); + padding: 0; + border: 2px solid var(--ds-color-border); + border-radius: 50px; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + transition: var(--ds-motion-transition); + } + + .ds-content-width-toggle:hover { + color: white; + background-color: var(--ds-color-primary); + + } + + .ds-content-width-toggle:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--ds-color-on-primary), + 0 0 0 4px var(--ds-color-primary); + } + + [data-md-color-scheme="slate"] .ds-content-width-toggle:focus-visible { + box-shadow: + 0 0 0 2px var(--ds-color-dark-canvas), + 0 0 0 4px var(--ds-color-dark-clickable); + } + + .ds-content-width-toggle[hidden] { + display: none; + } + + .ds-content-width-toggle__icon { + display: flex; + align-items: center; + justify-content: center; + } + + .ds-content-width-toggle__icon[hidden] { + display: none; + } + + html[data-ds-content-wide] .ds-content-width-toggle { + --ds-toggle-size: var(--ds-spacing-control-height); + left: var(--ds-spacing-xl); + margin-left: 0; + border: inset 0 0 0 2px var(--ds-color-primary); + border-radius: 50px; + } + + [data-md-color-scheme="slate"] html[data-ds-content-wide] .ds-content-width-toggle { + border-color: var(--ds-color-dark-clickable); + } + + .ds-toc-toggle-wrapper { + position: fixed; + top: 4.8rem; + right: var(--ds-spacing-xl); + z-index: 4; + } + + .ds-toc-toggle { + display: flex; + align-items: center; + justify-content: center; + width: var(--ds-spacing-control-height); + height: var(--ds-spacing-control-height); + padding: 0; + margin: 5px 0 0 0; + border: inset 0 0 0 2px var(--ds-color-primary); + border-radius: 50px; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + transition: var(--ds-motion-transition); + } + + [data-md-color-scheme="slate"] .ds-toc-toggle { + border-color: var(--ds-color-dark-clickable); + } + + .ds-toc-toggle:hover { + color: white; + background-color: var(--ds-color-primary); + } + + .ds-toc-toggle:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--ds-color-on-primary), + 0 0 0 4px var(--ds-color-primary); + } + + [data-md-color-scheme="slate"] .ds-toc-toggle:focus-visible { + box-shadow: + 0 0 0 2px var(--ds-color-dark-canvas), + 0 0 0 4px var(--ds-color-dark-clickable); + } + + .ds-toc-toggle[hidden], + .ds-toc-toggle-wrapper:has(.ds-toc-toggle[hidden]) { + display: none; + } + + .ds-toc-toggle__icon { + display: flex; + align-items: center; + justify-content: center; + } + + .ds-toc-toggle__icon[hidden] { + display: none; + } + + /* tooltip-light */ + .ds-toc-toggle__tooltip { + position: absolute; + top: calc(100% + var(--ds-spacing-xl)); + right: 0; + width: min(14rem, calc(100vw - 2 * var(--ds-spacing-xl))); + padding: var(--ds-spacing-xl) var(--ds-spacing-xxl); + border-radius: var(--ds-rounded-md); + background-color: var(--ds-color-surface-medium); + color: var(--ds-color-ink); + font-size: 15px; + line-height: 24px; + text-align: left; + white-space: normal; + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; + box-sizing: border-box; + box-shadow: 0 0 10px var(--ds-color-shadow-elevation); + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + } + + .ds-toc-toggle__tooltip::before { + content: ""; + position: absolute; + bottom: 100%; + right: calc(var(--ds-spacing-control-height) / 2); + transform: translateX(50%); + border: 8px solid transparent; + border-bottom-color: var(--ds-color-surface-medium); + filter: drop-shadow(0 -2px 1px var(--ds-color-shadow-elevation)); + } + + [data-md-color-scheme="slate"] .ds-toc-toggle__tooltip { + background-color: var(--ds-color-disabled-shape); + color: var(--ds-color-ink); + } + + [data-md-color-scheme="slate"] .ds-toc-toggle__tooltip::before { + border-bottom-color: var(--ds-color-disabled-shape); + } + + .ds-toc-toggle__tooltip--visible { + opacity: 1; + } + + .ds-toc-toggle__tooltip[hidden] { + display: none; + } + + @media (prefers-reduced-motion: reduce) { + .md-sidebar--primary, + .md-sidebar--secondary, + .md-main__inner, + .md-content, + .ds-content-width-toggle, + .ds-toc-toggle { + transition: none !important; + } + } +} diff --git a/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-left.svg b/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-left.svg new file mode 100644 index 0000000..3786f5e --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-left.svg @@ -0,0 +1 @@ + diff --git a/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-right.svg b/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-right.svg new file mode 100644 index 0000000..2fe0cbc --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/.icons/lucide/chevrons-right.svg @@ -0,0 +1 @@ + diff --git a/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html b/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html new file mode 100644 index 0000000..cbeb24b --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html @@ -0,0 +1,16 @@ + diff --git a/mkdocs_doubleslash_theme/overrides/partials/header.html b/mkdocs_doubleslash_theme/overrides/partials/header.html index 61bd393..1e5e00c 100644 --- a/mkdocs_doubleslash_theme/overrides/partials/header.html +++ b/mkdocs_doubleslash_theme/overrides/partials/header.html @@ -30,6 +30,18 @@ {% endif %} +{% if config.extra.ds_content_width_toggle | default(true) %} + +{% endif %}
+{% if config.extra.ds_content_width_toggle | default(true) %} + {% include "partials/content-width-toggle.html" %} + {% include "partials/toc-collapse-toggle.html" %} +{% endif %} \ No newline at end of file diff --git a/mkdocs_doubleslash_theme/overrides/partials/toc-collapse-toggle.html b/mkdocs_doubleslash_theme/overrides/partials/toc-collapse-toggle.html new file mode 100644 index 0000000..26de683 --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/partials/toc-collapse-toggle.html @@ -0,0 +1,26 @@ +
+ + +
diff --git a/mkdocs_doubleslash_theme/plugin.py b/mkdocs_doubleslash_theme/plugin.py index c6fcb89..e4c8081 100644 --- a/mkdocs_doubleslash_theme/plugin.py +++ b/mkdocs_doubleslash_theme/plugin.py @@ -1,5 +1,6 @@ import os +from mkdocs.config import config_options from mkdocs.plugins import BasePlugin from mkdocs.structure.files import File @@ -7,6 +8,10 @@ class DoubleSlashThemePlugin(BasePlugin): """MkDocs plugin that applies doubleSlash corporate styling to Material theme.""" + config_scheme = ( + ("content_width_toggle", config_options.Type(bool, default=True)), + ) + def __init__(self): super().__init__() plugin_dir = os.path.dirname(__file__) @@ -17,6 +22,16 @@ def on_config(self, config): theme = config["theme"] config["extra_css"].append("stylesheets/mkdocs-doubleslash-theme.css") + content_width_toggle = self.config.get("content_width_toggle", True) + config.setdefault("extra", {})["ds_content_width_toggle"] = content_width_toggle + if content_width_toggle: + config["extra_javascript"].append( + "javascripts/content-width-toggle.js" + ) + config["extra_javascript"].append( + "javascripts/toc-collapse-toggle.js" + ) + # Insert overrides after user's custom_dir (if any) so user overrides take precedence if self.overrides_dir not in theme.dirs: insert_pos = ( From fbc436978de1a2a46406b87ae80ac9d82f977b88 Mon Sep 17 00:00:00 2001 From: Kim Loos Date: Thu, 2 Jul 2026 14:36:59 +0200 Subject: [PATCH 2/8] UCD-180 adjust table style and dark mode switch --- CHANGELOG.md | 1 + .../stylesheets/mkdocs-doubleslash-theme.css | 209 ++++++++++++++++-- .../overrides/.icons/lucide/circle-half.svg | 1 + .../overrides/partials/palette.html | 56 +++++ mkdocs_doubleslash_theme/plugin.py | 26 +-- 5 files changed, 267 insertions(+), 26 deletions(-) create mode 100644 mkdocs_doubleslash_theme/overrides/.icons/lucide/circle-half.svg create mode 100644 mkdocs_doubleslash_theme/overrides/partials/palette.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e40f75..d9d8112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - add content width toggle to collapse sidebars and widen the content area on desktop - add table of contents collapse toggle with initial tooltip in the top right corner +- refactor styles to align better with styleguide ## v1.2.1 (2026-05-27) diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 0b9b128..1426774 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -18,6 +18,7 @@ --ds-color-dark-canvas: #15202b; --ds-color-dark-container: #22303c; --ds-color-dark-hover: #2d3740; + --ds-color-dark-disabled: #84999f; --ds-rounded-xs: 4px; --ds-rounded-md: 10px; --ds-rounded-pill: 32px; @@ -265,45 +266,74 @@ list-style-image: url("data:image/svg+xml;utf8,"); } - /* table-default */ + /* table-default — source: docs/components-html-css/Molecules/table.md */ .md-typeset table:not([class]) { + border: none; border-collapse: collapse; border-radius: var(--ds-rounded-md); + box-shadow: none; font-size: 15px; - line-height: 24px; + line-height: 19px; + white-space: nowrap; } .md-typeset table:not([class]) thead { font-family: "Inter SemiBold", Arial, Helvetica, sans-serif; font-weight: 600; + color: var(--md-typeset-color); + } + + /* Rahmen um tbody per Pseudo-Elemente (Default-Stil) */ + .md-typeset table:not([class]) tbody { + position: relative; + } + + .md-typeset table:not([class]) tbody::before, + .md-typeset table:not([class]) tbody::after { + content: ""; + position: absolute; + inset: 0; + border: 1px solid var(--ds-color-disabled-shape); + border-radius: var(--ds-rounded-md); + pointer-events: none; } .md-typeset table:not([class]) th { - padding: 15px 15px 7px 15px; + padding: 0 15px 7px 15px; + background-color: transparent; + text-align: left; } .md-typeset table:not([class]) td { - padding: 15px 30px 15px 15px; + padding: 15px; + border: none; } .md-typeset table:not([class]) tbody tr { + height: 50px; border-bottom: 1px solid var(--ds-color-disabled-shape); + text-align: left; + } + + .md-typeset table:not([class]) tbody tr:last-child { + border-bottom: none; } .md-typeset table:not([class]) tbody tr:hover { background-color: var(--ds-color-surface-bright); } - [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody tr:hover { - background-color: var(--ds-color-dark-hover); + [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody::before, + [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody::after { + border-color: var(--ds-color-dark-disabled); } - tr:last-child { - border-bottom: none; + [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody tr { + border-bottom-color: var(--ds-color-dark-disabled); } - td:last-child { - border-right: none; + [data-md-color-scheme="slate"] .md-typeset table:not([class]) tbody tr:hover { + background-color: var(--ds-color-dark-hover); } .md-path__item:not(:first-child) { @@ -329,6 +359,159 @@ width: 1rem; } +/* switch-icon-3 — source: docs/components-html-css/Molecules/switch.md (Icon-3) */ +.ds-switch-wrapper.md-header__option { + width: auto; + flex-shrink: 0; + overflow: visible; +} + +.ds-switch-wrapper { + padding: 0; + margin: 0 var(--ds-spacing-md) 0 0; +} + +.ds-switch-wrapper *, +.ds-switch-wrapper *::before, +.ds-switch-wrapper *::after { + box-sizing: border-box; +} + +.ds-switch-wrapper .switch-field { + display: flex; + overflow: hidden; + position: relative; + border-radius: 1rem; + align-items: center; + user-select: none; +} + +.ds-switch-wrapper .switch-field input[type="radio"] { + position: absolute; + clip: rect(0, 0, 0, 0); + height: 0.0625rem; + width: 0.0625rem; + border: 0; + overflow: hidden; +} + +.ds-switch-wrapper .switch-field label.option { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 2rem; + width: 2rem; + min-width: 0; + background-color: var(--ds-color-primary); + color: var(--ds-color-on-primary); + font: normal normal 600 0.75rem/1.563rem Inter, Arial, Helvetica, sans-serif; + text-align: center; + padding: 0.25rem; + margin-right: -0.0625rem; + cursor: pointer; + transition: all 0.1s ease-in-out; +} + +.ds-switch-wrapper .switch-field label.option:last-of-type { + margin-right: 0; +} + +.ds-switch-wrapper .switch-field label.option:hover { + cursor: pointer; +} + +.ds-switch-wrapper .switch { + display: flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.25rem; + padding: 0; + background-color: var(--ds-color-on-primary); + color: var(--ds-color-primary); + border-radius: 2rem; + position: absolute; + z-index: 2; + border: 0; + transition: 0.3s ease all; + left: 0.125rem; + pointer-events: none; +} + +.ds-switch-wrapper .switch__icon { + display: none; + align-items: center; + justify-content: center; +} + +.ds-switch-wrapper .switch__icon svg { + height: 0.875rem; + width: 0.875rem; +} + +.ds-switch-wrapper svg.lucide-circle-half path { + fill: currentColor; +} + +.ds-switch-wrapper .switch-field:hover .switch { + transform: translateX(0.125rem); +} + +.ds-switch-wrapper:has(#__palette_1:checked) .switch-field:hover .switch { + transform: none; +} + +.ds-switch-wrapper:has(#__palette_2:checked) .switch-field:hover .switch { + transform: translateX(-0.125rem); +} + +.ds-switch-wrapper:has(#__palette_0:checked) .switch { + left: 0.125rem; +} + +.ds-switch-wrapper:has(#__palette_1:checked) .switch { + left: 2.0625rem; +} + +.ds-switch-wrapper:has(#__palette_2:checked) .switch { + left: 4rem; +} + +.ds-switch-wrapper:has(#__palette_0:checked) .switch__icon--0, +.ds-switch-wrapper:has(#__palette_1:checked) .switch__icon--1, +.ds-switch-wrapper:has(#__palette_2:checked) .switch__icon--2 { + display: flex; +} + +.ds-switch-wrapper .switch-field label.option:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--ds-color-on-primary), + 0 0 0 4px var(--ds-color-primary); + border-radius: 2rem; +} + +/* Dark mode: darker track distinct from header, rounded sliding pill for the active segment */ +[data-md-color-scheme="slate"] .ds-switch-wrapper .switch-field { + background-color: var(--ds-color-dark-canvas); +} + +[data-md-color-scheme="slate"] .ds-switch-wrapper .switch-field label.option { + background-color: var(--ds-color-dark-canvas); + color: var(--ds-color-dark-clickable); +} + +[data-md-color-scheme="slate"] .ds-switch-wrapper .switch { + background-color: var(--ds-color-dark-container); + color: var(--ds-color-dark-clickable); +} + +[data-md-color-scheme="slate"] .ds-switch-wrapper .switch-field label.option:focus-visible { + box-shadow: + 0 0 0 2px var(--ds-color-dark-canvas), + 0 0 0 4px var(--ds-color-dark-clickable); +} + .md-grid { max-width: min(1800px, 100% - 4rem); } @@ -458,9 +641,8 @@ .ds-toc-toggle-wrapper { position: fixed; - top: 4.8rem; + top: 6rem; right: var(--ds-spacing-xl); - z-index: 4; } .ds-toc-toggle { @@ -574,7 +756,8 @@ .md-main__inner, .md-content, .ds-content-width-toggle, - .ds-toc-toggle { + .ds-toc-toggle, + .ds-switch-wrapper .switch { transition: none !important; } } diff --git a/mkdocs_doubleslash_theme/overrides/.icons/lucide/circle-half.svg b/mkdocs_doubleslash_theme/overrides/.icons/lucide/circle-half.svg new file mode 100644 index 0000000..4bd6c80 --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/.icons/lucide/circle-half.svg @@ -0,0 +1 @@ + diff --git a/mkdocs_doubleslash_theme/overrides/partials/palette.html b/mkdocs_doubleslash_theme/overrides/partials/palette.html new file mode 100644 index 0000000..759ff3e --- /dev/null +++ b/mkdocs_doubleslash_theme/overrides/partials/palette.html @@ -0,0 +1,56 @@ + + +
+
+
+ + + {% for option in config.theme.palette %} + {% set scheme = option.scheme | d("default", true) %} + {% set primary = option.primary | d("indigo", true) %} + {% set accent = option.accent | d("indigo", true) %} + + {% if option.toggle %} + + {% endif %} + {% endfor %} +
+
+
diff --git a/mkdocs_doubleslash_theme/plugin.py b/mkdocs_doubleslash_theme/plugin.py index e4c8081..1908056 100644 --- a/mkdocs_doubleslash_theme/plugin.py +++ b/mkdocs_doubleslash_theme/plugin.py @@ -45,30 +45,30 @@ def on_config(self, config): if not theme.get("palette"): theme["palette"] = [ { - "media": "(prefers-color-scheme)", + "media": "(prefers-color-scheme: dark)", + "primary": "custom", + "accent": "custom", + "scheme": "slate", "toggle": { - "icon": "lucide/sun-moon", - "name": "Switch to light mode", + "icon": "lucide/moon-star", + "name": "Dark mode", }, }, { - "media": "(prefers-color-scheme: light)", - "primary": "custom", - "accent": "custom", - "scheme": "default", + "media": "(prefers-color-scheme)", "toggle": { - "icon": "lucide/sun", - "name": "Switch to dark mode", + "icon": "lucide/circle-half", + "name": "System color scheme", }, }, { - "media": "(prefers-color-scheme: dark)", + "media": "(prefers-color-scheme: light)", "primary": "custom", "accent": "custom", - "scheme": "slate", + "scheme": "default", "toggle": { - "icon": "lucide/moon-star", - "name": "Switch to system preference", + "icon": "lucide/sun", + "name": "Light mode", }, }, ] From cee62cfda12e1a4f29546d35aae53f494bcb03d1 Mon Sep 17 00:00:00 2001 From: kloos-ds Date: Tue, 7 Jul 2026 12:12:37 +0200 Subject: [PATCH 3/8] Update mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css Co-authored-by: ds-giusy <108790481+ds-giusy@users.noreply.github.com> --- .../assets/stylesheets/mkdocs-doubleslash-theme.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 1426774..0c76f56 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -381,7 +381,7 @@ display: flex; overflow: hidden; position: relative; - border-radius: 1rem; + border-radius: var(--ds-rounded-lg); align-items: center; user-select: none; } From c90c1222a8640e499f678c73c33c8f3f07ee261c Mon Sep 17 00:00:00 2001 From: kloos-ds Date: Tue, 7 Jul 2026 12:13:10 +0200 Subject: [PATCH 4/8] Update mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css Co-authored-by: ds-giusy <108790481+ds-giusy@users.noreply.github.com> --- .../assets/stylesheets/mkdocs-doubleslash-theme.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 0c76f56..c3896c1 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -429,7 +429,7 @@ padding: 0; background-color: var(--ds-color-on-primary); color: var(--ds-color-primary); - border-radius: 2rem; + border-radius: var(--ds-rounded-pill); position: absolute; z-index: 2; border: 0; From 2270289f7d0327d6fbf0df1d28f73bca06cccc53 Mon Sep 17 00:00:00 2001 From: kloos-ds Date: Tue, 7 Jul 2026 12:13:55 +0200 Subject: [PATCH 5/8] Update mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css Co-authored-by: ds-giusy <108790481+ds-giusy@users.noreply.github.com> --- .../assets/stylesheets/mkdocs-doubleslash-theme.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index c3896c1..0aaf033 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -595,7 +595,7 @@ } .ds-content-width-toggle:hover { - color: white; + color: var(--ds-color-on-primary); background-color: var(--ds-color-primary); } From ab547392afddd0a2d70a8cfdd2ca163e14d4e5fb Mon Sep 17 00:00:00 2001 From: kloos-ds Date: Tue, 7 Jul 2026 12:14:24 +0200 Subject: [PATCH 6/8] Update mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css Co-authored-by: ds-giusy <108790481+ds-giusy@users.noreply.github.com> --- .../assets/stylesheets/mkdocs-doubleslash-theme.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 0aaf033..1c0c4fd 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -666,7 +666,7 @@ } .ds-toc-toggle:hover { - color: white; + color: var(--ds-color-on-primary); background-color: var(--ds-color-primary); } From d45408857f11aa8543bcbb24ce3f45e08917b743 Mon Sep 17 00:00:00 2001 From: Kim Loos Date: Tue, 7 Jul 2026 13:19:07 +0200 Subject: [PATCH 7/8] UCD-180 remove screen width restriction for toc collapse button, remove background from toc title, further feedback --- .../assets/javascripts/toc-collapse-toggle.js | 127 ++++++-- .../stylesheets/mkdocs-doubleslash-theme.css | 306 ++++++++++-------- .../partials/content-width-toggle.html | 4 +- .../partials/toc-collapse-toggle.html | 7 +- 4 files changed, 282 insertions(+), 162 deletions(-) diff --git a/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js b/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js index 74c74c9..3c2855a 100644 --- a/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js +++ b/mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js @@ -26,9 +26,24 @@ } } - function tocSidebarIsHidden() { + function tocIsUnavailable() { var secondary = document.querySelector(".md-sidebar--secondary"); - return !secondary || secondary.hasAttribute("hidden"); + if (!secondary || secondary.hasAttribute("hidden")) { + return true; + } + // Material hides the secondary sidebar on small screens (display: none), + // where the ToC is folded into the navigation drawer instead. offsetParent + // is null when the element (or an ancestor) is display:none, so this covers + // the responsive case without hardcoding a breakpoint. A collapsed sidebar + // uses visibility:hidden (not display:none), so it still keeps a layout box + // and the button stays available to expand it again. + if (secondary.offsetParent === null) { + return true; + } + // Material renders the secondary sidebar even for pages without headings, + // leaving an empty ToC. Treat an empty ToC as hidden so the toggle button + // is only shown when there is an actual table of contents to collapse. + return !secondary.querySelector(".md-nav--secondary .md-nav__item"); } function updateButtonState(button) { @@ -48,24 +63,85 @@ } } - function hideTooltip(tooltip) { - if (!tooltip || tooltip.hidden) { + function shouldKeepTooltipVisible(button, isHovered) { + return document.activeElement === button || isHovered; + } + + function showTooltip(tooltip) { + if (!tooltip) { + return; + } + tooltip.classList.add("ds-toc-toggle__tooltip--visible"); + } + + function hideTooltip(tooltip, button, isHovered, force) { + if (!tooltip || !tooltip.classList.contains("ds-toc-toggle__tooltip--visible")) { + return; + } + if (!force && button && shouldKeepTooltipVisible(button, isHovered)) { return; } tooltip.classList.remove("ds-toc-toggle__tooltip--visible"); - window.setTimeout(function () { - tooltip.hidden = true; - }, 200); } - function updateButtonVisibility(button, tooltip, desktopMedia) { + function bindTooltipInteractions(button, tooltip) { + var isHovered = false; + var autoHideTimer = null; + + function clearAutoHideTimer() { + if (autoHideTimer !== null) { + window.clearTimeout(autoHideTimer); + autoHideTimer = null; + } + } + + function hideIfUnpinned(force) { + hideTooltip(tooltip, button, isHovered, force); + } + + function scheduleAutoHide() { + clearAutoHideTimer(); + autoHideTimer = window.setTimeout(function () { + hideIfUnpinned(false); + }, TOOLTIP_DURATION_MS); + } + + button.addEventListener("focus", function () { + clearAutoHideTimer(); + showTooltip(tooltip); + }); + + button.addEventListener("blur", function () { + hideIfUnpinned(false); + }); + + button.addEventListener("mouseenter", function () { + isHovered = true; + clearAutoHideTimer(); + showTooltip(tooltip); + }); + + button.addEventListener("mouseleave", function () { + isHovered = false; + hideIfUnpinned(false); + }); + + return { + scheduleAutoHide: scheduleAutoHide, + clearAutoHideTimer: clearAutoHideTimer, + hideIfUnpinned: hideIfUnpinned, + }; + } + + function updateButtonVisibility(button, tooltip, tooltipControl) { var wasVisible = !button.hidden; - var shouldShow = desktopMedia.matches && !tocSidebarIsHidden(); + var shouldShow = !tocIsUnavailable(); button.hidden = !shouldShow; if (!shouldShow) { if (wasVisible) { - hideTooltip(tooltip); + tooltipControl.clearAutoHideTimer(); + tooltipControl.hideIfUnpinned(true); } if (isTocCollapsed()) { setTocCollapsed(false); @@ -74,10 +150,10 @@ return; } - maybeShowInitialTooltip(button, tooltip); + maybeShowInitialTooltip(button, tooltip, tooltipControl); } - function maybeShowInitialTooltip(button, tooltip) { + function maybeShowInitialTooltip(button, tooltip, tooltipControl) { if (!tooltip || button.hidden) { return; } @@ -90,10 +166,10 @@ return; } - showInitialTooltip(tooltip, button); + showInitialTooltip(tooltip, tooltipControl); } - function showInitialTooltip(tooltip, button) { + function showInitialTooltip(tooltip, tooltipControl) { if (!tooltip || tooltip.classList.contains("ds-toc-toggle__tooltip--visible")) { return; } @@ -102,22 +178,10 @@ sessionStorage.setItem(TOOLTIP_STORAGE_KEY, "true"); } catch (e) {} - tooltip.hidden = false; window.requestAnimationFrame(function () { - tooltip.classList.add("ds-toc-toggle__tooltip--visible"); + showTooltip(tooltip); + tooltipControl.scheduleAutoHide(); }); - - window.setTimeout(function () { - hideTooltip(tooltip); - }, TOOLTIP_DURATION_MS); - - button.addEventListener( - "click", - function () { - hideTooltip(tooltip); - }, - { once: true } - ); } function init() { @@ -127,6 +191,7 @@ return; } + var tooltipControl = bindTooltipInteractions(button, tooltip); var desktopMedia = window.matchMedia(DESKTOP_QUERY); try { @@ -136,7 +201,7 @@ } catch (e) {} updateButtonState(button); - updateButtonVisibility(button, tooltip, desktopMedia); + updateButtonVisibility(button, tooltip, tooltipControl); button.addEventListener("click", function () { setTocCollapsed(!isTocCollapsed()); @@ -145,12 +210,12 @@ }); desktopMedia.addEventListener("change", function () { - updateButtonVisibility(button, tooltip, desktopMedia); + updateButtonVisibility(button, tooltip, tooltipControl); updateButtonState(button); }); window.addEventListener("resize", function () { - updateButtonVisibility(button, tooltip, desktopMedia); + updateButtonVisibility(button, tooltip, tooltipControl); }); } diff --git a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css index 1426774..acd6da0 100644 --- a/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css +++ b/mkdocs_doubleslash_theme/assets/stylesheets/mkdocs-doubleslash-theme.css @@ -30,6 +30,36 @@ --ds-spacing-xxl: 24px; --ds-motion-duration: 0.3s; --ds-motion-transition: var(--ds-motion-duration) ease all; + + /* Typography tokens — source: frontend/DESIGN.md (living-styleguide, ref=main) */ + --ds-typography-body-website-font-size: 18px; + --ds-typography-body-website-line-height: 27px; + --ds-typography-body-md-font-size: 15px; + --ds-typography-body-md-line-height: 24px; + --ds-typography-body-strong-font-size: 15px; + --ds-typography-body-strong-line-height: 24px; + --ds-typography-footer-md-font-size: 15px; + --ds-typography-footer-md-line-height: 19px; + --ds-typography-caption-subfooter-font-size: 13px; + --ds-typography-caption-subfooter-line-height: 16px; + --ds-typography-heading-h1-website-font-size: 45px; + --ds-typography-heading-h1-website-line-height: 58.5px; + --ds-typography-heading-h1-website-margin-bottom: 30px; + --ds-typography-heading-h2-website-font-size: 40px; + --ds-typography-heading-h2-website-line-height: 52px; + --ds-typography-heading-h2-website-margin-bottom: 15px; + --ds-typography-heading-h3-website-font-size: 25px; + --ds-typography-heading-h3-website-line-height: 40px; + --ds-typography-heading-h3-website-margin-bottom: 12px; + --ds-typography-heading-h4-font-size: 23px; + --ds-typography-heading-h4-line-height: 29.9px; + --ds-typography-heading-h4-margin-bottom: 12px; + --ds-typography-heading-h5-font-size: 20px; + --ds-typography-heading-h5-line-height: 26px; + --ds-typography-heading-h5-margin-bottom: 12px; + --ds-typography-heading-h6-font-size: 18px; + --ds-typography-heading-h6-line-height: 24px; + --ds-typography-heading-h6-margin-bottom: 12px; } [data-md-color-scheme="default"] { @@ -39,7 +69,7 @@ --md-default-fg-color--light: var(--ds-color-ink); --md-accent-fg-color: var(--ds-color-primary); - --md-accent-fg-color--transparent: #00a5e120; + --md-accent-fg-color--transparent: color-mix(in srgb, var(--ds-color-brand-cyan) 12.5%, transparent); --md-accent-bg-color: var(--ds-color-canvas); --md-typeset-a-color: var(--ds-color-primary); @@ -68,7 +98,7 @@ --md-primary-bg-color--light: var(--ds-color-dark-clickable); --md-accent-fg-color: var(--ds-color-dark-clickable); - --md-accent-fg-color--transparent: #00a5e120; + --md-accent-fg-color--transparent: color-mix(in srgb, var(--ds-color-brand-cyan) 12.5%, transparent); --md-accent-bg-color: var(--ds-color-canvas); --md-typeset-a-color: var(--ds-color-dark-clickable); @@ -131,9 +161,9 @@ color: var(--md-typeset-color); opacity: 1; font-family: "Inter SemiBold", Arial, Helvetica, sans-serif; - font-size: 15px; + font-size: var(--ds-typography-body-strong-font-size); font-weight: 600; - line-height: 24px; + line-height: var(--ds-typography-body-strong-line-height); } .md-tabs__item a:hover { @@ -206,44 +236,44 @@ /* Website typography — source: typography.body-website, heading-h*-website */ body { font-family: Inter, Arial, Helvetica, sans-serif; - font-size: 18px; - line-height: 27px; + font-size: var(--ds-typography-body-website-font-size); + line-height: var(--ds-typography-body-website-line-height); } .md-content .md-typeset h1 { - font-size: 45px; - line-height: 58.5px; - margin-bottom: 30px; + font-size: var(--ds-typography-heading-h1-website-font-size); + line-height: var(--ds-typography-heading-h1-website-line-height); + margin-bottom: var(--ds-typography-heading-h1-website-margin-bottom); } .md-content .md-typeset h2 { - font-size: 40px; - line-height: 52px; - margin-bottom: 15px; + font-size: var(--ds-typography-heading-h2-website-font-size); + line-height: var(--ds-typography-heading-h2-website-line-height); + margin-bottom: var(--ds-typography-heading-h2-website-margin-bottom); } .md-content .md-typeset h3 { - font-size: 25px; - line-height: 40px; - margin-bottom: 12px; + font-size: var(--ds-typography-heading-h3-website-font-size); + line-height: var(--ds-typography-heading-h3-website-line-height); + margin-bottom: var(--ds-typography-heading-h3-website-margin-bottom); } .md-content .md-typeset h4 { - font-size: 23px; - line-height: 29.9px; - margin-bottom: 12px; + font-size: var(--ds-typography-heading-h4-font-size); + line-height: var(--ds-typography-heading-h4-line-height); + margin-bottom: var(--ds-typography-heading-h4-margin-bottom); } .md-content .md-typeset h5 { - font-size: 20px; - line-height: 26px; - margin-bottom: 12px; + font-size: var(--ds-typography-heading-h5-font-size); + line-height: var(--ds-typography-heading-h5-line-height); + margin-bottom: var(--ds-typography-heading-h5-margin-bottom); } .md-content .md-typeset h6 { - font-size: 18px; - line-height: 24px; - margin-bottom: 12px; + font-size: var(--ds-typography-heading-h6-font-size); + line-height: var(--ds-typography-heading-h6-line-height); + margin-bottom: var(--ds-typography-heading-h6-margin-bottom); } /* link-primary — no underline at rest */ @@ -272,8 +302,8 @@ border-collapse: collapse; border-radius: var(--ds-rounded-md); box-shadow: none; - font-size: 15px; - line-height: 19px; + font-size: var(--ds-typography-body-md-font-size); + line-height: var(--ds-typography-footer-md-line-height); white-space: nowrap; } @@ -338,13 +368,13 @@ .md-path__item:not(:first-child) { align-items: center; - font-size: 13px; - line-height: 16px; + font-size: var(--ds-typography-caption-subfooter-font-size); + line-height: var(--ds-typography-caption-subfooter-line-height); } .md-footer { - font-size: 15px; - line-height: 19px; + font-size: var(--ds-typography-footer-md-font-size); + line-height: var(--ds-typography-footer-md-line-height); } .md-footer-meta { @@ -516,12 +546,9 @@ max-width: min(1800px, 100% - 4rem); } -@media screen and (min-width: 76.25em) { - .md-content { - padding-left: 40px; - } - - .md-sidebar--primary, +/* ToC collapse toggle: available wherever Material renders the secondary + sidebar (>= 60em), not just on the wide desktop layout (>= 76.25em). */ +@media screen and (min-width: 60em) { .md-sidebar--secondary { transition: width var(--ds-motion-duration) ease, @@ -534,20 +561,6 @@ transition: margin var(--ds-motion-duration) ease; } - html[data-ds-content-wide] .md-sidebar--primary { - width: 0; - min-width: 0; - opacity: 0; - overflow: hidden; - pointer-events: none; - visibility: hidden; - transition: - width var(--ds-motion-duration) ease, - min-width var(--ds-motion-duration) ease, - opacity var(--ds-motion-duration) ease, - visibility 0s linear var(--ds-motion-duration); - } - html[data-ds-toc-collapsed] .md-sidebar--secondary { width: 0; min-width: 0; @@ -562,83 +575,10 @@ visibility 0s linear var(--ds-motion-duration); } - html[data-ds-content-wide] .md-main__inner { - margin-left: 0; - } - html[data-ds-toc-collapsed] .md-main__inner { margin-right: 80px; } - html[data-ds-content-wide] .md-content { - border-left-color: transparent; - } - - .ds-content-width-toggle { - --ds-toggle-size: 2rem; - position: fixed; - top: 50%; - margin-top: calc(var(--ds-toggle-size) / -2); - margin-left: calc(var(--ds-toggle-size) / -2); - display: flex; - align-items: center; - justify-content: center; - width: var(--ds-toggle-size); - height: var(--ds-toggle-size); - padding: 0; - border: 2px solid var(--ds-color-border); - border-radius: 50px; - background-color: var(--md-default-bg-color); - color: var(--md-default-fg-color); - cursor: pointer; - transition: var(--ds-motion-transition); - } - - .ds-content-width-toggle:hover { - color: white; - background-color: var(--ds-color-primary); - - } - - .ds-content-width-toggle:focus-visible { - outline: none; - box-shadow: - 0 0 0 2px var(--ds-color-on-primary), - 0 0 0 4px var(--ds-color-primary); - } - - [data-md-color-scheme="slate"] .ds-content-width-toggle:focus-visible { - box-shadow: - 0 0 0 2px var(--ds-color-dark-canvas), - 0 0 0 4px var(--ds-color-dark-clickable); - } - - .ds-content-width-toggle[hidden] { - display: none; - } - - .ds-content-width-toggle__icon { - display: flex; - align-items: center; - justify-content: center; - } - - .ds-content-width-toggle__icon[hidden] { - display: none; - } - - html[data-ds-content-wide] .ds-content-width-toggle { - --ds-toggle-size: var(--ds-spacing-control-height); - left: var(--ds-spacing-xl); - margin-left: 0; - border: inset 0 0 0 2px var(--ds-color-primary); - border-radius: 50px; - } - - [data-md-color-scheme="slate"] html[data-ds-content-wide] .ds-content-width-toggle { - border-color: var(--ds-color-dark-clickable); - } - .ds-toc-toggle-wrapper { position: fixed; top: 6rem; @@ -708,8 +648,8 @@ border-radius: var(--ds-rounded-md); background-color: var(--ds-color-surface-medium); color: var(--ds-color-ink); - font-size: 15px; - line-height: 24px; + font-size: var(--ds-typography-body-md-font-size); + line-height: var(--ds-typography-body-md-line-height); text-align: left; white-space: normal; overflow-wrap: break-word; @@ -746,10 +686,118 @@ opacity: 1; } - .ds-toc-toggle__tooltip[hidden] { + @media (prefers-reduced-motion: reduce) { + .md-sidebar--secondary, + .md-main__inner, + .ds-toc-toggle { + transition: none !important; + } + } +} + +@media screen and (min-width: 76.25em) { + .md-content { + padding-left: 40px; + } + + .md-sidebar--primary, + .md-sidebar--secondary { + transition: + width var(--ds-motion-duration) ease, + min-width var(--ds-motion-duration) ease, + opacity var(--ds-motion-duration) ease, + visibility 0s linear 0s; + } + + .md-main__inner { + transition: margin var(--ds-motion-duration) ease; + } + + html[data-ds-content-wide] .md-sidebar--primary { + width: 0; + min-width: 0; + opacity: 0; + overflow: hidden; + pointer-events: none; + visibility: hidden; + transition: + width var(--ds-motion-duration) ease, + min-width var(--ds-motion-duration) ease, + opacity var(--ds-motion-duration) ease, + visibility 0s linear var(--ds-motion-duration); + } + + html[data-ds-content-wide] .md-main__inner { + margin-left: 0; + } + + html[data-ds-content-wide] .md-content { + border-left-color: transparent; + } + + .ds-content-width-toggle { + --ds-toggle-size: var(--ds-spacing-control-height); + position: fixed; + top: 50%; + margin-top: calc(var(--ds-toggle-size) / -2); + margin-left: calc(var(--ds-toggle-size) / -2); + display: flex; + align-items: center; + justify-content: center; + width: var(--ds-toggle-size); + height: var(--ds-toggle-size); + padding: 0; + border: 2px solid var(--ds-color-border); + border-radius: 50px; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + transition: var(--ds-motion-transition); + } + + .ds-content-width-toggle:hover { + color: white; + background-color: var(--ds-color-primary); + } + + .ds-content-width-toggle:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--ds-color-on-primary), + 0 0 0 4px var(--ds-color-primary); + } + + [data-md-color-scheme="slate"] .ds-content-width-toggle:focus-visible { + box-shadow: + 0 0 0 2px var(--ds-color-dark-canvas), + 0 0 0 4px var(--ds-color-dark-clickable); + } + + .ds-content-width-toggle[hidden] { display: none; } + .ds-content-width-toggle__icon { + display: flex; + align-items: center; + justify-content: center; + } + + .ds-content-width-toggle__icon[hidden] { + display: none; + } + + html[data-ds-content-wide] .ds-content-width-toggle { + left: var(--ds-spacing-xl); + margin-left: 0; + border: inset 0 0 0 2px var(--ds-color-primary); + border-radius: 50px; + } + + [data-md-color-scheme="slate"] html[data-ds-content-wide] .ds-content-width-toggle { + border-color: var(--ds-color-dark-clickable); + } + @media (prefers-reduced-motion: reduce) { .md-sidebar--primary, .md-sidebar--secondary, @@ -762,3 +810,9 @@ } } } + +/* Remove the opaque background/box-shadow Material adds to the sticky ToC title */ +.md-nav--secondary .md-nav__title { + background-color: transparent; + box-shadow: none; +} diff --git a/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html b/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html index cbeb24b..810b7e5 100644 --- a/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html +++ b/mkdocs_doubleslash_theme/overrides/partials/content-width-toggle.html @@ -7,10 +7,10 @@ title="Navigation ausblenden" hidden > - + -