diff --git a/includes/SkinCitizen.php b/includes/SkinCitizen.php index 03a2c630b..5730c7d17 100644 --- a/includes/SkinCitizen.php +++ b/includes/SkinCitizen.php @@ -337,5 +337,14 @@ private function buildSkinFeatures( array &$options ): void { if ( $config->get( 'CitizenEnableARFonts' ) === true ) { $options['styles'][] = 'skins.citizen.styles.fonts.ar'; } + + // Header position + $headerPosition = $config->get( 'CitizenHeaderPosition' ); + + if ( !in_array( $headerPosition, [ 'left', 'right', 'top', 'bottom' ] ) ) { + $headerPosition = 'left'; + } + + $this->getOutput()->addHtmlClasses( 'citizen-header-position-' . $headerPosition ); } } diff --git a/resources/mixins.less b/resources/mixins.less index 4570eead4..7172f7643 100644 --- a/resources/mixins.less +++ b/resources/mixins.less @@ -55,7 +55,15 @@ } .mixin-citizen-sticky-header-element() { - top: var( --height-sticky-header ) !important; + --header-offset: var( --height-sticky-header ); + + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-top & { + --header-offset: calc( var( --header-size ) + var( --height-sticky-header ) ); + } + } + + top: var( --header-offset ) !important; transition-timing-function: var( --transition-timing-function-ease ); transition-duration: var( --transition-duration-medium ); transition-property: top; diff --git a/resources/skins.citizen.styles/components/Drawer.less b/resources/skins.citizen.styles/components/Drawer.less index 62317cec4..7a9526772 100644 --- a/resources/skins.citizen.styles/components/Drawer.less +++ b/resources/skins.citizen.styles/components/Drawer.less @@ -4,7 +4,33 @@ transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-end ); @media ( min-width: @min-width-breakpoint-desktop ) { - transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-start ); + .citizen-header-position-left & { + right: unset; + left: 100%; + transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-start ); + } + + .citizen-header-position-right & { + right: 100%; + left: unset; + transform-origin: var( --transform-origin-offset-end ) var( --transform-origin-offset-start ); + } + + .citizen-header-position-top & { + top: 100%; + right: unset; + left: 0; + bottom: unset; + transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-start ); + } + + .citizen-header-position-bottom & { + top: unset; + right: unset; + left: 0; + bottom: 100%; + transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-end ); + } } } diff --git a/resources/skins.citizen.styles/components/Dropdown.less b/resources/skins.citizen.styles/components/Dropdown.less index f3dd45669..538fc1b8b 100644 --- a/resources/skins.citizen.styles/components/Dropdown.less +++ b/resources/skins.citizen.styles/components/Dropdown.less @@ -85,8 +85,34 @@ .mixin-citizen-header-card( end ); transform-origin: var( --transform-origin-offset-end ) var( --transform-origin-offset-end ); - @media ( min-width: @min-width-breakpoint-desktop ) { - transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-end ); + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-left & { + right: unset; + left: 100%; + transform-origin: var( --transform-origin-offset-start ) var( --transform-origin-offset-end ); + } + + .citizen-header-position-right & { + right: 100%; + left: unset; + transform-origin: var( --transform-origin-offset-end ) var( --transform-origin-offset-end ); + } + + .citizen-header-position-top & { + top: 100%; + right: 0; + left: unset; + bottom: unset; + transform-origin: var( --transform-origin-offset-end ) var( --transform-origin-offset-start ); + } + + .citizen-header-position-bottom & { + top: unset; + right: 0; + left: unset; + bottom: 100%; + transform-origin: var( --transform-origin-offset-end ) var( --transform-origin-offset-end ); + } } } diff --git a/resources/skins.citizen.styles/components/Footer.less b/resources/skins.citizen.styles/components/Footer.less index f235ffa81..2eaa4acf1 100644 --- a/resources/skins.citizen.styles/components/Footer.less +++ b/resources/skins.citizen.styles/components/Footer.less @@ -3,14 +3,19 @@ clear: both; padding: var( --space-xxl ) var( --padding-page ); margin-top: 8rem; - // Reserve space for header - margin-bottom: var( --header-size ); contain: content; color: var( --color-subtle ); background-color: var( --color-surface-1 ); direction: ltr; .mixin-citizen-font-styles( 'small' ); + @media ( max-width: @min-width-breakpoint-desktop ) { + .citizen-feature-autohide-navigation-clientpref-0 & { + // Reserve space for header, add only if autohide is off to avoid empty space + margin-bottom: var( --header-size ); + } + } + &__container { max-width: var( --width-page ); margin-inline: auto; diff --git a/resources/skins.citizen.styles/components/Header.less b/resources/skins.citizen.styles/components/Header.less index d1eccba69..4be6387ca 100644 --- a/resources/skins.citizen.styles/components/Header.less +++ b/resources/skins.citizen.styles/components/Header.less @@ -12,7 +12,8 @@ gap: var( --space-xxs ); padding: ~'var( --space-xs ) max( env( safe-area-inset-right ), var( --space-xs ) ) max( env( safe-area-inset-bottom ), var( --space-xs ) ) max( env( safe-area-inset-left ), var( --space-xs ) )'; background-color: var( --color-surface-0 ); - border-top: var( --border-base ); + border: 0 solid var( --border-color-base ); + border-top-width: var( --border-width-base ); &__item { display: flex; @@ -69,7 +70,8 @@ &__logo { padding: 0 var( --space-xs ) 0 0; margin: 0 var( --space-xxs ); - border-right: var( --border-subtle ); + border: 0 solid var( --border-color-subtle ); + border-right-width: var( --border-width-base ); img { margin: auto; @@ -157,18 +159,46 @@ @media ( min-width: @min-width-breakpoint-desktop ) { .citizen-header { - --header-direction: column; top: 0; right: unset; left: 0; - border-top: 0; - border-right: var( --border-base ); - - &__logo { + + .citizen-header-position-left &__logo, + .citizen-header-position-right &__logo { padding: 0 0 var( --space-xs ) 0; margin: var( --space-xxs ) 0; border-right: 0; - border-bottom: var( --border-subtle ); + border-bottom-width: var( --border-width-base ); + } + + .citizen-header-position-left & { + --header-direction: column; + right: unset; + left: 0; + border-right-width: var( --border-width-base ); + } + + .citizen-header-position-right & { + --header-direction: column; + right: 0; + left: unset; + border-left-width: var( --border-width-base ); + } + + .citizen-header-position-top & { + top: 0; + right: 0; + bottom: unset; + left: 0; + border-bottom-width: var( --border-width-base ); + } + + .citizen-header-position-bottom & { + top: unset; + right: 0; + bottom: 0; + left: 0; + border-top-width: var( --border-width-base ); } } } diff --git a/resources/skins.citizen.styles/components/StickyHeader.less b/resources/skins.citizen.styles/components/StickyHeader.less index c9d7dc5e9..9fb3d6870 100644 --- a/resources/skins.citizen.styles/components/StickyHeader.less +++ b/resources/skins.citizen.styles/components/StickyHeader.less @@ -39,7 +39,17 @@ transition-property: transform, visibility; @media ( min-width: @min-width-breakpoint-desktop ) { - margin-left: var( --header-size ); + .citizen-header-position-left & { + margin-left: var( --header-size ); + } + + .citizen-header-position-right & { + margin-right: var( --header-size ); + } + + .citizen-header-position-top & { + transform: translateY( calc( var( --header-size ) - 100% ) ); + } } } @@ -168,6 +178,12 @@ &-container { visibility: visible; transform: none; + + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-top & { + transform: translateY( calc( var( --header-size ) + var( --border-width-base ) ) ); + } + } } } } diff --git a/resources/skins.citizen.styles/components/TableOfContents.less b/resources/skins.citizen.styles/components/TableOfContents.less index a57f75a00..86b895503 100644 --- a/resources/skins.citizen.styles/components/TableOfContents.less +++ b/resources/skins.citizen.styles/components/TableOfContents.less @@ -210,10 +210,24 @@ @media ( min-width: @min-width-breakpoint-desktop ) { .citizen-toc { + --header-offset: var( --height-sticky-header ); + + .citizen-header-position-top & { + --header-offset: calc( var( --header-size ) + var( --height-sticky-header ) ); + } + + .ve-active & { + --header-offset: 48px; + } + + .citizen-header-position-top.ve-active & { + --header-offset: calc( var( --header-size ) + 48px ); + } + position: -webkit-sticky; position: sticky; - top: var( --height-sticky-header ); - max-height: ~'calc( 100vh - var( --height-sticky-header ) )'; + top: var( --header-offset ); + max-height: ~'calc( 100vh - var( --header-offset ) )'; padding: var( --space-xs ) 0; overflow-y: auto; overscroll-behavior: contain; diff --git a/resources/skins.citizen.styles/layout.less b/resources/skins.citizen.styles/layout.less index cb997d04d..b57840767 100644 --- a/resources/skins.citizen.styles/layout.less +++ b/resources/skins.citizen.styles/layout.less @@ -39,7 +39,21 @@ @media ( min-width: @min-width-breakpoint-desktop ) { .citizen-page-container { // Reserve space for header - margin-left: var( --header-size ); + .citizen-header-position-left & { + margin-left: var( --header-size ); + } + + .citizen-header-position-right & { + margin-right: var( --header-size ); + } + + .citizen-header-position-top & { + margin-top: var( --header-size ); + } + + .citizen-header-position-bottom & { + margin-bottom: var( --header-size ); + } } .citizen-toc-enabled { diff --git a/skin.json b/skin.json index 7ab8be033..647fb287c 100644 --- a/skin.json +++ b/skin.json @@ -893,6 +893,12 @@ "description": "Enables or disable the command palette. Disable to use the old search module.", "descriptionmsg": "citizen-config-enablecommandpalette", "public": true + }, + "HeaderPosition": { + "value": "left", + "description": "Position of the header on the desktop layout. Possible values: 'left', 'right', 'top' and 'bottom'", + "descriptionmsg": "citizen-config-headerposition", + "public": true } }, "manifest_version": 2 diff --git a/skinStyles/extensions/CookieWarning/ext.CookieWarning.styles.less b/skinStyles/extensions/CookieWarning/ext.CookieWarning.styles.less index c9b84adbd..4747178f9 100644 --- a/skinStyles/extensions/CookieWarning/ext.CookieWarning.styles.less +++ b/skinStyles/extensions/CookieWarning/ext.CookieWarning.styles.less @@ -28,7 +28,15 @@ @media only screen and ( min-width: @min-width-breakpoint-desktop ) { right: unset; - left: var( --header-size ); + left: 0; + + .citizen-header-position-left & { + left: var( --header-size ); + } + } + + .citizen-header-position-bottom & { + bottom: var( --header-size ); } .mw-cookiewarning-text { diff --git a/skinStyles/extensions/Echo/ext.echo.ui.less b/skinStyles/extensions/Echo/ext.echo.ui.less index aaba9f972..28a6c6dec 100644 --- a/skinStyles/extensions/Echo/ext.echo.ui.less +++ b/skinStyles/extensions/Echo/ext.echo.ui.less @@ -23,8 +23,30 @@ box-shadow: var( --box-shadow-large ); @media ( min-width: @min-width-breakpoint-desktop ) { - bottom: 0 !important; - left: var( --header-size ) !important; + .citizen-header-position-left & { + right: unset !important; + left: var( --header-size ) !important; + bottom: 0 !important; + } + + .citizen-header-position-right & { + right: var( --header-size ) !important; + left: unset !important; + bottom: 0 !important; + } + + .citizen-header-position-top & { + right: 0 !important; + left: unset !important; + top: var( --header-size ) !important; + bottom: unset !important; + } + + .citizen-header-position-bottom & { + right: 0 !important; + left: unset !important; + bottom: var( --header-size ) !important; + } } .oo-ui-popupWidget-body { @@ -84,6 +106,13 @@ bottom: 0; z-index: @z-index-overlay; // Higher than header + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-top & { + top: 0; + bottom: unset; + } + } + // Add dismiss affordnance backdrop @media ( max-width: @max-width-breakpoint-tablet ) { &::before { diff --git a/skinStyles/extensions/MediaSearch/mediasearch.styles.less b/skinStyles/extensions/MediaSearch/mediasearch.styles.less index d1a02c65c..82ba88fc6 100644 --- a/skinStyles/extensions/MediaSearch/mediasearch.styles.less +++ b/skinStyles/extensions/MediaSearch/mediasearch.styles.less @@ -780,6 +780,10 @@ border-bottom-right-radius: 0; border-bottom-left-radius: 0; } + + .citizen-header-position-bottom & { + padding-bottom: var( --header-size ); + } } } diff --git a/skinStyles/extensions/VisualEditor/ext.visualEditor.core.less b/skinStyles/extensions/VisualEditor/ext.visualEditor.core.less index aba62ec64..140f57442 100644 --- a/skinStyles/extensions/VisualEditor/ext.visualEditor.core.less +++ b/skinStyles/extensions/VisualEditor/ext.visualEditor.core.less @@ -14,6 +14,12 @@ /* Fix weird gap between save button and toolbar bottom */ border-bottom: 0; box-shadow: 0 1px 0 0 var( --border-color-base ); + + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-top .ve-ui-toolbar-floating& { + top: var( --header-size ); + } + } } /* ve.ce.BranchNode.css */ diff --git a/skinStyles/extensions/VisualEditor/ext.visualEditor.desktopArticleTarget.init.less b/skinStyles/extensions/VisualEditor/ext.visualEditor.desktopArticleTarget.init.less index cd54e0cf2..212d1136d 100644 --- a/skinStyles/extensions/VisualEditor/ext.visualEditor.desktopArticleTarget.init.less +++ b/skinStyles/extensions/VisualEditor/ext.visualEditor.desktopArticleTarget.init.less @@ -49,6 +49,12 @@ &-bar { border-bottom-color: var( --border-color-base ); box-shadow: none; + + @media ( min-width: @min-width-breakpoint-desktop ) { + .citizen-header-position-top & { + top: var( --header-size ); + } + } } } diff --git a/skinStyles/mediawiki/debug/mediawiki.debug.less b/skinStyles/mediawiki/debug/mediawiki.debug.less index b16bb83a1..41ccff306 100644 --- a/skinStyles/mediawiki/debug/mediawiki.debug.less +++ b/skinStyles/mediawiki/debug/mediawiki.debug.less @@ -147,7 +147,17 @@ a.mw-debug-panelabel:visited { @media ( min-width: @min-width-breakpoint-desktop ) { // So that it is not hidden by header - margin-left: var( --header-size ); + .citizen-header-position-left & { + margin-left: var( --header-size ); + } + + .citizen-header-position-right & { + margin-right: var( --header-size ); + } + + .citizen-header-position-top & { + margin-top: var( --header-size ); + } } } diff --git a/skinStyles/mediawiki/special/mediawiki.special.preferences.styles.ooui.less b/skinStyles/mediawiki/special/mediawiki.special.preferences.styles.ooui.less index e7d853e9c..74f3932e4 100644 --- a/skinStyles/mediawiki/special/mediawiki.special.preferences.styles.ooui.less +++ b/skinStyles/mediawiki/special/mediawiki.special.preferences.styles.ooui.less @@ -36,6 +36,10 @@ @media ( max-width: @max-width-breakpoint-tablet ) { bottom: var( --header-size ); } + + .citizen-header-position-bottom & { + bottom: var( --header-size ); + } } }