Build the sticky header skeleton

The sticky header is currently disabled unconditionally
and nothing is wired up, with placeholders for data and
functionality which will be added in future.

Bug: T289716
Change-Id: I16223ce849267e718aad22b8a24b2327332ac8b7
This commit is contained in:
jdlrobson 2021-08-30 15:44:00 -07:00
parent a068d6125d
commit 8657171471
15 changed files with 188 additions and 19 deletions

View File

@ -5,7 +5,7 @@
},
{
"resourceModule": "skins.vector.styles",
"maxSize": "9.62 kB"
"maxSize": "9.7 kB"
},
{
"resourceModule": "skins.vector.legacy.js",

View File

@ -381,14 +381,6 @@ class Hooks {
$bodyAttrs['class'] .= ' skin-vector-search-vue';
}
if (
VectorServices::getFeatureManager()->isFeatureEnabled(
Constants::FEATURE_STICKY_HEADER
)
) {
$bodyAttrs['class'] .= ' skin-vector-sticky-header';
}
$config = $sk->getConfig();
// Should we disable the max-width styling?
if ( !self::isSkinVersionLegacy() && $sk->getTitle() && self::shouldDisableMaxWidth(

View File

@ -44,6 +44,10 @@ class SkinVector extends SkinMustache {
/** @var int */
private const MENU_TYPE_DROPDOWN = 2;
private const MENU_TYPE_PORTAL = 3;
private const NO_ICON = [
'icon' => 'none',
'class' => 'sticky-header-icon'
];
/**
* T243281: Code used to track clicks to opt-out link.
@ -296,6 +300,24 @@ class SkinVector extends SkinMustache {
Hooks::onSkinTemplateNavigation( $skin, $content_navigation );
}
/**
* Generate data needed to generate the sticky header.
* Lack of i18n is intentional and will be done as part of follow up work.
* @return array
*/
private function getStickyHeaderData() {
return [
'title' => 'Audre Lorde',
'heading' => 'Introduction',
'primary-action' => 'Primary action',
'data-icon-start' => self::NO_ICON,
'data-icon-end' => self::NO_ICON,
'data-icons' => [
self::NO_ICON, self::NO_ICON, self::NO_ICON, self::NO_ICON
]
];
}
/**
* @inheritDoc
*/
@ -338,7 +360,9 @@ class SkinVector extends SkinMustache {
'sidebar-visible' => $this->isSidebarVisible(),
'is-language-in-header' => $this->isLanguagesInHeader(),
'data-vector-sticky-header' => VectorServices::getFeatureManager()->isFeatureEnabled(
Constants::FEATURE_STICKY_HEADER
) ? $this->getStickyHeaderData() : false,
] );
if ( $skin->getUser()->isRegistered() ) {

View File

@ -0,0 +1 @@
<div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-{{icon}} {{class}}"></div>

View File

@ -0,0 +1,29 @@
<header id="vector-sticky-header"
class="vector-sticky-header {{#is-visible}}vector-sticky-header-visible{{/is-visible}}">
<div class="vector-sticky-header-start">
<div class="vector-sticky-header-icon-start">
{{#data-icon-start}}
{{>Icon}}
{{/data-icon-start}}
</div>
<div class="vector-sticky-header-context-bar">
<div class="vector-sticky-header-context-bar-primary">{{title}}</div>
<div class="vector-sticky-header-context-bar-secondary">{{heading}}</div>
</div>
</div>
<div class="vector-sticky-header-end">
<div class="vector-sticky-header-icons">
{{#data-icons}}
{{>Icon}}
{{/data-icons}}
</div>
<div class="mw-ui-button">
{{primary-action}}
</div>
<div class="vector-sticky-header-icon-end">
{{#data-icon-end}}
{{>Icon}}
{{/data-icon-end}}
</div>
</div>
</header>

View File

@ -42,7 +42,6 @@
{{#sidebar-visible}}checked{{/sidebar-visible}}>
{{>Header}}
<div class="mw-workspace-container">
{{>Navigation}}
<div class="mw-content-container">
@ -98,3 +97,6 @@
</div>
</div> {{! END mw-page-container-inner }}
</div> {{! END mw-page-container }}
{{#data-vector-sticky-header}}
{{>StickyHeader}}
{{/data-vector-sticky-header}}

View File

@ -152,3 +152,9 @@
// Transitions
@transition-duration-base: 100ms;
//
// Layout
//
@max-width-page-container: unit( 1650px / @font-size-browser, em ); // 103.125em @ 16
@padding-horizontal-page-container: unit( 30px / @font-size-browser, em ); // 1.875em @ 16

View File

@ -1,5 +1,6 @@
var collapsibleTabs = require( '../skins.vector.legacy.js/collapsibleTabs.js' ),
vector = require( '../skins.vector.legacy.js/vector.js' ),
stickyHeader = require( './stickyHeader.js' ),
languageButton = require( './languageButton.js' ),
initSearchLoader = require( './searchLoader.js' ).initSearchLoader,
dropdownMenus = require( './dropdownMenus.js' ),
@ -72,6 +73,7 @@ function main( window ) {
initSearchLoader( document );
searchToggle();
languageButton();
stickyHeader();
}
main( window );

View File

@ -0,0 +1,8 @@
module.exports = function () {
var header = document.getElementById( 'vector-sticky-header' );
if ( !header ) {
return;
}
// TODO: Use IntersectionObserver
header.classList.add( 'vector-sticky-header-visible' );
};

View File

@ -0,0 +1,73 @@
@import '../../common/variables.less';
@import 'mediawiki.mixins.less';
.vector-sticky-header {
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: @z-index-header;
transform: translateY( -100% );
transition: transform 250ms linear;
display: flex;
align-items: center;
max-width: @max-width-page-container + @padding-horizontal-page-container + @padding-horizontal-page-container;
margin: 0 auto;
background: @background-color-base;
background-color: #fffffff7;
border-bottom: 1px solid @colorGray14;
// FIXME: Should this adapt to different thresholds? Ask Alex!
padding: 6px 8px 6px 10px;
justify-content: space-between;
box-sizing: border-box;
@media ( min-width: @width-breakpoint-desktop ) {
padding: 6px 25px;
}
&-visible {
transform: translateY( 0% );
}
//
// Layout
//
&-start {
display: flex;
align-items: center;
}
&-end {
display: flex;
align-items: center;
}
//
// Components
//
&-icons,
&-context-bar {
display: flex;
align-items: center;
white-space: nowrap;
margin: 0 15px;
padding-left: 30px;
}
&-context-bar {
border-left: 1px solid #c8c8c8;
}
&-context-bar-primary {
padding-right: 15px;
font-size: unit( 22 / @font-size-browser, em );
}
&-context-bar-secondary {
&:before {
padding-right: 15px;
content: '|';
}
}
}

View File

@ -65,9 +65,7 @@
// Page container
@max-width-page-container: unit( 1650px / @font-size-browser, em ); // 103.125em @ 16
@min-width-page-container--padded: @max-width-page-container + ( 2 * @padding-horizontal-page-container ); // 106.875em
@padding-horizontal-page-container: unit( 30px / @font-size-browser, em ); // 1.875em @ 16
// Content containers
@ -147,12 +145,6 @@ body {
// allow z-index to apply so search results overlay article
position: relative;
z-index: @z-index-header;
.skin-vector-sticky-header & {
position: sticky;
top: 0;
background: @background-color-base;
}
}
/* Searchbox */

View File

@ -16,6 +16,7 @@
@import './components/Sidebar.less';
@import './components/LanguageButton.less';
@import './components/UserLinks.less';
@import './components/StickyHeader.less';
}
@media all {

View File

@ -192,6 +192,7 @@
"name": "resources/skins.vector.js/config.json",
"callback": "Vector\\Hooks::getVectorResourceLoaderConfig"
},
"resources/skins.vector.js/stickyHeader.js",
"resources/skins.vector.js/dropdownMenus.js",
"resources/skins.vector.js/sidebar.js",
"resources/skins.vector.legacy.js/collapsibleTabs.js",

View File

@ -0,0 +1,25 @@
import template from '!!raw-loader!../includes/templates/StickyHeader.mustache';
import Icon from '!!raw-loader!../includes/templates/Icon.mustache';
const NO_ICON = {
icon: 'none',
class: 'sticky-header-icon'
};
const data = {
title: 'Audre Lorde',
heading: 'Introduction',
'primary-action': 'Primary action',
'is-visible': true,
'data-icon-start': NO_ICON,
'data-icon-end': NO_ICON,
'data-icons': [
NO_ICON, NO_ICON, NO_ICON, NO_ICON
]
};
export const STICKY_HEADER_TEMPLATE_PARTIALS = {
Icon
};
export { template, data };

View File

@ -0,0 +1,13 @@
import mustache from 'mustache';
import '../resources/skins.vector.styles/components/StickyHeader.less';
import { template, data,
STICKY_HEADER_TEMPLATE_PARTIALS } from './StickyHeader.stories.data';
export default {
title: 'StickyHeader'
};
export const stickyHeader = () => mustache.render(
template, data, STICKY_HEADER_TEMPLATE_PARTIALS
);