diff --git a/blocks/tabs/tabs.css b/blocks/tabs/tabs.css new file mode 100644 index 0000000..6ec14e1 --- /dev/null +++ b/blocks/tabs/tabs.css @@ -0,0 +1,64 @@ +.tabs { + background-color: var(--spectrum-gray-100); + padding: 14px 21px 21px; + border-radius: 7px; + margin-bottom: 28px; +} + +.tabs::before { + content: ""; + display: block; + width: 100%; + height: 2px; + background: var(--spectrum-gray-300); + position: relative; + top: 56px; +} + +.tabs .tab-list { + overflow-x: auto; + white-space: nowrap; +} + +.tabs .tab-list button.tab-title { + background: none; + color: var(--spectrum-gray-700); + padding: 0; + margin: 0; + height: 56px; + line-height: 56px; + white-space: nowrap; + font-family: var(--body-font-family); + font-size: var(--exlm-font-size-content); + font-weight: 400; + border: 0; + border-radius: 0; + position: relative; +} + +.tabs .tab-list button.tab-title:not(:first-child) { + margin-left: 24px; +} + +.tabs .tab-list button.tab-title[aria-selected=true] { + color: var(--text-color); + border-bottom: 2px solid var(--text-color); + border-radius: 0; +} + +.tabs .tab-content { + padding: 10.5px 0; + color: var(--spectrum-gray-800); + font-family: var(--body-font-family); + font-size: var(--exlm-font-size-content); + font-weight: 400; +} + +.tabs .tab-content .tabpanel { + display: none; + margin-bottom: 12px; +} + +.tabs .tab-content .tabpanel.active { + display: block; +} \ No newline at end of file diff --git a/blocks/tabs/tabs.js b/blocks/tabs/tabs.js new file mode 100644 index 0000000..829415d --- /dev/null +++ b/blocks/tabs/tabs.js @@ -0,0 +1,113 @@ +import { loadBlocks } from '../../scripts/lib-franklin.js'; +import { createTag, moveInstrumentation } from '../../scripts/scripts.js'; + +function changeTabs(e) { + const { target } = e; + const tabMenu = target.parentNode; + const tabContent = tabMenu.nextElementSibling; + + tabMenu.querySelectorAll('[aria-selected="true"]').forEach((t) => t.setAttribute('aria-selected', false)); + + target.setAttribute('aria-selected', true); + + tabContent.querySelectorAll('[role="tabpanel"]').forEach((p) => p.classList.remove('active')); + + tabContent.parentNode.querySelector(`#${target.getAttribute('aria-controls')}`).classList.add('active'); +} + +function initTabs(block) { + const tabs = block.querySelectorAll('[role="tab"]'); + + tabs.forEach((tab) => { + tab.addEventListener('click', changeTabs); + }); +} + +let initCount = 0; +export default async function decorate(block) { + const tabIndex = block?.dataset?.tabIndex; + if (tabIndex) { + block.textContent = ''; + document.querySelectorAll(`div.tab-section.tab-index-${tabIndex}`).forEach((tabSection, i) => { + tabSection.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((heading) => { + heading.classList.add('no-mtoc'); + }); + const tabTitle = tabSection?.dataset.title || `tab-${i}`; + const container = document.createElement('div'); + moveInstrumentation(tabSection, container); + const titleContainer = document.createElement('div'); + titleContainer.textContent = tabTitle.trim(); + container.append(titleContainer); + container.append(tabSection); + block.append(container); + }); + await loadBlocks(block); + } + const tabList = createTag('div', { class: 'tab-list', role: 'tablist' }); + const tabContent = createTag('div', { class: 'tab-content' }); + + const tabNames = []; + const tabContents = []; + // list of Universal Editor instrumented 'tab content' divs + const tabInstrumentedDiv = []; + + [...block.children].forEach((child) => { + // keep the div that has been instrumented for UE + tabInstrumentedDiv.push(child); + + [...child.children].forEach((el, index) => { + if (index === 0) { + tabNames.push(el.textContent.trim()); + } else { + tabContents.push(el.childNodes); + } + }); + }); + + tabNames.forEach((name, i) => { + const tabBtnAttributes = { + role: 'tab', + class: 'tab-title', + id: `tab-${initCount}-${i}`, + tabindex: i > 0 ? '0' : '-1', + 'aria-selected': i === 0 ? 'true' : 'false', + 'aria-controls': `tab-panel-${initCount}-${i}`, + 'aria-label': name, + 'data-tab-id': i, + }; + + const tabNameDiv = createTag('button', tabBtnAttributes); + tabNameDiv.textContent = name; + tabList.appendChild(tabNameDiv); + }); + + tabContents.forEach((content, i) => { + const tabContentAttributes = { + id: `tab-panel-${initCount}-${i}`, + role: 'tabpanel', + class: 'tabpanel', + tabindex: '0', + 'aria-labelledby': `tab-${initCount}-${i}`, + }; + + // get the instrumented div + const tabContentDiv = tabInstrumentedDiv[i]; + // add all additional attributes + Object.entries(tabContentAttributes).forEach(([key, val]) => { + tabContentDiv.setAttribute(key, val); + }); + + // default first tab is active + if (i === 0) tabContentDiv.classList.add('active'); + tabContentDiv.replaceChildren(...Array.from(content)); + tabContent.appendChild(tabContentDiv); + }); + + // Replace the existing content with the new tab list and tab content + block.innerHTML = ''; // Clear the existing content + block.appendChild(tabList); + block.appendChild(tabContent); + + initTabs(block); + initCount += 1; +}