mirror of
https://github.com/StarCitizenTools/mediawiki-extensions-TabberNeue.git
synced 2024-11-23 16:06:45 +00:00
feat: separate indicator from active tab
This will allow us to animate the indicator separately. Which will be needed to make the indicator scroll with the panel.
This commit is contained in:
parent
096ef941ce
commit
f1e0df2112
|
@ -5,13 +5,16 @@
|
||||||
* @param {number} count
|
* @param {number} count
|
||||||
*/
|
*/
|
||||||
function initTabber( tabber, count ) {
|
function initTabber( tabber, count ) {
|
||||||
|
var ACTIVETABCLASS = 'tabber__tab--active';
|
||||||
|
|
||||||
var tabPanels = tabber.querySelectorAll( ':scope > .tabber__section > .tabber__panel' );
|
var tabPanels = tabber.querySelectorAll( ':scope > .tabber__section > .tabber__panel' );
|
||||||
|
|
||||||
var config = require( './config.json' ),
|
var config = require( './config.json' ),
|
||||||
header = tabber.querySelector( '.tabber__header' ),
|
header = tabber.querySelector( '.tabber__header' ),
|
||||||
tabList = document.createElement( 'nav' ),
|
tabList = document.createElement( 'nav' ),
|
||||||
prevButton = document.createElement( 'div' ),
|
prevButton = document.createElement( 'div' ),
|
||||||
nextButton = document.createElement( 'div' );
|
nextButton = document.createElement( 'div' ),
|
||||||
|
indicator = document.createElement( 'div' );
|
||||||
|
|
||||||
var buildTabs = function () {
|
var buildTabs = function () {
|
||||||
var fragment = new DocumentFragment();
|
var fragment = new DocumentFragment();
|
||||||
|
@ -26,10 +29,14 @@ function initTabber( tabber, count ) {
|
||||||
|
|
||||||
// check if the hash is already used before
|
// check if the hash is already used before
|
||||||
var hashCount = 0;
|
var hashCount = 0;
|
||||||
hashList.forEach( function(h) { hashCount += ( h == hash ) ? 1 : 0; } );
|
hashList.forEach(
|
||||||
|
function ( h ) {
|
||||||
|
hashCount += ( h === hash ) ? 1 : 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// append counter if the same hash already used
|
// append counter if the same hash already used
|
||||||
hash += ( 1 == hashCount ) ? '' : ( '-' + hashCount );
|
hash += ( hashCount === 1 ) ? '' : ( '-' + hashCount );
|
||||||
|
|
||||||
tabPanel.setAttribute( 'id', hash );
|
tabPanel.setAttribute( 'id', hash );
|
||||||
tabPanel.setAttribute( 'role', 'tabpanel' );
|
tabPanel.setAttribute( 'role', 'tabpanel' );
|
||||||
|
@ -53,8 +60,9 @@ function initTabber( tabber, count ) {
|
||||||
tabList.setAttribute( 'role', 'tablist' );
|
tabList.setAttribute( 'role', 'tablist' );
|
||||||
prevButton.classList.add( 'tabber__header__prev' );
|
prevButton.classList.add( 'tabber__header__prev' );
|
||||||
nextButton.classList.add( 'tabber__header__next' );
|
nextButton.classList.add( 'tabber__header__next' );
|
||||||
|
indicator.classList.add( 'tabber__indicator' );
|
||||||
|
|
||||||
header.append( prevButton, tabList, nextButton );
|
header.append( prevButton, tabList, nextButton, indicator );
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateSectionHeight = function ( section, activePanel ) {
|
var updateSectionHeight = function ( section, activePanel ) {
|
||||||
|
@ -85,6 +93,12 @@ function initTabber( tabber, count ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var updateIndicator = function () {
|
||||||
|
var activeTab = tabList.querySelector( '.' + ACTIVETABCLASS );
|
||||||
|
indicator.style.width = activeTab.offsetWidth + 'px';
|
||||||
|
indicator.style.transform = 'translateX(' + ( activeTab.offsetLeft - tabList.scrollLeft ) + 'px)';
|
||||||
|
};
|
||||||
|
|
||||||
var resizeObserver = null;
|
var resizeObserver = null;
|
||||||
if ( window.ResizeObserver ) {
|
if ( window.ResizeObserver ) {
|
||||||
resizeObserver = new ResizeObserver( mw.util.debounce( 250, onElementResize ) );
|
resizeObserver = new ResizeObserver( mw.util.debounce( 250, onElementResize ) );
|
||||||
|
@ -161,6 +175,7 @@ function initTabber( tabber, count ) {
|
||||||
// Also triggered by side-scrolling using other means other than the buttons
|
// Also triggered by side-scrolling using other means other than the buttons
|
||||||
tabList.addEventListener( 'scroll', function () {
|
tabList.addEventListener( 'scroll', function () {
|
||||||
updateButtons();
|
updateButtons();
|
||||||
|
updateIndicator();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Add class to enable animation
|
// Add class to enable animation
|
||||||
|
@ -215,8 +230,7 @@ function initTabber( tabber, count ) {
|
||||||
* @param {boolean} scrollIntoView
|
* @param {boolean} scrollIntoView
|
||||||
*/
|
*/
|
||||||
function showPanel( targetHash, allowRemoteLoad, scrollIntoView ) {
|
function showPanel( targetHash, allowRemoteLoad, scrollIntoView ) {
|
||||||
var ACTIVETABCLASS = 'tabber__tab--active',
|
var ACTIVEPANELCLASS = 'tabber__panel--active',
|
||||||
ACTIVEPANELCLASS = 'tabber__panel--active',
|
|
||||||
targetPanel = document.getElementById( targetHash ),
|
targetPanel = document.getElementById( targetHash ),
|
||||||
targetTab = document.getElementById( 'tab-' + targetHash ),
|
targetTab = document.getElementById( 'tab-' + targetHash ),
|
||||||
section = targetPanel.parentNode,
|
section = targetPanel.parentNode,
|
||||||
|
@ -225,13 +239,13 @@ function initTabber( tabber, count ) {
|
||||||
|
|
||||||
var loadTransclusion = function () {
|
var loadTransclusion = function () {
|
||||||
var loading = document.createElement( 'div' ),
|
var loading = document.createElement( 'div' ),
|
||||||
indicator = document.createElement( 'div' );
|
loadingIndicator = document.createElement( 'div' );
|
||||||
|
|
||||||
targetPanel.setAttribute( 'aria-live', 'polite' );
|
targetPanel.setAttribute( 'aria-live', 'polite' );
|
||||||
targetPanel.setAttribute( 'aria-busy', 'true' );
|
targetPanel.setAttribute( 'aria-busy', 'true' );
|
||||||
loading.setAttribute( 'class', 'tabber__transclusion--loading' );
|
loading.setAttribute( 'class', 'tabber__transclusion--loading' );
|
||||||
indicator.setAttribute( 'class', 'tabber__loading-indicator' );
|
loadingIndicator.setAttribute( 'class', 'tabber__loading-indicator' );
|
||||||
loading.appendChild( indicator );
|
loading.appendChild( loadingIndicator );
|
||||||
targetPanel.textContent = '';
|
targetPanel.textContent = '';
|
||||||
targetPanel.appendChild( loading );
|
targetPanel.appendChild( loading );
|
||||||
loadPage( targetPanel, targetPanel.dataset.tabberLoadUrl );
|
loadPage( targetPanel, targetPanel.dataset.tabberLoadUrl );
|
||||||
|
@ -264,6 +278,8 @@ function initTabber( tabber, count ) {
|
||||||
targetPanel.classList.add( ACTIVEPANELCLASS );
|
targetPanel.classList.add( ACTIVEPANELCLASS );
|
||||||
targetPanel.setAttribute( 'aria-hidden', false );
|
targetPanel.setAttribute( 'aria-hidden', false );
|
||||||
|
|
||||||
|
updateIndicator();
|
||||||
|
|
||||||
// Lazyload transclusion if needed
|
// Lazyload transclusion if needed
|
||||||
if ( allowRemoteLoad &&
|
if ( allowRemoteLoad &&
|
||||||
targetPanel.dataset.tabberPendingLoad &&
|
targetPanel.dataset.tabberPendingLoad &&
|
||||||
|
@ -357,7 +373,7 @@ function initTabber( tabber, count ) {
|
||||||
// Switch to the first tab if no targetHash or no tab is detected and do not scroll to it
|
// Switch to the first tab if no targetHash or no tab is detected and do not scroll to it
|
||||||
// TODO: Remove the polyfill with CSS.escape when we are dropping IE support
|
// TODO: Remove the polyfill with CSS.escape when we are dropping IE support
|
||||||
if ( !targetHash || !tabList.querySelector( '#tab-' + targetHash.replace( /[^a-zA-Z0-9-_]/g, '\\$&' ) ) ) {
|
if ( !targetHash || !tabList.querySelector( '#tab-' + targetHash.replace( /[^a-zA-Z0-9-_]/g, '\\$&' ) ) ) {
|
||||||
targetHash = tabList.firstElementChild.getAttribute( 'id' ).substring( 4 );
|
targetHash = tabList.firstElementChild.getAttribute( 'id' ).slice( 4 );
|
||||||
scrollIntoView = false;
|
scrollIntoView = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +389,7 @@ function initTabber( tabber, count ) {
|
||||||
// Respond to clicks on the nav tabs
|
// Respond to clicks on the nav tabs
|
||||||
Array.prototype.forEach.call( tabList.children, function ( tab ) {
|
Array.prototype.forEach.call( tabList.children, function ( tab ) {
|
||||||
tab.addEventListener( 'click', function ( event ) {
|
tab.addEventListener( 'click', function ( event ) {
|
||||||
var targetHash = tab.getAttribute( 'href' ).substring( 1 );
|
var targetHash = tab.getAttribute( 'href' ).slice( 1 );
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if ( !config || config.updateLocationOnTabChange ) {
|
if ( !config || config.updateLocationOnTabChange ) {
|
||||||
// Add hash to the end of the URL
|
// Add hash to the end of the URL
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
&__header {
|
&__header {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
/* defend against <section> needing 100% */
|
/* defend against <section> needing 100% */
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-shadow: inset 0 -1px 0 0 #a2a9b1;
|
box-shadow: inset 0 -1px 0 0 #a2a9b1;
|
||||||
|
@ -89,6 +90,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__indicator {
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #36c;
|
||||||
|
block-size: 2px;
|
||||||
|
inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__header,
|
&__header,
|
||||||
&__section {
|
&__section {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
@ -120,7 +128,6 @@
|
||||||
|
|
||||||
&--active,
|
&--active,
|
||||||
&--active:visited {
|
&--active:visited {
|
||||||
box-shadow: inset 0 -2px 0 0 #36c;
|
|
||||||
color: #36c;
|
color: #36c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,12 +214,10 @@
|
||||||
.tabber {
|
.tabber {
|
||||||
&__tab {
|
&__tab {
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: inset 0 -2px 0 0 #447ff5;
|
|
||||||
color: #447ff5;
|
color: #447ff5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
box-shadow: inset 0 -2px 0 0 #2a4b8d;
|
|
||||||
color: #2a4b8d;
|
color: #2a4b8d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,14 +239,27 @@
|
||||||
|
|
||||||
// Smooth scrolling through a large number of panels hurt performance on mobile
|
// Smooth scrolling through a large number of panels hurt performance on mobile
|
||||||
// Also it will trigger unnessecary lazyloading as lazyload content show up momentarily
|
// Also it will trigger unnessecary lazyloading as lazyload content show up momentarily
|
||||||
@media ( prefers-reduced-motion: no-preference ) and ( min-width: 720px ) {
|
@media ( prefers-reduced-motion: no-preference ) {
|
||||||
|
.tabber {
|
||||||
|
&__header {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__indicator {
|
||||||
|
// Placeholder animation
|
||||||
|
// TODO: Allow indicator to sync with panel scrolling
|
||||||
|
transition: transform 250ms ease, width 250ms ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tabber--animate {
|
.tabber--animate {
|
||||||
.tabber {
|
.tabber {
|
||||||
&__header,
|
|
||||||
&__section,
|
&__section,
|
||||||
&__tabs {
|
&__tabs {
|
||||||
|
@media ( min-width: 720px ) {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue