You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.2 KiB

  1. <template lang="pug">
  2. .tabset.elevation-2
  3. ul.tabset-tabs(ref='tabs', role='tablist')
  4. slot(name='tabs')
  5. .tabset-content(ref='content')
  6. slot(name='content')
  7. </template>
  8. <script>
  9. import { customAlphabet } from 'nanoid/non-secure'
  10. const nanoid = customAlphabet('1234567890abcdef', 10)
  11. export default {
  12. data() {
  13. return {
  14. currentTab: 0
  15. }
  16. },
  17. watch: {
  18. currentTab (newValue, oldValue) {
  19. this.setActiveTab()
  20. }
  21. },
  22. methods: {
  23. setActiveTab () {
  24. this.$refs.tabs.childNodes.forEach((node, idx) => {
  25. if (idx === this.currentTab) {
  26. node.className = 'is-active'
  27. node.setAttribute('aria-selected', 'true')
  28. } else {
  29. node.className = ''
  30. node.setAttribute('aria-selected', 'false')
  31. }
  32. })
  33. this.$refs.content.childNodes.forEach((node, idx) => {
  34. if (idx === this.currentTab) {
  35. node.className = 'tabset-panel is-active'
  36. node.removeAttribute('hidden')
  37. } else {
  38. node.className = 'tabset-panel'
  39. node.setAttribute('hidden', '')
  40. }
  41. })
  42. }
  43. },
  44. mounted () {
  45. // Handle scroll to header on load within hidden tab content
  46. if (window.location.hash && window.location.hash.length > 1) {
  47. const headerId = decodeURIComponent(window.location.hash)
  48. let foundIdx = -1
  49. this.$refs.content.childNodes.forEach((node, idx) => {
  50. if (node.querySelector(headerId)) {
  51. foundIdx = idx
  52. }
  53. })
  54. if (foundIdx >= 0) {
  55. this.currentTab = foundIdx
  56. }
  57. }
  58. this.setActiveTab()
  59. const tabRefId = nanoid()
  60. this.$refs.tabs.childNodes.forEach((node, idx) => {
  61. node.setAttribute('id', `${tabRefId}-${idx}`)
  62. node.setAttribute('role', 'tab')
  63. node.setAttribute('aria-controls', `${tabRefId}-${idx}-tab`)
  64. node.setAttribute('tabindex', '0')
  65. node.addEventListener('click', ev => {
  66. this.currentTab = [].indexOf.call(ev.target.parentNode.children, ev.target)
  67. })
  68. node.addEventListener('keydown', ev => {
  69. if (ev.key === 'ArrowLeft' && idx > 0) {
  70. this.currentTab = idx - 1
  71. this.$refs.tabs.childNodes[idx - 1].focus()
  72. } else if (ev.key === 'ArrowRight' && idx < this.$refs.tabs.childNodes.length - 1) {
  73. this.currentTab = idx + 1
  74. this.$refs.tabs.childNodes[idx + 1].focus()
  75. } else if (ev.key === 'Enter' || ev.key === ' ') {
  76. this.currentTab = idx
  77. node.focus()
  78. } else if (ev.key === 'Home') {
  79. this.currentTab = 0
  80. ev.preventDefault()
  81. ev.target.parentNode.children[0].focus()
  82. } else if (ev.key === 'End') {
  83. this.currentTab = this.$refs.tabs.childNodes.length - 1
  84. ev.preventDefault()
  85. ev.target.parentNode.children[this.$refs.tabs.childNodes.length - 1].focus()
  86. }
  87. })
  88. })
  89. this.$refs.content.childNodes.forEach((node, idx) => {
  90. node.setAttribute('id', `${tabRefId}-${idx}-tab`)
  91. node.setAttribute('role', 'tabpanel')
  92. node.setAttribute('aria-labelledby', `${tabRefId}-${idx}`)
  93. node.setAttribute('tabindex', '0')
  94. })
  95. }
  96. }
  97. </script>
  98. <style lang="scss">
  99. .tabset {
  100. border-radius: 5px;
  101. margin-top: 10px;
  102. @at-root .theme--dark & {
  103. background-color: #292929;
  104. }
  105. > .tabset-tabs {
  106. padding-left: 0;
  107. margin: 0;
  108. display: flex;
  109. align-items: stretch;
  110. background: linear-gradient(to bottom, #FFF, #FAFAFA);
  111. box-shadow: inset 0 -1px 0 0 #DDD;
  112. border-radius: 5px 5px 0 0;
  113. overflow: auto;
  114. @at-root .theme--dark & {
  115. background: linear-gradient(to bottom, #424242, #333);
  116. box-shadow: inset 0 -1px 0 0 #555;
  117. }
  118. > li {
  119. display: block;
  120. padding: 16px;
  121. margin-top: 0;
  122. cursor: pointer;
  123. transition: color 1s ease;
  124. border-right: 1px solid #FFF;
  125. font-size: 14px;
  126. font-weight: 500;
  127. margin-bottom: 1px;
  128. user-select: none;
  129. @at-root .theme--dark & {
  130. border-right-color: #555;
  131. }
  132. &.is-active {
  133. background-color: #FFF;
  134. margin-bottom: 0;
  135. padding-bottom: 17px;
  136. padding-top: 13px;
  137. color: mc('blue', '700');
  138. border-top: 3px solid mc('blue', '700');
  139. @at-root .theme--dark & {
  140. background-color: #292929;
  141. color: mc('blue', '300');
  142. }
  143. }
  144. &:last-child {
  145. border-right: none;
  146. &.is-active {
  147. border-right: 1px solid #EEE;
  148. @at-root .theme--dark & {
  149. border-right-color: #555;
  150. }
  151. }
  152. }
  153. &:hover {
  154. background-color: rgba(#CCC, .1);
  155. @at-root .theme--dark & {
  156. background-color: rgba(#222, .25);
  157. }
  158. &.is-active {
  159. background-color: #FFF;
  160. @at-root .theme--dark & {
  161. background-color: #292929;
  162. }
  163. }
  164. }
  165. & + li {
  166. border-left: 1px solid #EEE;
  167. @at-root .theme--dark & {
  168. border-left-color: #222;
  169. }
  170. }
  171. }
  172. }
  173. > .tabset-content {
  174. .tabset-panel {
  175. padding: 2px 16px 16px;
  176. display: none;
  177. &.is-active {
  178. display: block;
  179. }
  180. }
  181. }
  182. }
  183. </style>