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.
这个提交包含在:
alistair3149 2022-10-21 18:01:38 -04:00
父节点 096ef941ce
当前提交 f1e0df2112
找不到此签名对应的密钥
共有 3 个文件被更改,包括 61 次插入27 次删除

查看文件

@ -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;
}
}
}
}