<div class="tabs js-tabs ">
<div class="tabs__nav">
<ul class="tabs__nav-list">
<li class="tabs__nav-item">
<a href="#tab-1" class="tabs__nav-link js-tabs-control is-current">
Tab 1
</a>
</li>
<li class="tabs__nav-item">
<a href="#tab-2" class="tabs__nav-link js-tabs-control ">
Tab 2
</a>
</li>
<li class="tabs__nav-item">
<a href="#tab-3" class="tabs__nav-link js-tabs-control ">
Tab 3
</a>
</li>
</ul>
</div>
<div class="tabs__content">
<div class="tabs__content-item js-tabs-content is-open" id="tab-1">
<div class="tabs__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid labore aut quae architecto modi eius quod repudiandae quis culpa, unde sint, magnam voluptate earum ipsa tempora voluptatem temporibus nam odio.
</div>
</div>
<div class="tabs__content-item js-tabs-content " id="tab-2">
<div class="tabs__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus!
</div>
</div>
<div class="tabs__content-item js-tabs-content " id="tab-3">
<div class="tabs__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus!
</div>
</div>
</div>
</div>
{% set navItems = [] %}
{% for item in data.items %}
{% set href = '#' ~ item.id %}
{% set navItems = navItems|merge([item|merge({ class: 'js-tabs-control', href: href })]) %}
{% endfor %}
<div class="tabs js-tabs {{ modifier }} {{ class }}">
<div class="tabs__nav">
<ul class="tabs__nav-list">
{% for item in navItems %}
<li class="tabs__nav-item">
<a href="{{ item.href }}" class="tabs__nav-link {{ item.class }} {% if item.current == true %}is-current{% endif %}">
{{ item.title }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="tabs__content">
{% for item in data.items %}
<div class="tabs__content-item js-tabs-content {% if item.current == true %}is-open{% endif %}" id="{{ item.id }}">
<div class="tabs__content-inner">
{{ item.content }}
</div>
</div>
{% endfor %}
</div>
</div>
{
"language": "en-US",
"prefix": "tabs",
"data": {
"items": [
{
"title": "Tab 1",
"id": "tab-1",
"content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid labore aut quae architecto modi eius quod repudiandae quis culpa, unde sint, magnam voluptate earum ipsa tempora voluptatem temporibus nam odio.",
"current": true
},
{
"title": "Tab 2",
"id": "tab-2",
"content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus!"
},
{
"title": "Tab 3",
"id": "tab-3",
"content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint cum id dolore quam recusandae, repellendus perspiciatis, libero doloremque! Quis dolorum ipsum, rerum vel mollitia, nemo maiores porro quaerat minus temporibus!"
}
]
}
}
.tabs__nav {
padding: 20px;
}
.tabs__nav-item {
display: inline-block;
}
.tabs__nav-link {
text-decoration: none;
&.is-current {
text-decoration: underline;
}
}
.tabs__content-item {
display: none;
html.no-js &,
&.is-open {
display: block;
}
}
.tabs__content-inner {
padding: 20px;
}
import Helpers from '../helpers/helpers';
import './tabs.scss';
export enum TabsAnimationType {
slide,
fade,
fadeAndSlide,
}
export enum TabsAnimationDirection {
in,
out,
}
export interface ITabsSettings {
openClass?: string;
currentClass?: string;
tabsClass?: string;
controlClass?: string;
contentClass?: string;
animationSlideTime?: number;
animationFadeTime?: number;
animationSlideEase?: string;
animationFadeEase?: string;
animationType?: TabsAnimationType;
}
export const tabsSettings: ITabsSettings = {
animationFadeEase: 'swing',
animationFadeTime: 500,
animationSlideEase: 'swing',
animationSlideTime: 500,
animationType: TabsAnimationType.fadeAndSlide,
contentClass: 'js-tabs-content',
controlClass: 'js-tabs-control',
currentClass: 'is-current',
openClass: 'is-open',
tabsClass: 'js-tabs',
};
export interface IAnimationArgs {
height: string;
opacity: number;
}
export default class Tabs {
settings: ITabsSettings;
element: JQuery;
timeout: number;
private onOpenComplete: () => void;
private onCloseComplete: () => void;
constructor(target: HTMLElement, settings: ITabsSettings = {}) {
this.settings = jQuery.extend(tabsSettings, settings) as ITabsSettings;
this.element = $(target);
}
getTabs(): JQuery {
let tabs: JQuery;
if (this.element.hasClass(this.settings.tabsClass)) {
tabs = this.element.addClass(this.settings.tabsClass);
} else {
tabs = this.element.parents('.' + this.settings.tabsClass);
}
return tabs.length ? $(tabs.get(0)) : tabs;
}
getCurrentTab(): JQuery {
let element: JQuery;
if (this.element.hasClass(this.settings.controlClass)) {
element = $(this.element.attr('href'));
} else if (this.element.hasClass(this.settings.contentClass)) {
element = this.element;
}
return element;
}
open(tab: JQuery = this.getCurrentTab(), recursive: boolean = false, initialLoad: boolean = false): void {
const parents: JQuery = this.getTabs();
let hasActiveControlClass: boolean = false;
if (parents.length) {
hasActiveControlClass = parents.find('[href="#' + tab.attr('id') + '"].' + this.settings.controlClass).hasClass(this.settings.currentClass);
}
if (tab.length && !tab.hasClass(this.settings.openClass) && !hasActiveControlClass) {
this.onCloseComplete = (): void => {
if (tab.length) {
tab.addClass(this.settings.openClass);
if (!recursive && !Helpers.isOnScreen($(tab.get(0)))) {
Helpers.scrollToTarget(tab);
}
}
if (!recursive && !initialLoad) {
this.animate(TabsAnimationDirection.in, tab);
}
};
if (parents.length) {
this.closeAll(parents, recursive || initialLoad, tab);
const parent: JQuery = parents.parents('.' + this.settings.tabsClass);
if (parent.length) {
const parentTabs: Tabs = new Tabs(parent.get(0));
parentTabs.open(null, true, initialLoad);
}
if (!recursive && !initialLoad) {
// sweet dreams
} else {
this.onCloseComplete();
}
} else {
this.onCloseComplete();
}
if (parents.length) {
parents.find('[href="#' + tab.attr('id') + '"].' + this.settings.controlClass).addClass(this.settings.currentClass);
}
}
}
close(tab: JQuery = this.getCurrentTab(), recursive: boolean = false, newTab: JQuery = null): void {
if (tab.length) {
if (!recursive) {
this.animate(TabsAnimationDirection.out, tab, (): void => {
tab.removeClass(this.settings.openClass);
}, newTab);
} else {
tab.removeClass(this.settings.openClass);
}
}
}
closeAll(tabs: JQuery = this.getTabs(), recursive: boolean = false, newTab: JQuery = null): void {
if (tabs.length) {
const tab: JQuery = this.getCurrentTab();
const open: JQuery = tabs.find('.' + this.settings.contentClass);
open.each((index: number, element: HTMLElement): void => {
if (!$(element).is(tab)) {
this.close($(element), recursive, newTab);
}
});
tabs.find('.' + this.settings.controlClass).removeClass(this.settings.currentClass);
}
}
animate(type: TabsAnimationDirection, tab: JQuery = this.getCurrentTab(), onComplete: () => void = null, newtab: JQuery = null): void {
if (tab.length) {
const triggerOnComplete: () => void = (): void => {
tab.removeAttr('style');
if (onComplete) {
onComplete();
}
switch (type) {
case TabsAnimationDirection.in:
if (this.onOpenComplete) {
this.onOpenComplete();
}
this.onOpenComplete = null;
break;
case TabsAnimationDirection.out:
if (this.onCloseComplete) {
this.onCloseComplete();
}
this.onCloseComplete = null;
break;
}
};
if (tab.length) {
if (tab.get(0).offsetHeight === 0) {
tab.removeAttr('style');
}
const contentHeight: string = tab.get(0).offsetHeight + 'px';
let newContentHeight: string = '0px';
if (newtab) {
const isOpen: boolean = newtab.hasClass(this.settings.openClass);
if (!isOpen) {
newtab.addClass(this.settings.openClass);
}
newContentHeight = newtab.get(0).offsetHeight + 'px';
if (!isOpen) {
newtab.removeClass(this.settings.openClass);
}
}
const firstTab: JQuery = tab.length ? $(tab.get(0)) : tab;
switch (this.settings.animationType) {
case TabsAnimationType.slide:
switch (type) {
case TabsAnimationDirection.in:
firstTab.stop().css({
height: 0,
}).animate({
height: contentHeight,
}, this.settings.animationFadeTime, this.settings.animationFadeEase, triggerOnComplete);
break;
case TabsAnimationDirection.out:
firstTab.stop().animate({
height: 0,
}, this.settings.animationSlideTime, this.settings.animationSlideEase, triggerOnComplete);
break;
}
break;
case TabsAnimationType.fade:
switch (type) {
case TabsAnimationDirection.in:
firstTab.stop().css({
opacity: 0,
}).animate({
opacity: 1,
}, this.settings.animationFadeTime, this.settings.animationFadeEase, triggerOnComplete);
break;
case TabsAnimationDirection.out:
firstTab.stop().animate({
opacity: 0,
}, this.settings.animationSlideTime, this.settings.animationSlideEase, triggerOnComplete);
break;
}
break;
case TabsAnimationType.fadeAndSlide:
switch (type) {
case TabsAnimationDirection.in:
firstTab.stop().css({
height: contentHeight,
opacity: 0,
}).animate({
height: contentHeight,
opacity: 1,
}, this.settings.animationFadeTime, this.settings.animationFadeEase, triggerOnComplete);
break;
case TabsAnimationDirection.out:
firstTab.stop().animate({
height: newContentHeight,
opacity: 0,
}, this.settings.animationSlideTime, this.settings.animationSlideEase, triggerOnComplete);
break;
}
break;
}
}
}
}
}
function onControlClick(event: JQuery.Event): void {
event.preventDefault();
const tabs: Tabs = new Tabs(this);
tabs.open();
}
const onLoadHashCheck: () => void = (): void => {
if (window.location.hash && $(window.location.hash).length && $(window.location.hash).hasClass(tabsSettings.contentClass)) {
const tabs: Tabs = new Tabs($(window.location.hash).get(0));
tabs.open(null, false, true);
}
};
$((): void => {
$(document).on('click', '.' + tabsSettings.controlClass, onControlClick);
onLoadHashCheck();
});