From 4898fe39cff4df21b740e7887f5d2507178788f6 Mon Sep 17 00:00:00 2001 From: petheanraj-mitrah Date: Wed, 12 Aug 2026 16:54:23 +0530 Subject: [PATCH] feat : expandable sidebar menu --- .../expandable-sidebar-menu.css | 192 ++++++++++++++++ .../expandable-sidebar-menu.js | 208 ++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.css create mode 100644 hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.js diff --git a/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.css b/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.css new file mode 100644 index 00000000..8d95e1ac --- /dev/null +++ b/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.css @@ -0,0 +1,192 @@ +main div.expandable-sidebar-menu-wrapper { + display: flex; + justify-content: center; + width: 100%; +} + +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.block { + margin: auto; + max-width: 1280px; + padding: 20px; + width: 100%; +} + +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-gray, +main div.expandable-sidebar-menu.background-color-gray > .expandable-sidebar-wrapper { + background-color: rgb(245, 245, 245); +} + +main div.expandable-sidebar-menu-wrapper:has(.expandable-sidebar-menu.background-color-white), +main div.expandable-sidebar-menu-wrapper.background-color-white, +main div.expandable-sidebar-menu.background-color-white, +main div.expandable-sidebar-menu.background-color-white > .expandable-sidebar-wrapper { + background-color: white; +} + +main div.expandable-sidebar-menu-wrapper:has(.expandable-sidebar-menu.background-color-navy), +main div.expandable-sidebar-menu-wrapper.background-color-navy, +main div.expandable-sidebar-menu.background-color-navy, +main div.expandable-sidebar-menu.background-color-navy > .expandable-sidebar-wrapper { + background-color: rgb(15, 55, 95); +} + +main div.expandable-sidebar-menu-wrapper:has(.expandable-sidebar-menu.background-color-dark-gray), +main div.expandable-sidebar-menu-wrapper.background-color-dark-gray, +main div.expandable-sidebar-menu.background-color-dark-gray, +main div.expandable-sidebar-menu.background-color-dark-gray > .expandable-sidebar-wrapper { + background-color: #323232; +} + +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-dark-gray .sidebar-accordion-header, +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-dark-gray p, +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-navy .sidebar-accordion-header, +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-navy p { + color: white; +} + +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-navy .sidebar-accordion-list li:hover, +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-dark-gray .sidebar-accordion-list li:hover { + background: rgba(255, 255, 255, 0.1); +} + +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-navy .sidebar-accordion-list li.active, +main div.expandable-sidebar-menu-wrapper div.expandable-sidebar-menu.background-color-dark-gray .sidebar-accordion-list li.active { + background: rgba(255, 255, 255, 0.2); + color: white; +} + +main div.expandable-sidebar-menu-wrapper .expandable-sidebar-wrapper { + width: 100%; + max-width: 1280px; + margin: auto; + display: flex; + gap: 48px; +} + +main div.expandable-sidebar-menu-wrapper .expandable-sidebar { + width: calc(var(--spectrum-global-dimension-size-1000) + var(--spectrum-global-dimension-size-3000)); + flex-shrink: 0; +} + +main div.expandable-sidebar-menu-wrapper .expandable-content { + flex: 1; + min-width: 0; +} + +/* Accordion */ + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion { + margin-bottom: 16px; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-header { + width: 100%; + display: flex; + align-items: center; + gap: 12px; + border: none; + background: transparent; + font-size: 18px; + font-weight: 700; + cursor: pointer; + text-align: left; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-list { + list-style: none; + margin: 0; + padding: 0 0 0 32px; + display: none; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion.has-header-image .sidebar-accordion-list { + padding: 0 0 0 80px; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion.open .sidebar-accordion-list { + display: block; +} + +/* Child items */ + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-list li { + padding: 8px 10px; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-list li:hover { + background: #f5f5f5; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-list li.active { + background: #ececec; + font-weight: 500; +} + +/* Content Panels */ + +main div.expandable-sidebar-menu-wrapper .expandable-panel { + display: none; +} + +main div.expandable-sidebar-menu-wrapper .expandable-panel.active { + display: block; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-title { + display: flex; + align-items: center; + gap: 12px; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-title picture, +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-title img { + width: 40px; + height: 40px; + display: block; + flex-shrink: 0; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-title span { + font-weight: 700; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-icon { + display: flex; + align-items: center; + transition: transform 0.2s ease; +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion.open .sidebar-accordion-icon { + transform: rotate(90deg); +} + +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-list-item , +main div.expandable-sidebar-menu-wrapper .sidebar-accordion-title span { + font-size : 16px; +} + +main div.expandable-sidebar-menu-wrapper h1, +main div.expandable-sidebar-menu-wrapper h2, +main div.expandable-sidebar-menu-wrapper p, +main div.expandable-sidebar-menu-wrapper ul, +main div.expandable-sidebar-menu-wrapper ol { + margin-bottom: 18px !important; +} + +main div.expandable-sidebar-menu-wrapper ul, +main div.expandable-sidebar-menu-wrapper ol { + margin-top: 18px !important; +} + +@media screen and (max-width: 768px) { + main div.expandable-sidebar-menu-wrapper .expandable-sidebar { + width: 100%; + } + + main div.expandable-sidebar-menu-wrapper .expandable-sidebar-wrapper { + flex-direction: column; + } +} \ No newline at end of file diff --git a/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.js b/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.js new file mode 100644 index 00000000..4167b3c9 --- /dev/null +++ b/hlx_statics/blocks/expandable-sidebar-menu/expandable-sidebar-menu.js @@ -0,0 +1,208 @@ +import { decorateLightOrDark } from '../../scripts/lib-helix.js'; + +const ALLOWED_BACKGROUND_COLORS = [ + 'background-color-white', + 'background-color-navy', + 'background-color-dark-gray', + 'background-color-gray', +]; + +function applyBackgroundColor(block) { + const backgroundColor = block.getAttribute('data-backgroundcolor'); + + if (ALLOWED_BACKGROUND_COLORS.includes(backgroundColor)) { + block.className = block.className.split(/\s+/).filter((c) => !c.startsWith('background-color-')).join(' ').trim(); + block.classList.add(backgroundColor); + } + if (!ALLOWED_BACKGROUND_COLORS.some((color) => block.classList.contains(color))) { + block.classList.add('background-color-gray'); + } + + const bgClass = [...block.classList].find((c) => c.startsWith('background-color-')); + const blockWrapper = block.parentElement; + if (bgClass && blockWrapper?.classList.contains('expandable-sidebar-menu-wrapper')) { + blockWrapper.classList.add(bgClass); + } +} + +export default async function decorate(block) { + applyBackgroundColor(block); + decorateLightOrDark(block); + + const rows = [...block.children]; + const wrapper = document.createElement('div'); + wrapper.className = 'expandable-sidebar-wrapper'; + + const sidebar = document.createElement('div'); + sidebar.className = 'expandable-sidebar'; + + const contentArea = document.createElement('div'); + contentArea.className = 'expandable-content'; + + let currentAccordion; + let currentList; + let firstContent = true; + + rows.forEach((row) => { + const cols = [...row.children]; + + const titleEl = cols[0]; + const hasParent = titleEl.querySelector('strong'); + + if (hasParent) { + currentAccordion = document.createElement('div'); + currentAccordion.className = 'sidebar-accordion'; + + const header = document.createElement('button'); + header.className = 'sidebar-accordion-header'; + + const icon = document.createElement('span'); + icon.className = 'sidebar-accordion-icon'; + icon.innerHTML = ` + + + + `; + + const title = document.createElement('span'); + title.className = 'sidebar-accordion-title'; + + const picture = titleEl.querySelector('picture'); + if (picture) { + title.append(picture.cloneNode(true)); + currentAccordion.classList.add('has-header-image'); + } + + const text = document.createElement('span'); + text.classList.add('spectrum-Heading', 'spectrum-Heading--sizeM'); + text.textContent = titleEl.textContent.trim(); + title.append(text); + + header.append(icon, title); + + currentList = document.createElement('ul'); + currentList.className = 'sidebar-accordion-list'; + + currentAccordion.append(header, currentList); + sidebar.append(currentAccordion); + + const accordion = currentAccordion; + + header.addEventListener('click', () => { + const isOpen = accordion.classList.contains('open'); + + // close all accordions + sidebar.querySelectorAll('.sidebar-accordion').forEach((item) => { + item.classList.remove('open'); + }); + + if (!isOpen) { + accordion.classList.add('open'); + + // Automatically select first child + const firstItem = accordion.querySelector( + '.sidebar-accordion-list li' + ); + + if (firstItem) { + firstItem.click(); + } + } + }); + } + + if (!currentList) { + return; + } + + const childTitle = hasParent ? cols[1] : cols[0]; + const childContent = hasParent ? cols[2] : cols[1]; + + if (!childTitle || !childContent) { + return; + } + + const item = document.createElement('li'); + item.className = 'sidebar-accordion-list-item'; + item.textContent = childTitle.textContent.trim(); + currentList.append(item); + + const panel = document.createElement('div'); + panel.className = 'expandable-panel'; + panel.append(childContent); + + contentArea.append(panel); + + item.addEventListener('click', () => { + sidebar.querySelectorAll('.sidebar-accordion-list-item').forEach((li) => { + li.classList.remove('active'); + }); + + contentArea + .querySelectorAll('.expandable-panel') + .forEach((el) => { + el.classList.remove('active'); + }); + + item.classList.add('active'); + panel.classList.add('active'); + + const parentAccordion = item.closest('.sidebar-accordion'); + if (parentAccordion) { + sidebar.querySelectorAll('.sidebar-accordion').forEach((acc) => { + acc.classList.remove('open'); + }); + + parentAccordion.classList.add('open'); + } + }); + + // Initial state + if (firstContent) { + item.classList.add('active'); + panel.classList.add('active'); + currentAccordion.classList.add('open'); + firstContent = false; + } + }); + + wrapper.append(sidebar, contentArea); + + block.innerHTML = ''; + block.append(wrapper); + block.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach((h) => { + h.style.whiteSpace = "normal"; + }); + block.querySelectorAll('h1').forEach((h) => { + h.classList.add('spectrum-Heading', 'spectrum-Heading--sizeL', 'expandable-sidebar-menu-heading'); + }); + block.querySelectorAll('h2').forEach((h) => { + h.classList.add('spectrum-Heading', 'spectrum-Heading--sizeM', 'expandable-sidebar-menu-heading'); + }); + block.querySelectorAll('h3, h4, h5, h6').forEach((h) => { + h.classList.add('spectrum-Heading', 'spectrum-Heading--sizeS', 'expandable-sidebar-menu-heading'); + }); + block.querySelectorAll('p').forEach((p) => { + p.classList.add('spectrum-Body', 'spectrum-Body--sizeM', 'expandable-sidebar-menu-body'); + }); + block.querySelectorAll('ul, ol').forEach((unorder) => { + unorder.classList.add('spectrum-Body', 'spectrum-Body--sizeS'); + }); + block.querySelectorAll('li').forEach((li) => { + li.classList.add('spectrum-Body', 'spectrum-Body--sizeM'); + }); + + applyBackgroundColor(block); +}