-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add assets to support the Cdocs stepper (not in use yet) #35312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6293678
c3820ad
d06049f
54dc466
165f00d
a1ffc1c
be39def
33e11e0
62dd2de
c0a7666
7f8519e
627d8c5
b1eb45f
8673f1d
c353840
844dedf
e834ed5
5879ab1
6bbed70
0742832
109f1ab
ab326d9
6ceee61
8a2ce38
3094b0e
9720b32
8debd09
8a7b385
482e70d
05cf62f
2617e44
c61a9ef
fedc140
bf346ae
8c7c58a
ffe5f61
d67ee73
68554c2
0f1ddb6
95c7e16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| const STORAGE_PREFIX = 'stepper-progress-'; | ||
| const MAX_STORED_STEPPERS = 10; | ||
|
|
||
| function getStorageKey(stepperId) { | ||
| return `${STORAGE_PREFIX}${location.pathname}:${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 { | ||
| 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 | ||
| } | ||
| } | ||
|
|
||
| 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, the stepper will still work without localStorage persistence | ||
| } | ||
| } | ||
|
|
||
| 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 resetEl = stepper.querySelector('.stepper__reset'); | ||
| const showAllBtn = stepper.querySelector('.stepper__show-all-btn'); | ||
| const collapseBtn = stepper.querySelector('.stepper__collapse-btn'); | ||
|
|
||
| if (!steps.length) return; | ||
|
|
||
| // 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 isAllExpanded = stepper.classList.contains('stepper--open'); | ||
|
|
||
| // Restore saved progress | ||
| const saved = loadProgress(stepperId); | ||
| if (saved) { | ||
| if (saved.finished) { | ||
| finished = true; | ||
| } else if (typeof saved.stepIndex === 'number' && saved.stepIndex >= 0 && saved.stepIndex < steps.length) { | ||
| currentIndex = saved.stepIndex; | ||
| } | ||
| if (typeof saved.isAllExpanded === 'boolean') { | ||
| isAllExpanded = saved.isAllExpanded; | ||
| } | ||
| } | ||
|
|
||
| function persist() { | ||
| saveProgress(stepperId, { | ||
| stepIndex: currentIndex, | ||
| finished, | ||
| 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; | ||
|
|
||
| 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) { | ||
| setHidden(nav, isAllExpanded || !isActive || finished); | ||
| } | ||
| }); | ||
|
|
||
| // Finished message | ||
| setHidden(finishedEl, !finished); | ||
|
|
||
| // Start over: visible only when finished | ||
| setHidden(resetEl, !finished); | ||
| } | ||
|
|
||
| function goToStep(index) { | ||
| finished = false; | ||
| currentIndex = Math.max(0, Math.min(index, steps.length - 1)); | ||
| persist(); | ||
| render(); | ||
| } | ||
|
|
||
| function handleFinish() { | ||
| finished = true; | ||
| persist(); | ||
| render(); | ||
| } | ||
|
|
||
| function handleReset() { | ||
| finished = false; | ||
| currentIndex = 0; | ||
| persist(); | ||
| render(); | ||
| } | ||
|
|
||
| // 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 (isAllExpanded) return; | ||
| goToStep(i); | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| 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__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(); | ||
| } | ||
| }); | ||
|
|
||
| // Initial render | ||
| render(); | ||
| stepper.classList.add('stepper--initialized'); | ||
| } | ||
|
|
||
| function initAllSteppers() { | ||
| const steppers = document.querySelectorAll('.stepper'); | ||
| steppers.forEach(initStepper); | ||
| } | ||
|
|
||
| if (document.readyState === 'loading') { | ||
| document.addEventListener('DOMContentLoaded', initAllSteppers); | ||
| } else { | ||
| initAllSteppers(); | ||
| } | ||
|
|
||
| export { initAllSteppers, initStepper }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
| } | ||
|
Comment on lines
+258
to
+265
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jhgilbert hello! these styles seem to be a duplicate of what's in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. by the order of imports here https://github.com/DataDog/documentation/blob/master/assets/styles/style.scss, the pages/global styles would overwrite the bootstrap-custom styles |
||
| .form-control:focus { | ||
| box-shadow: none; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.