diff --git a/_src /atoms/tab/Tab.spec.js b/_src /atoms/tab/Tab.spec.js
deleted file mode 100644
index 8acbb04c0..000000000
--- a/_src /atoms/tab/Tab.spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { mount } from '@vue/test-utils'
-import ATab from './Tab.vue'
-
-describe('Tab', () => {
- it('has default structure', () => {
- const wrapper = mount(ATab, {
- propsData: {
- name: 'Tab'
- }
- })
-
- expect(wrapper.is('div')).toBe(true)
- expect(wrapper.attributes().role).toBeDefined()
- expect(wrapper.attributes().role).toEqual('tabpanel')
- expect(wrapper.attributes('data-tab')).toBeDefined()
- })
-
- it('renders slot text when passed', () => {
- const wrapper = mount(ATab, {
- slots: {
- default: 'Tab default text'
- },
- propsData: {
- name: 'Tab'
- }
- })
-
- expect(wrapper.find('div > span').exists()).toBe(true)
- expect(wrapper.find('div > span').text()).toEqual('Tab default text')
- })
-
- it('has correct id attribute', () => {
- const wrapper = mount(ATab, {
- propsData: {
- name: 'Sample'
- }
- })
-
- expect(wrapper.props().name).toBe('Sample')
- expect(wrapper.attributes().id).toBeDefined()
- expect(wrapper.attributes().id).toEqual('sample-tab')
- })
-})
diff --git a/_src /molecules/tabs/Tabs.html b/_src /molecules/tabs/Tabs.html
deleted file mode 100644
index 7da8bb083..000000000
--- a/_src /molecules/tabs/Tabs.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
diff --git a/_src /molecules/tabs/Tabs.js b/_src /molecules/tabs/Tabs.js
deleted file mode 100644
index c03be6b9f..000000000
--- a/_src /molecules/tabs/Tabs.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import KeyCodes from '../../utils/key-codes'
-
-// @vue/component
-
-export default {
- data () {
- return {
- tabs: null,
- activeFocusedTab: 0
- }
- },
- computed: {
- tabsChildren () {
- return this.$children.filter(child => {
- return child.$el.dataset.tab
- })
- }
- },
- mounted () {
- this.tabs = this.tabsChildren
- },
- methods: {
- selectTab (selectedTab) {
- this.$emit('click', selectedTab.name)
- this.tabs.forEach(tab => {
- if (selectedTab === this.activeFocusedTab) {
- tab.isActive = tab.name === this.tabs[this.activeFocusedTab].name
- } else {
- tab.isActive = tab.name === selectedTab.name
- }
- })
- },
- focus (el) {
- this.$refs[el][0].focus()
- },
- onKeydown (e) {
- const key = e.keyCode
-
- if (key === KeyCodes.TAB) {
- e.preventDefault()
- e.stopPropagation()
- this.$refs.content.focus()
- } else if (
- (key === KeyCodes.LEFT ||
- key === KeyCodes.UP ||
- key === KeyCodes.HOME) &&
- this.activeFocusedTab > 0
- ) {
- if (key === KeyCodes.HOME) {
- this.focus('button_0')
- } else {
- this.activeFocusedTab = this.activeFocusedTab - 1
- this.focus('button_' + this.activeFocusedTab)
- }
- } else if (
- (key === KeyCodes.RIGHT ||
- key === KeyCodes.DOWN ||
- key === KeyCodes.END) &&
- this.activeFocusedTab < this.tabs.length - 1
- ) {
- if (key === KeyCodes.END) {
- this.focus('button_' + (this.tabs.length - 1))
- } else {
- this.activeFocusedTab = this.activeFocusedTab + 1
- this.focus('button_' + this.activeFocusedTab)
- }
- }
- this.selectTab(this.activeFocusedTab)
- }
- }
-}
diff --git a/_src /molecules/tabs/Tabs.scss b/_src /molecules/tabs/Tabs.scss
deleted file mode 100644
index 4f67175c1..000000000
--- a/_src /molecules/tabs/Tabs.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-@import '../../../assets/styles/_globals.scss';
-
-// common
-$tabs__border-width : 8px !default;
-$tabs__outline-focus : $blue solid 1px !default;
-
-// button
-$tabs__button-padding : $spacer--medium !default;
-$tabs__button-border : $tabs__border-width solid $gray-lightest !default;
-$tabs__button-border--active : $tabs__border-width solid $color-primary !default;
-$tabs__button-color : $gray !default;
-$tabs__button-color--active : $gray-darker !default;
-$tabs__button-font-weight : $font-weight-bold !default;
-$tabs__button-background : $bg-color-base !default;
-$tabs__button-background--active: $gray-lightest !default;
-
-// content
-$tabs__content-padding : $spacer--medium 0 !default;
-$tabs__content-padding\@large : $spacer--large $spacer--extra-large !default;
-
-.a-tabs {
- display: flex;
- flex-wrap: wrap;
-
- &__nav-button {
- flex: 1;
- padding: $tabs__button-padding;
- color: $tabs__button-color;
- font-weight: $tabs__button-font-weight;
- background: $tabs__button-background;
- border: none;
- border-bottom: $tabs__button-border;
- cursor: pointer;
-
- &--active {
- color: $tabs__button-color--active;
- background-color: $tabs__button-background--active;
- border-bottom: $tabs__button-border--active;
- }
-
- &:focus {
- z-index: 1;
- outline: $tabs__outline-focus;
- }
- }
-
- &__content {
- flex: 100%;
- padding: $tabs__content-padding;
-
- &:focus {
- outline: $tabs__outline-focus;
- }
-
- @include mq($screen-l) {
- padding: $tabs__content-padding\@large;
- }
- }
-}
diff --git a/_src /molecules/tabs/Tabs.spec.js b/_src /molecules/tabs/Tabs.spec.js
deleted file mode 100644
index eece5f2cc..000000000
--- a/_src /molecules/tabs/Tabs.spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import Vue from 'vue'
-import { mount } from '@vue/test-utils'
-import ATabs from './Tabs.vue'
-import ATab from '../../atoms/tab/Tab.vue'
-
-describe('Tabs', () => {
- it('has default structure', () => {
- const wrapper = mount(ATabs)
-
- expect(wrapper.is('div')).toBe(true)
- expect(wrapper.classes()).toContain('a-tabs')
- expect(wrapper.classes().length).toBe(1)
- })
-
- it('renders slot content when passed', () => {
- const App = Vue.extend({
- render (h) {
- return h(ATabs, {}, [
- h(ATab, { props: { name: 'First' } }, 'First content'),
- h(ATab, { props: { name: 'Second' } }, 'Second content')
- ])
- }
- })
- const wrapper = mount(App)
-
- expect(wrapper.find('#first-tab-trigger').text()).toEqual('First')
- expect(wrapper.find('#first-tab').text()).toEqual('First content')
- expect(wrapper.find('#second-tab-trigger').text()).toEqual('Second')
- expect(wrapper.find('#second-tab').text()).toEqual('Second content')
- })
-
- it('switches between tabs', () => {
- const App = Vue.extend({
- render (h) {
- return h(ATabs, {}, [
- h(ATab, { props: { name: 'First' } }, 'First content'),
- h(ATab, { props: { name: 'Second' } }, 'Second content')
- ])
- }
- })
- const wrapper = mount(App)
- const firstTrigger = wrapper.find('#first-tab-trigger')
- const secondTrigger = wrapper.find('#second-tab-trigger')
- const firstContent = wrapper.find('#first-tab')
- const secondContent = wrapper.find('#second-tab')
-
- firstTrigger.trigger('click')
- expect(firstTrigger.attributes('aria-selected')).toEqual('true')
- expect(firstContent.isVisible()).toBe(true)
- expect(secondTrigger.attributes('aria-selected')).toEqual('false')
- expect(secondContent.isVisible()).toBe(false)
-
- secondTrigger.trigger('click')
- expect(firstTrigger.attributes('aria-selected')).toEqual('false')
- expect(firstContent.isVisible()).toBe(false)
- expect(secondTrigger.attributes('aria-selected')).toEqual('true')
- expect(secondContent.isVisible()).toBe(true)
- })
-})
diff --git a/_src /molecules/tabs/Tabs.stories.js b/_src /molecules/tabs/Tabs.stories.js
deleted file mode 100644
index de182b71f..000000000
--- a/_src /molecules/tabs/Tabs.stories.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { storiesOf } from '@storybook/vue'
-import { action } from '@storybook/addon-actions'
-
-import ATabs from './Tabs.vue'
-import ATab from './../../atoms/tab/Tab.vue'
-import AButton from './../../atoms/button/Button.vue'
-import AParagraph from './../../atoms/paragraph/Paragraph.vue'
-import AIcon from './../../atoms/icon/Icon.vue'
-import AIconStarBorder from './../../atoms/icon/templates/IconStarBorder.vue'
-
-storiesOf('Molecules/Tabs', module)
- .addParameters({ info: true })
- .add('Default', () => ({
- components: {
- ATabs,
- ATab,
- AParagraph
- },
- methods: {
- tabClick: action('Clicked tab')
- },
- template: `
-
-
-
- Toe in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
- Sed do eiusmod tempor incididunt ut labo. Ut enim ad minim veniam, re et dolore magna aliqua. quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
-
-
-
- `
- }))
- .add('With slot', () => ({
- components: {
- ATabs,
- ATab,
- AButton,
- AParagraph,
- AIcon,
- AIconStarBorder
- },
- methods: {
- tabClick: action('Clicked tab')
- },
- template: `
-
-
-
-
-
- {{ tab.name }}
-
- (3)
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
- Alpaca Button
-
-
-
-
- Sed do eiusmod tempor incididunt ut labo. Ut enim ad minim veniam, re et dolore magna aliqua. quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
-
-
-
- `
- }))
diff --git a/_src /molecules/tabs/Tabs.vue b/_src /molecules/tabs/Tabs.vue
deleted file mode 100644
index b2f3a7786..000000000
--- a/_src /molecules/tabs/Tabs.vue
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
diff --git a/mocks/tabs.json b/mocks/tabs.json
new file mode 100644
index 000000000..e0505c637
--- /dev/null
+++ b/mocks/tabs.json
@@ -0,0 +1,32 @@
+[
+ {
+ "name": "Tab 1",
+ "selected": true,
+ "paragraph": "Toe in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ "button": "Alpaca button"
+ },
+ {
+ "name": "Tab 2",
+ "selected": false,
+ "paragraph": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ "button": ""
+ },
+ {
+ "name": "Tab 3",
+ "selected": false,
+ "paragraph": "Sed do eiusmod tempor incididunt ut labo. Ut enim ad minim veniam, re et dolore magna aliqua. quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ "button": ""
+ },
+ {
+ "name": "Tab 4",
+ "selected": false,
+ "paragraph": "Toe in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ "button": ""
+ },
+ {
+ "name": "Tab 5",
+ "selected": false,
+ "paragraph": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
+ "button": ""
+ }
+]
diff --git a/package.json b/package.json
index 389117065..44548887b 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.14.0",
+ "lodash.merge": "^4.6.2",
"postcss": "^8.3.6",
"stylelint": "^13.13.1",
"stylelint-config-rational-order": "^0.1.2",
diff --git a/_src /atoms/tab/Tab.html b/src/atoms/tab/Tab.html
similarity index 100%
rename from _src /atoms/tab/Tab.html
rename to src/atoms/tab/Tab.html
diff --git a/_src /atoms/tab/Tab.js b/src/atoms/tab/Tab.js
similarity index 95%
rename from _src /atoms/tab/Tab.js
rename to src/atoms/tab/Tab.js
index 67997572d..0cf4aee8e 100644
--- a/_src /atoms/tab/Tab.js
+++ b/src/atoms/tab/Tab.js
@@ -1,4 +1,3 @@
-// @vue/component
export default {
props: {
/**
diff --git a/src/atoms/tab/Tab.spec.js b/src/atoms/tab/Tab.spec.js
new file mode 100644
index 000000000..f11f5235a
--- /dev/null
+++ b/src/atoms/tab/Tab.spec.js
@@ -0,0 +1,45 @@
+import { mount } from '@vue/test-utils'
+import ATab from './Tab.vue'
+
+const factory = () => {
+ return mount(ATab, {
+ slots: {
+ default: `
+
+ Tab text
+
+ `
+ },
+ propsData: {
+ name: 'Tab'
+ }
+ })
+}
+
+describe('Tab', () => {
+ it('has default structure', async () => {
+ const wrapper = factory()
+
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.element.tagName).toBe('DIV')
+ expect(wrapper.attributes().role).toBeDefined()
+ expect(wrapper.attributes().role).toEqual('tabpanel')
+ expect(wrapper.attributes('data-tab')).toBeDefined()
+ })
+
+ it('renders slot text when passed', () => {
+ const wrapper = factory()
+
+ expect(wrapper.find('div > span').exists()).toBe(true)
+ expect(wrapper.find('div > span').text()).toEqual('Tab text')
+ })
+
+ it('has correct id attribute', () => {
+ const wrapper = factory()
+
+ expect(wrapper.props().name).toBe('Tab')
+ expect(wrapper.attributes().id).toBeDefined()
+ expect(wrapper.attributes().id).toEqual('tab-tab')
+ })
+})
diff --git a/_src /atoms/tab/Tab.vue b/src/atoms/tab/Tab.vue
similarity index 100%
rename from _src /atoms/tab/Tab.vue
rename to src/atoms/tab/Tab.vue
diff --git a/src/molecules/tabs/Tabs.html b/src/molecules/tabs/Tabs.html
new file mode 100644
index 000000000..0d35e7489
--- /dev/null
+++ b/src/molecules/tabs/Tabs.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/molecules/tabs/Tabs.js b/src/molecules/tabs/Tabs.js
new file mode 100644
index 000000000..033611343
--- /dev/null
+++ b/src/molecules/tabs/Tabs.js
@@ -0,0 +1,58 @@
+export default {
+ data () {
+ return {
+ tabs: null,
+ activeFocusedTab: 0
+ }
+ },
+ computed: {
+ tabsChildren () {
+ return this.$children.filter(child => {
+ return child.$el.dataset.tab
+ })
+ }
+ },
+ mounted () {
+ this.tabs = this.tabsChildren
+ },
+ methods: {
+ selectTab (selectedTab) {
+ this.$emit('click', selectedTab.name)
+ this.tabs.forEach((tab, index) => {
+ if (selectedTab === this.activeFocusedTab) {
+ tab.isActive = tab.name === this.tabs[this.activeFocusedTab].name
+ } else if (tab.name === selectedTab.name) {
+ tab.isActive = true
+ this.activeFocusedTab = index
+ } else {
+ tab.isActive = false
+ }
+ })
+ },
+ focus (el) {
+ this.$refs[el][0].focus()
+ },
+ onKeydown (e) {
+ const key = e.key
+ const tabsCount = this.tabs.length
+
+ if (key === 'ArrowRight' || key === 'ArrowDown') {
+ this.activeFocusedTab = (this.activeFocusedTab + 1) % tabsCount
+ this.selectTab(this.activeFocusedTab)
+ this.focus(`button_${this.activeFocusedTab}`)
+ } else if (key === 'ArrowLeft' || key === 'ArrowUp') {
+ this.activeFocusedTab = (this.activeFocusedTab - 1 + tabsCount) % tabsCount
+ this.selectTab(this.activeFocusedTab)
+ this.focus(`button_${this.activeFocusedTab}`)
+ } else if (key === 'Home') {
+ this.activeFocusedTab = 0
+ this.selectTab(this.activeFocusedTab)
+ this.focus(`button_${this.activeFocusedTab}`)
+ } else if (key === 'End') {
+ this.activeFocusedTab = tabsCount - 1
+ this.selectTab(this.activeFocusedTab)
+ this.focus(`button_${this.activeFocusedTab}`)
+ }
+ }
+ }
+}
diff --git a/src/molecules/tabs/Tabs.spec.js b/src/molecules/tabs/Tabs.spec.js
new file mode 100644
index 000000000..fa8c81ed5
--- /dev/null
+++ b/src/molecules/tabs/Tabs.spec.js
@@ -0,0 +1,108 @@
+import { shallowMount } from '@vue/test-utils'
+import merge from 'lodash.merge'
+import ATab from '@atoms/tab/Tab.vue'
+import ATabs from './Tabs.vue'
+
+const factory = (customData = {}) => {
+ const defaultData = {
+ slots: {
+ default: `
+
+ Tab text 1
+
+
+ Tab text 2
+
+ `
+ },
+ stubs: {
+ 'a-tab': ATab
+ }
+ }
+
+ return shallowMount(ATabs, merge(defaultData, customData))
+}
+
+describe('Tabs', () => {
+ it('has default structure', async () => {
+ const wrapper = factory()
+
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.element.tagName).toBe('DIV')
+ })
+
+ it('has default structure when content rendered', async () => {
+ const wrapper = factory()
+
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('#test-tab1-tab-trigger').exists()).toBe(true)
+ expect(wrapper.find('#test-tab1-tab').exists()).toBe(true)
+ expect(wrapper.find('#test-tab2-tab-trigger').exists()).toBe(true)
+ expect(wrapper.find('#test-tab2-tab').exists()).toBe(true)
+ })
+
+ it('renders slot content when passed', async () => {
+ const wrapper = factory()
+
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('#test-tab1-tab-trigger').exists()).toBe(true)
+ expect(wrapper.find('#test-tab1-tab-trigger').text()).toEqual('test tab1')
+ expect(wrapper.find('#test-tab1-tab').exists()).toBe(true)
+ expect(wrapper.find('#test-tab1-tab').text()).toEqual('Tab text 1')
+ expect(wrapper.find('#test-tab2-tab-trigger').exists()).toBe(true)
+ expect(wrapper.find('#test-tab2-tab-trigger').text()).toEqual('test tab2')
+ expect(wrapper.find('#test-tab2-tab').exists()).toBe(true)
+ expect(wrapper.find('#test-tab2-tab').text()).toEqual('Tab text 2')
+ })
+
+ it('renders custom button', async () => {
+ const wrapper = factory({
+ slots: {
+ button: `
+
+ Test slot
+
+ `
+ }
+ })
+
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('[data-test="button-slot"]').exists()).toBe(true)
+ })
+
+ it('switches between tabs', async () => {
+ const wrapper = factory()
+
+ await wrapper.vm.$nextTick()
+
+ const firstTrigger = wrapper.find('#test-tab1-tab-trigger')
+ const secondTrigger = wrapper.find('#test-tab2-tab-trigger')
+ const firstContent = wrapper.find('#test-tab1-tab')
+ const secondContent = wrapper.find('#test-tab2-tab')
+
+ await firstTrigger.trigger('click')
+
+ expect(firstTrigger.attributes('aria-selected')).toEqual('true')
+ expect(firstTrigger.attributes('aria-expanded')).toEqual('true')
+ expect(firstContent.isVisible()).toBe(true)
+ expect(secondTrigger.attributes('aria-selected')).toEqual('false')
+ expect(secondTrigger.attributes('aria-expanded')).toEqual('false')
+ expect(secondContent.isVisible()).toBe(false)
+
+ await secondTrigger.trigger('click')
+
+ expect(firstTrigger.attributes('aria-selected')).toEqual('false')
+ expect(firstTrigger.attributes('aria-expanded')).toEqual('false')
+ expect(firstContent.isVisible()).toBe(false)
+ expect(secondTrigger.attributes('aria-selected')).toEqual('true')
+ expect(secondTrigger.attributes('aria-expanded')).toEqual('true')
+ expect(secondContent.isVisible()).toBe(true)
+ })
+})
diff --git a/src/molecules/tabs/Tabs.stories.js b/src/molecules/tabs/Tabs.stories.js
new file mode 100644
index 000000000..8d4567bdb
--- /dev/null
+++ b/src/molecules/tabs/Tabs.stories.js
@@ -0,0 +1,92 @@
+import ATabs from './Tabs.vue'
+import ATab from '@atoms/tab/Tab.vue'
+import AButton from '@atoms/button/Button.vue'
+import AParagraph from '@atoms/paragraph/Paragraph.vue'
+import AIcon from '@atoms/icon/Icon.vue'
+import AIconStarBorder from '@atoms/icon/templates/IconStarBorder.vue'
+
+import tabs from '@mocks/tabs.json'
+
+export default {
+ title: 'Molecules/Tabs',
+ component: ATabs,
+ argTypes: {
+ tabClick: {
+ action: 'clicked tab'
+ }
+ }
+}
+
+const Template = (args, { argTypes }) => ({
+ components: {
+ ATabs,
+ ATab,
+ AButton,
+ AParagraph
+ },
+ props: Object.keys(argTypes),
+ data () {
+ return {
+ tabs
+ }
+ },
+ template: `
+
+
+
+
+ {{ tab.paragraph }}
+
+
+
+
+ `
+})
+
+export const Default = Template.bind({})
+
+export const WithSlots = (args, { argTypes }) => ({
+ components: {
+ ATabs,
+ ATab,
+ AButton,
+ AParagraph,
+ AIcon,
+ AIconStarBorder
+ },
+ props: Object.keys(argTypes),
+ data () {
+ return {
+ tabs
+ }
+ },
+ template: `
+
+
+
+
+
+ {{ tab.name }}
+
+
+
+
+ {{ tab.paragraph }}
+
+
+ {{ tab.button}}
+
+
+
+
+ `
+})
diff --git a/src/molecules/tabs/Tabs.vue b/src/molecules/tabs/Tabs.vue
new file mode 100644
index 000000000..68ded6578
--- /dev/null
+++ b/src/molecules/tabs/Tabs.vue
@@ -0,0 +1,13 @@
+
+
+