<div class="js-pill-tabs m-pill-tabs" role="tablist" aria-orientation="horizontal" tabindex="0">
    <button type="button" class="m-pill-tabs__item js-pill-tabs__tab is-active" role="tab" aria-selected="true" aria-controls="" data-state="active" id="" tabindex="-1" data-orientation="horizontal">Overview
    </button>
    <button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls="" data-state="inactive" id="" tabindex="-1" data-orientation="horizontal">Analytics
    </button>
    <button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls="" data-state="inactive" id="" tabindex="-1" data-orientation="horizontal">Reports
    </button>
    <button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls="" data-state="inactive" id="" tabindex="-1" data-orientation="horizontal">Settings
    </button>
</div>
<div class="js-pill-tabs__panel" role="tabpanel">Overview content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Analytics content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Reports content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Settings content</div>

<style>
    body {
        background-color: #fff;
    }
</style>
{{#if links}}
<nav class="m-pill-tabs" aria-orientation="horizontal">
	<a href="#overview" class="m-pill-tabs__item is-active">Overview</a>
	<a href="#analytics" class="m-pill-tabs__item">Analytics</a>
	<a href="#reports" class="m-pill-tabs__item">Reports</a>
	<a href="#settings" class="m-pill-tabs__item">Settings</a>
</nav>
{{else}}
<div class="js-pill-tabs m-pill-tabs" role="tablist" aria-orientation="horizontal" tabindex="0">
	<button type="button" class="m-pill-tabs__item js-pill-tabs__tab is-active" role="tab" aria-selected="true" aria-controls=""
			data-state="active" id=""
			tabindex="-1" data-orientation="horizontal">Overview
	</button>
	<button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls=""
			data-state="inactive" id=""
			tabindex="-1" data-orientation="horizontal">Analytics
	</button>
	<button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls=""
			data-state="inactive" id=""
			tabindex="-1" data-orientation="horizontal">Reports
	</button>
	<button type="button" class="m-pill-tabs__item js-pill-tabs__tab" role="tab" aria-selected="false" aria-controls=""
			data-state="inactive" id=""
			tabindex="-1" data-orientation="horizontal">Settings
	</button>
</div>
<div class="js-pill-tabs__panel" role="tabpanel">Overview content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Analytics content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Reports content</div>
<div class="js-pill-tabs__panel" role="tabpanel" hidden>Settings content</div>
{{/if}}

<style>
	body {
		background-color: #fff;
	}
</style>
{
  "links": false
}
  • Content:
    @charset "UTF-8";
    @use '../../configurations/mixins' as mixin;
    @use '../../configurations/bem' as bem;
    @use '../../configurations/config' as config;
    @use '../../configurations/variables' as var;
    @use '../../configurations/functions' as func;
    @use '../../configurations/colors/colors' as colors;
    @use '../../configurations/colors/colors-functions' as colorFunc;
    @use '../../vendor/grid/breakpoints' as breakpoint;
    @use '../../vendor/grid/grid' as grid;
    
    @include mixin.molecule(pill-tabs) {
    	background: colors.$color-ash;
    	border-radius: var.$border-radius;
    	border: 1px solid colors.$color-concrete;
    	padding: func.rhythm(0.5);
    	display: inline-flex;
    	flex-wrap: wrap;
    	gap: func.rhythm(0.5);
    
    	@include bem.e(item) {
    		padding: func.rhythm(0.5);
    		border-radius: var.$border-radius;
    		font-size: var.$size-medium;
    		color: colors.$color-cyberspace;
    		border: 1px solid transparent;
    
    		&.is-active {
    			pointer-events: none;
    		}
    
    		&.is-active,
    		&:active {
    			background-color: colors.$color-snow;
    			border: 1px solid colors.$color-concrete;
    			color: colors.$color-cyberspace;
    			text-decoration: none;
    		}
    
    		&:hover,
    		&:focus {
    			background-color: colors.$color-granit;
    			border-color: colors.$color-granit;
    			color: colors.$color-snow;
    			text-decoration: none;
    		}
    
    		&:focus:active {
    			background-color: colors.$color-ash;
    			border: 1px solid colors.$color-ash;
    			color: colors.$color-granit;
    			text-shadow: 0 1px 0 colors.$color-snow;
    			text-decoration: none;
    		}
    	}
    }
    
  • URL: /components/raw/pill-tabs/_pill-tabs.scss
  • Filesystem Path: src/molecules/pill-tabs/_pill-tabs.scss
  • Size: 1.5 KB
  • Content:
    const TABLIST_SELECTOR = '.js-pill-tabs[role="tablist"]';
    const TAB_SELECTOR = '.js-pill-tabs__tab[role="tab"]';
    const PANEL_SELECTOR = '.js-pill-tabs__panel[role="tabpanel"]';
    
    let tablistCounter = 0;
    
    function ensureUniqueId(element, fallbackId) {
    	const preferredId = element.id || fallbackId;
    	let nextId = preferredId;
    	let duplicate = document.getElementById(nextId);
    	let suffix = 0;
    
    	while (duplicate && duplicate !== element) {
    		suffix += 1;
    		nextId = `${preferredId}-${suffix}`;
    		duplicate = document.getElementById(nextId);
    	}
    
    	element.id = nextId;
    
    	return nextId;
    }
    
    function getInitialActiveIndex(tabs) {
    	let classIndex = -1;
    
    	for (let index = 0; index < tabs.length; index += 1) {
    		const tab = tabs[index];
    
    		if (tab.getAttribute('aria-selected') === 'true') {
    			return index;
    		}
    
    		if (classIndex === -1 && tab.classList.contains('is-active')) {
    			classIndex = index;
    		}
    	}
    
    	return classIndex !== -1 ? classIndex : 0;
    }
    
    function getPanelsForTablist(tablist, maxPanels) {
    	const panels = [];
    	let nextSibling = tablist.nextElementSibling;
    
    	while (nextSibling && panels.length < maxPanels) {
    		if (nextSibling.matches(TABLIST_SELECTOR)) {
    			break;
    		}
    
    		if (nextSibling.matches(PANEL_SELECTOR)) {
    			panels.push(nextSibling);
    		}
    
    		nextSibling = nextSibling.nextElementSibling;
    	}
    
    	return panels;
    }
    
    function getTabFromEventTarget(target, tablist) {
    	const tab = target.closest(TAB_SELECTOR);
    
    	return tab && tab.parentElement === tablist ? tab : null;
    }
    
    function getWrappedIndex(index, length) {
    	if (index < 0) {
    		return length - 1;
    	}
    
    	if (index >= length) {
    		return 0;
    	}
    
    	return index;
    }
    
    function setupTablist(tablist) {
    	const tabs = Array.from(tablist.children)
    		.filter((element) => element.matches(TAB_SELECTOR));
    
    	if (!tabs.length) {
    		return;
    	}
    
    	tablistCounter += 1;
    	const tablistId = ensureUniqueId(tablist, `pill-tabs-${tablistCounter}`);
    	const panels = getPanelsForTablist(tablist, tabs.length);
    	const panelSet = new Set(panels);
    
    	const panelByIndex = [];
    	let activeIndex = getInitialActiveIndex(tabs);
    
    	tablist.removeAttribute('tabindex');
    
    	tabs.forEach((tab, index) => {
    		const tabId = ensureUniqueId(tab, `${tablistId}-tab-${index + 1}`);
    		let panel = null;
    		const existingControl = tab.getAttribute('aria-controls');
    
    		tab.dataset.pillTabIndex = `${index}`;
    
    		if (existingControl) {
    			const existingPanel = document.getElementById(existingControl);
    
    			if (existingPanel && panelSet.has(existingPanel)) {
    				panel = existingPanel;
    			}
    		}
    
    		if (!panel) {
    			panel = panels[index];
    		}
    
    		if (panel) {
    			const panelId = ensureUniqueId(panel, `${tablistId}-panel-${index + 1}`);
    
    			tab.setAttribute('aria-controls', panelId);
    			panel.setAttribute('aria-labelledby', tabId);
    			panel.setAttribute('tabindex', '-1');
    			panelByIndex[index] = panel;
    		} else {
    			tab.removeAttribute('aria-controls');
    			panelByIndex[index] = null;
    		}
    	});
    
    	function setActiveTab(nextIndex, focusTab = false) {
    		activeIndex = nextIndex;
    
    		tabs.forEach((tab, index) => {
    			const isActive = index === activeIndex;
    			const panel = panelByIndex[index];
    
    			tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
    			tab.setAttribute('tabindex', isActive ? '0' : '-1');
    			tab.classList.toggle('is-active', isActive);
    			tab.setAttribute('data-state', isActive ? 'active' : 'inactive');
    
    			if (panel) {
    				panel.hidden = !isActive;
    			}
    		});
    
    		if (focusTab) {
    			tabs[activeIndex].focus();
    		}
    	}
    
    	tablist.addEventListener('click', (event) => {
    		const tab = getTabFromEventTarget(event.target, tablist);
    
    		if (!tab) {
    			return;
    		}
    
    		const nextIndex = Number(tab.dataset.pillTabIndex);
    
    		if (Number.isNaN(nextIndex)) {
    			return;
    		}
    
    		setActiveTab(nextIndex, true);
    	});
    
    	tablist.addEventListener('keydown', (event) => {
    		const tab = getTabFromEventTarget(event.target, tablist);
    
    		if (!tab) {
    			return;
    		}
    
    		const currentIndex = Number(tab.dataset.pillTabIndex);
    		const orientation = tablist.getAttribute('aria-orientation') === 'vertical' ? 'vertical' : 'horizontal';
    		let nextIndex = null;
    
    		if (Number.isNaN(currentIndex)) {
    			return;
    		}
    
    		if ((event.key === 'ArrowRight' && orientation === 'horizontal')
    			|| (event.key === 'ArrowDown' && orientation === 'vertical')) {
    			nextIndex = currentIndex + 1;
    		}
    
    		if (event.key === 'Home') {
    			nextIndex = 0;
    		}
    
    		if (event.key === 'End') {
    			nextIndex = tabs.length - 1;
    		}
    
    		if ((event.key === 'ArrowLeft' && orientation === 'horizontal')
    			|| (event.key === 'ArrowUp' && orientation === 'vertical')) {
    			nextIndex = currentIndex - 1;
    		}
    
    		if (nextIndex !== null) {
    			event.preventDefault();
    			setActiveTab(getWrappedIndex(nextIndex, tabs.length), true);
    		}
    	});
    
    	setActiveTab(activeIndex);
    }
    
    document.querySelectorAll(TABLIST_SELECTOR).forEach(setupTablist);
    
  • URL: /components/raw/pill-tabs/pill-tabs.js
  • Filesystem Path: src/molecules/pill-tabs/pill-tabs.js
  • Size: 4.8 KB

No notes defined.