Implemented non-link sub menus
authorAlexander Ebert <ebert@woltlab.com>
Wed, 29 Dec 2021 16:42:47 +0000 (17:42 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 29 Dec 2021 16:42:47 +0000 (17:42 +0100)
ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js
wcfsetup/install/files/style/ui/pageMenu.scss

index 282274bc13c8379a77952953e91a69a855aaf381..33cf97dbbb067b4eb1264d308c45ad49ddd61eec 100644 (file)
@@ -12,15 +12,18 @@ import { PageMenuProvider } from "./Provider";
 import * as Language from "../../../Language";
 import DomUtil from "../../../Dom/Util";
 
+type MenuItemDepth = 0 | 1 | 2;
+
 type MenuItem = {
   active: boolean;
   children: MenuItem[];
   counter: number;
+  depth: MenuItemDepth;
   link?: string;
   title: string;
 };
 
-function normalizeMenuItem(menuItem: HTMLElement): MenuItem {
+function normalizeMenuItem(menuItem: HTMLElement, depth: MenuItemDepth): MenuItem {
   const anchor = menuItem.querySelector(".boxMenuLink") as HTMLAnchorElement;
   const title = anchor.querySelector(".boxMenuLinkTitle")!.textContent as string;
 
@@ -33,8 +36,13 @@ function normalizeMenuItem(menuItem: HTMLElement): MenuItem {
   const subMenu = menuItem.querySelector("ol");
   let children: MenuItem[] = [];
   if (subMenu instanceof HTMLOListElement) {
+    let childDepth = depth;
+    if (childDepth < 2) {
+      childDepth = (depth + 1) as MenuItemDepth;
+    }
+
     children = Array.from(subMenu.children).map((subMenuItem: HTMLElement) => {
-      return normalizeMenuItem(subMenuItem);
+      return normalizeMenuItem(subMenuItem, childDepth);
     });
   }
 
@@ -51,6 +59,7 @@ function normalizeMenuItem(menuItem: HTMLElement): MenuItem {
     active,
     children,
     counter,
+    depth,
     link,
     title,
   };
@@ -158,7 +167,7 @@ export class PageMenuMain implements PageMenuProvider {
 
   private buildMenu(boxMenu: HTMLElement): HTMLElement {
     const menuItems: MenuItem[] = Array.from(boxMenu.children).map((element: HTMLElement) => {
-      return normalizeMenuItem(element);
+      return normalizeMenuItem(element, 0);
     });
 
     const nav = document.createElement("nav");
@@ -190,6 +199,7 @@ export class PageMenuMain implements PageMenuProvider {
 
   private buildMenuItem(menuItem: MenuItem): HTMLLIElement {
     const listItem = document.createElement("li");
+    listItem.dataset.depth = menuItem.depth.toString();
     listItem.classList.add("pageMenuMainItem");
 
     if (menuItem.link) {
@@ -203,8 +213,19 @@ export class PageMenuMain implements PageMenuProvider {
 
       listItem.append(link);
     } else {
-      const label = document.createElement("span");
+      const label = document.createElement("a");
+      label.classList.add("pageMenuMainItemLabel");
+      label.href = "#";
       label.textContent = menuItem.title;
+      label.addEventListener("click", (event) => {
+        event.preventDefault();
+
+        const button = label.nextElementSibling as HTMLAnchorElement;
+        button.click();
+      });
+
+      // The button to expand the link group is used instead.
+      label.setAttribute("aria-hidden", "true");
 
       listItem.append(label);
     }
@@ -230,9 +251,14 @@ export class PageMenuMain implements PageMenuProvider {
       button.setAttribute("role", "button");
       button.setAttribute("aria-expanded", "false");
       button.setAttribute("aria-controls", menuId);
-      button.setAttribute("aria-label", Language.get("TODO"));
       button.innerHTML = '<span class="icon icon24 fa-angle-down" aria-hidden="true"></span>';
 
+      let ariaLabel = menuItem.title;
+      if (menuItem.link) {
+        ariaLabel = Language.get("TODO");
+      }
+      button.setAttribute("aria-label", ariaLabel);
+
       const list = this.buildMenuItemList(menuItem.children);
       list.id = menuId;
       list.hidden = true;
index 1c36140212cbbe5de56cb07f223a4928f6dc68ca..e4f2cc18acc09a7cac7523f135416d8f34f0db8f 100644 (file)
@@ -13,7 +13,7 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
     Container_1 = (0, tslib_1.__importDefault)(Container_1);
     Language = (0, tslib_1.__importStar)(Language);
     Util_1 = (0, tslib_1.__importDefault)(Util_1);
-    function normalizeMenuItem(menuItem) {
+    function normalizeMenuItem(menuItem, depth) {
         const anchor = menuItem.querySelector(".boxMenuLink");
         const title = anchor.querySelector(".boxMenuLinkTitle").textContent;
         let counter = 0;
@@ -24,8 +24,12 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
         const subMenu = menuItem.querySelector("ol");
         let children = [];
         if (subMenu instanceof HTMLOListElement) {
+            let childDepth = depth;
+            if (childDepth < 2) {
+                childDepth = (depth + 1);
+            }
             children = Array.from(subMenu.children).map((subMenuItem) => {
-                return normalizeMenuItem(subMenuItem);
+                return normalizeMenuItem(subMenuItem, childDepth);
             });
         }
         // `link.href` represents the computed link, not the raw value.
@@ -39,6 +43,7 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             active,
             children,
             counter,
+            depth,
             link,
             title,
         };
@@ -115,7 +120,7 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
         }
         buildMenu(boxMenu) {
             const menuItems = Array.from(boxMenu.children).map((element) => {
-                return normalizeMenuItem(element);
+                return normalizeMenuItem(element, 0);
             });
             const nav = document.createElement("nav");
             nav.classList.add("pageMenuMainNavigation");
@@ -140,6 +145,7 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
         }
         buildMenuItem(menuItem) {
             const listItem = document.createElement("li");
+            listItem.dataset.depth = menuItem.depth.toString();
             listItem.classList.add("pageMenuMainItem");
             if (menuItem.link) {
                 const link = document.createElement("a");
@@ -152,8 +158,17 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                 listItem.append(link);
             }
             else {
-                const label = document.createElement("span");
+                const label = document.createElement("a");
+                label.classList.add("pageMenuMainItemLabel");
+                label.href = "#";
                 label.textContent = menuItem.title;
+                label.addEventListener("click", (event) => {
+                    event.preventDefault();
+                    const button = label.nextElementSibling;
+                    button.click();
+                });
+                // The button to expand the link group is used instead.
+                label.setAttribute("aria-hidden", "true");
                 listItem.append(label);
             }
             if (menuItem.counter > 0) {
@@ -173,8 +188,12 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                 button.setAttribute("role", "button");
                 button.setAttribute("aria-expanded", "false");
                 button.setAttribute("aria-controls", menuId);
-                button.setAttribute("aria-label", Language.get("TODO"));
                 button.innerHTML = '<span class="icon icon24 fa-angle-down" aria-hidden="true"></span>';
+                let ariaLabel = menuItem.title;
+                if (menuItem.link) {
+                    ariaLabel = Language.get("TODO");
+                }
+                button.setAttribute("aria-label", ariaLabel);
                 const list = this.buildMenuItemList(menuItem.children);
                 list.id = menuId;
                 list.hidden = true;
index ca902ae3ec5b79172fe147dc9ced2e1707fc1ed4..379f930e7e3d4a6be45a90f968b010f3efb694ce 100644 (file)
@@ -91,6 +91,7 @@
        }
 }
 
+.pageMenuMainItemLabel,
 .pageMenuMainItemLink {
        align-items: center;
        color: inherit;
        }
 }
 
+.pageMenuMainItemLabel + .pageMenuMainItemToggle::before {
+       display: none;
+}
+
 .pageMenuMainItemList {
        grid-area: list;
 }
 
-.pageMenuMainItem .pageMenuMainItemList {
+.pageMenuMainItem[data-depth="1"],
+.pageMenuMainItem[data-depth="2"] {
+       border-bottom-width: 0;
+
+       .pageMenuMainItemLabel,
+       .pageMenuMainItemLink {
+               font-weight: 400;
+               min-height: 34px;
+       }
+
+       .pageMenuMainItemToggle::before {
+               bottom: 5px;
+               top: 5px;
+       }
+}
+
+.pageMenuMainItem[data-depth="0"] .pageMenuMainItemList {
        padding: 10px 0 20px 0;
+}
 
-       .pageMenuMainItem {
-               border-bottom-width: 0;
+.pageMenuMainItem[data-depth="1"] {
+       .pageMenuMainItemList {
+               padding: 10px 0;
        }
 
+       .pageMenuMainItemLabel,
        .pageMenuMainItemLink {
-               font-weight: 400;
-               min-height: 34px;
                padding-left: 20px;
        }
 }
 
+.pageMenuMainItem[data-depth="2"] {
+       .pageMenuMainItemList {
+               padding: 0;
+       }
+
+       .pageMenuMainItemLabel,
+       .pageMenuMainItemLink {
+               padding-left: 30px;
+       }
+}
+
 .pageMenuUserTabContainer {
        display: flex;
        flex-direction: column;