<template lang="pug"> .tabset.elevation-2 ul.tabset-tabs(ref='tabs', role='tablist') slot(name='tabs') .tabset-content(ref='content') slot(name='content') </template> <script> import { customAlphabet } from 'nanoid/non-secure' const nanoid = customAlphabet('1234567890abcdef', 10) export default { data() { return { currentTab: 0 } }, watch: { currentTab (newValue, oldValue) { this.setActiveTab() } }, methods: { setActiveTab () { this.$refs.tabs.childNodes.forEach((node, idx) => { if (idx === this.currentTab) { node.className = 'is-active' node.setAttribute('aria-selected', 'true') } else { node.className = '' node.setAttribute('aria-selected', 'false') } }) this.$refs.content.childNodes.forEach((node, idx) => { if (idx === this.currentTab) { node.className = 'tabset-panel is-active' node.removeAttribute('hidden') } else { node.className = 'tabset-panel' node.setAttribute('hidden', '') } }) } }, mounted () { // Handle scroll to header on load within hidden tab content if (window.location.hash && window.location.hash.length > 1) { const headerId = decodeURIComponent(window.location.hash) let foundIdx = -1 this.$refs.content.childNodes.forEach((node, idx) => { if (node.querySelector(headerId)) { foundIdx = idx } }) if (foundIdx >= 0) { this.currentTab = foundIdx } } this.setActiveTab() const tabRefId = nanoid() this.$refs.tabs.childNodes.forEach((node, idx) => { node.setAttribute('id', `${tabRefId}-${idx}`) node.setAttribute('role', 'tab') node.setAttribute('aria-controls', `${tabRefId}-${idx}-tab`) node.setAttribute('tabindex', '0') node.addEventListener('click', ev => { this.currentTab = [].indexOf.call(ev.target.parentNode.children, ev.target) }) node.addEventListener('keydown', ev => { if (ev.key === 'ArrowLeft' && idx > 0) { this.currentTab = idx - 1 this.$refs.tabs.childNodes[idx - 1].focus() } else if (ev.key === 'ArrowRight' && idx < this.$refs.tabs.childNodes.length - 1) { this.currentTab = idx + 1 this.$refs.tabs.childNodes[idx + 1].focus() } else if (ev.key === 'Enter' || ev.key === ' ') { this.currentTab = idx node.focus() } else if (ev.key === 'Home') { this.currentTab = 0 ev.preventDefault() ev.target.parentNode.children[0].focus() } else if (ev.key === 'End') { this.currentTab = this.$refs.tabs.childNodes.length - 1 ev.preventDefault() ev.target.parentNode.children[this.$refs.tabs.childNodes.length - 1].focus() } }) }) this.$refs.content.childNodes.forEach((node, idx) => { node.setAttribute('id', `${tabRefId}-${idx}-tab`) node.setAttribute('role', 'tabpanel') node.setAttribute('aria-labelledby', `${tabRefId}-${idx}`) node.setAttribute('tabindex', '0') }) } } </script> <style lang="scss"> .tabset { border-radius: 5px; margin-top: 10px; @at-root .theme--dark & { background-color: #292929; } > .tabset-tabs { padding-left: 0; margin: 0; display: flex; align-items: stretch; background: linear-gradient(to bottom, #FFF, #FAFAFA); box-shadow: inset 0 -1px 0 0 #DDD; border-radius: 5px 5px 0 0; overflow: auto; @at-root .theme--dark & { background: linear-gradient(to bottom, #424242, #333); box-shadow: inset 0 -1px 0 0 #555; } > li { display: block; padding: 16px; margin-top: 0; cursor: pointer; transition: color 1s ease; border-right: 1px solid #FFF; font-size: 14px; font-weight: 500; margin-bottom: 1px; user-select: none; @at-root .theme--dark & { border-right-color: #555; } &.is-active { background-color: #FFF; margin-bottom: 0; padding-bottom: 17px; padding-top: 13px; color: mc('blue', '700'); border-top: 3px solid mc('blue', '700'); @at-root .theme--dark & { background-color: #292929; color: mc('blue', '300'); } } &:last-child { border-right: none; &.is-active { border-right: 1px solid #EEE; @at-root .theme--dark & { border-right-color: #555; } } } &:hover { background-color: rgba(#CCC, .1); @at-root .theme--dark & { background-color: rgba(#222, .25); } &.is-active { background-color: #FFF; @at-root .theme--dark & { background-color: #292929; } } } & + li { border-left: 1px solid #EEE; @at-root .theme--dark & { border-left-color: #222; } } } } > .tabset-content { .tabset-panel { padding: 2px 16px 16px; display: none; &.is-active { display: block; } } } } </style>