Advertisement

#20 Creating A Tab Component Starting With The Tab Header

This article will be part one in at least a two part mini-series focusing on creating a tab component. Where we left off in the last article was displaying all of the securities, categories, markets, segments, territories and types on the screen all at the same time. All of these things are related but they are distinct objects all on their own. So in order to make our app more understandable and usable we need to have a way of only showing a particular type information at one time and to do this we will be using tabs. By the end of this article we will have set up the actual tabs themselves in such a way that the active tab is highlighted and notification of which tab has been clicked will be sent to the outside tab container.

Code Snippets

TabHeader.vue (1:59)

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class TabHeader extends Vue {
    @Prop() private readonly tabs!: string[];
}
</script>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabHeader.vue (2:32)

<template lang="pug">
ul
    li(
        v-for="tab in tabs"
        v-bind:key="tab")
        a(href="#void") {{tab}}
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabContainer.vue (3:48)

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

import TabHeader from "@/components/tabs/TabHeader.vue";

@Component({
    components: {
        TabHeader,
    },
})
export default class TabContainer extends Vue {
    @Prop() private readonly tabs!: string[];
}
</script>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabContainer.vue

TabContainer.vue (4:37)

<template lang="pug">
div
    TabHeader(v-bind:tabs="tabs")
    slot
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabContainer.vue

Securities.vue (5:04)

<script lang="tsx">
...
import TabContainer from "@/components/tabs/TabContainer.vue";

@Component({
    components: {
        ...
        TabContainer,
    },
})
export default class Securities extends Vue {
    ...
    private readonly tabs = [
        "Securities",
        "Categories",
        "Markets",
        "Segments",
        "Territories",
        "Types",
    ];
    ...
}
</script>
root ⟩ src ⟩ views ⟩ Securities.vue

Securities.vue (5:43)

<template lang="pug">
TabContainer(v-bind:tabs="tabs")
    ListView(
        v-bind:items="securities"
        v-bind:renderFn="renderFnSecurities")/
    ListView(
        v-bind:items="categories"
        v-bind:renderFn="renderFnCategory")/
    ListView(
        v-bind:items="markets"
        v-bind:renderFn="renderFnDescriptor('markets')")/
    ListView(
        v-bind:items="segments"
        v-bind:renderFn="renderFnDescriptor('segments')")/
    ListView(
        v-bind:items="territories"
        v-bind:renderFn="renderFnDescriptor('territories')")/
    ListView(
        v-bind:items="types"
        v-bind:renderFn="renderFnDescriptor('types')")/
</template>
root ⟩ src ⟩ views ⟩ Securities.vue

TabHeader.vue (6:14)

<template lang="pug">
ul.tab-headings
    ...
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

Advertisement

TabHeader.vue (6:24)

<style lang="sass">
@import "../../bitters/functions"
@import "../../bitters/variables"

$tab-border: 1px solid $light-gray

.tab-headings
    display: flex
    flex-wrap: wrap
    border-top: $tab-border
    border-left: $tab-border

    li
        flex: 1
        text-align: center

    a
        display: block
        padding: 0.75rem 0.5rem
        text-decoration: none
        border-right: $tab-border
        border-bottom: $tab-border
        color: $app-bar-background-color
</style>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

Securities.vue (9:19)

<style lang="sass" scoped>
/deep/ .tab-headings li a
    min-width: 6.3125rem
</style>
root ⟩ src ⟩ views ⟩ Securities.vue

TabHeader.vue (10:56)

<script lang="ts">
...
export default class TabHeader extends Vue {
    ...
    private click(index: number) {
        this.$emit("click", index);
    }
}
</script>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabHeader.vue (13:12)

<template lang="pug">
ul.tab-headings
    li(
        v-for="(tab, index) in tabs"
        ...)
        a(
            ...
            v-on:click.prevent="click(index)") ...
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabContainer.vue (13:32)

<script lang="ts">
...
export default class TabContainer extends Vue {
    ...
    private setActive(pos: number) {
        console.log(pos);
    }
}
</script>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabContainer.vue

TabContainer.vue (13:54)

<template lang="pug">
div
    TabHeader(
        ...
        v-on:click="setActive")
    slot
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabContainer.vue

TabHeader.vue (14:46)

<script lang="ts">
...
export default class TabHeader extends Vue {
    @Prop({default: 0}) private readonly activePos!: number;
    ...
    private activePosInternal = -1;

    private get activePosition() {
        return this.activePosInternal !== -1
            ? this.activePosInternal
            : this.activePos;
    }
    ...
    private click(index: number) {
        this.activePosInternal = index;
        this.$emit("click", index);
    }
}
</script>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabHeader.vue (17:13)

<template lang="pug">
ul.tab-headings
    li(..)
        a(
            ...
            v-bind:class="{ 'active': index===activePosition }") ...
</template>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

TabHeader.vue (18:46)

<style lang="sass">
...
.tab-headings
    ...
    a
        ...
        &.active
            background-color: $light-gray
            font-weight: 600
            color: $app-bar-background-color
</style>
root ⟩ src ⟩ components ⟩ tabs ⟩ TabHeader.vue

Exciton Interactive LLC
Advertisement