diff --git a/css/base.css b/css/base.css index 9817cb34..ced7d968 100644 --- a/css/base.css +++ b/css/base.css @@ -31,18 +31,19 @@ } h1 { font-size: var(--font-size-6); + font-weight: normal; } h2 { - font-size: var(--font-size-5); + font-size: var(--font-size-4); } h3 { - font-size: var(--font-size-4); + font-size: var(--font-size-3); } h4 { - font-size: var(--font-size-3); + font-size: var(--font-size-2); } h5 { - font-size: var(--font-size-2); + font-size: var(--font-size-1); } h6 { font-size: var(--font-size-1); diff --git a/css/components/menu.css b/css/components/menu.css index 0f2556e4..b93622ee 100644 --- a/css/components/menu.css +++ b/css/components/menu.css @@ -1,38 +1,86 @@ @layer components { - -/** + /** * @file * Visual styles for menu. */ -ul.menu { - margin-left: 1em; /* LTR */ - padding: 0; - list-style: none outside; - text-align: left; /* LTR */ -} -[dir="rtl"] ul.menu { - margin-right: 1em; - margin-left: 0; - text-align: right; -} -.menu-item--expanded { - list-style-type: circle; - list-style-image: url(../../images/icons/menu-expanded.png); -} -.menu-item--collapsed { - list-style-type: disc; - list-style-image: url(../../images/icons/menu-collapsed.png); /* LTR */ -} -[dir="rtl"] .menu-item--collapsed { - list-style-image: url(../../images/icons/menu-collapsed-rtl.png); -} -.menu-item { - margin: 0; - padding-top: 0.2em; -} -ul.menu a.is-active { - color: #000; -} + ul.menu { + margin-left: 1em; /* LTR */ + padding: 0; + list-style: none outside; + text-align: left; /* LTR */ + } + [dir="rtl"] ul.menu { + margin-right: 1em; + margin-left: 0; + text-align: right; + } + .menu-item--expanded { + list-style-type: circle; + list-style-image: url(../../images/icons/menu-expanded.png); + } + .menu-item--collapsed { + list-style-type: disc; + list-style-image: url(../../images/icons/menu-collapsed.png); /* LTR */ + } + [dir="rtl"] .menu-item--collapsed { + list-style-image: url(../../images/icons/menu-collapsed-rtl.png); + } + .menu-item { + margin: 0; + padding-top: 0.2em; + } + ul.menu a.is-active { + color: #000; + } + /* dropdown behaviours */ + + /* add the region with a menu in it to convert to a dropdown */ + /* check the menu js behaviour when adding new dropdowns */ + header { + .menu { + display: flex; + } + .menu ul { + list-style: none; + margin: 0; + padding: 0; + } + + .menu > ul { + display: flex; + gap: 1rem; + } + + .menu li { + position: relative; + } + + /* Hide submenus */ + .menu li ul { + position: absolute; + top: 100%; + left: 0; + display: none; + min-width: 200px; + background: white; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + } + + /* Show submenu on hover */ + .menu li:hover > ul { + display: block; + } + + /* Optional styling */ + .menu a { + display: block; + padding: 0.75rem 1rem; + text-decoration: none; + } + .menu li ul li a { + padding: 0.5rem 1rem; + } + } } diff --git a/css/components/unsorted.css b/css/components/unsorted.css new file mode 100644 index 00000000..b0208bec --- /dev/null +++ b/css/components/unsorted.css @@ -0,0 +1,122 @@ +@layer components { + /* ======================================= */ + /* sidebar menu styles */ + /* ======================================= */ + + /* sliding yellow links, no dropdowns */ + .null.region-sidebar-second { + .menu { + margin: 0; + } + .menu a { + text-decoration: none; + display: block; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAARCAYAAADkIz3lAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAh5JREFUeNpi/PPnz//Xb98xMDMy7mFgYHBlgAImZiYGZMB0++59Bke/SIbKlm4XBkbGbUDMAMZogOnps+cv//3/y7Bq0zaGsoZ2z1+/fq4DYkyFP3/+MK0vyXkiLS7KsGL9Vob6zomBzMzMK4BybMgKmSMiIj+xs7JuNtbX8bl9/5Hg7oNHGd6+fa9jY26szMbKugvoiF8ghzAnpaQwsLKyvuPj5dmtpa7scvPWPRGQ4u8/f+o62FjIsbOz7wPa8JM5OjqG4f+/fyD8hpeba7++jqbdw8fPxDbv3sfw4eMnPUtTQ1mgwgPM0bFxyE55zcPNdVhJXsby0ZPnkrsPHmP4+vWbnrG+rjzDi+fPsOHSurae/yxiKv8ZBOX+r1y/+T8Lv6AgekgY3bn3MPHYmQsMf/78ZvB1d2bgZGWZy/Dp0ydkbHD2wuXbdj5h/xmEFf4Hxqb+37Zrz+xde/fzsfz+8wdmku7Dx083FtW2yB0+fprB3cGGISLAa/avnz8LfjEwfGOBRpbGo6fP9uRWNoidPHORwdfDmSEjIWIuEyNjNlDuNzRmfik8ePzsWHZZvdi5i1cZnG3MGWKDfBZ9/fQlBaYIBFjevv9wMbO0hu/ajdsMTjYWDPnpCauA4vEgyX///sJ9yPLp02e+p89eMFgY6zMkRwZv+vLpczhMkouHC66Q8efPn/9v3r7L8PPXr+1AvhdyOL199xbOBggwAPE+9T0FxGv+AAAAAElFTkSuQmCC); + background-position: 96% 10px; + background-repeat: no-repeat; + padding: 6px 20px 6px 0; + background-color: transparent; + color: #192a36; + /* display: inline-block; */ + /* padding: 0 3px; */ + margin-left: 0; + margin-right: -3px; + position: relative; + text-decoration: none; + transition: color ease 0.3s; + z-index: 0; + } + li.menu-item { + border-bottom: 2px solid var(--upei-orange); + font-size: 16px; + padding-top: 0; + a:before { + content: ""; + position: absolute; + z-index: -1; + width: 0; + height: 100%; + left: 0; + bottom: 0; + background-color: var(--upei-orange); + transition: all ease 0.3s; + } + a:hover:before { + width: 100%; + } + } + } + /* sidebar block menu with dropdowns (may need to adjust .block-menu and/or regions) */ + /* *see the associated behavior */ + .region-sidebar-second { + nav h2 { + margin-bottom: 0; + } + nav ul.menu li a { + text-decoration: none; + padding: 7px 0 7px 10px; + display: block; + border-bottom: 2px solid #8c2004; + color: #192a36; + } + .menu { + margin: 0; + /* margin-bottom: 1rem; */ + } + .menu-item { + padding: 0; + } + .menu-item--expanded .menu a { + margin-left: 1em; + border: none; + } + /* Hide nested menus by default */ + .block-menu .menu-item--expanded > ul { + display: none; + } + + /* Show when open */ + .block-menu .menu-item--expanded.open > ul { + display: block; + } + + /* Position container */ + .block-menu .menu-item--expanded { + position: relative; + list-style-type: none; + list-style-image: none; + } + + /* Caret button */ + .block-menu .menu-caret { + position: absolute; + right: 0.5rem; + top: 0.75rem; + cursor: pointer; + font-size: 0.8rem; + user-select: none; + } + + /* Optional caret icon (triangle) */ + .block-menu .menu-caret::after { + content: "▼"; + } + + /* Rotate when open */ + .block-menu .menu-item--expanded.open > .menu-caret::after { + transform: rotate(180deg); + display: inline-block; + } + + .block-menu .menu-item--expanded > ul { + display: block; + max-height: 0; + overflow: hidden; + transition: max-height 0.5s ease; + } + + .block-menu .menu-item--expanded.open > ul { + max-height: 500px; + } + } +} diff --git a/css/layout.css b/css/layout.css index 6b183d2b..64164439 100644 --- a/css/layout.css +++ b/css/layout.css @@ -18,7 +18,6 @@ * responsiveness — see todo.md for the rationale. */ @layer layout { - .content-rail { max-width: var(--content-max-width); margin-inline: auto; @@ -36,6 +35,7 @@ padding-block: var(--size-3); } + .region-header, .top-nav__inner { display: flex; justify-content: space-between; @@ -65,7 +65,9 @@ No-sidebar pages skip the grid entirely; .layout-content just fills its parent rail. */ - body:is(.layout-sidebar-first, .layout-sidebar-second, .layout-two-sidebars) main > .content-rail { + body:is(.layout-sidebar-first, .layout-sidebar-second, .layout-two-sidebars) + main + > .content-rail { display: grid; gap: var(--size-4); grid-template-columns: minmax(0, 1fr); @@ -81,9 +83,15 @@ grid-template-areas: "content" "first" "second"; } - .layout-content { grid-area: content; } - .layout-sidebar-first { grid-area: first; } - .layout-sidebar-second { grid-area: second; } + .layout-content { + grid-area: content; + } + .layout-sidebar-first { + grid-area: first; + } + .layout-sidebar-second { + grid-area: second; + } /* Tablet+: single-sidebar layouts side-by-side. */ @media (min-width: 768px) { @@ -100,9 +108,10 @@ /* Small-desktop+: two-sidebar layout becomes three columns. */ @media (min-width: 1024px) { body.layout-two-sidebars main > .content-rail { - grid-template-columns: var(--sidebar-width) minmax(0, 1fr) var(--sidebar-width); + grid-template-columns: var(--sidebar-width) minmax(0, 1fr) var( + --sidebar-width + ); grid-template-areas: "first content second"; } } - } diff --git a/druid.libraries.yml b/druid.libraries.yml index b43df62a..32de0faa 100644 --- a/druid.libraries.yml +++ b/druid.libraries.yml @@ -122,6 +122,8 @@ components: weight: -10 css/components/ui-dialog.css: weight: -10 + css/components/unsorted.css: + weight: -10 dialog: version: VERSION css: @@ -144,12 +146,12 @@ image-widget: version: VERSION css: component: - css/components/image-widget.css: { } + css/components/image-widget.css: {} indented: version: VERSION css: component: - css/components/indented.css: { } + css/components/indented.css: {} messages: version: VERSION css: @@ -184,7 +186,7 @@ search-results: version: VERSION css: component: - css/components/search-results.css: { } + css/components/search-results.css: {} user: version: VERSION css: diff --git a/druid.theme b/druid.theme index 72398f06..7f5d4647 100644 --- a/druid.theme +++ b/druid.theme @@ -15,37 +15,44 @@ * - layout-one-sidebar + layout-sidebar-second (only sidebar_second has content) * - layout-two-sidebars */ -function druid_preprocess_html(array &$variables): void { - $has_first = !empty($variables['page']['sidebar_first']); - $has_second = !empty($variables['page']['sidebar_second']); +function druid_preprocess_html(array &$variables): void +{ + $has_first = !empty($variables['page']['sidebar_first']); + $has_second = !empty($variables['page']['sidebar_second']); - if ($has_first && $has_second) { - $variables['attributes']['class'][] = 'layout-two-sidebars'; - } - elseif ($has_first) { - $variables['attributes']['class'][] = 'layout-one-sidebar'; - $variables['attributes']['class'][] = 'layout-sidebar-first'; - } - elseif ($has_second) { - $variables['attributes']['class'][] = 'layout-one-sidebar'; - $variables['attributes']['class'][] = 'layout-sidebar-second'; - } - else { - $variables['attributes']['class'][] = 'layout-no-sidebars'; - } + if ($has_first && $has_second) { + $variables['attributes']['class'][] = 'layout-two-sidebars'; + } elseif ($has_first) { + $variables['attributes']['class'][] = 'layout-one-sidebar'; + $variables['attributes']['class'][] = 'layout-sidebar-first'; + } elseif ($has_second) { + $variables['attributes']['class'][] = 'layout-one-sidebar'; + $variables['attributes']['class'][] = 'layout-sidebar-second'; + } else { + $variables['attributes']['class'][] = 'layout-no-sidebars'; + } } /** * Implements hook_preprocess_image_widget(). */ -function druid_preprocess_image_widget(array &$variables): void { - $data = &$variables['data']; +function druid_preprocess_image_widget(array &$variables): void +{ + $data = &$variables['data']; - // This prevents image widget templates from rendering preview container HTML - // to users that do not have permission to access these previews. - // @todo revisit in https://drupal.org/node/953034 - // @todo revisit in https://drupal.org/node/3114318 - if (isset($data['preview']['#access']) && $data['preview']['#access'] === FALSE) { - unset($data['preview']); - } + // This prevents image widget templates from rendering preview container HTML + // to users that do not have permission to access these previews. + // @todo revisit in https://drupal.org/node/953034 + // @todo revisit in https://drupal.org/node/3114318 + if (isset($data['preview']['#access']) && $data['preview']['#access'] === false) { + unset($data['preview']); + } +} +function druid_preprocess_menu(&$variables) +{ + foreach ($variables['items'] as &$item) { + if (!empty($item['below'])) { + $item['attributes']->addClass('has-dropdown'); + } + } } diff --git a/js/druid.behaviors.js b/js/druid.behaviors.js index 0cf041c2..07186827 100644 --- a/js/druid.behaviors.js +++ b/js/druid.behaviors.js @@ -68,4 +68,90 @@ // }); // }, // }; + Drupal.behaviors.mainMenuToggle = { + attach(context) { + once( + "dropdown-menu-toggle", + "nav .menu li.menu-item--expanded > a", + context + ).forEach((link) => { + let touched = false; + + const handler = function (e) { + // Prevent duplicate firing (touch + click) + if (e.type === "click" && touched) { + touched = false; + return; + } + + if (e.type === "touchstart") { + touched = true; + } + + const parent = this.parentElement; + const submenu = parent.querySelector("ul"); + + // Only toggle if submenu exists + if (submenu) { + e.preventDefault(); + parent.classList.toggle("open"); + } + }; + + // Mobile-first interaction + link.addEventListener("touchstart", handler, { passive: true }); + + // Desktop + accessibility fallback + link.addEventListener("click", handler); + }); + }, + }; +})(Drupal, once); +((Drupal, once) => { + Drupal.behaviors.sidebarMenuDropdown = { + attach(context) { + once( + "sidebar-menu-dropdown", + ".block-menu .menu-item--expanded", + context + ).forEach((item) => { + const link = item.querySelector("a"); + const submenu = item.querySelector("ul"); + + if (!submenu || !link) return; + + // ✅ Create caret element + const caret = document.createElement("span"); + caret.className = "menu-caret"; + caret.setAttribute("aria-expanded", "false"); + + // Insert caret after link + link.insertAdjacentElement("afterend", caret); + + let touched = false; + + const toggleMenu = (e) => { + // Prevent touch + click double fire + if (e.type === "click" && touched) { + touched = false; + return; + } + + if (e.type === "touchstart") { + touched = true; + } + + e.preventDefault(); + e.stopPropagation(); + + const isOpen = item.classList.toggle("open"); + caret.setAttribute("aria-expanded", isOpen); + }; + + // ✅ Only caret toggles menu (link still navigates) + caret.addEventListener("touchstart", toggleMenu, { passive: false }); + caret.addEventListener("click", toggleMenu); + }); + }, + }; })(Drupal, once);