From 6293678513fdd3bed5051ec2198d938de2c1f6e0 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 13:46:41 -0600 Subject: [PATCH 01/39] Sketch in stepper styles --- assets/scripts/components/stepper.js | 191 +++++++++++++++++++++++++ assets/scripts/main-dd-js.js | 1 + assets/styles/components/_stepper.scss | 184 ++++++++++++++++++++++++ assets/styles/style.scss | 1 + content/.gitignore | 1 + content/en/a_demo/stepper.mdoc.md | 23 +++ package.json | 2 +- yarn.lock | 10 +- 8 files changed, 407 insertions(+), 6 deletions(-) create mode 100644 assets/scripts/components/stepper.js create mode 100644 assets/styles/components/_stepper.scss create mode 100644 content/en/a_demo/stepper.mdoc.md diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js new file mode 100644 index 00000000000..00b48efdc67 --- /dev/null +++ b/assets/scripts/components/stepper.js @@ -0,0 +1,191 @@ +const STORAGE_PREFIX = 'stepper-progress-'; +const MAX_STORED_STEPPERS = 10; + +function getStorageKey(stepperId) { + return `${STORAGE_PREFIX}${stepperId}`; +} + +function loadProgress(stepperId) { + try { + const data = localStorage.getItem(getStorageKey(stepperId)); + return data ? JSON.parse(data) : null; + } catch { + return null; + } +} + +function saveProgress(stepperId, state) { + try { + pruneOldEntries(); + localStorage.setItem( + getStorageKey(stepperId), + JSON.stringify({ ...state, timestamp: Date.now() }) + ); + } catch { + // localStorage unavailable or full — silently ignore + } +} + +function pruneOldEntries() { + try { + const entries = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(STORAGE_PREFIX)) { + const data = JSON.parse(localStorage.getItem(key)); + entries.push({ key, timestamp: data.timestamp || 0 }); + } + } + // If at the cap, remove the oldest entry to make room + if (entries.length >= MAX_STORED_STEPPERS) { + entries.sort((a, b) => a.timestamp - b.timestamp); + const toRemove = entries.length - MAX_STORED_STEPPERS + 1; + for (let i = 0; i < toRemove; i++) { + localStorage.removeItem(entries[i].key); + } + } + } catch { + // Ignore storage errors + } +} + +function initStepper(stepper) { + const stepperId = stepper.id; + const steps = stepper.querySelectorAll('.stepper__step'); + const finishedEl = stepper.querySelector('.stepper__finished'); + const controlsEl = stepper.querySelector('.stepper__controls'); + const controlsBtn = controlsEl ? controlsEl.querySelector('.stepper__btn') : null; + + if (!steps.length) return; + + let currentIndex = 0; + let showAll = false; + let finished = false; + + // Restore saved progress + const saved = loadProgress(stepperId); + if (saved) { + if (saved.showAll) { + showAll = true; + } else if (saved.finished) { + finished = true; + currentIndex = steps.length - 1; + } else if (typeof saved.stepIndex === 'number' && saved.stepIndex < steps.length) { + currentIndex = saved.stepIndex; + } + } + + function persist() { + saveProgress(stepperId, { stepIndex: currentIndex, showAll, finished }); + } + + function render() { + if (showAll) { + stepper.classList.add('stepper--show-all'); + steps.forEach((step) => { + step.classList.remove('stepper__step--active'); + // In show-all mode, hide nav on every step (CSS shows content) + const nav = step.querySelector('.stepper__nav'); + if (nav) nav.style.display = 'none'; + }); + if (finishedEl) finishedEl.style.display = 'none'; + if (controlsEl) { + controlsEl.style.display = 'block'; + if (controlsBtn) controlsBtn.textContent = 'Hide other steps'; + } + } else { + stepper.classList.remove('stepper--show-all'); + steps.forEach((step, i) => { + step.classList.toggle('stepper__step--active', i === currentIndex); + const nav = step.querySelector('.stepper__nav'); + if (nav) nav.style.display = ''; + }); + if (finishedEl) { + finishedEl.style.display = finished ? '' : 'none'; + } + if (controlsEl) { + controlsEl.style.display = 'none'; + } + // Toggle show-all / hide-others buttons within each step's nav + steps.forEach((step) => { + const showBtn = step.querySelector('.stepper__show-all-btn'); + const hideBtn = step.querySelector('.stepper__hide-others-btn'); + if (showBtn) showBtn.style.display = ''; + if (hideBtn) hideBtn.style.display = 'none'; + }); + } + } + + function goToStep(index) { + finished = false; + currentIndex = Math.max(0, Math.min(index, steps.length - 1)); + showAll = false; + persist(); + render(); + } + + function handleFinish() { + finished = true; + currentIndex = steps.length - 1; + showAll = false; + persist(); + render(); + } + + function toggleShowAll() { + showAll = !showAll; + if (!showAll) { + // Return to single-step view at current position + finished = false; + } + persist(); + render(); + } + + // Bind title clicks to toggle steps (accordion behavior) + steps.forEach((step, i) => { + const title = step.querySelector('.stepper__step-title'); + if (title) { + title.addEventListener('click', () => { + goToStep(i); + }); + } + }); + + // Bind navigation buttons + stepper.addEventListener('click', (e) => { + const btn = e.target.closest('.stepper__btn'); + if (!btn) return; + + if (btn.classList.contains('stepper__next-btn')) { + goToStep(currentIndex + 1); + } else if (btn.classList.contains('stepper__prev-btn')) { + goToStep(currentIndex - 1); + } else if (btn.classList.contains('stepper__finish-btn')) { + handleFinish(); + } else if ( + btn.classList.contains('stepper__show-all-btn') || + btn.classList.contains('stepper__hide-others-btn') + ) { + toggleShowAll(); + } else if (controlsEl && controlsEl.contains(btn)) { + toggleShowAll(); + } + }); + + // Initial render + render(); +} + +function initAllSteppers() { + const steppers = document.querySelectorAll('.stepper'); + steppers.forEach(initStepper); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initAllSteppers); +} else { + initAllSteppers(); +} + +export { initAllSteppers, initStepper }; diff --git a/assets/scripts/main-dd-js.js b/assets/scripts/main-dd-js.js index 57cd5b4c21d..5596e1d2d32 100644 --- a/assets/scripts/main-dd-js.js +++ b/assets/scripts/main-dd-js.js @@ -19,6 +19,7 @@ import './components/mobile-nav'; // should move this to websites-modules import './components/accordion-auto-open'; import './components/signup'; import './components/conversational-search'; +import './components/stepper'; // Add Bootstrap Tooltip across docs const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss new file mode 100644 index 00000000000..803f3221485 --- /dev/null +++ b/assets/styles/components/_stepper.scss @@ -0,0 +1,184 @@ +$stepper-circle-size: 28px; +$stepper-gutter: 48px; +$stepper-line-left: ($stepper-circle-size * 0.5) - 1px; +$stepper-title-pad-top: 16px; +$stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); + +.stepper { + margin-bottom: 24px; + counter-reset: stepper-step; +} + +.stepper__steps { + padding: 0; +} + +.stepper__step { + position: relative; + padding-left: $stepper-gutter; + counter-increment: stepper-step; + + // Vertical connecting line + &::before { + content: ''; + position: absolute; + left: $stepper-line-left; + top: 0; + bottom: 0; + width: 2px; + background-color: $gray-light; + } + + // Number circle + &::after { + content: counter(stepper-step); + position: absolute; + left: 0; + top: $stepper-title-pad-top; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: $stepper-circle-size; + height: $stepper-circle-size; + border-radius: 50%; + background-color: $gray-dark; + color: $white; + font-size: 14px; + font-weight: 700; + line-height: 1; + transition: background-color 0.15s; + } + + // First step: line starts below the first circle + &:first-child::before { + top: $stepper-circle-center; + } + + // Last step: line ends at the circle center + &:last-child::before { + bottom: auto; + height: $stepper-circle-center; + } + + // Single step: no line + &:only-child::before { + display: none; + } +} + +// Active step: purple circle +.stepper__step--active::after { + background-color: $brand-primary; +} + +.stepper__step-title { + display: block; + margin: 0; + padding: $stepper-title-pad-top 0; + font-size: 18px; + font-weight: 600; + line-height: $stepper-circle-size; + cursor: pointer; + user-select: none; + transition: color 0.15s; + + &:hover { + color: $brand-primary; + } +} + +.stepper__step-content { + display: none; + padding-bottom: 8px; + + > *:last-child { + margin-bottom: 0; + } +} + +.stepper__step--active .stepper__step-content { + display: block; +} + +.stepper__nav { + display: none; + justify-content: space-between; + align-items: center; + padding: 8px 0 16px; +} + +.stepper__step--active .stepper__nav { + display: flex; +} + +.stepper__nav-left, +.stepper__nav-right { + display: flex; + gap: 8px; +} + +.stepper__btn { + display: inline-flex; + align-items: center; + padding: 6px 16px; + border: 1px solid $brand-primary; + border-radius: 4px; + background-color: $white; + color: $brand-primary; + font-size: 14px; + font-weight: 500; + line-height: 1.5; + cursor: pointer; + transition: background-color 0.15s, color 0.15s; + + &:hover { + background-color: $brand-primary; + color: $white; + } +} + +.stepper__next-btn, +.stepper__finish-btn { + background-color: $brand-primary; + color: $white; + + &:hover { + background-color: darken($brand-primary, 10%); + border-color: darken($brand-primary, 10%); + } +} + +.stepper__finished { + padding: 16px 0; + margin-left: $stepper-gutter; + + > *:last-child { + margin-bottom: 0; + } +} + +.stepper__controls { + display: none; + padding: 8px 0; + margin-left: $stepper-gutter; +} + +// Show-all mode: expand every step's content +.stepper--show-all { + .stepper__step-content { + display: block; + } + + .stepper__step::after { + background-color: $brand-primary; + } + + .stepper__controls { + display: block; + } + + .stepper__finished { + display: none; + } +} diff --git a/assets/styles/style.scss b/assets/styles/style.scss index a2360c8ba1b..8cc343d288a 100644 --- a/assets/styles/style.scss +++ b/assets/styles/style.scss @@ -76,6 +76,7 @@ $baseImgURL: '{{.Site.Params.img_url}}'; @import 'components/demo-request-modal'; // imported from websites-modules @import 'components/collapsible-section'; // Collapsible section styles +@import 'components/stepper'; @import 'components/tooltip'; // Tooltip shortcode styles @import "components/multifilter-search"; diff --git a/content/.gitignore b/content/.gitignore index 68ef9eaeb51..07ab0e6ff82 100644 --- a/content/.gitignore +++ b/content/.gitignore @@ -18,3 +18,4 @@ /en/logs/error_tracking/error_grouping.md /en/real_user_monitoring/error_tracking/error_grouping.md /en/tracing/error_tracking/error_grouping.md +/en/a_demo/stepper.md diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md new file mode 100644 index 00000000000..062a3f34ef9 --- /dev/null +++ b/content/en/a_demo/stepper.mdoc.md @@ -0,0 +1,23 @@ +--- +title: Stepper test +--- + +{% stepper %} + +{% step title="Step 1 title" %} +Step 1 contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 2 title" %} +Step 2 contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 3 title" %} +Step 3 contents go here, including tabs, sublists, etc. +{% /step %} + +{% stepper-finished %} +Great job, you've finished all the steps. You might like to browse this super helpful doc next. +{% /stepper-finished %} + +{% /stepper %} \ No newline at end of file diff --git a/package.json b/package.json index a1e1eddaa55..eb0dea0de43 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.7.0.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 1901282daf1..25b7fbba848 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.7.0.tgz": - version: 2.7.0 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.7.0.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz": + version: 2.8.0-rc.1 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/0a3fc59522ce9e401aef5b486da7215cd5e1ec32f4a9a252a93770360e4e46de5d998c29344c4f89d38d1d62335750713631e4dc66ba3ae1050193daf2c2657f + checksum: 10/b72bf7d2ed025cb4cb4afdca4d3a51c2484ff0ab58534c9af0e1cf5f4abcf394aed66a941c81ee5e23421fec8acceda56e6ab951d2bf4cb0a4f22d742e19cb99 languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.7.0.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From c3820ad5a4d7c3101040636afdf89eb6fff8a9a4 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 13:55:24 -0600 Subject: [PATCH 02/39] Tweak styles --- assets/scripts/components/stepper.js | 5 + assets/styles/components/_stepper.scss | 253 +++++++++++++------------ 2 files changed, 133 insertions(+), 125 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 00b48efdc67..4ddd2c05281 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -58,6 +58,11 @@ function initStepper(stepper) { if (!steps.length) return; + // Mark first/last steps for CSS (can't rely on :first-child/:last-child + // because .stepper__finished is a sibling inside .stepper__steps) + steps[0].classList.add('stepper__step--first'); + steps[steps.length - 1].classList.add('stepper__step--last'); + let currentIndex = 0; let showAll = false; let finished = false; diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 803f3221485..0ba90340789 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -1,184 +1,187 @@ $stepper-circle-size: 28px; $stepper-gutter: 48px; -$stepper-line-left: ($stepper-circle-size * 0.5) - 1px; +$stepper-line-width: 1.5px; +$stepper-line-left: ($stepper-circle-size * 0.5) - ($stepper-line-width * 0.5); $stepper-title-pad-top: 16px; $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); .stepper { - margin-bottom: 24px; - counter-reset: stepper-step; + margin-bottom: 24px; + counter-reset: stepper-step; } .stepper__steps { - padding: 0; + padding: 0; } .stepper__step { - position: relative; - padding-left: $stepper-gutter; - counter-increment: stepper-step; - - // Vertical connecting line - &::before { - content: ''; - position: absolute; - left: $stepper-line-left; - top: 0; - bottom: 0; - width: 2px; - background-color: $gray-light; - } - - // Number circle - &::after { - content: counter(stepper-step); - position: absolute; - left: 0; - top: $stepper-title-pad-top; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: $stepper-circle-size; - height: $stepper-circle-size; - border-radius: 50%; - background-color: $gray-dark; - color: $white; - font-size: 14px; - font-weight: 700; - line-height: 1; - transition: background-color 0.15s; - } - - // First step: line starts below the first circle - &:first-child::before { - top: $stepper-circle-center; - } - - // Last step: line ends at the circle center - &:last-child::before { - bottom: auto; - height: $stepper-circle-center; - } - - // Single step: no line - &:only-child::before { - display: none; - } + position: relative; + padding-left: $stepper-gutter; + counter-increment: stepper-step; + + // Vertical connecting line + &::before { + content: ''; + position: absolute; + left: $stepper-line-left; + top: 0; + bottom: 0; + width: $stepper-line-width; + background-color: $gray-light; + } + + // Number circle + &::after { + content: counter(stepper-step); + position: absolute; + left: 0; + top: $stepper-title-pad-top; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: $stepper-circle-size; + height: $stepper-circle-size; + border-radius: 50%; + background-color: $gray-dark; + color: $white; + font-size: 14px; + font-weight: 700; + line-height: 1; + font-variant-numeric: lining-nums; + transition: background-color 0.15s; + box-shadow: 0 0 0 5px $white; + } + + // First step: line starts below the first circle + &--first::before { + top: $stepper-circle-center; + } + + // Last step: line ends at the circle center + &--last::before { + bottom: auto; + height: $stepper-circle-center; + } + + // Single step: no line + &--first#{&}--last::before { + display: none; + } } // Active step: purple circle .stepper__step--active::after { - background-color: $brand-primary; + background-color: $brand-primary; } .stepper__step-title { - display: block; - margin: 0; - padding: $stepper-title-pad-top 0; - font-size: 18px; - font-weight: 600; - line-height: $stepper-circle-size; - cursor: pointer; - user-select: none; - transition: color 0.15s; - - &:hover { - color: $brand-primary; - } + display: block; + margin: 0; + padding: $stepper-title-pad-top 0; + font-size: 18px; + font-weight: 600; + line-height: $stepper-circle-size; + cursor: pointer; + user-select: none; + transition: color 0.15s; + + &:hover { + color: $brand-primary; + } } .stepper__step-content { - display: none; - padding-bottom: 8px; + display: none; + padding-bottom: 8px; - > *:last-child { - margin-bottom: 0; - } + > *:last-child { + margin-bottom: 0; + } } .stepper__step--active .stepper__step-content { - display: block; + display: block; } .stepper__nav { - display: none; - justify-content: space-between; - align-items: center; - padding: 8px 0 16px; + display: none; + justify-content: space-between; + align-items: center; + padding: 8px 0 16px; } .stepper__step--active .stepper__nav { - display: flex; + display: flex; } .stepper__nav-left, .stepper__nav-right { - display: flex; - gap: 8px; + display: flex; + gap: 8px; } .stepper__btn { - display: inline-flex; - align-items: center; - padding: 6px 16px; - border: 1px solid $brand-primary; - border-radius: 4px; - background-color: $white; - color: $brand-primary; - font-size: 14px; - font-weight: 500; - line-height: 1.5; - cursor: pointer; - transition: background-color 0.15s, color 0.15s; - - &:hover { - background-color: $brand-primary; - color: $white; - } + display: inline-flex; + align-items: center; + padding: 6px 16px; + border: 1px solid $brand-primary; + border-radius: 4px; + background-color: $white; + color: $brand-primary; + font-size: 14px; + font-weight: 500; + line-height: 1.5; + cursor: pointer; + transition: background-color 0.15s, color 0.15s; + + &:hover { + background-color: $brand-primary; + color: $white; + } } .stepper__next-btn, .stepper__finish-btn { - background-color: $brand-primary; - color: $white; + background-color: $brand-primary; + color: $white; - &:hover { - background-color: darken($brand-primary, 10%); - border-color: darken($brand-primary, 10%); - } + &:hover { + background-color: darken($brand-primary, 10%); + border-color: darken($brand-primary, 10%); + } } .stepper__finished { - padding: 16px 0; - margin-left: $stepper-gutter; + padding: 16px 0; + margin-left: $stepper-gutter; - > *:last-child { - margin-bottom: 0; - } + > *:last-child { + margin-bottom: 0; + } } .stepper__controls { - display: none; - padding: 8px 0; - margin-left: $stepper-gutter; + display: none; + padding: 8px 0; + margin-left: $stepper-gutter; } // Show-all mode: expand every step's content .stepper--show-all { - .stepper__step-content { - display: block; - } + .stepper__step-content { + display: block; + } - .stepper__step::after { - background-color: $brand-primary; - } + .stepper__step::after { + background-color: $brand-primary; + } - .stepper__controls { - display: block; - } + .stepper__controls { + display: block; + } - .stepper__finished { - display: none; - } + .stepper__finished { + display: none; + } } From d06049fd4a7a447b744a165bf41dcb5577773f7e Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 13:58:39 -0600 Subject: [PATCH 03/39] Check off completed steps --- assets/scripts/components/stepper.js | 4 +++- assets/styles/components/_stepper.scss | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 4ddd2c05281..ac3560cbcc0 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -89,6 +89,7 @@ function initStepper(stepper) { stepper.classList.add('stepper--show-all'); steps.forEach((step) => { step.classList.remove('stepper__step--active'); + step.classList.remove('stepper__step--completed'); // In show-all mode, hide nav on every step (CSS shows content) const nav = step.querySelector('.stepper__nav'); if (nav) nav.style.display = 'none'; @@ -101,7 +102,8 @@ function initStepper(stepper) { } else { stepper.classList.remove('stepper--show-all'); steps.forEach((step, i) => { - step.classList.toggle('stepper__step--active', i === currentIndex); + step.classList.toggle('stepper__step--active', !finished && i === currentIndex); + step.classList.toggle('stepper__step--completed', finished || i < currentIndex); const nav = step.querySelector('.stepper__nav'); if (nav) nav.style.display = ''; }); diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 0ba90340789..c7c343f5d88 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -75,6 +75,12 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); background-color: $brand-primary; } +// Completed step: purple circle with checkmark +.stepper__step--completed::after { + content: '\2713'; + background-color: $brand-primary; +} + .stepper__step-title { display: block; margin: 0; From 54dc466ce6a1313b783c28671d5cd8bcc1115882 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 14:18:23 -0600 Subject: [PATCH 04/39] Flesh out example steps --- assets/scripts/components/stepper.js | 60 ++++++++++-- assets/styles/components/_stepper.scss | 5 +- content/en/a_demo/stepper.mdoc.md | 126 ++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 15 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index ac3560cbcc0..6da9e8b4d50 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -74,8 +74,8 @@ function initStepper(stepper) { showAll = true; } else if (saved.finished) { finished = true; - currentIndex = steps.length - 1; - } else if (typeof saved.stepIndex === 'number' && saved.stepIndex < steps.length) { + currentIndex = typeof saved.stepIndex === 'number' ? saved.stepIndex : -1; + } else if (typeof saved.stepIndex === 'number' && saved.stepIndex >= 0 && saved.stepIndex < steps.length) { currentIndex = saved.stepIndex; } } @@ -90,28 +90,42 @@ function initStepper(stepper) { steps.forEach((step) => { step.classList.remove('stepper__step--active'); step.classList.remove('stepper__step--completed'); - // In show-all mode, hide nav on every step (CSS shows content) + // In show-all mode, CSS shows content; hide nav + const content = step.querySelector('.stepper__step-content'); + if (content) content.style.display = ''; const nav = step.querySelector('.stepper__nav'); if (nav) nav.style.display = 'none'; }); if (finishedEl) finishedEl.style.display = 'none'; if (controlsEl) { - controlsEl.style.display = 'block'; + controlsEl.style.display = 'flex'; if (controlsBtn) controlsBtn.textContent = 'Hide other steps'; + if (resetBtn) resetBtn.style.display = 'none'; } } else { stepper.classList.remove('stepper--show-all'); steps.forEach((step, i) => { - step.classList.toggle('stepper__step--active', !finished && i === currentIndex); + const isActive = i === currentIndex; + step.classList.toggle('stepper__step--active', isActive); step.classList.toggle('stepper__step--completed', finished || i < currentIndex); + const content = step.querySelector('.stepper__step-content'); + if (content) content.style.display = isActive ? '' : 'none'; const nav = step.querySelector('.stepper__nav'); - if (nav) nav.style.display = ''; + if (nav) nav.style.display = (isActive && !finished) ? '' : 'none'; }); if (finishedEl) { finishedEl.style.display = finished ? '' : 'none'; } if (controlsEl) { - controlsEl.style.display = 'none'; + // Show controls in finished state so user can "Show all" or "Reset" + if (finished) { + controlsEl.style.display = 'flex'; + if (controlsBtn) controlsBtn.textContent = 'Show all steps'; + if (resetBtn) resetBtn.style.display = ''; + } else { + controlsEl.style.display = 'none'; + if (resetBtn) resetBtn.style.display = 'none'; + } } // Toggle show-all / hide-others buttons within each step's nav steps.forEach((step) => { @@ -133,12 +147,31 @@ function initStepper(stepper) { function handleFinish() { finished = true; - currentIndex = steps.length - 1; + currentIndex = -1; // no step expanded initially showAll = false; persist(); render(); } + function handleReset() { + finished = false; + currentIndex = 0; + showAll = false; + persist(); + render(); + } + + // Create a Reset button inside the controls area + let resetBtn = null; + if (controlsEl) { + resetBtn = document.createElement('button'); + resetBtn.className = 'stepper__btn stepper__reset-btn'; + resetBtn.textContent = 'Reset'; + resetBtn.style.display = 'none'; + controlsEl.appendChild(resetBtn); + resetBtn.addEventListener('click', handleReset); + } + function toggleShowAll() { showAll = !showAll; if (!showAll) { @@ -154,7 +187,14 @@ function initStepper(stepper) { const title = step.querySelector('.stepper__step-title'); if (title) { title.addEventListener('click', () => { - goToStep(i); + if (finished) { + // In finished state, toggle expand/collapse without leaving finished + currentIndex = (currentIndex === i) ? -1 : i; + persist(); + render(); + } else { + goToStep(i); + } }); } }); @@ -175,6 +215,8 @@ function initStepper(stepper) { btn.classList.contains('stepper__hide-others-btn') ) { toggleShowAll(); + } else if (btn.classList.contains('stepper__reset-btn')) { + handleReset(); } else if (controlsEl && controlsEl.contains(btn)) { toggleShowAll(); } diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index c7c343f5d88..054d9d2e223 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -1,4 +1,4 @@ -$stepper-circle-size: 28px; +$stepper-circle-size: 24px; $stepper-gutter: 48px; $stepper-line-width: 1.5px; $stepper-line-left: ($stepper-circle-size * 0.5) - ($stepper-line-width * 0.5); @@ -45,7 +45,7 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); border-radius: 50%; background-color: $gray-dark; color: $white; - font-size: 14px; + font-size: 12px; font-weight: 700; line-height: 1; font-variant-numeric: lining-nums; @@ -171,6 +171,7 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); display: none; padding: 8px 0; margin-left: $stepper-gutter; + gap: 8px; } // Show-all mode: expand every step's content diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md index 062a3f34ef9..ca2b830811d 100644 --- a/content/en/a_demo/stepper.mdoc.md +++ b/content/en/a_demo/stepper.mdoc.md @@ -2,6 +2,126 @@ title: Stepper test --- +## With "finished" section + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +{% stepper %} + +{% step title="Verify the prerequisites" %} +Make sure you meet these prerequisites: + +- You have a computer. +- You have permission to install things on the computer. +- You are competent. +- You are in the mood to do this today. + +{% collapse-content title="Bonus prereqs" level="h4" %} +- Somebody is paying you to do this. +{% /collapse-content %} +{% /step %} + +{% step title="Install the software" %} +{% tabs %} + +{% tab label="Python" %} +Run this script to install with Python: + +```python +import time +import sys + +def fake_install(): + steps = [ + "Checking system requirements", + "Downloading packages", + "Installing dependencies", + "Configuring environment", + "Finalizing setup" + ] + + for step in steps: + print(f"{step}...", end="") + sys.stdout.flush() + time.sleep(1) + print(" Done.") + + print("\nInstallation completed successfully!") + +if __name__ == "__main__": + fake_install() +``` +{% /tab %} + +{% tab label="JavaScript" %} +Run this script to install with JavaScript: + +```javascript +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function fakeInstall() { + const steps = [ + "Checking system requirements", + "Downloading packages", + "Installing dependencies", + "Configuring environment", + "Finalizing setup" + ]; + + for (const step of steps) { + process.stdout.write(step + "... "); + await sleep(1000); + console.log("Done."); + } + + console.log("\nInstallation completed successfully!"); +} + +fakeInstall(); +``` +{% /tab %} + +{% /tabs %} +{% /step %} + +{% step title="Update the configuration file" %} +{% alert level="warning" %} +This is made-up YAML. Proceed with caution. +{% /alert %} +Use this YAML: + +```yaml +server: + host: 0.0.0.0 + port: 8080 + +database: + type: postgres + host: localhost + port: 5432 + username: admin + password: secret123 +``` +{% /step %} + +{% step title="Verify the installation" %} +Step 3 contents go here, including tabs, sublists, etc. +{% /step %} + +{% stepper-finished %} +Great job, you've finished all the steps. You might like to browse this super helpful doc next. +{% /stepper-finished %} + +{% /stepper %} + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## Without "finished" section + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + {% stepper %} {% step title="Step 1 title" %} @@ -16,8 +136,6 @@ Step 2 contents go here, including tabs, sublists, etc. Step 3 contents go here, including tabs, sublists, etc. {% /step %} -{% stepper-finished %} -Great job, you've finished all the steps. You might like to browse this super helpful doc next. -{% /stepper-finished %} +{% /stepper %} -{% /stepper %} \ No newline at end of file +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \ No newline at end of file From 165f00d22153b0df5c152fe4d6c0359764979a4c Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 14:29:13 -0600 Subject: [PATCH 05/39] Make steps searchable --- assets/scripts/components/stepper.js | 29 +++++++++++-- assets/styles/components/_stepper.scss | 8 ++++ content/en/a_demo/stepper.mdoc.md | 56 ++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 6da9e8b4d50..cfd7e4e701f 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -90,9 +90,9 @@ function initStepper(stepper) { steps.forEach((step) => { step.classList.remove('stepper__step--active'); step.classList.remove('stepper__step--completed'); - // In show-all mode, CSS shows content; hide nav + // In show-all mode, reveal all content; hide nav const content = step.querySelector('.stepper__step-content'); - if (content) content.style.display = ''; + if (content) content.removeAttribute('hidden'); const nav = step.querySelector('.stepper__nav'); if (nav) nav.style.display = 'none'; }); @@ -109,7 +109,13 @@ function initStepper(stepper) { step.classList.toggle('stepper__step--active', isActive); step.classList.toggle('stepper__step--completed', finished || i < currentIndex); const content = step.querySelector('.stepper__step-content'); - if (content) content.style.display = isActive ? '' : 'none'; + if (content) { + if (isActive) { + content.removeAttribute('hidden'); + } else { + content.setAttribute('hidden', 'until-found'); + } + } const nav = step.querySelector('.stepper__nav'); if (nav) nav.style.display = (isActive && !finished) ? '' : 'none'; }); @@ -182,6 +188,23 @@ function initStepper(stepper) { render(); } + // Auto-expand step when browser find-in-page matches hidden content + steps.forEach((step, i) => { + const content = step.querySelector('.stepper__step-content'); + if (content) { + content.addEventListener('beforematch', () => { + if (showAll) return; + if (finished) { + currentIndex = i; + persist(); + render(); + } else { + goToStep(i); + } + }); + } + }); + // Bind title clicks to toggle steps (accordion behavior) steps.forEach((step, i) => { const title = step.querySelector('.stepper__step-title'); diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 054d9d2e223..dc9b56f3af9 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -101,6 +101,14 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); display: none; padding-bottom: 8px; + // Let hidden="until-found" use content-visibility instead of display:none, + // so browser find-in-page can still search collapsed step content. + // The !important is needed to override Bootstrap's [hidden] { display: none !important; }. + &[hidden="until-found"] { + display: block !important; + content-visibility: hidden; + } + > *:last-child { margin-bottom: 0; } diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md index ca2b830811d..167e9dc71c0 100644 --- a/content/en/a_demo/stepper.mdoc.md +++ b/content/en/a_demo/stepper.mdoc.md @@ -132,10 +132,60 @@ Step 1 contents go here, including tabs, sublists, etc. Step 2 contents go here, including tabs, sublists, etc. {% /step %} +{% /stepper %} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +## Lots of steps + +{% stepper %} + +{% step title="Step 1 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 2 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + {% step title="Step 3 title" %} -Step 3 contents go here, including tabs, sublists, etc. +Contents go here, including tabs, sublists, etc. {% /step %} -{% /stepper %} +{% step title="Step 4 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 5 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 6 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 7 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 8 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 9 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 10 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 11 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 12 title" %} +Contents go here, including tabs, sublists, etc. +{% /step %} -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \ No newline at end of file +{% /stepper %} \ No newline at end of file From a1ffc1c173c2239229e3ba75bc398bc8e0f7eec4 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 14:49:20 -0600 Subject: [PATCH 06/39] Nudge elements --- assets/styles/components/_stepper.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index dc9b56f3af9..87d65e669a6 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -3,7 +3,7 @@ $stepper-gutter: 48px; $stepper-line-width: 1.5px; $stepper-line-left: ($stepper-circle-size * 0.5) - ($stepper-line-width * 0.5); $stepper-title-pad-top: 16px; -$stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); +$stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0.5); .stepper { margin-bottom: 24px; @@ -35,7 +35,7 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); content: counter(stepper-step); position: absolute; left: 0; - top: $stepper-title-pad-top; + top: $stepper-title-pad-top + 2px; z-index: 1; display: flex; align-items: center; @@ -49,6 +49,7 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); font-weight: 700; line-height: 1; font-variant-numeric: lining-nums; + padding-bottom: 1px; transition: background-color 0.15s; box-shadow: 0 0 0 5px $white; } @@ -104,7 +105,7 @@ $stepper-circle-center: $stepper-title-pad-top + ($stepper-circle-size * 0.5); // Let hidden="until-found" use content-visibility instead of display:none, // so browser find-in-page can still search collapsed step content. // The !important is needed to override Bootstrap's [hidden] { display: none !important; }. - &[hidden="until-found"] { + &[hidden='until-found'] { display: block !important; content-visibility: hidden; } From be39def9d84e09ce8c4aa47069922e57da4de23a Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Tue, 3 Mar 2026 15:26:23 -0600 Subject: [PATCH 07/39] Update example step --- content/en/a_demo/stepper.mdoc.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md index 167e9dc71c0..f19777c2d76 100644 --- a/content/en/a_demo/stepper.mdoc.md +++ b/content/en/a_demo/stepper.mdoc.md @@ -107,7 +107,11 @@ database: {% /step %} {% step title="Verify the installation" %} -Step 3 contents go here, including tabs, sublists, etc. +Verify the installation by following these steps: + +1. Ask Claude if it seems installed. +2. Ask ChatGPT if it seems installed, just in case. +3. Ask the person nearest to you if they feel as though the software was in fact installed. {% /step %} {% stepper-finished %} From 33e11e01e2f3dacd29214082661d0ab4afc11c62 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 4 Mar 2026 16:24:42 -0600 Subject: [PATCH 08/39] Tweak stepper behavior --- assets/scripts/components/stepper.js | 149 ++++++++++--------------- assets/styles/components/_stepper.scss | 3 +- package.json | 2 +- yarn.lock | 10 +- 4 files changed, 69 insertions(+), 95 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index cfd7e4e701f..ef57defac54 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -64,97 +64,83 @@ function initStepper(stepper) { steps[steps.length - 1].classList.add('stepper__step--last'); let currentIndex = 0; - let showAll = false; let finished = false; + // Tracks which non-active steps are manually expanded (by index) + let expandedSteps = new Set(); // Restore saved progress const saved = loadProgress(stepperId); if (saved) { - if (saved.showAll) { - showAll = true; - } else if (saved.finished) { + if (saved.finished) { finished = true; - currentIndex = typeof saved.stepIndex === 'number' ? saved.stepIndex : -1; } else if (typeof saved.stepIndex === 'number' && saved.stepIndex >= 0 && saved.stepIndex < steps.length) { currentIndex = saved.stepIndex; } + if (Array.isArray(saved.expandedSteps)) { + expandedSteps = new Set(saved.expandedSteps); + } } function persist() { - saveProgress(stepperId, { stepIndex: currentIndex, showAll, finished }); + saveProgress(stepperId, { + stepIndex: currentIndex, + finished, + expandedSteps: [...expandedSteps] + }); } function render() { - if (showAll) { - stepper.classList.add('stepper--show-all'); - steps.forEach((step) => { - step.classList.remove('stepper__step--active'); - step.classList.remove('stepper__step--completed'); - // In show-all mode, reveal all content; hide nav - const content = step.querySelector('.stepper__step-content'); - if (content) content.removeAttribute('hidden'); - const nav = step.querySelector('.stepper__nav'); - if (nav) nav.style.display = 'none'; - }); - if (finishedEl) finishedEl.style.display = 'none'; - if (controlsEl) { - controlsEl.style.display = 'flex'; - if (controlsBtn) controlsBtn.textContent = 'Hide other steps'; - if (resetBtn) resetBtn.style.display = 'none'; - } - } else { - stepper.classList.remove('stepper--show-all'); - steps.forEach((step, i) => { - const isActive = i === currentIndex; - step.classList.toggle('stepper__step--active', isActive); - step.classList.toggle('stepper__step--completed', finished || i < currentIndex); - const content = step.querySelector('.stepper__step-content'); - if (content) { - if (isActive) { - content.removeAttribute('hidden'); - } else { - content.setAttribute('hidden', 'until-found'); - } - } - const nav = step.querySelector('.stepper__nav'); - if (nav) nav.style.display = (isActive && !finished) ? '' : 'none'; - }); - if (finishedEl) { - finishedEl.style.display = finished ? '' : 'none'; - } - if (controlsEl) { - // Show controls in finished state so user can "Show all" or "Reset" - if (finished) { - controlsEl.style.display = 'flex'; - if (controlsBtn) controlsBtn.textContent = 'Show all steps'; - if (resetBtn) resetBtn.style.display = ''; + steps.forEach((step, i) => { + const isActive = !finished && i === currentIndex; + const isCompleted = finished || i < currentIndex; + const isExpanded = isActive || expandedSteps.has(i); + + step.classList.toggle('stepper__step--active', isActive); + step.classList.toggle('stepper__step--completed', isCompleted); + + const content = step.querySelector('.stepper__step-content'); + if (content) { + if (isActive) { + content.removeAttribute('hidden'); + content.style.removeProperty('display'); + } else if (isExpanded) { + content.removeAttribute('hidden'); + content.style.display = 'block'; } else { - controlsEl.style.display = 'none'; - if (resetBtn) resetBtn.style.display = 'none'; + content.setAttribute('hidden', 'until-found'); + content.style.removeProperty('display'); } } - // Toggle show-all / hide-others buttons within each step's nav - steps.forEach((step) => { - const showBtn = step.querySelector('.stepper__show-all-btn'); - const hideBtn = step.querySelector('.stepper__hide-others-btn'); - if (showBtn) showBtn.style.display = ''; - if (hideBtn) hideBtn.style.display = 'none'; - }); + const nav = step.querySelector('.stepper__nav'); + if (nav) nav.style.display = (isActive && !finished) ? '' : 'none'; + }); + + if (finishedEl) { + finishedEl.style.display = finished ? '' : 'none'; + } + + if (controlsEl) { + if (finished) { + controlsEl.style.display = 'flex'; + // Hide the original "Show all" button; only show Reset + if (controlsBtn) controlsBtn.style.display = 'none'; + if (resetBtn) resetBtn.style.display = ''; + } else { + controlsEl.style.display = 'none'; + } } } function goToStep(index) { finished = false; currentIndex = Math.max(0, Math.min(index, steps.length - 1)); - showAll = false; persist(); render(); } function handleFinish() { finished = true; - currentIndex = -1; // no step expanded initially - showAll = false; + expandedSteps.clear(); persist(); render(); } @@ -162,7 +148,7 @@ function initStepper(stepper) { function handleReset() { finished = false; currentIndex = 0; - showAll = false; + expandedSteps.clear(); persist(); render(); } @@ -178,24 +164,13 @@ function initStepper(stepper) { resetBtn.addEventListener('click', handleReset); } - function toggleShowAll() { - showAll = !showAll; - if (!showAll) { - // Return to single-step view at current position - finished = false; - } - persist(); - render(); - } - // Auto-expand step when browser find-in-page matches hidden content steps.forEach((step, i) => { const content = step.querySelector('.stepper__step-content'); if (content) { content.addEventListener('beforematch', () => { - if (showAll) return; if (finished) { - currentIndex = i; + expandedSteps.add(i); persist(); render(); } else { @@ -209,15 +184,20 @@ function initStepper(stepper) { steps.forEach((step, i) => { const title = step.querySelector('.stepper__step-title'); if (title) { - title.addEventListener('click', () => { - if (finished) { - // In finished state, toggle expand/collapse without leaving finished - currentIndex = (currentIndex === i) ? -1 : i; - persist(); - render(); + title.addEventListener('click', (e) => { + e.preventDefault(); + if (!finished && i === currentIndex) { + // Clicking the active step does nothing + return; + } + // Toggle expand/collapse for non-active and finished steps + if (expandedSteps.has(i)) { + expandedSteps.delete(i); } else { - goToStep(i); + expandedSteps.add(i); } + persist(); + render(); }); } }); @@ -233,15 +213,8 @@ function initStepper(stepper) { goToStep(currentIndex - 1); } else if (btn.classList.contains('stepper__finish-btn')) { handleFinish(); - } else if ( - btn.classList.contains('stepper__show-all-btn') || - btn.classList.contains('stepper__hide-others-btn') - ) { - toggleShowAll(); } else if (btn.classList.contains('stepper__reset-btn')) { handleReset(); - } else if (controlsEl && controlsEl.contains(btn)) { - toggleShowAll(); } }); diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 87d65e669a6..8c0d7279363 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -121,8 +121,9 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__nav { display: none; - justify-content: space-between; + justify-content: flex-end; align-items: center; + gap: 8px; padding: 8px 0 16px; } diff --git a/package.json b/package.json index eb0dea0de43..60d0e7d2896 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 25b7fbba848..23a7ab4a1bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz": - version: 2.8.0-rc.1 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz": + version: 2.8.0-rc.3 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/b72bf7d2ed025cb4cb4afdca4d3a51c2484ff0ab58534c9af0e1cf5f4abcf394aed66a941c81ee5e23421fec8acceda56e6ab951d2bf4cb0a4f22d742e19cb99 + checksum: 10/2a08cf0792e5bd59efaec37126790a594cae590544d48881265c022ed6d324e77a84f108b45c688952f04e5a7f0052f547b57a55535d43b71a726ad586c70942 languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.1.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 62dd2de16addb92c670cb2a2ccae181e4daf28aa Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 4 Mar 2026 16:39:52 -0600 Subject: [PATCH 09/39] Use a green checkmark circle to mark completed tasks --- assets/scripts/components/stepper.js | 65 ++++++++++++++++++++++++++ assets/styles/components/_stepper.scss | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index ef57defac54..c7a5d6d6e30 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -65,6 +65,8 @@ function initStepper(stepper) { let currentIndex = 0; let finished = false; + let animating = false; + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; // Tracks which non-active steps are manually expanded (by index) let expandedSteps = new Set(); @@ -132,10 +134,73 @@ function initStepper(stepper) { } function goToStep(index) { + if (animating) return; + + const prevContent = !finished ? steps[currentIndex]?.querySelector('.stepper__step-content') : null; + const prevIsVisible = prevContent && !prevContent.hasAttribute('hidden'); + finished = false; currentIndex = Math.max(0, Math.min(index, steps.length - 1)); persist(); + + if (prefersReducedMotion || !prevIsVisible) { + render(); + return; + } + + animating = true; + + const newContent = steps[currentIndex]?.querySelector('.stepper__step-content'); + const newAlreadyVisible = newContent && !newContent.hasAttribute('hidden'); + + // Measure old content height before render() changes the DOM + const prevHeight = prevContent.offsetHeight; + + // Prime new content at height 0 so render() won't flash it open + if (newContent && !newAlreadyVisible) { + newContent.style.overflow = 'hidden'; + newContent.style.height = '0'; + } + + // Update all state immediately (classes, nav, active step, etc.) render(); + + // render() set hidden="until-found" on prevContent (content-visibility: hidden). + // Override that with an inline style so it stays visible for the slide-up. + prevContent.style.setProperty('content-visibility', 'visible'); + prevContent.style.overflow = 'hidden'; + prevContent.style.height = prevHeight + 'px'; + + // Track completion; both animations start in the same frame + let pending = newAlreadyVisible ? 1 : 2; + const onDone = () => { + if (--pending > 0) return; + prevContent.style.removeProperty('content-visibility'); + prevContent.style.removeProperty('overflow'); + prevContent.style.removeProperty('height'); + prevContent.style.removeProperty('transition'); + animating = false; + }; + + requestAnimationFrame(() => { + // Slide up old content + prevContent.style.transition = 'height 0.2s ease'; + prevContent.style.height = '0'; + prevContent.addEventListener('transitionend', () => onDone(), { once: true }); + + // Slide down new content + if (newContent && !newAlreadyVisible) { + const targetHeight = newContent.scrollHeight; + newContent.style.transition = 'height 0.2s ease'; + newContent.style.height = targetHeight + 'px'; + newContent.addEventListener('transitionend', () => { + newContent.style.removeProperty('transition'); + newContent.style.removeProperty('overflow'); + newContent.style.removeProperty('height'); + onDone(); + }, { once: true }); + } + }); } function handleFinish() { diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 8c0d7279363..4b19c13c9b9 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -76,10 +76,10 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 background-color: $brand-primary; } -// Completed step: purple circle with checkmark +// Completed step: green circle with checkmark .stepper__step--completed::after { content: '\2713'; - background-color: $brand-primary; + background-color: #2a7e41; } .stepper__step-title { From c0a76669706d199b5d44044cb4121edd0da97c02 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 4 Mar 2026 16:45:31 -0600 Subject: [PATCH 10/39] Tweak button wording --- assets/scripts/components/stepper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index c7a5d6d6e30..31806d9c69b 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -223,7 +223,7 @@ function initStepper(stepper) { if (controlsEl) { resetBtn = document.createElement('button'); resetBtn.className = 'stepper__btn stepper__reset-btn'; - resetBtn.textContent = 'Reset'; + resetBtn.textContent = 'Start over'; resetBtn.style.display = 'none'; controlsEl.appendChild(resetBtn); resetBtn.addEventListener('click', handleReset); From 7f8519eb534f4faa9e558c17cd15d1f93f1ac129 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 4 Mar 2026 16:54:44 -0600 Subject: [PATCH 11/39] Tweak wording --- assets/scripts/components/stepper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 31806d9c69b..9c48550ea8b 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -223,7 +223,7 @@ function initStepper(stepper) { if (controlsEl) { resetBtn = document.createElement('button'); resetBtn.className = 'stepper__btn stepper__reset-btn'; - resetBtn.textContent = 'Start over'; + resetBtn.textContent = 'Reset step list'; resetBtn.style.display = 'none'; controlsEl.appendChild(resetBtn); resetBtn.addEventListener('click', handleReset); From 627d8c56b4537508bdb7a4701d81964319a56a78 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 4 Mar 2026 17:01:47 -0600 Subject: [PATCH 12/39] Tweak stepper line width --- assets/styles/components/_stepper.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 4b19c13c9b9..2076b59d398 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -1,6 +1,6 @@ $stepper-circle-size: 24px; $stepper-gutter: 48px; -$stepper-line-width: 1.5px; +$stepper-line-width: 1px; $stepper-line-left: ($stepper-circle-size * 0.5) - ($stepper-line-width * 0.5); $stepper-title-pad-top: 16px; $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0.5); From b1eb45ff644ea1bbddbeb631f4b01440abb7936a Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 12:50:24 -0500 Subject: [PATCH 13/39] Tweak appearance --- assets/scripts/components/stepper.js | 21 +-------------------- assets/styles/components/_stepper.scss | 18 +++++++++++++----- package.json | 2 +- yarn.lock | 10 +++++----- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 9c48550ea8b..79fbd2a699a 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -54,7 +54,6 @@ function initStepper(stepper) { const steps = stepper.querySelectorAll('.stepper__step'); const finishedEl = stepper.querySelector('.stepper__finished'); const controlsEl = stepper.querySelector('.stepper__controls'); - const controlsBtn = controlsEl ? controlsEl.querySelector('.stepper__btn') : null; if (!steps.length) return; @@ -122,14 +121,7 @@ function initStepper(stepper) { } if (controlsEl) { - if (finished) { - controlsEl.style.display = 'flex'; - // Hide the original "Show all" button; only show Reset - if (controlsBtn) controlsBtn.style.display = 'none'; - if (resetBtn) resetBtn.style.display = ''; - } else { - controlsEl.style.display = 'none'; - } + controlsEl.style.display = finished ? 'flex' : 'none'; } } @@ -218,17 +210,6 @@ function initStepper(stepper) { render(); } - // Create a Reset button inside the controls area - let resetBtn = null; - if (controlsEl) { - resetBtn = document.createElement('button'); - resetBtn.className = 'stepper__btn stepper__reset-btn'; - resetBtn.textContent = 'Reset step list'; - resetBtn.style.display = 'none'; - controlsEl.appendChild(resetBtn); - resetBtn.addEventListener('click', handleReset); - } - // Auto-expand step when browser find-in-page matches hidden content steps.forEach((step, i) => { const content = step.querySelector('.stepper__step-content'); diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 2076b59d398..7a73b51c19e 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -45,11 +45,11 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 border-radius: 50%; background-color: $gray-dark; color: $white; - font-size: 12px; + font-size: 15px; font-weight: 700; line-height: 1; font-variant-numeric: lining-nums; - padding-bottom: 1px; + padding-bottom: 2px; transition: background-color 0.15s; box-shadow: 0 0 0 5px $white; } @@ -76,10 +76,14 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 background-color: $brand-primary; } -// Completed step: green circle with checkmark +// Completed step: green circle with checkmark icon .stepper__step--completed::after { - content: '\2713'; + content: ''; background-color: #2a7e41; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.5 2L6 9.5L2.5 6L0 8.5l6 6l10-10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 14px 14px; } .stepper__step-title { @@ -115,6 +119,8 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } } +// Show the first step's content before JS adds --active classes +.stepper__step:first-child .stepper__step-content, .stepper__step--active .stepper__step-content { display: block; } @@ -127,6 +133,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 padding: 8px 0 16px; } +.stepper__step:first-child .stepper__nav, .stepper__step--active .stepper__nav { display: flex; } @@ -158,7 +165,8 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } .stepper__next-btn, -.stepper__finish-btn { +.stepper__finish-btn, +.stepper__reset-btn { background-color: $brand-primary; color: $white; diff --git a/package.json b/package.json index 60d0e7d2896..663e4386d0a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 23a7ab4a1bd..04e1076167d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz": - version: 2.8.0-rc.3 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz": + version: 2.8.0-rc.6 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/2a08cf0792e5bd59efaec37126790a594cae590544d48881265c022ed6d324e77a84f108b45c688952f04e5a7f0052f547b57a55535d43b71a726ad586c70942 + checksum: 10/eec5e7f0a45365de01a0efcc85e81024e0912939ece2328e6a5108dbff5d9aeab2dad28c901a510d55504a4012e1d1cb8b83f6115ed5f80ef94f1992f659427e languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.3.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 8673f1db99249e6d9e3b8aebc11329532c6f8351 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 12:54:03 -0500 Subject: [PATCH 14/39] Improve focus visibility --- assets/styles/_bootstrap-custom.scss | 8 ++++++-- assets/styles/pages/_global.scss | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/assets/styles/_bootstrap-custom.scss b/assets/styles/_bootstrap-custom.scss index f49266a2fea..f756c383ebd 100644 --- a/assets/styles/_bootstrap-custom.scss +++ b/assets/styles/_bootstrap-custom.scss @@ -255,10 +255,14 @@ button.list-group-item-white:hover { } } -// remove outlines -button:focus { +// Hide focus outlines for mouse/touch only; keep for keyboard +button:focus:not(:focus-visible) { outline: none; } +button:focus-visible { + outline: 2px solid $ddpurple; + outline-offset: 2px; +} .form-control:focus { box-shadow: none; } diff --git a/assets/styles/pages/_global.scss b/assets/styles/pages/_global.scss index f35ab2d681b..bab0472e680 100644 --- a/assets/styles/pages/_global.scss +++ b/assets/styles/pages/_global.scss @@ -104,11 +104,19 @@ a { color: #000; } -button:focus, -a:focus { +// Hide focus outlines for mouse/touch interactions only +button:focus:not(:focus-visible), +a:focus:not(:focus-visible) { outline: none !important; } +// Show focus outlines for keyboard navigation (accessibility) +button:focus-visible, +a:focus-visible { + outline: 2px solid $ddpurple; + outline-offset: 2px; +} + .custom-select { width: 100%; background-color: #fff; From c35384088e7fabaa98a021300aceadc2e6ce9ef7 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 12:55:44 -0500 Subject: [PATCH 15/39] Improve accessibility --- assets/styles/components/_tab-toggle.scss | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/assets/styles/components/_tab-toggle.scss b/assets/styles/components/_tab-toggle.scss index 31b5ecac0c8..15a2f04775c 100644 --- a/assets/styles/components/_tab-toggle.scss +++ b/assets/styles/components/_tab-toggle.scss @@ -138,13 +138,17 @@ $tab-active-shadow: 0 -4px 12px rgba(99, 44, 166, 0.08); display: inline-block; position: relative; color: rgba(0, 0, 0, 0.6); - outline: none !important; transition: none; - &:focus { + &:focus:not(:focus-visible) { outline: none !important; box-shadow: none !important; } + + &:focus-visible { + outline: none; + box-shadow: inset 0 0 0 2px $tab-active-color; + } } // Styles for the currently active tab - includes white background, purple text, and subtle shadow @@ -161,13 +165,17 @@ $tab-active-shadow: 0 -4px 12px rgba(99, 44, 166, 0.08); padding-bottom: 9px; border-top-left-radius: 4px; border-top-right-radius: 4px; - outline: none !important; transition: none; - &:focus { + &:focus:not(:focus-visible) { outline: none !important; box-shadow: $tab-active-shadow !important; } + + &:focus-visible { + outline: none; + box-shadow: inset 0 0 0 2px $tab-active-color, $tab-active-shadow; + } } // Container for tab content - white background with border and shadow From 844dedfbd3dde58419c1bd7d0a22132066ea064d Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 12:57:26 -0500 Subject: [PATCH 16/39] Improve accessibility --- assets/styles/components/_collapsible-section.scss | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/styles/components/_collapsible-section.scss b/assets/styles/components/_collapsible-section.scss index 5b4485d6431..2f90f8637b2 100644 --- a/assets/styles/components/_collapsible-section.scss +++ b/assets/styles/components/_collapsible-section.scss @@ -11,7 +11,6 @@ align-items: center; padding: 8px; cursor: pointer; - outline: none; background-color: #ffffff00; border-radius: 7px 7px 7px 7px; transition: background-color 0.3s, @@ -22,6 +21,15 @@ display: none; } +.collapsible-header:focus:not(:focus-visible) { + outline: none; +} + +.collapsible-header:focus-visible { + outline: 2px solid #632DA6; + outline-offset: -2px; +} + .collapsible-header:hover { background-color: #eae2f8; } From e834ed54706a7bbb9eb4f9c627e3964df8cd696d Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 13:09:37 -0500 Subject: [PATCH 17/39] Tweak checkmark --- assets/styles/components/_stepper.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 7a73b51c19e..ed667abda0f 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -80,7 +80,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__step--completed::after { content: ''; background-color: #2a7e41; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.5 2L6 9.5L2.5 6L0 8.5l6 6l10-10z'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cpath fill='%23fff' d='m22.567 4.73l-1.795-1.219a1.09 1.09 0 0 0-1.507.287l-8.787 12.959l-4.039-4.039a1.09 1.09 0 0 0-1.533 0l-1.533 1.536a1.084 1.084 0 0 0 0 1.534L9.582 22c.349.347.895.615 1.387.615s.988-.31 1.307-.774l10.58-15.606a1.085 1.085 0 0 0-.289-1.505'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center; background-size: 14px 14px; From 5879ab1b3a9b005501385a93d36a6d75f17e3453 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 13:11:42 -0500 Subject: [PATCH 18/39] Tweak button text size --- assets/styles/components/_stepper.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index ed667abda0f..4e4754e8fa8 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -152,7 +152,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 border-radius: 4px; background-color: $white; color: $brand-primary; - font-size: 14px; + font-size: 16px; font-weight: 500; line-height: 1.5; cursor: pointer; From 6bbed709c8126efb1b1ea5a5856e53d5cdaf3385 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 13:34:20 -0500 Subject: [PATCH 19/39] Tweak loading behavior --- assets/scripts/components/stepper.js | 1 + assets/styles/components/_stepper.scss | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 79fbd2a699a..de2dc48c08c 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -266,6 +266,7 @@ function initStepper(stepper) { // Initial render render(); + stepper.classList.add('stepper--initialized'); } function initAllSteppers() { diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 4e4754e8fa8..7348eb76421 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -8,6 +8,11 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper { margin-bottom: 24px; counter-reset: stepper-step; + opacity: 0; + + &--initialized { + opacity: 1; + } } .stepper__steps { From 0742832fff4cb266805dd25441768a2a90f647fb Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 13:38:00 -0500 Subject: [PATCH 20/39] Button tweaks --- assets/styles/components/_stepper.scss | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 7348eb76421..45f9028937a 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -124,8 +124,6 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } } -// Show the first step's content before JS adds --active classes -.stepper__step:first-child .stepper__step-content, .stepper__step--active .stepper__step-content { display: block; } @@ -138,7 +136,6 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 padding: 8px 0 16px; } -.stepper__step:first-child .stepper__nav, .stepper__step--active .stepper__nav { display: flex; } @@ -152,14 +149,14 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__btn { display: inline-flex; align-items: center; - padding: 6px 16px; + padding: 9px 16px 11px; border: 1px solid $brand-primary; border-radius: 4px; background-color: $white; color: $brand-primary; font-size: 16px; font-weight: 500; - line-height: 1.5; + line-height: 1; cursor: pointer; transition: background-color 0.15s, color 0.15s; From 109f1ab92acdf562f184d5818d4d91b880a52eb9 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 9 Mar 2026 14:00:47 -0500 Subject: [PATCH 21/39] Tweaks --- assets/scripts/components/stepper.js | 68 +++++++++++++++------------- content/en/a_demo/stepper.mdoc.md | 23 ++++++++++ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index de2dc48c08c..1bd9cee10e3 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -17,12 +17,9 @@ function loadProgress(stepperId) { function saveProgress(stepperId, state) { try { pruneOldEntries(); - localStorage.setItem( - getStorageKey(stepperId), - JSON.stringify({ ...state, timestamp: Date.now() }) - ); + localStorage.setItem(getStorageKey(stepperId), JSON.stringify({ ...state, timestamp: Date.now() })); } catch { - // localStorage unavailable or full — silently ignore + // Ignore storage errors, the stepper will still work without localStorage persistence } } @@ -45,7 +42,7 @@ function pruneOldEntries() { } } } catch { - // Ignore storage errors + // Ignore storage errors, the stepper will still work without localStorage persistence } } @@ -113,7 +110,7 @@ function initStepper(stepper) { } } const nav = step.querySelector('.stepper__nav'); - if (nav) nav.style.display = (isActive && !finished) ? '' : 'none'; + if (nav) nav.style.display = isActive && !finished ? '' : 'none'; }); if (finishedEl) { @@ -125,24 +122,7 @@ function initStepper(stepper) { } } - function goToStep(index) { - if (animating) return; - - const prevContent = !finished ? steps[currentIndex]?.querySelector('.stepper__step-content') : null; - const prevIsVisible = prevContent && !prevContent.hasAttribute('hidden'); - - finished = false; - currentIndex = Math.max(0, Math.min(index, steps.length - 1)); - persist(); - - if (prefersReducedMotion || !prevIsVisible) { - render(); - return; - } - - animating = true; - - const newContent = steps[currentIndex]?.querySelector('.stepper__step-content'); + function animateTransition(prevContent, newContent, onComplete) { const newAlreadyVisible = newContent && !newContent.hasAttribute('hidden'); // Measure old content height before render() changes the DOM @@ -171,7 +151,7 @@ function initStepper(stepper) { prevContent.style.removeProperty('overflow'); prevContent.style.removeProperty('height'); prevContent.style.removeProperty('transition'); - animating = false; + onComplete(); }; requestAnimationFrame(() => { @@ -185,16 +165,40 @@ function initStepper(stepper) { const targetHeight = newContent.scrollHeight; newContent.style.transition = 'height 0.2s ease'; newContent.style.height = targetHeight + 'px'; - newContent.addEventListener('transitionend', () => { - newContent.style.removeProperty('transition'); - newContent.style.removeProperty('overflow'); - newContent.style.removeProperty('height'); - onDone(); - }, { once: true }); + newContent.addEventListener( + 'transitionend', + () => { + newContent.style.removeProperty('transition'); + newContent.style.removeProperty('overflow'); + newContent.style.removeProperty('height'); + onDone(); + }, + { once: true } + ); } }); } + function goToStep(index) { + if (animating) return; + + const prevContent = !finished ? steps[currentIndex]?.querySelector('.stepper__step-content') : null; + const prevIsVisible = prevContent && !prevContent.hasAttribute('hidden'); + + finished = false; + currentIndex = Math.max(0, Math.min(index, steps.length - 1)); + persist(); + + if (prefersReducedMotion || !prevIsVisible) { + render(); + return; + } + + animating = true; + const newContent = steps[currentIndex]?.querySelector('.stepper__step-content'); + animateTransition(prevContent, newContent, () => { animating = false; }); + } + function handleFinish() { finished = true; expandedSteps.clear(); diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md index f19777c2d76..49fa2e89bc1 100644 --- a/content/en/a_demo/stepper.mdoc.md +++ b/content/en/a_demo/stepper.mdoc.md @@ -1,5 +1,8 @@ --- title: Stepper test +content_filters: + - trait_id: prog_lang + option_group_id: otel_api_support_language_options --- ## With "finished" section @@ -138,6 +141,26 @@ Step 2 contents go here, including tabs, sublists, etc. {% /stepper %} +## Conditionally displayed filter (for rendering / listener glitches) + +{% if not(equals($prog_lang, "java")) %} +Set the prog lang to Java to see the stepper. +{% /if %} + +{% if equals($prog_lang, "java") %} +{% stepper %} + +{% step title="Step 1 title" %} +Step 1 contents go here, including tabs, sublists, etc. +{% /step %} + +{% step title="Step 2 title" %} +Step 2 contents go here, including tabs, sublists, etc. +{% /step %} + +{% /stepper %} +{% /if %} + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ## Lots of steps From ab326d91d872c8a3ae38ebfd2f67f53eef296f17 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 11 Mar 2026 15:06:52 -0500 Subject: [PATCH 22/39] Update demo markup --- content/en/a_demo/stepper.mdoc.md | 152 ++++++++++++++++++------------ package.json | 2 +- yarn.lock | 20 ++-- 3 files changed, 101 insertions(+), 73 deletions(-) diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md index 49fa2e89bc1..f70606fa3df 100644 --- a/content/en/a_demo/stepper.mdoc.md +++ b/content/en/a_demo/stepper.mdoc.md @@ -5,7 +5,7 @@ content_filters: option_group_id: otel_api_support_language_options --- -## With "finished" section +## With no "open" attribute Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -125,94 +125,122 @@ Great job, you've finished all the steps. You might like to browse this super he Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -## Without "finished" section +## With "open" attribute -Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. -{% stepper %} +{% stepper open="true" %} -{% step title="Step 1 title" %} -Step 1 contents go here, including tabs, sublists, etc. -{% /step %} +{% step title="Verify the prerequisites" %} +Make sure you meet these prerequisites: -{% step title="Step 2 title" %} -Step 2 contents go here, including tabs, sublists, etc. -{% /step %} +- You have a computer. +- You have permission to install things on the computer. +- You are competent. +- You are in the mood to do this today. -{% /stepper %} +{% collapse-content title="Bonus prereqs" level="h4" %} +- Somebody is paying you to do this. +{% /collapse-content %} +{% /step %} -## Conditionally displayed filter (for rendering / listener glitches) +{% step title="Install the software" %} +{% tabs %} -{% if not(equals($prog_lang, "java")) %} -Set the prog lang to Java to see the stepper. -{% /if %} +{% tab label="Python" %} +Run this script to install with Python: -{% if equals($prog_lang, "java") %} -{% stepper %} +```python +import time +import sys -{% step title="Step 1 title" %} -Step 1 contents go here, including tabs, sublists, etc. -{% /step %} +def fake_install(): + steps = [ + "Checking system requirements", + "Downloading packages", + "Installing dependencies", + "Configuring environment", + "Finalizing setup" + ] -{% step title="Step 2 title" %} -Step 2 contents go here, including tabs, sublists, etc. -{% /step %} + for step in steps: + print(f"{step}...", end="") + sys.stdout.flush() + time.sleep(1) + print(" Done.") -{% /stepper %} -{% /if %} + print("\nInstallation completed successfully!") -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +if __name__ == "__main__": + fake_install() +``` +{% /tab %} -## Lots of steps +{% tab label="JavaScript" %} +Run this script to install with JavaScript: -{% stepper %} +```javascript +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} -{% step title="Step 1 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +async function fakeInstall() { + const steps = [ + "Checking system requirements", + "Downloading packages", + "Installing dependencies", + "Configuring environment", + "Finalizing setup" + ]; -{% step title="Step 2 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} + for (const step of steps) { + process.stdout.write(step + "... "); + await sleep(1000); + console.log("Done."); + } -{% step title="Step 3 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} + console.log("\nInstallation completed successfully!"); +} -{% step title="Step 4 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +fakeInstall(); +``` +{% /tab %} -{% step title="Step 5 title" %} -Contents go here, including tabs, sublists, etc. +{% /tabs %} {% /step %} -{% step title="Step 6 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +{% step title="Update the configuration file" %} +{% alert level="warning" %} +This is made-up YAML. Proceed with caution. +{% /alert %} +Use this YAML: -{% step title="Step 7 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +```yaml +server: + host: 0.0.0.0 + port: 8080 -{% step title="Step 8 title" %} -Contents go here, including tabs, sublists, etc. +database: + type: postgres + host: localhost + port: 5432 + username: admin + password: secret123 +``` {% /step %} -{% step title="Step 9 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +{% step title="Verify the installation" %} +Verify the installation by following these steps: -{% step title="Step 10 title" %} -Contents go here, including tabs, sublists, etc. +1. Ask Claude if it seems installed. +2. Ask ChatGPT if it seems installed, just in case. +3. Ask the person nearest to you if they feel as though the software was in fact installed. {% /step %} -{% step title="Step 11 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +{% stepper-finished %} +Great job, you've finished all the steps. You might like to browse this super helpful doc next. +{% /stepper-finished %} -{% step title="Step 12 title" %} -Contents go here, including tabs, sublists, etc. -{% /step %} +{% /stepper %} -{% /stepper %} \ No newline at end of file +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. diff --git a/package.json b/package.json index 663e4386d0a..bfcabd9cc2f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 04e1076167d..6d7dfecf83c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,15 +6636,15 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz": - version: 2.8.0-rc.6 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz": + version: 2.14.0-rc.3 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" "@vitejs/plugin-react": "npm:^4.3.3" cdocs-data: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-data-v1.3.2.tgz" - cdocs-markdoc: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.1.1.tgz" + cdocs-markdoc: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz" chokidar: "npm:^4.0.3" chroma-highlight: "npm:^2.4.2" interweave: "npm:^13.1.0" @@ -6663,13 +6663,13 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/eec5e7f0a45365de01a0efcc85e81024e0912939ece2328e6a5108dbff5d9aeab2dad28c901a510d55504a4012e1d1cb8b83f6115ed5f80ef94f1992f659427e + checksum: 10/bd88aee26ed1b127c158370fa9f9db01979090380c46be07d3593e6750894dc85ab226fc90ee5b80e73294800bb4ec1f78c8d9503c8236323e349d3094962c73 languageName: node linkType: hard -"cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.1.1.tgz": - version: 1.1.1 - resolution: "cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.1.1.tgz" +"cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz": + version: 1.2.1 + resolution: "cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz" dependencies: "@types/linkify-it": "npm:^3.0.1" "@types/markdown-it": "npm:12.2.3" @@ -6678,7 +6678,7 @@ __metadata: optional: true "@types/markdown-it": optional: true - checksum: 10/d0754d0020ab4a7f3eac48e2c729f10d0ccc9c374e6be0552c53225b4461c12f0106d46cda886a2585bb25b78c11374b5f88c3b8a5a725b0cd08fe1f8e1252e9 + checksum: 10/cf7549edda85a7dafe4b16c88887c6c1af9c0cd2e188a7fbba69e5f3dca38fd5a4743419ee377798e5c2850d10f0ddc15c5ad54975a6ab0dfa69dab43a23d114 languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.8.0-rc.6.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 6ceee614170607c908d13cf76ea36bef8c5381c2 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Wed, 11 Mar 2026 16:46:24 -0500 Subject: [PATCH 23/39] [wip] Incorporate feedback --- assets/scripts/components/stepper.js | 192 ++++++++----------------- assets/styles/components/_stepper.scss | 82 +++++------ 2 files changed, 93 insertions(+), 181 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 1bd9cee10e3..6ba5a6944db 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -46,25 +46,38 @@ function pruneOldEntries() { } } +function setHidden(el, hidden) { + if (!el) return; + if (hidden) { + el.setAttribute('data-hidden', 'true'); + } else { + el.removeAttribute('data-hidden'); + } +} + function initStepper(stepper) { const stepperId = stepper.id; const steps = stepper.querySelectorAll('.stepper__step'); const finishedEl = stepper.querySelector('.stepper__finished'); - const controlsEl = stepper.querySelector('.stepper__controls'); + const resetEl = stepper.querySelector('.stepper__reset'); + const showAllBtn = stepper.querySelector('.stepper__show-all-btn'); + const collapseBtn = stepper.querySelector('.stepper__collapse-btn'); if (!steps.length) return; - // Mark first/last steps for CSS (can't rely on :first-child/:last-child - // because .stepper__finished is a sibling inside .stepper__steps) + // Set step numbers as data attributes for CSS + // (CSS counters don't work reliably when steps use display:none) + steps.forEach((step, i) => { + step.dataset.stepNumber = String(i + 1); + }); + + // Mark first/last steps for CSS line endpoints steps[0].classList.add('stepper__step--first'); steps[steps.length - 1].classList.add('stepper__step--last'); let currentIndex = 0; let finished = false; - let animating = false; - const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - // Tracks which non-active steps are manually expanded (by index) - let expandedSteps = new Set(); + let isAllExpanded = stepper.classList.contains('stepper--open'); // Restore saved progress const saved = loadProgress(stepperId); @@ -74,8 +87,8 @@ function initStepper(stepper) { } else if (typeof saved.stepIndex === 'number' && saved.stepIndex >= 0 && saved.stepIndex < steps.length) { currentIndex = saved.stepIndex; } - if (Array.isArray(saved.expandedSteps)) { - expandedSteps = new Set(saved.expandedSteps); + if (typeof saved.isAllExpanded === 'boolean') { + isAllExpanded = saved.isAllExpanded; } } @@ -83,125 +96,51 @@ function initStepper(stepper) { saveProgress(stepperId, { stepIndex: currentIndex, finished, - expandedSteps: [...expandedSteps] + isAllExpanded }); } function render() { + stepper.classList.toggle('stepper--all-expanded', isAllExpanded); + + // Toggle viz control buttons + setHidden(showAllBtn, isAllExpanded); + setHidden(collapseBtn, !isAllExpanded); + steps.forEach((step, i) => { const isActive = !finished && i === currentIndex; const isCompleted = finished || i < currentIndex; - const isExpanded = isActive || expandedSteps.has(i); step.classList.toggle('stepper__step--active', isActive); step.classList.toggle('stepper__step--completed', isCompleted); - const content = step.querySelector('.stepper__step-content'); - if (content) { - if (isActive) { - content.removeAttribute('hidden'); - content.style.removeProperty('display'); - } else if (isExpanded) { - content.removeAttribute('hidden'); - content.style.display = 'block'; - } else { - content.setAttribute('hidden', 'until-found'); - content.style.removeProperty('display'); - } - } + // Steps are always visible (titles, circles, lines always show). + // Content visibility is handled by CSS via --active and --all-expanded classes. + setHidden(step, false); + + // Nav: hidden when expanded or finished, visible only for active step const nav = step.querySelector('.stepper__nav'); - if (nav) nav.style.display = isActive && !finished ? '' : 'none'; + if (nav) { + setHidden(nav, isAllExpanded || !isActive || finished); + } }); - if (finishedEl) { - finishedEl.style.display = finished ? '' : 'none'; - } - - if (controlsEl) { - controlsEl.style.display = finished ? 'flex' : 'none'; - } - } - - function animateTransition(prevContent, newContent, onComplete) { - const newAlreadyVisible = newContent && !newContent.hasAttribute('hidden'); - - // Measure old content height before render() changes the DOM - const prevHeight = prevContent.offsetHeight; - - // Prime new content at height 0 so render() won't flash it open - if (newContent && !newAlreadyVisible) { - newContent.style.overflow = 'hidden'; - newContent.style.height = '0'; - } - - // Update all state immediately (classes, nav, active step, etc.) - render(); - - // render() set hidden="until-found" on prevContent (content-visibility: hidden). - // Override that with an inline style so it stays visible for the slide-up. - prevContent.style.setProperty('content-visibility', 'visible'); - prevContent.style.overflow = 'hidden'; - prevContent.style.height = prevHeight + 'px'; - - // Track completion; both animations start in the same frame - let pending = newAlreadyVisible ? 1 : 2; - const onDone = () => { - if (--pending > 0) return; - prevContent.style.removeProperty('content-visibility'); - prevContent.style.removeProperty('overflow'); - prevContent.style.removeProperty('height'); - prevContent.style.removeProperty('transition'); - onComplete(); - }; + // Finished message + setHidden(finishedEl, !finished); - requestAnimationFrame(() => { - // Slide up old content - prevContent.style.transition = 'height 0.2s ease'; - prevContent.style.height = '0'; - prevContent.addEventListener('transitionend', () => onDone(), { once: true }); - - // Slide down new content - if (newContent && !newAlreadyVisible) { - const targetHeight = newContent.scrollHeight; - newContent.style.transition = 'height 0.2s ease'; - newContent.style.height = targetHeight + 'px'; - newContent.addEventListener( - 'transitionend', - () => { - newContent.style.removeProperty('transition'); - newContent.style.removeProperty('overflow'); - newContent.style.removeProperty('height'); - onDone(); - }, - { once: true } - ); - } - }); + // Start over: visible only when finished + setHidden(resetEl, !finished); } function goToStep(index) { - if (animating) return; - - const prevContent = !finished ? steps[currentIndex]?.querySelector('.stepper__step-content') : null; - const prevIsVisible = prevContent && !prevContent.hasAttribute('hidden'); - finished = false; currentIndex = Math.max(0, Math.min(index, steps.length - 1)); persist(); - - if (prefersReducedMotion || !prevIsVisible) { - render(); - return; - } - - animating = true; - const newContent = steps[currentIndex]?.querySelector('.stepper__step-content'); - animateTransition(prevContent, newContent, () => { animating = false; }); + render(); } function handleFinish() { finished = true; - expandedSteps.clear(); persist(); render(); } @@ -209,50 +148,23 @@ function initStepper(stepper) { function handleReset() { finished = false; currentIndex = 0; - expandedSteps.clear(); + isAllExpanded = stepper.classList.contains('stepper--open'); persist(); render(); } - // Auto-expand step when browser find-in-page matches hidden content - steps.forEach((step, i) => { - const content = step.querySelector('.stepper__step-content'); - if (content) { - content.addEventListener('beforematch', () => { - if (finished) { - expandedSteps.add(i); - persist(); - render(); - } else { - goToStep(i); - } - }); - } - }); - - // Bind title clicks to toggle steps (accordion behavior) + // Clicking a step title in accordion (collapsed) mode makes it the active step steps.forEach((step, i) => { const title = step.querySelector('.stepper__step-title'); if (title) { - title.addEventListener('click', (e) => { - e.preventDefault(); - if (!finished && i === currentIndex) { - // Clicking the active step does nothing - return; - } - // Toggle expand/collapse for non-active and finished steps - if (expandedSteps.has(i)) { - expandedSteps.delete(i); - } else { - expandedSteps.add(i); - } - persist(); - render(); + title.addEventListener('click', () => { + if (isAllExpanded) return; + goToStep(i); }); } }); - // Bind navigation buttons + // Bind all button clicks via delegation stepper.addEventListener('click', (e) => { const btn = e.target.closest('.stepper__btn'); if (!btn) return; @@ -265,6 +177,14 @@ function initStepper(stepper) { handleFinish(); } else if (btn.classList.contains('stepper__reset-btn')) { handleReset(); + } else if (btn.classList.contains('stepper__show-all-btn')) { + isAllExpanded = true; + persist(); + render(); + } else if (btn.classList.contains('stepper__collapse-btn')) { + isAllExpanded = false; + persist(); + render(); } }); diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 45f9028937a..c6b88e8502a 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -6,13 +6,22 @@ $stepper-title-pad-top: 16px; $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0.5); .stepper { + position: relative; margin-bottom: 24px; - counter-reset: stepper-step; opacity: 0; + &--open { + opacity: 1; + } + &--initialized { opacity: 1; } + + // Hide any element with data-hidden="true" within steppers + [data-hidden="true"] { + display: none !important; + } } .stepper__steps { @@ -22,7 +31,6 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__step { position: relative; padding-left: $stepper-gutter; - counter-increment: stepper-step; // Vertical connecting line &::before { @@ -35,9 +43,9 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 background-color: $gray-light; } - // Number circle + // Number circle (uses data attribute set by JS instead of CSS counter) &::after { - content: counter(stepper-step); + content: attr(data-step-number); position: absolute; left: 0; top: $stepper-title-pad-top + 2px; @@ -81,6 +89,13 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 background-color: $brand-primary; } +// All-expanded mode: all circles purple with step numbers +.stepper--all-expanded .stepper__step::after { + content: attr(data-step-number); + background-color: $brand-primary; + background-image: none; +} + // Completed step: green circle with checkmark icon .stepper__step--completed::after { content: ''; @@ -100,10 +115,9 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 line-height: $stepper-circle-size; cursor: pointer; user-select: none; - transition: color 0.15s; - &:hover { - color: $brand-primary; + .stepper--all-expanded & { + cursor: default; } } @@ -111,37 +125,34 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 display: none; padding-bottom: 8px; - // Let hidden="until-found" use content-visibility instead of display:none, - // so browser find-in-page can still search collapsed step content. - // The !important is needed to override Bootstrap's [hidden] { display: none !important; }. - &[hidden='until-found'] { - display: block !important; - content-visibility: hidden; - } - > *:last-child { margin-bottom: 0; } } +// Show content for the active step .stepper__step--active .stepper__step-content { display: block; } +// Show all content in expanded mode +.stepper--all-expanded .stepper__step-content { + display: block; +} + .stepper__nav { - display: none; + display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 8px 0 16px; } -.stepper__step--active .stepper__nav { - display: flex; -} - -.stepper__nav-left, -.stepper__nav-right { +.stepper__viz-controls { + position: absolute; + top: $stepper-title-pad-top; + right: 0; + z-index: 2; display: flex; gap: 8px; } @@ -168,7 +179,9 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__next-btn, .stepper__finish-btn, -.stepper__reset-btn { +.stepper__reset-btn, +.stepper__show-all-btn, +.stepper__collapse-btn { background-color: $brand-primary; color: $white; @@ -187,28 +200,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } } -.stepper__controls { - display: none; +.stepper__reset { padding: 8px 0; margin-left: $stepper-gutter; - gap: 8px; -} - -// Show-all mode: expand every step's content -.stepper--show-all { - .stepper__step-content { - display: block; - } - - .stepper__step::after { - background-color: $brand-primary; - } - - .stepper__controls { - display: block; - } - - .stepper__finished { - display: none; - } } From 8a2ce3816610fb6fbf38a2d0721fb5b254b4cf74 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Thu, 12 Mar 2026 08:22:20 -0500 Subject: [PATCH 24/39] Make the clicked step the active step --- assets/scripts/components/stepper.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 6ba5a6944db..4dff01b020d 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -157,7 +157,8 @@ function initStepper(stepper) { steps.forEach((step, i) => { const title = step.querySelector('.stepper__step-title'); if (title) { - title.addEventListener('click', () => { + title.addEventListener('click', (e) => { + e.preventDefault(); if (isAllExpanded) return; goToStep(i); }); From 3094b0e9b9332c3b4f70d6d6541ed1068b0f7793 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Thu, 12 Mar 2026 08:42:00 -0500 Subject: [PATCH 25/39] Prevent step titles from being hidden under the sticky menu --- assets/styles/components/_stepper.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index c6b88e8502a..2fbd94a22af 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -107,6 +107,9 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } .stepper__step-title { + #cdoc-content.customizable & { + scroll-margin-top: 175px; + } display: block; margin: 0; padding: $stepper-title-pad-top 0; From 9720b32a5ac4b57d89dacac0de5aebd363d3a3ac Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Thu, 12 Mar 2026 09:11:44 -0500 Subject: [PATCH 26/39] Tweak reset behavior --- assets/scripts/components/stepper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 4dff01b020d..a43021e57f1 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -148,7 +148,6 @@ function initStepper(stepper) { function handleReset() { finished = false; currentIndex = 0; - isAllExpanded = stepper.classList.contains('stepper--open'); persist(); render(); } From 8debd0970cf98d35665d322cfc837332bb4d5039 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 09:37:15 -0500 Subject: [PATCH 27/39] Style expand/collapse buttons as links --- assets/styles/components/_stepper.scss | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 2fbd94a22af..cc4369ffc10 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -182,9 +182,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__next-btn, .stepper__finish-btn, -.stepper__reset-btn, -.stepper__show-all-btn, -.stepper__collapse-btn { +.stepper__reset-btn { background-color: $brand-primary; color: $white; @@ -194,6 +192,25 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } } +.stepper__show-all-btn, +.stepper__collapse-btn { + padding: 0; + border: none; + border-radius: 0; + background-color: transparent; + color: $brand-primary; + font-size: 18px; + font-weight: 600; + text-transform: uppercase; + line-height: $stepper-circle-size; + + &:hover { + background-color: transparent; + color: darken($brand-primary, 10%); + text-decoration: underline; + } +} + .stepper__finished { padding: 16px 0; margin-left: $stepper-gutter; From 8a7b385ba3d334c1024601ac97ea3db59e27c8e7 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 10:12:10 -0500 Subject: [PATCH 28/39] Improve responsiveness --- assets/styles/components/_stepper.scss | 12 ++++++++---- package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index cc4369ffc10..f167173283f 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -151,13 +151,17 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 padding: 8px 0 16px; } +.stepper__step-heading { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 16px; +} + .stepper__viz-controls { - position: absolute; - top: $stepper-title-pad-top; - right: 0; - z-index: 2; display: flex; gap: 8px; + flex-shrink: 0; } .stepper__btn { diff --git a/package.json b/package.json index bfcabd9cc2f..1972a085ac5 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 6d7dfecf83c..55fdf105e1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz": - version: 2.14.0-rc.3 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz": + version: 2.14.0-rc.4 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/bd88aee26ed1b127c158370fa9f9db01979090380c46be07d3593e6750894dc85ab226fc90ee5b80e73294800bb4ec1f78c8d9503c8236323e349d3094962c73 + checksum: 10/8a75dd49de4a1e74ab8a4154a94a13d35ac92d94f60d5cec28dacb22665d76afc201d24e706770e58c1aa9694ec0cc8451d7131d159ae806f02a017e2e76ae46 languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.3.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 482e70d104aed6823e376f2e2a2532457d2f3957 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 10:47:24 -0500 Subject: [PATCH 29/39] Tweak styles --- assets/styles/components/_stepper.scss | 22 +++++++++++++++++++++- package.json | 2 +- static/images/icons/collapse-mdi.svg | 1 + static/images/icons/expand-mdi.svg | 1 + yarn.lock | 10 +++++----- 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 static/images/icons/collapse-mdi.svg create mode 100644 static/images/icons/expand-mdi.svg diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index f167173283f..d21e06004dc 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -162,6 +162,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 display: flex; gap: 8px; flex-shrink: 0; + margin-right: 4px; } .stepper__btn { @@ -203,10 +204,11 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 border-radius: 0; background-color: transparent; color: $brand-primary; - font-size: 18px; + font-size: 16px; font-weight: 600; text-transform: uppercase; line-height: $stepper-circle-size; + gap: 4px; &:hover { background-color: transparent; @@ -215,6 +217,24 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 } } +.stepper__btn-icon { + display: none; + width: 26px; + height: 26px; + position: relative; + top: 6px; + + @media (max-width: 479px) { + display: inline-block; + } +} + +.stepper__btn-label { + @media (max-width: 479px) { + display: none; + } +} + .stepper__finished { padding: 16px 0; margin-left: $stepper-gutter; diff --git a/package.json b/package.json index 1972a085ac5..ac6929160fd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/static/images/icons/collapse-mdi.svg b/static/images/icons/collapse-mdi.svg new file mode 100644 index 00000000000..81226d01df2 --- /dev/null +++ b/static/images/icons/collapse-mdi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/icons/expand-mdi.svg b/static/images/icons/expand-mdi.svg new file mode 100644 index 00000000000..c11c0c47aff --- /dev/null +++ b/static/images/icons/expand-mdi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 55fdf105e1d..3fd38f52510 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz": - version: 2.14.0-rc.4 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz": + version: 2.14.0-rc.5 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/8a75dd49de4a1e74ab8a4154a94a13d35ac92d94f60d5cec28dacb22665d76afc201d24e706770e58c1aa9694ec0cc8451d7131d159ae806f02a017e2e76ae46 + checksum: 10/e18dcc7a83d8d7cc3f3da7548efb458ed6153b8a3270c9a70cde585303a19f80fd3c458b6fef291d9d93deb7999a5effa52223028d5a8ff91cf90a448098321b languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.4.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 05cf62fee55681242cf61d3ff03dd21975a355ff Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 11:01:21 -0500 Subject: [PATCH 30/39] Tweak icons --- assets/styles/components/_stepper.scss | 6 +++--- static/images/icons/collapse-mdi.svg | 2 +- static/images/icons/expand-mdi.svg | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index d21e06004dc..b63a9393fab 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -219,10 +219,10 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 .stepper__btn-icon { display: none; - width: 26px; - height: 26px; + width: 28px; + height: 28px; position: relative; - top: 6px; + top: 8px; @media (max-width: 479px) { display: inline-block; diff --git a/static/images/icons/collapse-mdi.svg b/static/images/icons/collapse-mdi.svg index 81226d01df2..d90450d26c6 100644 --- a/static/images/icons/collapse-mdi.svg +++ b/static/images/icons/collapse-mdi.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/static/images/icons/expand-mdi.svg b/static/images/icons/expand-mdi.svg index c11c0c47aff..2770056dcea 100644 --- a/static/images/icons/expand-mdi.svg +++ b/static/images/icons/expand-mdi.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 2617e44d8a93d61b9ed59e8c164b4b4212e146d4 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 11:21:29 -0500 Subject: [PATCH 31/39] Tweak spacing --- assets/styles/components/_stepper.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index b63a9393fab..e0f39112e60 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -226,6 +226,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 @media (max-width: 479px) { display: inline-block; + left: 9px; } } From c61a9efca790d73e65b493cb42fc0840710a9bde Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Fri, 13 Mar 2026 12:19:32 -0500 Subject: [PATCH 32/39] Fix stepper icon URLs --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ac6929160fd..6363ce47c40 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index 3fd38f52510..f2e2f3b9022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,9 +6636,9 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz": - version: 2.14.0-rc.5 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz": + version: 2.14.0-rc.6 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" @@ -6663,7 +6663,7 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/e18dcc7a83d8d7cc3f3da7548efb458ed6153b8a3270c9a70cde585303a19f80fd3c458b6fef291d9d93deb7999a5effa52223028d5a8ff91cf90a448098321b + checksum: 10/5e07f314164eefec4c43389591d278f1c27375f294d4d02932ef7f8218d7b90dca6805d27b88b6f2cccaa0bb13f4e181c4912593f5b774b0766cee299720ce3c languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.5.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From fedc140042eb4f11b69165c7db8ea625e4edaf24 Mon Sep 17 00:00:00 2001 From: Brett Blue <84536271+brett0000FF@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:39:55 -0600 Subject: [PATCH 33/39] Tone down expand/collapse toggle styling (#35284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce visual weight of the expand all / collapse toggle so it reads as a quiet utility control rather than competing with step titles. - font-size: 16px → 14px - font-weight: 600 → 500 - text-transform: uppercase → none (sentence case) - Add subtle letter-spacing --- assets/styles/components/_stepper.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index e0f39112e60..5fd6a636167 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -204,9 +204,10 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 border-radius: 0; background-color: transparent; color: $brand-primary; - font-size: 16px; - font-weight: 600; - text-transform: uppercase; + font-size: 14px; + font-weight: 500; + text-transform: none; + letter-spacing: 0.01em; line-height: $stepper-circle-size; gap: 4px; From bf346ae16f5c169679dfc52ff555437f3097c6f6 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 13:32:13 -0500 Subject: [PATCH 34/39] Tweaks --- assets/scripts/components/stepper.js | 5 ----- assets/styles/components/_stepper.scss | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index a43021e57f1..85a59e2de41 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -114,10 +114,6 @@ function initStepper(stepper) { step.classList.toggle('stepper__step--active', isActive); step.classList.toggle('stepper__step--completed', isCompleted); - // Steps are always visible (titles, circles, lines always show). - // Content visibility is handled by CSS via --active and --all-expanded classes. - setHidden(step, false); - // Nav: hidden when expanded or finished, visible only for active step const nav = step.querySelector('.stepper__nav'); if (nav) { @@ -164,7 +160,6 @@ function initStepper(stepper) { } }); - // Bind all button clicks via delegation stepper.addEventListener('click', (e) => { const btn = e.target.closest('.stepper__btn'); if (!btn) return; diff --git a/assets/styles/components/_stepper.scss b/assets/styles/components/_stepper.scss index 5fd6a636167..09e1eeb13b1 100644 --- a/assets/styles/components/_stepper.scss +++ b/assets/styles/components/_stepper.scss @@ -1,5 +1,6 @@ $stepper-circle-size: 24px; $stepper-gutter: 48px; +$stepper-completed-color: #2a7e41; $stepper-line-width: 1px; $stepper-line-left: ($stepper-circle-size * 0.5) - ($stepper-line-width * 0.5); $stepper-title-pad-top: 16px; @@ -99,7 +100,7 @@ $stepper-circle-center: $stepper-title-pad-top + 1px + ($stepper-circle-size * 0 // Completed step: green circle with checkmark icon .stepper__step--completed::after { content: ''; - background-color: #2a7e41; + background-color: $stepper-completed-color; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cpath fill='%23fff' d='m22.567 4.73l-1.795-1.219a1.09 1.09 0 0 0-1.507.287l-8.787 12.959l-4.039-4.039a1.09 1.09 0 0 0-1.533 0l-1.533 1.536a1.084 1.084 0 0 0 0 1.534L9.582 22c.349.347.895.615 1.387.615s.988-.31 1.307-.774l10.58-15.606a1.085 1.085 0 0 0-.289-1.505'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center; From 8c7c58a94ad1c1d9b076eb4f6ad5915971638c1f Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 13:37:59 -0500 Subject: [PATCH 35/39] Delete stepper demo file --- content/en/a_demo/stepper.mdoc.md | 246 ------------------------------ 1 file changed, 246 deletions(-) delete mode 100644 content/en/a_demo/stepper.mdoc.md diff --git a/content/en/a_demo/stepper.mdoc.md b/content/en/a_demo/stepper.mdoc.md deleted file mode 100644 index f70606fa3df..00000000000 --- a/content/en/a_demo/stepper.mdoc.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: Stepper test -content_filters: - - trait_id: prog_lang - option_group_id: otel_api_support_language_options ---- - -## With no "open" attribute - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - -{% stepper %} - -{% step title="Verify the prerequisites" %} -Make sure you meet these prerequisites: - -- You have a computer. -- You have permission to install things on the computer. -- You are competent. -- You are in the mood to do this today. - -{% collapse-content title="Bonus prereqs" level="h4" %} -- Somebody is paying you to do this. -{% /collapse-content %} -{% /step %} - -{% step title="Install the software" %} -{% tabs %} - -{% tab label="Python" %} -Run this script to install with Python: - -```python -import time -import sys - -def fake_install(): - steps = [ - "Checking system requirements", - "Downloading packages", - "Installing dependencies", - "Configuring environment", - "Finalizing setup" - ] - - for step in steps: - print(f"{step}...", end="") - sys.stdout.flush() - time.sleep(1) - print(" Done.") - - print("\nInstallation completed successfully!") - -if __name__ == "__main__": - fake_install() -``` -{% /tab %} - -{% tab label="JavaScript" %} -Run this script to install with JavaScript: - -```javascript -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function fakeInstall() { - const steps = [ - "Checking system requirements", - "Downloading packages", - "Installing dependencies", - "Configuring environment", - "Finalizing setup" - ]; - - for (const step of steps) { - process.stdout.write(step + "... "); - await sleep(1000); - console.log("Done."); - } - - console.log("\nInstallation completed successfully!"); -} - -fakeInstall(); -``` -{% /tab %} - -{% /tabs %} -{% /step %} - -{% step title="Update the configuration file" %} -{% alert level="warning" %} -This is made-up YAML. Proceed with caution. -{% /alert %} -Use this YAML: - -```yaml -server: - host: 0.0.0.0 - port: 8080 - -database: - type: postgres - host: localhost - port: 5432 - username: admin - password: secret123 -``` -{% /step %} - -{% step title="Verify the installation" %} -Verify the installation by following these steps: - -1. Ask Claude if it seems installed. -2. Ask ChatGPT if it seems installed, just in case. -3. Ask the person nearest to you if they feel as though the software was in fact installed. -{% /step %} - -{% stepper-finished %} -Great job, you've finished all the steps. You might like to browse this super helpful doc next. -{% /stepper-finished %} - -{% /stepper %} - -Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - -## With "open" attribute - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - -{% stepper open="true" %} - -{% step title="Verify the prerequisites" %} -Make sure you meet these prerequisites: - -- You have a computer. -- You have permission to install things on the computer. -- You are competent. -- You are in the mood to do this today. - -{% collapse-content title="Bonus prereqs" level="h4" %} -- Somebody is paying you to do this. -{% /collapse-content %} -{% /step %} - -{% step title="Install the software" %} -{% tabs %} - -{% tab label="Python" %} -Run this script to install with Python: - -```python -import time -import sys - -def fake_install(): - steps = [ - "Checking system requirements", - "Downloading packages", - "Installing dependencies", - "Configuring environment", - "Finalizing setup" - ] - - for step in steps: - print(f"{step}...", end="") - sys.stdout.flush() - time.sleep(1) - print(" Done.") - - print("\nInstallation completed successfully!") - -if __name__ == "__main__": - fake_install() -``` -{% /tab %} - -{% tab label="JavaScript" %} -Run this script to install with JavaScript: - -```javascript -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function fakeInstall() { - const steps = [ - "Checking system requirements", - "Downloading packages", - "Installing dependencies", - "Configuring environment", - "Finalizing setup" - ]; - - for (const step of steps) { - process.stdout.write(step + "... "); - await sleep(1000); - console.log("Done."); - } - - console.log("\nInstallation completed successfully!"); -} - -fakeInstall(); -``` -{% /tab %} - -{% /tabs %} -{% /step %} - -{% step title="Update the configuration file" %} -{% alert level="warning" %} -This is made-up YAML. Proceed with caution. -{% /alert %} -Use this YAML: - -```yaml -server: - host: 0.0.0.0 - port: 8080 - -database: - type: postgres - host: localhost - port: 5432 - username: admin - password: secret123 -``` -{% /step %} - -{% step title="Verify the installation" %} -Verify the installation by following these steps: - -1. Ask Claude if it seems installed. -2. Ask ChatGPT if it seems installed, just in case. -3. Ask the person nearest to you if they feel as though the software was in fact installed. -{% /step %} - -{% stepper-finished %} -Great job, you've finished all the steps. You might like to browse this super helpful doc next. -{% /stepper-finished %} - -{% /stepper %} - -Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. From ffe5f61c32922595e4edddd3d6e1721069f4c565 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 13:42:14 -0500 Subject: [PATCH 36/39] Revert changes in package.json --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 6363ce47c40..40e1eaf05e4 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@popperjs/core": "^2.11.8", "alpinejs": "^3.13.7", "bootstrap": "^5.2", - "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz", + "cdocs-hugo-integration": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.10.1.tgz", "del": "4.1.1", "fancy-log": "^1.3.3", "geo-locate": "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/geo-locate-v1.0.2.tgz", diff --git a/yarn.lock b/yarn.lock index f2e2f3b9022..d868ec6e5d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6636,15 +6636,15 @@ __metadata: languageName: node linkType: hard -"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz": - version: 2.14.0-rc.6 - resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz" +"cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.10.1.tgz": + version: 2.10.1 + resolution: "cdocs-hugo-integration@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.10.1.tgz" dependencies: "@prettier/sync": "npm:^0.5.2" "@types/markdown-it": "npm:^14.1.2" "@vitejs/plugin-react": "npm:^4.3.3" cdocs-data: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-data-v1.3.2.tgz" - cdocs-markdoc: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz" + cdocs-markdoc: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.0.tgz" chokidar: "npm:^4.0.3" chroma-highlight: "npm:^2.4.2" interweave: "npm:^13.1.0" @@ -6663,13 +6663,13 @@ __metadata: vite: "npm:^5.4.10" vite-plugin-singlefile: "npm:^2.0.2" zod: "npm:^4.1.12" - checksum: 10/5e07f314164eefec4c43389591d278f1c27375f294d4d02932ef7f8218d7b90dca6805d27b88b6f2cccaa0bb13f4e181c4912593f5b774b0766cee299720ce3c + checksum: 10/922207931bf31f80c75f8c95c6850c5b78178ea0dcbbdeb54a483b028dcb5a7083d31b1056bf91214b653c8a3b34feba28676729e4db9951ee8f03d296c54901 languageName: node linkType: hard -"cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz": - version: 1.2.1 - resolution: "cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.1.tgz" +"cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.0.tgz": + version: 1.2.0 + resolution: "cdocs-markdoc@https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-markdoc-v1.2.0.tgz" dependencies: "@types/linkify-it": "npm:^3.0.1" "@types/markdown-it": "npm:12.2.3" @@ -6678,7 +6678,7 @@ __metadata: optional: true "@types/markdown-it": optional: true - checksum: 10/cf7549edda85a7dafe4b16c88887c6c1af9c0cd2e188a7fbba69e5f3dca38fd5a4743419ee377798e5c2850d10f0ddc15c5ad54975a6ab0dfa69dab43a23d114 + checksum: 10/587a2341310d78044aa0838729ef069284e77496d905c785af3225d16f35051e6bb1b1dd2e5556097827edc42edc0063960ef6b20f8c0fb6c0fcbcacc0a3d1a9 languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: acorn: "npm:^7.4.1" alpinejs: "npm:^3.13.7" bootstrap: "npm:^5.2" - cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/jen.gilbert/cdocs-stepper/cdocs-hugo-integration-v2.14.0-rc.6.tgz" + cdocs-hugo-integration: "https://s3.amazonaws.com/origin-static-assets/corp-node-packages/master/cdocs-hugo-integration-v2.10.1.tgz" cross-env: "npm:^5.2.1" del: "npm:4.1.1" eslint: "npm:^6.8.0" From 68554c26d6c12f19fc597a59b3f01476e32cf585 Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 14:01:03 -0500 Subject: [PATCH 37/39] Implement Codex feedback --- assets/scripts/components/stepper.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 85a59e2de41..3ce977e67fe 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -2,7 +2,7 @@ const STORAGE_PREFIX = 'stepper-progress-'; const MAX_STORED_STEPPERS = 10; function getStorageKey(stepperId) { - return `${STORAGE_PREFIX}${stepperId}`; + return `${STORAGE_PREFIX}${location.pathname}:${stepperId}`; } function loadProgress(stepperId) { @@ -16,8 +16,11 @@ function loadProgress(stepperId) { function saveProgress(stepperId, state) { try { - pruneOldEntries(); - localStorage.setItem(getStorageKey(stepperId), JSON.stringify({ ...state, timestamp: Date.now() })); + const key = getStorageKey(stepperId); + if (!localStorage.getItem(key)) { + pruneOldEntries(); + } + localStorage.setItem(key, JSON.stringify({ ...state, timestamp: Date.now() })); } catch { // Ignore storage errors, the stepper will still work without localStorage persistence } From 0f1ddb6a473bbf4c5279cd274e1c6be675e28c7a Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 14:21:25 -0500 Subject: [PATCH 38/39] Fix bug --- assets/scripts/components/stepper.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assets/scripts/components/stepper.js b/assets/scripts/components/stepper.js index 3ce977e67fe..eff4a7f8dd4 100644 --- a/assets/scripts/components/stepper.js +++ b/assets/scripts/components/stepper.js @@ -117,6 +117,10 @@ function initStepper(stepper) { step.classList.toggle('stepper__step--active', isActive); step.classList.toggle('stepper__step--completed', isCompleted); + // The step title, number, etc. should always be visible, + // even if the step body itself is not visible + setHidden(step, false); + // Nav: hidden when expanded or finished, visible only for active step const nav = step.querySelector('.stepper__nav'); if (nav) { From 95c7e162bbd6753c55cac916bc272501ca8d310b Mon Sep 17 00:00:00 2001 From: Jen Gilbert Date: Mon, 16 Mar 2026 16:05:52 -0500 Subject: [PATCH 39/39] Update assets/styles/components/_collapsible-section.scss Co-authored-by: StefonSimmons <57869435+StefonSimmons@users.noreply.github.com> --- assets/styles/components/_collapsible-section.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/components/_collapsible-section.scss b/assets/styles/components/_collapsible-section.scss index 2f90f8637b2..2d80e69ba97 100644 --- a/assets/styles/components/_collapsible-section.scss +++ b/assets/styles/components/_collapsible-section.scss @@ -26,7 +26,7 @@ } .collapsible-header:focus-visible { - outline: 2px solid #632DA6; + outline: 2px solid $ddpurple; outline-offset: -2px; }