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.
这个提交包含在:
父节点
096ef941ce
当前提交
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
正在加载...
在新工单中引用