Skip to content

Instantly share code, notes, and snippets.

@kitsune7
Last active November 5, 2019 06:26
Show Gist options
  • Save kitsune7/5f3073e72adf5031b961c4620b22edf3 to your computer and use it in GitHub Desktop.
Save kitsune7/5f3073e72adf5031b961c4620b22edf3 to your computer and use it in GitHub Desktop.
A custom web component for mobile navigation
// Requires `ion-icon` web component
const css = `
<style>
:host([hidden]) { display: none; }
:host {
--background-color: #fff;
--border-color: #eee;
--link-color: dodgerblue;
--link-padding: 8px;
--top-offset: 0;
--icon-size: 24px;
display: block;
height: var(--icon-size);
}
a {
color: var(--link-color);
text-decoration: none;
}
ion-icon {
font-size: var(--icon-size);
}
nav {
background-color: var(--background-color);
border: 1px solid var(--border-color);
box-sizing: border-box;
display: flex;
flex-direction: column;
position: absolute;
top: var(--top-offset);
left: 0;
visibility: hidden;
width: 100vw;
}
::slotted(a) {
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
font-weight: bold;
padding: var(--link-padding) !important;
white-space: nowrap;
width: 100%;
}
</style>
`
const html = `
<a href="#">
<ion-icon name="menu"></ion-icon>
</a>
<nav>
<slot><p>Empty mobile navigation</p></slot>
</nav>
`
class MobileNav extends HTMLElement {
static get observedAttributes () {
return ['open']
}
constructor () {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.appendChild(template.content.cloneNode(true))
this.readyToClose = false
this.openMenu = (event) => {
const debounceDelay = 100
event.preventDefault()
if (this.hasAttribute('open')) {
return
}
this.setAttribute('open', '')
setTimeout(() => { this.readyToClose = true }, debounceDelay)
}
this.closeMenu = () => {
if (this.readyToClose) {
this.removeAttribute('open')
this.readyToClose = false
}
}
}
connectedCallback () {
document.addEventListener('click', this.closeMenu)
this.shadowRoot.querySelector('a').addEventListener('click', this.openMenu)
}
disconnectedCallback () {
document.removeEventListener('click', this.closeMenu)
this.shadowRoot.querySelector('a').removeEventListener('click', this.openMenu)
}
attributeChangedCallback (name) {
if (name === 'open') {
const $nav = this.shadowRoot.querySelector('nav')
if (this.hasAttribute('open')) {
$nav.style.setProperty('visibility', 'visible')
} else {
$nav.style.setProperty('visibility', 'hidden')
}
}
}
get open () {
return this.getAttribute('open')
}
set open (value) {
this.setAttribute('open', value)
}
}
const template = document.createElement('template')
template.innerHTML = `${css}${html}`
window.customElements.define('mobile-nav', MobileNav)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment