From b13ad68eeae5ac079b72dc572bd40a77f1d2d1a3 Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Wed, 20 Sep 2023 13:14:16 -0600 Subject: [PATCH 1/6] Fixed all the lint issues. --- patterns/sidebar-with-navigation.php | 15 +- .../block-patterns/sidebar-with-navigation.js | 130 ++++++++++++++ src/js/core/block-patterns.js | 12 ++ src/js/core/ready.js | 7 +- .../_sidebar-with-navigation.scss | 167 ++++++++++++++++++ src/scss/style.scss | 1 + 6 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 src/js/block-patterns/sidebar-with-navigation.js create mode 100644 src/js/core/block-patterns.js create mode 100644 src/scss/block-patterns/_sidebar-with-navigation.scss diff --git a/patterns/sidebar-with-navigation.php b/patterns/sidebar-with-navigation.php index a6ec2ec3..ac297f27 100644 --- a/patterns/sidebar-with-navigation.php +++ b/patterns/sidebar-with-navigation.php @@ -5,17 +5,18 @@ * Categories: navigation */ ?> - - -
-
-

Navigation header

+ +
+
+

Navigation header

-
+
-
+
+

Page title

+
diff --git a/src/js/block-patterns/sidebar-with-navigation.js b/src/js/block-patterns/sidebar-with-navigation.js new file mode 100644 index 00000000..6808666e --- /dev/null +++ b/src/js/block-patterns/sidebar-with-navigation.js @@ -0,0 +1,130 @@ +const blocks = document.querySelectorAll( '.ucsc__sidebar-with-navigation' ); + +// Cache object, populated upon initialization. +let c = {}; + +/** + * Show or hide the submenu associated with a given toggle button. + */ +const toggleSubmenu = ( toggle ) => { + const item = toggle.closest( c.itemSelector ); + const submenu = item.querySelector( c.submenuSelector ); + + // Collapse + if ( item.classList.contains( c.itemExpandedClass ) ) { + item.classList.remove( c.itemExpandedClass ); + submenu.setAttribute( 'aria-hidden', true ); + submenu.querySelectorAll( c.linkSelector ).forEach( ( subItemLink ) => { + subItemLink.setAttribute( 'tabIndex', -1 ); + } ); + } + // Expand + else { + item.classList.add( c.itemExpandedClass ); + submenu.setAttribute( 'aria-hidden', false ); + submenu.querySelectorAll( c.linkSelector ).forEach( ( subItemLink ) => { + subItemLink.removeAttribute( 'tabIndex' ); + } ); + } +}; + +/** + * Fix toggle button ARIA attributes, which the core block JS sets incorrectly + * since we've disabled accordion behavior. + */ +const fixAriaAttributes = () => { + blocks.forEach( ( block ) => { + block.querySelectorAll( c.itemSelector ).forEach( ( item ) => { + const isExpanded = item.classList.contains( c.itemExpandedClass ); + + const toggle = item.querySelector( c.toggleSelector ); + if ( toggle ) { + toggle.setAttribute( 'aria-expanded', isExpanded ); + } + + const submenu = item.querySelector( c.submenuSelector ); + if ( submenu ) { + toggle.setAttribute( 'aria-hidden', ! isExpanded ); + } + } ); + } ); +}; + +/** + * Bind event listeners. + */ +const bindEvents = () => { + blocks.forEach( ( block ) => { + block.querySelectorAll( c.toggleSelector ).forEach( ( toggle ) => { + toggle.addEventListener( 'click', () => { + toggleSubmenu( toggle ); + } ); + } ); + + block + .querySelectorAll( '.' + c.submenuWrapperClass ) + .forEach( ( wrapper ) => { + wrapper.addEventListener( 'transitionend', () => { + fixAriaAttributes(); + } ); + } ); + } ); +}; + +/** + * Set up custom block markup. + */ +const initBlockMarkup = () => { + blocks.forEach( ( block ) => { + block.querySelectorAll( c.submenuSelector ).forEach( ( submenu ) => { + // Initialize submenu ARIA attribute. + submenu.setAttribute( 'aria-hidden', true ); + submenu + .querySelectorAll( c.linkSelector ) + .forEach( ( subItemLink ) => { + subItemLink.setAttribute( 'tabIndex', -1 ); + } ); + + // Add a wrapper around submenus in order to do height transitions. + const item = submenu.closest( c.itemSelector ); + const wrapper = document.createElement( 'div' ); + wrapper.classList.add( c.submenuWrapperClass ); + item.append( wrapper ); + wrapper.append( submenu ); + } ); + + // Remove nested submenus and their toggles. + block + .querySelectorAll( c.submenuSelector + ' ' + c.submenuSelector ) + .forEach( ( nestedSubmenu ) => { + const item = nestedSubmenu.closest( c.itemSelector ); + const toggle = item.querySelector( c.toggleSelector ); + toggle.remove(); + nestedSubmenu.remove(); + } ); + } ); +}; + +/** + * Initialize custom block behaviors. + */ +const init = () => { + if ( ! blocks.length ) { + return; + } + + c = { + itemSelector: '.wp-block-navigation-item', + itemExpandedClass: 'ucsc__sidebar-with-navigation--expanded', + linkSelector: 'a.wp-block-navigation-item__content', + toggleSelector: '.wp-block-navigation-submenu__toggle', + submenuWrapperClass: 'wp-block-navigation__submenu-transition-wrapper', + submenuSelector: '.wp-block-navigation__submenu-container', + }; + + initBlockMarkup(); + bindEvents(); + fixAriaAttributes(); +}; + +export default init; diff --git a/src/js/core/block-patterns.js b/src/js/core/block-patterns.js new file mode 100644 index 00000000..cec404d7 --- /dev/null +++ b/src/js/core/block-patterns.js @@ -0,0 +1,12 @@ +/** + * @module block-patterns + * @description clearing house for all block pattern behaviors. + */ + +import sidebarWithNavigation from '../block-patterns/sidebar-with-navigation.js'; + +const init = () => { + sidebarWithNavigation(); +}; + +export default init; diff --git a/src/js/core/ready.js b/src/js/core/ready.js index 564cdda7..65f1da54 100644 --- a/src/js/core/ready.js +++ b/src/js/core/ready.js @@ -7,6 +7,7 @@ import debounce from 'lodash/debounce'; import { on, ready } from '../utils/events'; import resize from './resize'; import components from './components'; +import blockPatterns from './block-patterns'; import viewportDims from './viewport-dims'; const bindEvents = () => { @@ -15,14 +16,16 @@ const bindEvents = () => { const init = () => { // set initial states - viewportDims(); // initialize global events - bindEvents(); + // initialize components components(); + + // initialize block pattern behaviors + blockPatterns(); }; /** diff --git a/src/scss/block-patterns/_sidebar-with-navigation.scss b/src/scss/block-patterns/_sidebar-with-navigation.scss new file mode 100644 index 00000000..41087bd2 --- /dev/null +++ b/src/scss/block-patterns/_sidebar-with-navigation.scss @@ -0,0 +1,167 @@ +/* BLOCK PATTERN: Content with left navigation */ + +.ucsc__sidebar-with-navigation { + flex-direction: column-reverse; + + @include media-query($wp-columns-unstack) { + flex-direction: row; + } + + // Block + .wp-block-navigation { + margin-top: 0; + + .wp-block-navigation-item__content { + + &:hover, + &:focus-visible { + text-decoration: underline; + text-decoration-color: var(--wp--preset--color--ucsc-primary-yellow); + text-decoration-skip-ink: auto; + text-decoration-thickness: 0.12rem; + text-underline-offset: 0.12em; + } + + &[aria-current="page"] { + font-weight: 700; + } + } + + .wp-block-navigation__submenu-container .wp-block-navigation-item__content { + + &:focus-visible { + outline-offset: -1px; + } + } + + // Navigation menu + > .wp-block-navigation__container { + width: 100%; + gap: 0; + + // Top-level menu item + > .wp-block-navigation-item { + width: 100%; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto 0fr; + grid-template-areas: + "link toggle" + "submenu submenu"; + border-top: 1px solid var(--wp--preset--color--light-gray); + transition: grid-template-rows 0.25s ease; + + &:last-child { + border-bottom: 1px solid var(--wp--preset--color--light-gray); + } + + // Top-level menu link + > .wp-block-navigation-item__content { + grid-area: link; + padding: 12px 0; + display: block; + } + + // Submenu toggle button + > .wp-block-navigation-submenu__toggle { + grid-area: toggle; + display: flex; + height: 100%; + min-width: 32px; + align-items: flex-start; + justify-content: flex-end; + color: inherit; + position: relative; + + &::before { + content: ""; + display: none; + width: 28px; + height: calc(100% - 2px); + position: absolute; + border: 2px solid #000; + border-radius: 3px; + transform: translateX(5px); + } + + &::after { + content: ""; + display: block; + width: 10px; + height: 10px; + border-width: 1px 1px 0 0; + border-style: solid; + border-color: currentcolor; + transform: translate(-5px, 12px) rotate(-225deg); + transition: transform 0.25s ease; + } + + &:hover, + &:focus-visible { + outline: 0 none; + + &::after { + border-width: 2px 2px 0 0; + } + } + + &:focus-visible { + + &::before { + display: block; + } + } + + svg { + display: none; + } + } + + // Unwrapped submenu prior to JS initialization + > .wp-block-navigation__submenu-container { + display: none; + } + + // Submenu transition wrapper + > .wp-block-navigation__submenu-transition-wrapper { + grid-area: submenu; + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.25s ease; + + // Submenu + > .wp-block-navigation__submenu-container { + position: static; + width: 100%; + background-color: transparent; + border: 0 none; + color: inherit; + height: auto; + opacity: 1; + overflow: hidden; + visibility: visible; + + // Hide everything beyond the top two levels of the menu. + .wp-block-navigation-submenu__toggle, + .wp-block-navigation__submenu-container { + display: none; + } + } + } + + // Top-level menu item with submenu expanded + &.ucsc__sidebar-with-navigation--expanded { + + > .wp-block-navigation-submenu__toggle::after { + transform: translate(-5px, 18px) rotate(-45deg); + } + + > .wp-block-navigation__submenu-transition-wrapper { + grid-template-rows: 1fr; + padding-bottom: 12px; + } + } + } + } + } +} diff --git a/src/scss/style.scss b/src/scss/style.scss index fe06e6e8..23dcbc18 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -48,3 +48,4 @@ @import "block-patterns/three-columns-text"; @import "block-patterns/grid-with-background-image"; @import "block-patterns/map-contact"; +@import "block-patterns/sidebar-with-navigation"; From 1fb4ef1165a44e263df99869b1306e21931bfeca Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Wed, 20 Sep 2023 13:21:05 -0600 Subject: [PATCH 2/6] Normalized comment verbage. --- src/js/block-patterns/sidebar-with-navigation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/block-patterns/sidebar-with-navigation.js b/src/js/block-patterns/sidebar-with-navigation.js index 6808666e..bb570453 100644 --- a/src/js/block-patterns/sidebar-with-navigation.js +++ b/src/js/block-patterns/sidebar-with-navigation.js @@ -10,7 +10,7 @@ const toggleSubmenu = ( toggle ) => { const item = toggle.closest( c.itemSelector ); const submenu = item.querySelector( c.submenuSelector ); - // Collapse + // Hide if ( item.classList.contains( c.itemExpandedClass ) ) { item.classList.remove( c.itemExpandedClass ); submenu.setAttribute( 'aria-hidden', true ); @@ -18,7 +18,7 @@ const toggleSubmenu = ( toggle ) => { subItemLink.setAttribute( 'tabIndex', -1 ); } ); } - // Expand + // Show else { item.classList.add( c.itemExpandedClass ); submenu.setAttribute( 'aria-hidden', false ); From 58560903ff123ac063e566e193102cf5821432d6 Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Tue, 10 Oct 2023 10:41:25 -0600 Subject: [PATCH 3/6] Removed ref from block pattern. --- patterns/sidebar-with-navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patterns/sidebar-with-navigation.php b/patterns/sidebar-with-navigation.php index ac297f27..bfa8aa32 100644 --- a/patterns/sidebar-with-navigation.php +++ b/patterns/sidebar-with-navigation.php @@ -11,7 +11,7 @@

