diff --git a/index.html b/index.html index 064bc904..84648efc 100644 --- a/index.html +++ b/index.html @@ -62,9 +62,11 @@

diff --git a/public/css/SampleLayout.css b/public/css/SampleLayout.css index 72b1b8f8..998590d8 100644 --- a/public/css/SampleLayout.css +++ b/public/css/SampleLayout.css @@ -47,12 +47,18 @@ transform: translateY(0.25em); } +nav.sourceFileNav { + display: flex; + align-items: center; +} + nav.sourceFileNav ul { box-sizing: border-box; list-style-type: none; padding: 0; margin: 0; margin-top: 15px; + position: relative; } nav.sourceFileNav li { @@ -97,12 +103,59 @@ nav.sourceFileNav[data-right=true]::after { background: linear-gradient(270deg, rgba(0, 0, 0, 0.35), transparent); } +.sourceLR { + display: none; + cursor: pointer; + width: 5em; + padding: 10px; + margin-top: 15px; + text-align: center; + color: var(--source-tab-color); + background-color: var(--source-tab-background); + border-left: 1px solid rgba(0, 0, 0, 0.5); + border-right: 1px solid rgba(0, 0, 0, 0.5); +} +.sourceLR:hover { + text-decoration: underline; +} +.sourceLRShow .sourceLR { + display: block; +} + +nav.sourceFileNav div.sourceFileScrollContainer { + white-space: nowrap; + overflow-x: auto; + scrollbar-width: thin; +} + +nav.sourceFileNav div.sourceFileScrollContainer::-webkit-scrollbar { + display: inline; + margin-top: 10px; + margin-bottom: 10px; + height: 11px; + width: 10px; +} + +nav.sourceFileNav div.sourceFileScrollContainer::-webkit-scrollbar-thumb { + background: rgb(200, 200, 200); + height: 4px; + border-radius: 20px; + -webkit-box-shadow: inset 0px 0px 10px rgb(45, 33, 33); + border: 0.5px solid transparent; + background-clip: content-box; +} + +nav.sourceFileNav div.sourceFileScrollContainer::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); +} + nav.sourceFileNav li a { display: block; margin: 0; padding: 10px; color: var(--source-tab-color); background-color: var(--source-tab-background); + border-right: 1px solid rgba(0, 0, 0, 0.5); } nav.sourceFileNav li:hover { diff --git a/src/main.ts b/src/main.ts index 33e2ada0..f7af9b15 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,6 +31,10 @@ const sampleContainerElem = getElem('.sampleContainer', sampleElem); const titleElem = getElem('#title', sampleElem); const descriptionElem = getElem('#description', sampleElem); const menuToggleElem = getElem('#menuToggle') as HTMLInputElement; +const codeElem = getElem('#code'); +const sourceTabsElem = getElem('#sourceTabs'); +const sourceLElem = getElem('#sourceL'); +const sourceRElem = getElem('#sourceR'); // Get the parts of a string past the last `/` const basename = (name: string) => name.substring(name.lastIndexOf('/') + 1); @@ -66,6 +70,35 @@ function setURL(url: string) { // Handle when the URL changes (browser back / forward) window.addEventListener('popstate', parseURL); +/** + * Scrolls the current tab into view. + */ +function moveIntoView(parent: HTMLElement, element: HTMLElement) { + const parentLeft = parent.scrollLeft; + const parentRight = parentLeft + parent.clientWidth; + + const elemLeft = element.offsetLeft; + const elemRight = elemLeft + element.clientWidth; + + if (elemLeft < parentLeft) { + parent.scrollLeft -= parentLeft - elemLeft; + } else if (elemRight > parentRight) { + parent.scrollLeft += elemRight - parentRight; + } +} + +/** + * Switches to a tab relative to the current tab + */ +function switchToRelativeTab(direction: number) { + const tabs = [...sourceTabsElem.querySelectorAll('a')]; + const activeNdx = tabs.findIndex((tab) => tab.dataset.active === 'true'); + const newNdx = (activeNdx + tabs.length + direction) % tabs.length; + const tab = tabs[newNdx]; + moveIntoView(sourceTabsElem, tab.parentElement!); + tab.click(); +} + /** * Show/hide source tabs */ @@ -75,6 +108,7 @@ function setSourceTab(sourceInfo: SourceInfo) { const elem = e as HTMLElement; elem.dataset.active = (elem.dataset.name === name).toString(); }); + switchToRelativeTab(0); } /** @@ -241,6 +275,43 @@ for (const { title, description, samples } of pageCategories) { ); } +sourceLElem.addEventListener('click', () => switchToRelativeTab(-1)); +sourceRElem.addEventListener('click', () => switchToRelativeTab(1)); + +function checkIfSourceTabsFit() { + const parentWidth = sourceTabsElem.clientWidth; + const childWidth = [...sourceTabsElem.querySelectorAll('li')].reduce( + (sum, elem) => sum + elem.clientWidth, + 0 + ); + const showLR = childWidth > parentWidth; + codeElem.classList.toggle('sourceLRShow', showLR); +} + +const registerResizeCallback = (() => { + const elemToResizeCallback = new Map< + Element, + (entry: ResizeObserverEntry) => void + >(); + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const cb = elemToResizeCallback.get(entry.target); + if (cb) { + cb(entry); + } + } + }); + return function ( + elem: Element, + callback: (entry: ResizeObserverEntry) => void + ) { + elemToResizeCallback.set(elem, callback); + observer.observe(elem); + }; +})(); + +registerResizeCallback(sourceTabsElem, checkIfSourceTabsFit); + /** * Parse the page's current URL and then set the iframe appropriately. */