All files / components/tab Tab.vue

100% Statements 20/20
90% Branches 9/10
100% Functions 3/3
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103    2x     2x 2x       2x                                                                         102x 102x   102x 1x             101x 101x 109x   101x           2x 2x 2x 2x 2x   110x                           2x 2x                          
<template>
	<section
		v-show="isActive"
		:id="tab.id"
		:aria-hidden="!isActive ? true : undefined"
		:aria-labelledby="`${tab.id}-label`"
		class="cdx-tab"
		role="tabpanel"
		tabindex="-1"
	>
		<!-- @slot Tab content -->
		<slot />
	</section>
</template>
 
<script lang="ts">
import { defineComponent, computed, inject } from 'vue';
import { TabsKey, ActiveTabKey } from '../../constants';
import { TabData } from '../../types';
 
/**
 * A section of content within a Tabs layout.
 */
export default defineComponent( {
	name: 'CdxTab',
 
	/**
	 * The "label" and "disabled" props are referenced by the parent Tabs
	 * component during the generation of a list of labels.
	 */
	props: {
		/**
		 * String name of the tab, used for programmatic selection. Each Tab
		 * inside a layout must have a unique name. This prop will also be
		 * used as the tab label if no "label" prop is provided.
		 */
		name: {
			type: String,
			required: true
		},
 
		/**
		 * Label that corresponds to this Tab in the Tabs component's header.
		 * Lengthy labels will be truncated.
		 */
		// eslint-disable-next-line vue/no-unused-properties
		label: {
			type: String,
			default: ''
		},
 
		/**
		 * Whether or not the tab is disabled. Disabled tabs cannot be accessed
		 * via label clicks or keyboard navigation.
		 */
		// eslint-disable-next-line vue/no-unused-properties
		disabled: {
			type: Boolean,
			default: false
		}
	},
 
	setup( props ) {
		const tabsData = inject( TabsKey );
		const activeTab = inject( ActiveTabKey );
 
		// Throw error if used outside of Tabs
		if ( !tabsData || !activeTab ) {
			throw new Error( 'Tab component must be used inside a Tabs component' );
		}
 
		// This component must rely on data that is provided from the parent Tabs
		// instance for two additional pieces of information:
		// * unique generated ID for the tab
		// * whether or not the given tab is active
		// In both cases this info is needed to support correct ARIA attributes.
		const tab = tabsData.value.get( props.name ) ?? {} as TabData;
		const isActive = computed( () => props.name === activeTab.value );
 
		return {
			tab,
			isActive
		};
	}
 
} );
</script>
 
<style lang="less">
@import ( reference ) '@wikimedia/codex-design-tokens/theme-wikimedia-ui.less';
 
.cdx-tab {
	// For CSS-only tab.
	&[ aria-hidden='true' ] {
		display: none;
	}
 
	&:focus {
		outline: @outline-base--focus;
	}
}
</style>