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
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-config-idiomatic-order",
|
||||
"stylelint-config-wikimedia"
|
||||
],
|
||||
"rules": {
|
||||
"selector-max-id": null,
|
||||
"selector-class-pattern": "^(tabber)"
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-idiomatic-order",
|
||||
"stylelint-config-wikimedia"
|
||||
],
|
||||
"rules": {
|
||||
"selector-max-id": null,
|
||||
"selector-class-pattern": "^(tabber)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
* @param {number} count
|
||||
*/
|
||||
function initTabber( tabber, count ) {
|
||||
var ACTIVETABCLASS = 'tabber__tab--active';
|
||||
|
||||
var tabPanels = tabber.querySelectorAll( ':scope > .tabber__section > .tabber__panel' );
|
||||
|
||||
var config = require( './config.json' ),
|
||||
header = tabber.querySelector( '.tabber__header' ),
|
||||
tabList = document.createElement( 'nav' ),
|
||||
prevButton = document.createElement( 'div' ),
|
||||
nextButton = document.createElement( 'div' );
|
||||
nextButton = document.createElement( 'div' ),
|
||||
indicator = document.createElement( 'div' );
|
||||
|
||||
var buildTabs = function () {
|
||||
var fragment = new DocumentFragment();
|
||||
|
@ -26,10 +29,14 @@ function initTabber( tabber, count ) {
|
|||
|
||||
// check if the hash is already used before
|
||||
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
|
||||
hash += ( 1 == hashCount ) ? '' : ( '-' + hashCount );
|
||||
hash += ( hashCount === 1 ) ? '' : ( '-' + hashCount );
|
||||
|
||||
tabPanel.setAttribute( 'id', hash );
|
||||
tabPanel.setAttribute( 'role', 'tabpanel' );
|
||||
|
@ -53,8 +60,9 @@ function initTabber( tabber, count ) {
|
|||
tabList.setAttribute( 'role', 'tablist' );
|
||||
prevButton.classList.add( 'tabber__header__prev' );
|
||||
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 ) {
|
||||
|
@ -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;
|
||||
if ( window.ResizeObserver ) {
|
||||
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
|
||||
tabList.addEventListener( 'scroll', function () {
|
||||
updateButtons();
|
||||
updateIndicator();
|
||||
} );
|
||||
|
||||
// Add class to enable animation
|
||||
|
@ -215,8 +230,7 @@ function initTabber( tabber, count ) {
|
|||
* @param {boolean} scrollIntoView
|
||||
*/
|
||||
function showPanel( targetHash, allowRemoteLoad, scrollIntoView ) {
|
||||
var ACTIVETABCLASS = 'tabber__tab--active',
|
||||
ACTIVEPANELCLASS = 'tabber__panel--active',
|
||||
var ACTIVEPANELCLASS = 'tabber__panel--active',
|
||||
targetPanel = document.getElementById( targetHash ),
|
||||
targetTab = document.getElementById( 'tab-' + targetHash ),
|
||||
section = targetPanel.parentNode,
|
||||
|
@ -225,13 +239,13 @@ function initTabber( tabber, count ) {
|
|||
|
||||
var loadTransclusion = function () {
|
||||
var loading = document.createElement( 'div' ),
|
||||
indicator = document.createElement( 'div' );
|
||||
loadingIndicator = document.createElement( 'div' );
|
||||
|
||||
targetPanel.setAttribute( 'aria-live', 'polite' );
|
||||
targetPanel.setAttribute( 'aria-busy', 'true' );
|
||||
loading.setAttribute( 'class', 'tabber__transclusion--loading' );
|
||||
indicator.setAttribute( 'class', 'tabber__loading-indicator' );
|
||||
loading.appendChild( indicator );
|
||||
loadingIndicator.setAttribute( 'class', 'tabber__loading-indicator' );
|
||||
loading.appendChild( loadingIndicator );
|
||||
targetPanel.textContent = '';
|
||||
targetPanel.appendChild( loading );
|
||||
loadPage( targetPanel, targetPanel.dataset.tabberLoadUrl );
|
||||
|
@ -264,6 +278,8 @@ function initTabber( tabber, count ) {
|
|||
targetPanel.classList.add( ACTIVEPANELCLASS );
|
||||
targetPanel.setAttribute( 'aria-hidden', false );
|
||||
|
||||
updateIndicator();
|
||||
|
||||
// Lazyload transclusion if needed
|
||||
if ( allowRemoteLoad &&
|
||||
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
|
||||
// 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, '\\$&' ) ) ) {
|
||||
targetHash = tabList.firstElementChild.getAttribute( 'id' ).substring( 4 );
|
||||
targetHash = tabList.firstElementChild.getAttribute( 'id' ).slice( 4 );
|
||||
scrollIntoView = false;
|
||||
}
|
||||
|
||||
|
@ -373,7 +389,7 @@ function initTabber( tabber, count ) {
|
|||
// Respond to clicks on the nav tabs
|
||||
Array.prototype.forEach.call( tabList.children, function ( tab ) {
|
||||
tab.addEventListener( 'click', function ( event ) {
|
||||
var targetHash = tab.getAttribute( 'href' ).substring( 1 );
|
||||
var targetHash = tab.getAttribute( 'href' ).slice( 1 );
|
||||
event.preventDefault();
|
||||
if ( !config || config.updateLocationOnTabChange ) {
|
||||
// Add hash to the end of the URL
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
&__header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* defend against <section> needing 100% */
|
||||
flex-shrink: 0;
|
||||
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,
|
||||
&__section {
|
||||
scrollbar-width: none;
|
||||
|
@ -120,7 +128,6 @@
|
|||
|
||||
&--active,
|
||||
&--active:visited {
|
||||
box-shadow: inset 0 -2px 0 0 #36c;
|
||||
color: #36c;
|
||||
}
|
||||
}
|
||||
|
@ -207,12 +214,10 @@
|
|||
.tabber {
|
||||
&__tab {
|
||||
&:hover {
|
||||
box-shadow: inset 0 -2px 0 0 #447ff5;
|
||||
color: #447ff5;
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0 -2px 0 0 #2a4b8d;
|
||||
color: #2a4b8d;
|
||||
}
|
||||
}
|
||||
|
@ -234,13 +239,26 @@
|
|||
|
||||
// Smooth scrolling through a large number of panels hurt performance on mobile
|
||||
// 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 {
|
||||
&__header,
|
||||
&__section,
|
||||
&__tabs {
|
||||
scroll-behavior: smooth;
|
||||
@media ( min-width: 720px ) {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue