Refactor chevron across components + separate watchstar

Consolidates the CSS responsible for styling chevrons into one
implementation. This removes the need for custom padding and
background positioning for the following components:

- "more" menu
- user menu
- watchstar
- language button
- languge button in sticky header

Instead of absolutely positioning the chevron on these components,
the parent label element is set to `display: inline-flex` so that
the chevron is vertically aligned and given enough space by default.

The watchstar, although not a chevron, is also given the
`display: inline-flex` treatment so that it can be aligned with
other elements in the toolbar.

This new implementation requires splitting the watchstar
component into legacy and modern due to a quirk in Firefox that
causes a bug with the watchstar in legacy Vector.

NOTE: This change causes visual changes due to the difference in
centering the chevrons via flexbox vs percentage positions.

Bug: T308344, T310838
Change-Id: Ie9e0fce1366cd25a5899fee49770de4a09424fe2
This commit is contained in:
Jan Drewniak 2022-07-15 14:27:57 -04:00
parent e83ca244af
commit 3c0559a71d
13 changed files with 170 additions and 135 deletions

View file

@ -15,7 +15,6 @@
@import './components/Indicators.less';
@import './components/SiteNotice.less';
@import './components/Menu.less';
@import './components/TabWatchstarLink.less';
@import './components/MenuDropdown.less';
@import './components/MenuPortal.less';
@import './components/SearchBox.less';

View file

@ -1,43 +1,43 @@
@import '../variables.less';
@import 'mediawiki.mixins.less';
/* Variants and Actions */
/**
* Targets:
* - language variants, Actions menus
* - more menu, user menu
* - ULS button in sticky header
*/
.emptyPortlet .vector-menu-heading,
.vector-menu-dropdown .vector-menu-heading,
.mw-interlanguage-selector {
display: flex;
color: @color-base--subtle;
&:after {
content: '';
background: url( images/arrow-down.svg ) 100% 50% no-repeat;
width: unit( 16 / @font-size-tabs / @font-size-browser, em );
height: unit( 16 / @font-size-tabs / @font-size-browser, em );
// Modify the color of the image from the default #202122 to approx. #404244 to match the text.
opacity: 0.84;
}
&:hover,
&:focus {
color: @color-base;
&:after {
opacity: 1;
}
}
}
.vector-menu-dropdown {
direction: ltr;
float: left;
cursor: pointer;
position: relative;
.vector-menu-heading {
color: @color-base--subtle;
position: relative;
display: block;
box-sizing: border-box;
&:after {
content: '';
background-image: url( images/arrow-down.svg );
background-position: 100% 50%;
background-repeat: no-repeat;
position: absolute;
top: unit( 10 / @font-size-tabs / @font-size-browser, em );
right: 8px;
bottom: 0;
width: unit( 16 / @font-size-tabs / @font-size-browser, em );
// Modify the color of the image from the default #202122 to approx. #404244 to match the text.
opacity: 0.84;
}
&:hover,
&:focus {
color: @color-base;
&:after {
opacity: 1;
}
}
}
// The menu itself.
.vector-menu-content {
background-color: @background-color-base;
@ -46,7 +46,7 @@
// Match the width of the dropdown "heading" (the tab)
min-width: 100%;
position: absolute;
top: 2.5em;
top: 100%;
left: -@border-width-base;
margin: 0;
border: @border-width-base @border-style-base @border-color-base;
@ -117,9 +117,7 @@
.vector-menu-dropdown-noicon {
.vector-menu-heading {
// `padding-top` needs to scale with font-size.
padding-top: 1.25em;
padding-left: 8px;
padding-right: unit( 24 / @font-size-tabs / @font-size-browser, em );
padding: 1.25em 8px 6px;
font-weight: normal;
}

View file

@ -20,7 +20,7 @@
line-height: 1.125em;
li {
span:not( .mw-ui-icon ) {
a:not( .mw-ui-icon ) {
font-size: @font-size-tabs;
}
@ -51,12 +51,6 @@
#mw-head .vector-menu-dropdown .vector-menu-heading {
// Tab separator: Outer end (right in LTR) border of "Actions" menu.
background-position: right bottom;
float: left;
height: unit( 40 / @font-size-tabs / @font-size-browser, em );
margin: 0 -@border-width-base 0 0;
// `padding-right` >= `1px` effectively moves the "background border" outside of the element to
// act like a real border. It is necessary for `.vector-menu-dropdown .vector-menu-content-list` dropdown to align well.
// 0.5em equals `8px` at computed `font-size` of `14px` as visually harmonically with
// `padding-left` in `.vector-menu-dropdown .vector-menu-heading a`
padding-right: unit( 24 / @font-size-tabs / @font-size-browser, em );
float: left;
}

View file

@ -95,3 +95,26 @@
// Contain gradient to 1px × 100% size and draw from top to bottom-left or -right corner.
background-size: @border-width-base 100%;
}
#mw-head .vector-menu-dropdown .vector-menu-heading {
// Tab separator: Outer end (right in LTR) border of "Actions" menu.
background-position: right bottom;
}
.vector-menu-dropdown-noicon {
.vector-menu-heading {
// `padding-top` needs to scale with font-size.
padding-top: 1.25em;
padding-left: 8px;
padding-right: 8px;
font-size: @font-size-tabs;
font-weight: normal;
}
// Add focus state to legacy menu dropdown buttons (i.e. p-variants, p-cactions)
.vector-menu-checkbox:focus + .vector-menu-heading {
// Simulate browser focus ring
outline: dotted 1px; // Firefox style
outline: auto -webkit-focus-ring-color; // Webkit style
}
}

View file

@ -1,4 +1,4 @@
@import '../variables.less';
@import '../../common/variables.less';
@import 'mediawiki.mixins.less';
@import 'mediawiki.mixins.rotation.less';
@ -33,30 +33,30 @@
}
#ca-unwatch.icon a:before {
background-image: url( images/unwatch-icon.svg );
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-unwatch.mw-watchlink-temp.icon a:before {
background-image: url( images/unwatch-temp-icon.svg );
background-image: url( ../common/images/unwatch-temp-icon.svg );
}
#ca-watch.icon a:before {
background-image: url( images/watch-icon.svg );
background-image: url( ../common/images/watch-icon.svg );
}
#ca-unwatch.icon a:hover:before,
#ca-unwatch.icon a:focus:before {
background-image: url( images/unwatch-icon-hl.svg );
background-image: url( ../common/images/unwatch-icon-hl.svg );
}
#ca-unwatch.mw-watchlink-temp.icon a:hover:before,
#ca-unwatch.mw-watchlink-temp.icon a:focus:before {
background-image: url( images/unwatch-temp-icon-hl.svg );
background-image: url( ../common/images/unwatch-temp-icon-hl.svg );
}
#ca-watch.icon a:hover:before,
#ca-watch.icon a:focus:before {
background-image: url( images/watch-icon-hl.svg );
background-image: url( ../common/images/watch-icon-hl.svg );
}
// Loading watchstar link class.

View file

@ -12,6 +12,7 @@
@import './components/MenuTabs.less';
@import './components/SearchBox.less';
@import './components/Sidebar.less';
@import './components/TabWatchstarLink.less';
@import './components/UserLinks.less';
// Overrides

View file

@ -18,11 +18,6 @@
}
.vector-menu-heading {
// stylelint-disable-next-line plugin/no-unsupported-browser-features
font-size: initial;
// reset padding styles in MenuDropdown.less with right padding for arrow.
padding-right: 30px;
padding-left: 8px;
// Prevent select of span text "X languages"
user-select: none;
// Remove opacity on language button (it applies to more menu because of label color).
@ -33,10 +28,6 @@
font-size: @font-size-base;
}
&:after {
top: 0;
}
// T291286: Temporarily use progressive ULS style
&.mw-ui-progressive.mw-ui-quiet {
.mw-ui-icon:before {
@ -98,25 +89,3 @@
#p-lang-btn.mw-portlet-empty {
display: none;
}
#p-lang-btn-sticky-header {
@button-padding: 12px;
@arrow-width: 18px;
position: relative;
padding-right: ~'calc( @{button-padding} + @{arrow-width} )';
white-space: nowrap;
&:after {
content: '';
background-image: url( ../common/images/arrow-down.svg );
background-position: 100% 50%;
background-repeat: no-repeat;
position: absolute;
top: 0;
right: @button-padding;
bottom: 0;
width: @arrow-width;
// Modify the color of the image from the default #202122 to approx. #404244 to match the text.
opacity: 0.84;
}
}

View file

@ -10,56 +10,52 @@
float: left;
padding-left: @border-width-base;
li {
display: inline-block;
vertical-align: middle;
white-space: nowrap;
margin: 0;
// Make first and last elements flush with edge of header.
&:first-child {
margin-left: -8px;
}
&:last-child {
margin-right: -8px;
}
}
/* focus and hover have outlines. Text underline interferes with bottom border */
li a:focus,
li a:hover {
.mw-list-item a:focus,
.mw-list-item a:hover {
text-decoration: none;
border-bottom: @border-width-base @border-style-base;
}
.new a,
.new a:visited {
.mw-list-item.new a,
.mw-list-item.new a:visited {
color: @color-link-new;
}
.selected a,
.selected a:visited {
.mw-list-item.selected a,
.mw-list-item.selected a:visited {
color: @color-link-selected;
border-bottom: @border-width-base @border-style-base;
}
}
/**
* Tab link appearance, applies to
* - <a> inside vector-menu-tabs (e.g. read, edit, view, history)
* - vector-menu-dropdown headings (e.g. more menu, language variants, gadgets)
*/
.vector-menu-tabs li a,
.vector-article-toolbar .vector-menu-heading {
display: block;
position: relative;
// Top bottom padding to increase clickable area.
padding-top: 18px;
padding-bottom: 7px;
// left & right margin separate bottom border between words,
// bottom margin makes link border overlap toolbar border.
margin: 0 8px -1px 8px;
cursor: pointer;
border-bottom: @border-width-base @border-style-base transparent;
* Tab list item appearance. Applies to both <li>'s inside .vector-menu-tabs
* and dropdown menus inside the article toolbar
*/
.vector-menu-tabs .mw-list-item,
.mw-article-toolbar-container .vector-menu-dropdown {
float: left;
white-space: nowrap;
margin: 0 @padding-horizontal-tabs;
// target links inside of .vector-tab-menu
// and dropdown menu headings inside the article toolbar.
// NOTE: Consider adding a single selector to define both items,
// since they both appear beside each other in the article toolbar.
& > a,
.vector-menu-heading {
display: inline-flex;
position: relative;
// Top & bottom padding to increase clickable area.
padding: 18px 0 7px 0;
// bottom margin to overlap border with toolbar border.
margin-bottom: -1px;
cursor: pointer;
border-bottom: @border-width-base @border-style-base transparent;
// max-height & box-sizing to make link, watchstar & dropdown height consistent.
// NOTE: Was 40px instead of 41, but changed to avoid visual regressions.
max-height: unit( 41 / @font-size-tabs / @font-size-browser, em );
box-sizing: border-box;
}
}

View file

@ -0,0 +1,61 @@
@import '../../common/variables.less';
@import 'mediawiki.mixins.less';
@import 'mediawiki.mixins.rotation.less';
/* Watch/Unwatch Icon Styling */
/* Only use icon if the menu item is not collapsed into the "More" dropdown
* (in which case it is inside `.vector-menu-dropdown` instead of `.vector-menu-tabs`). */
.vector-menu-tabs {
@size-watchlink-icon: unit( 16 / @font-size-tabs / @font-size-browser, em );
.mw-watchlink.icon a {
overflow: hidden;
text-indent: -99999px;
color: transparent;
&:before {
content: '';
display: block;
width: @size-watchlink-icon;
height: @size-watchlink-icon;
}
}
#ca-unwatch.icon a:before {
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-unwatch.mw-watchlink-temp.icon a:before {
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-watch.icon a:before {
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-unwatch.icon a:hover:before,
#ca-unwatch.icon a:focus:before {
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-unwatch.mw-watchlink-temp.icon a:hover:before,
#ca-unwatch.mw-watchlink-temp.icon a:focus:before {
background-image: url( ../common/images/unwatch-icon.svg );
}
#ca-watch.icon a:hover:before,
#ca-watch.icon a:focus:before {
background-image: url( ../common/images/watch-icon-hl.svg );
}
// Loading watchstar link class.
#ca-unwatch.icon .loading:before,
#ca-watch.icon .loading:before {
.rotation( 700ms );
/* Suppress the hilarious rotating focus outline on Firefox */
outline: 0;
cursor: default;
pointer-events: none;
transform-origin: 50% 50%;
}
}

View file

@ -178,20 +178,13 @@
}
.vector-user-menu-logged-in .vector-menu-heading {
// FIXME: Ideally this variable should be accessible from mediawiki.skin.variables
// Remove it when we can.
@icon-padding-md: unit( 12 / @font-size-browser, em );
@icon-arrow-size: unit( 12px / @font-size-browser, em );
padding-right: @icon-padding-md + @icon-arrow-size;
// override user menu (.mw-ui-icon) fixed width,
// so chevron beside icon is visible.
width: auto;
// and override again to ensure the user icon is 20px wide.
&:before {
color: #54595d;
}
&:after {
background-position: 100% 0%;
top: @icon-padding-md + ( @icon-arrow-size / 2 );
right: @icon-arrow-size;
width: auto;
}
}
}

View file

@ -114,7 +114,6 @@ body {
/* Main column */
.mw-body,
#mw-data-after-content,
#left-navigation,
.mw-footer {
margin-left: 0;
}
@ -156,11 +155,12 @@ body {
#left-navigation {
float: left;
margin-left: -@padding-horizontal-tabs;
}
#right-navigation {
float: right;
margin-right: -@padding-horizontal-tabs;
// Any dropdowns inside the right navigation in modern Vector (e.g. "more" menu).
// should be right-aligned to prevent horizontal scrolling.
.vector-menu-content {

View file

@ -20,6 +20,7 @@
@import './components/Header.less';
@import './components/MenuTabs.less';
@import './components/StickyHeader.less';
@import './components/TabWatchstarLink.less';
@import './components/TableOfContents.less';
@import './components/TableOfContentsCollapsed.less';
}

View file

@ -2,7 +2,7 @@ import mustache from 'mustache';
import { menuTemplate as vectorTabsTemplate } from './Menu.stories.data';
import { namespaceTabsData, pageActionsData } from './MenuTabs.stories.data';
import '../resources/skins.vector.styles/components/MenuTabs.less';
import '../resources/skins.vector.styles/TabWatchstarLink.less';
import '../resources/skins.vector.styles/components/TabWatchstarLink.less';
export default {
title: 'MenuTabs'