Navigation header

-
+ From 16c1f546349b1857dfde6a65718c6c29899c9f44 Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Tue, 10 Oct 2023 10:46:04 -0600 Subject: [PATCH 4/6] Adjusted variable names and initialization. --- .../block-patterns/sidebar-with-navigation.js | 117 ++++++++++-------- 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/src/js/block-patterns/sidebar-with-navigation.js b/src/js/block-patterns/sidebar-with-navigation.js index bb570453..4223176e 100644 --- a/src/js/block-patterns/sidebar-with-navigation.js +++ b/src/js/block-patterns/sidebar-with-navigation.js @@ -1,30 +1,40 @@ -const blocks = document.querySelectorAll( '.ucsc__sidebar-with-navigation' ); +const el = document.querySelectorAll( '.ucsc__sidebar-with-navigation' ); -// Cache object, populated upon initialization. -let c = {}; +const selectors = { + itemSelector: '.wp-block-navigation-item', + itemExpandedClass: 'ucsc__sidebar-with-navigation--expanded', + linkSelector: 'a.wp-block-navigation-item__content', + toggleSelector: '.wp-block-navigation-submenu__toggle', + submenuWrapperClass: 'wp-block-navigation__submenu-transition-wrapper', + submenuSelector: '.wp-block-navigation__submenu-container', +}; /** * Show or hide the submenu associated with a given toggle button. */ const toggleSubmenu = ( toggle ) => { - const item = toggle.closest( c.itemSelector ); - const submenu = item.querySelector( c.submenuSelector ); + const item = toggle.closest( selectors.itemSelector ); + const submenu = item.querySelector( selectors.submenuSelector ); // Hide - if ( item.classList.contains( c.itemExpandedClass ) ) { - item.classList.remove( c.itemExpandedClass ); + if ( item.classList.contains( selectors.itemExpandedClass ) ) { + item.classList.remove( selectors.itemExpandedClass ); submenu.setAttribute( 'aria-hidden', true ); - submenu.querySelectorAll( c.linkSelector ).forEach( ( subItemLink ) => { - subItemLink.setAttribute( 'tabIndex', -1 ); - } ); + submenu + .querySelectorAll( selectors.linkSelector ) + .forEach( ( subItemLink ) => { + subItemLink.setAttribute( 'tabIndex', -1 ); + } ); } // Show else { - item.classList.add( c.itemExpandedClass ); + item.classList.add( selectors.itemExpandedClass ); submenu.setAttribute( 'aria-hidden', false ); - submenu.querySelectorAll( c.linkSelector ).forEach( ( subItemLink ) => { - subItemLink.removeAttribute( 'tabIndex' ); - } ); + submenu + .querySelectorAll( selectors.linkSelector ) + .forEach( ( subItemLink ) => { + subItemLink.removeAttribute( 'tabIndex' ); + } ); } }; @@ -33,16 +43,18 @@ const toggleSubmenu = ( toggle ) => { * since we've disabled accordion behavior. */ const fixAriaAttributes = () => { - blocks.forEach( ( block ) => { - block.querySelectorAll( c.itemSelector ).forEach( ( item ) => { - const isExpanded = item.classList.contains( c.itemExpandedClass ); + el.forEach( ( block ) => { + block.querySelectorAll( selectors.itemSelector ).forEach( ( item ) => { + const isExpanded = item.classList.contains( + selectors.itemExpandedClass + ); - const toggle = item.querySelector( c.toggleSelector ); + const toggle = item.querySelector( selectors.toggleSelector ); if ( toggle ) { toggle.setAttribute( 'aria-expanded', isExpanded ); } - const submenu = item.querySelector( c.submenuSelector ); + const submenu = item.querySelector( selectors.submenuSelector ); if ( submenu ) { toggle.setAttribute( 'aria-hidden', ! isExpanded ); } @@ -54,15 +66,17 @@ const fixAriaAttributes = () => { * Bind event listeners. */ const bindEvents = () => { - blocks.forEach( ( block ) => { - block.querySelectorAll( c.toggleSelector ).forEach( ( toggle ) => { - toggle.addEventListener( 'click', () => { - toggleSubmenu( toggle ); + el.forEach( ( block ) => { + block + .querySelectorAll( selectors.toggleSelector ) + .forEach( ( toggle ) => { + toggle.addEventListener( 'click', () => { + toggleSubmenu( toggle ); + } ); } ); - } ); block - .querySelectorAll( '.' + c.submenuWrapperClass ) + .querySelectorAll( '.' + selectors.submenuWrapperClass ) .forEach( ( wrapper ) => { wrapper.addEventListener( 'transitionend', () => { fixAriaAttributes(); @@ -75,30 +89,34 @@ const bindEvents = () => { * Set up custom block markup. */ const initBlockMarkup = () => { - blocks.forEach( ( block ) => { - block.querySelectorAll( c.submenuSelector ).forEach( ( submenu ) => { - // Initialize submenu ARIA attribute. - submenu.setAttribute( 'aria-hidden', true ); - submenu - .querySelectorAll( c.linkSelector ) - .forEach( ( subItemLink ) => { - subItemLink.setAttribute( 'tabIndex', -1 ); - } ); + el.forEach( ( block ) => { + block + .querySelectorAll( selectors.submenuSelector ) + .forEach( ( submenu ) => { + // Initialize submenu ARIA attribute. + submenu.setAttribute( 'aria-hidden', true ); + submenu + .querySelectorAll( selectors.linkSelector ) + .forEach( ( subItemLink ) => { + subItemLink.setAttribute( 'tabIndex', -1 ); + } ); - // Add a wrapper around submenus in order to do height transitions. - const item = submenu.closest( c.itemSelector ); - const wrapper = document.createElement( 'div' ); - wrapper.classList.add( c.submenuWrapperClass ); - item.append( wrapper ); - wrapper.append( submenu ); - } ); + // Add a wrapper around submenus in order to do height transitions. + const item = submenu.closest( selectors.itemSelector ); + const wrapper = document.createElement( 'div' ); + wrapper.classList.add( selectors.submenuWrapperClass ); + item.append( wrapper ); + wrapper.append( submenu ); + } ); // Remove nested submenus and their toggles. block - .querySelectorAll( c.submenuSelector + ' ' + c.submenuSelector ) + .querySelectorAll( + selectors.submenuSelector + ' ' + selectors.submenuSelector + ) .forEach( ( nestedSubmenu ) => { - const item = nestedSubmenu.closest( c.itemSelector ); - const toggle = item.querySelector( c.toggleSelector ); + const item = nestedSubmenu.closest( selectors.itemSelector ); + const toggle = item.querySelector( selectors.toggleSelector ); toggle.remove(); nestedSubmenu.remove(); } ); @@ -109,19 +127,10 @@ const initBlockMarkup = () => { * Initialize custom block behaviors. */ const init = () => { - if ( ! blocks.length ) { + if ( ! el.length ) { return; } - c = { - itemSelector: '.wp-block-navigation-item', - itemExpandedClass: 'ucsc__sidebar-with-navigation--expanded', - linkSelector: 'a.wp-block-navigation-item__content', - toggleSelector: '.wp-block-navigation-submenu__toggle', - submenuWrapperClass: 'wp-block-navigation__submenu-transition-wrapper', - submenuSelector: '.wp-block-navigation__submenu-container', - }; - initBlockMarkup(); bindEvents(); fixAriaAttributes(); From 1600b85cd386606ed289e3ecc83bfa7208d3b0d4 Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Tue, 10 Oct 2023 10:48:05 -0600 Subject: [PATCH 5/6] Switched to ES6 interpolation. --- src/js/block-patterns/sidebar-with-navigation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/block-patterns/sidebar-with-navigation.js b/src/js/block-patterns/sidebar-with-navigation.js index 4223176e..b22e6a27 100644 --- a/src/js/block-patterns/sidebar-with-navigation.js +++ b/src/js/block-patterns/sidebar-with-navigation.js @@ -112,7 +112,7 @@ const initBlockMarkup = () => { // Remove nested submenus and their toggles. block .querySelectorAll( - selectors.submenuSelector + ' ' + selectors.submenuSelector + `${ selectors.submenuSelector } ${ selectors.submenuSelector }` ) .forEach( ( nestedSubmenu ) => { const item = nestedSubmenu.closest( selectors.itemSelector ); From 6ca16f653633beed49706ac3d3d2d572ffe40054 Mon Sep 17 00:00:00 2001 From: Brett Cooper Date: Tue, 10 Oct 2023 10:52:08 -0600 Subject: [PATCH 6/6] Added missing JSdoc param doc. --- src/js/block-patterns/sidebar-with-navigation.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/block-patterns/sidebar-with-navigation.js b/src/js/block-patterns/sidebar-with-navigation.js index b22e6a27..9445ad66 100644 --- a/src/js/block-patterns/sidebar-with-navigation.js +++ b/src/js/block-patterns/sidebar-with-navigation.js @@ -11,6 +11,8 @@ const selectors = { /** * Show or hide the submenu associated with a given toggle button. + * + * @param {HTMLElement} toggle The actioned submenu toggle button. */ const toggleSubmenu = ( toggle ) => { const item = toggle.closest( selectors.itemSelector );