Created
January 8, 2022 15:36
-
-
Save kimili/cb184f35647a7614011b33d906b20938 to your computer and use it in GitHub Desktop.
Toggle Section
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
toggle-section { | |
display: block; | |
--heading-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
--heading-font-size: 2rem; | |
--primary-color: #222; | |
} | |
toggle-section[role="region"] { | |
border-top: 2px solid #ddd; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ToggleSection extends HTMLElement { | |
constructor() { | |
super(); | |
} | |
connectedCallback() { | |
this.render(); | |
this.btn.addEventListener('click', () => { | |
this.setAttribute('open', this.getAttribute('open') === 'true' ? 'false' : 'true'); | |
}) | |
} | |
attributeChangedCallback(name) { | |
if (name === 'open') { | |
this.switchState() | |
} | |
} | |
render() { | |
const tmpl = document.createElement('template'); | |
tmpl.innerHTML = getTemplateHTML(); | |
this.setAttribute('role', 'region'); | |
this.attachShadow({ mode: 'open' }); | |
this.shadowRoot.appendChild(tmpl.content.cloneNode(true)); | |
this.btn = this.shadowRoot.querySelector('h3 button'); | |
const oldHeading = this.querySelector(':first-child'); | |
let level = parseInt(oldHeading.tagName.substr(1)); | |
this.heading = this.shadowRoot.querySelector('h3') | |
if (!level) { | |
console.warn('The first element inside each <toggle-section> should be a heading of an appropriate level.') | |
} | |
if (level && level !== 3) { | |
this.heading.setAttribute('aria-level', level) | |
} | |
this.btn.innerHTML = `${this.btn.innerHTML} <span>${oldHeading.textContent}</span>`; | |
oldHeading.parentNode.removeChild(oldHeading); | |
} | |
// The main state switching function | |
switchState() { | |
let expanded = this.getAttribute('open') === 'true' || false; | |
this.btn.setAttribute('aria-expanded', expanded); | |
this.shadowRoot.querySelector('.content').hidden = !expanded; | |
} | |
static get observedAttributes() { | |
return ['open'] | |
} | |
}; | |
const getTemplateHTML = () => { | |
const templateHTML = ` | |
<h3> | |
<button aria-expanded="false"> | |
<svg aria-hidden="true" focusable="false" viewBox="0 0 10 10"> | |
<rect class="vert" height="8" width="2" y="1" x="4"/> | |
<rect height="2" width="8" y="4" x="1"/> | |
</svg> | |
</button> | |
</h3> | |
<div class="content" hidden> | |
<slot></slot> | |
</div> | |
<style> | |
h3 { | |
margin: 0; | |
font-family: var(--heading-font-family); | |
font-size: var(--heading-font-size); | |
} | |
h3 button { | |
all: inherit; | |
box-sizing: border-box; | |
display: flex; | |
align-items: center; | |
width: 100%; | |
padding: 1em 0; | |
cursor: pointer; | |
} | |
h3 button span { | |
border-bottom: 2px solid transparent; | |
margin-bottom: -2px; | |
} | |
h3 button:focus span { | |
border-color: var(--primary-color); | |
} | |
button svg { | |
height: 1rem; | |
margin-right: 0.5em; | |
flex-shrink: 0; | |
} | |
[aria-expanded="true"] .vert { | |
display: none; | |
} | |
[aria-expanded] rect { | |
fill: currentColor; | |
} | |
div.content { | |
padding-bottom: 1em; | |
} | |
</style>` | |
return templateHTML; | |
}; | |
const initToggleSection = () => { | |
if (!'content' in document.createElement('template') || !document.head.attachShadow) { | |
return; | |
} | |
window.customElements.define('toggle-section', ToggleSection); | |
}; | |
module.exports = initToggleSection; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment