diff --git a/blocks/tabs/tabs.css b/blocks/tabs/tabs.css new file mode 100644 index 0000000..0244877 --- /dev/null +++ b/blocks/tabs/tabs.css @@ -0,0 +1,46 @@ +.tabs .tabs-list { + display: flex; + gap: 8px; + max-width: 100%; + overflow-x: auto; +} + +.tabs .tabs-list button { + flex: 0 0 max-content; + margin: 0; + border: 1px solid var(--dark-color); + border-radius: 0; + padding: 8px 16px; + background-color: var(--light-color); + color: initial; + font-size: unset; + font-weight: bold; + line-height: unset; + text-align: initial; + text-overflow: unset; + overflow: unset; + white-space: unset; + transition: background-color 0.2s; +} + +.tabs .tabs-list button[aria-selected="true"] { + border-bottom: 1px solid var(--background-color); + background-color: var(--background-color); + cursor: initial; +} + +.tabs .tabs-list button[aria-selected="false"]:hover, +.tabs .tabs-list button[aria-selected="false"]:focus { + background-color: var(--dark-color); +} + +.tabs .tabs-panel { + margin-top: -1px; + padding: 0 16px; + border: 1px solid var(--dark-color); + overflow: auto; +} + +.tabs .tabs-panel[aria-hidden="true"] { + display: none; +} \ No newline at end of file diff --git a/blocks/tabs/tabs.js b/blocks/tabs/tabs.js new file mode 100644 index 0000000..89a2885 --- /dev/null +++ b/blocks/tabs/tabs.js @@ -0,0 +1,54 @@ +// eslint-disable-next-line import/no-unresolved +import { toClassName } from '../../scripts/aem.js'; + +function hasWrapper(el) { + return !!el.firstElementChild && window.getComputedStyle(el.firstElementChild).display === 'block'; +} + +export default async function decorate(block) { + // build tablist + const tablist = document.createElement('div'); + tablist.className = 'tabs-list'; + tablist.setAttribute('role', 'tablist'); + + // decorate tabs and tabpanels + const tabs = [...block.children].map((child) => child.firstElementChild); + tabs.forEach((tab, i) => { + const id = toClassName(tab.textContent); + + // decorate tabpanel + const tabpanel = block.children[i]; + tabpanel.className = 'tabs-panel'; + tabpanel.id = `tabpanel-${id}`; + tabpanel.setAttribute('aria-hidden', !!i); + tabpanel.setAttribute('aria-labelledby', `tab-${id}`); + tabpanel.setAttribute('role', 'tabpanel'); + if (!hasWrapper(tabpanel.lastElementChild)) { + tabpanel.lastElementChild.innerHTML = `
${tabpanel.lastElementChild.innerHTML}
`; + } + + // build tab button + const button = document.createElement('button'); + button.className = 'tabs-tab'; + button.id = `tab-${id}`; + button.innerHTML = tab.innerHTML; + button.setAttribute('aria-controls', `tabpanel-${id}`); + button.setAttribute('aria-selected', !i); + button.setAttribute('role', 'tab'); + button.setAttribute('type', 'button'); + button.addEventListener('click', () => { + block.querySelectorAll('[role=tabpanel]').forEach((panel) => { + panel.setAttribute('aria-hidden', true); + }); + tablist.querySelectorAll('button').forEach((btn) => { + btn.setAttribute('aria-selected', false); + }); + tabpanel.setAttribute('aria-hidden', false); + button.setAttribute('aria-selected', true); + }); + tablist.append(button); + tab.remove(); + }); + + block.prepend(tablist); +}