From 48aaa1a832e0c1507e3c4e8ae6d9d771b96c2310 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 28 Dec 2021 17:35:24 +0100 Subject: [PATCH] Menu overflow behavior, indicators for overflowing content --- ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts | 26 ++++++++++++++++ .../js/WoltLabSuite/Core/Ui/Page/Menu/Main.js | 26 ++++++++++++++++ wcfsetup/install/files/style/ui/pageMenu.scss | 31 +++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts index e22ae59960..aa6433d12d 100644 --- a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts @@ -97,6 +97,7 @@ export class PageMenuMain implements PageMenuProvider { getContent(): DocumentFragment { const container = document.createElement("div"); container.classList.add("pageMenuMainContainer"); + container.addEventListener("scroll", () => this.updateOverflowIndicator(container), { passive: true }); container.append(...this.buildMainMenu()); @@ -105,6 +106,12 @@ export class PageMenuMain implements PageMenuProvider { container.append(footerMenu); } + // Detect changes to the height of the children, for example, when a submenu is being expanded. + const observer = new ResizeObserver(() => this.updateOverflowIndicator(container)); + Array.from(container.children).forEach((menu) => { + observer.observe(menu); + }); + const fragment = document.createDocumentFragment(); fragment.append(container); @@ -280,6 +287,25 @@ export class PageMenuMain implements PageMenuProvider { this.mainMenu.classList.remove("pageMenuMobileButtonHasContent"); } } + + private updateOverflowIndicator(container: HTMLElement): void { + const hasOverflow = container.clientHeight < container.scrollHeight; + if (hasOverflow) { + if (container.scrollTop > 0) { + container.classList.add("pageMenuMainContainerOverflowTop"); + } else { + container.classList.remove("pageMenuMainContainerOverflowTop"); + } + + if (container.clientHeight + container.scrollTop < container.scrollHeight) { + container.classList.add("pageMenuMainContainerOverflowBottom"); + } else { + container.classList.remove("pageMenuMainContainerOverflowBottom"); + } + } else { + container.classList.remove("pageMenuMainContainerOverflowTop", "pageMenuMainContainerOverflowBottom"); + } + } } export default PageMenuMain; diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js index e39c6dbb9a..04ffcba6b8 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js @@ -70,11 +70,17 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../. getContent() { const container = document.createElement("div"); container.classList.add("pageMenuMainContainer"); + container.addEventListener("scroll", () => this.updateOverflowIndicator(container), { passive: true }); container.append(...this.buildMainMenu()); const footerMenu = this.buildFooterMenu(); if (footerMenu) { container.append(footerMenu); } + // Detect changes to the height of the children, for example, when a submenu is being expanded. + const observer = new ResizeObserver(() => this.updateOverflowIndicator(container)); + Array.from(container.children).forEach((menu) => { + observer.observe(menu); + }); const fragment = document.createDocumentFragment(); fragment.append(container); return fragment; @@ -215,6 +221,26 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../. this.mainMenu.classList.remove("pageMenuMobileButtonHasContent"); } } + updateOverflowIndicator(container) { + const hasOverflow = container.clientHeight < container.scrollHeight; + if (hasOverflow) { + if (container.scrollTop > 0) { + container.classList.add("pageMenuMainContainerOverflowTop"); + } + else { + container.classList.remove("pageMenuMainContainerOverflowTop"); + } + if (container.clientHeight + container.scrollTop < container.scrollHeight) { + container.classList.add("pageMenuMainContainerOverflowBottom"); + } + else { + container.classList.remove("pageMenuMainContainerOverflowBottom"); + } + } + else { + container.classList.remove("pageMenuMainContainerOverflowTop", "pageMenuMainContainerOverflowBottom"); + } + } } exports.PageMenuMain = PageMenuMain; exports.default = PageMenuMain; diff --git a/wcfsetup/install/files/style/ui/pageMenu.scss b/wcfsetup/install/files/style/ui/pageMenu.scss index 280b6cd18b..77ab2f04ff 100644 --- a/wcfsetup/install/files/style/ui/pageMenu.scss +++ b/wcfsetup/install/files/style/ui/pageMenu.scss @@ -26,6 +26,31 @@ display: flex; flex-direction: column; height: 100%; + overflow: auto; + + &::after, + &::before { + --box-shadow-size: 20px; + --box-shadow-size-inverted: calc(-1 * var(--box-shadow-size)); + + bottom: 0; + content: ""; + left: 0; + pointer-events: none; + position: absolute; + transition: box-shadow 0.24s ease-out; + right: 0; + top: 0; + z-index: 1; + } + + &.pageMenuMainContainerOverflowTop::before { + box-shadow: 0 var(--box-shadow-size) var(--box-shadow-size) var(--box-shadow-size-inverted) #000 inset; + } + + &.pageMenuMainContainerOverflowBottom::after { + box-shadow: 0 var(--box-shadow-size-inverted) var(--box-shadow-size) var(--box-shadow-size-inverted) #000 inset; + } } .pageMenuMainNavigationFooter { @@ -195,6 +220,12 @@ } @include screen-sm-md { + .pageMenuMainContainer::after, + .pageMenuMainContainer::before { + right: auto; + width: 400px; + } + .pageMenuContainer[data-orientation="right"] .pageMenuContent { margin-left: auto; } -- 2.20.1