diff --git a/core/src/main/resources/hudson/PluginManager/sidepanel.jelly b/core/src/main/resources/hudson/PluginManager/sidepanel.jelly index 0d3fc3fb43..21755b3207 100644 --- a/core/src/main/resources/hudson/PluginManager/sidepanel.jelly +++ b/core/src/main/resources/hudson/PluginManager/sidepanel.jelly @@ -27,7 +27,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/hudson/model/Job/configure.jelly b/core/src/main/resources/hudson/model/Job/configure.jelly index 18a914f4bc..78e0aed76b 100644 --- a/core/src/main/resources/hudson/model/Job/configure.jelly +++ b/core/src/main/resources/hudson/model/Job/configure.jelly @@ -36,14 +36,14 @@ THE SOFTWARE. - +
- + diff --git a/core/src/main/resources/hudson/model/View/footer.jelly b/core/src/main/resources/hudson/model/View/footer.jelly new file mode 100644 index 0000000000..f600eb2fdc --- /dev/null +++ b/core/src/main/resources/hudson/model/View/footer.jelly @@ -0,0 +1,52 @@ + + + + +
+ +
+
diff --git a/core/src/main/resources/hudson/model/View/index.jelly b/core/src/main/resources/hudson/model/View/index.jelly index 4592096233..fd75659f51 100644 --- a/core/src/main/resources/hudson/model/View/index.jelly +++ b/core/src/main/resources/hudson/model/View/index.jelly @@ -48,6 +48,8 @@ THE SOFTWARE. + +
diff --git a/core/src/main/resources/hudson/model/View/new-view-page.jelly b/core/src/main/resources/hudson/model/View/new-view-page.jelly index fa88acbb68..f315aefded 100644 --- a/core/src/main/resources/hudson/model/View/new-view-page.jelly +++ b/core/src/main/resources/hudson/model/View/new-view-page.jelly @@ -139,6 +139,8 @@ THE SOFTWARE. + +
diff --git a/core/src/main/resources/lib/layout/layout.jelly b/core/src/main/resources/lib/layout/layout.jelly index 399aade734..0e1312f5f3 100644 --- a/core/src/main/resources/lib/layout/layout.jelly +++ b/core/src/main/resources/lib/layout/layout.jelly @@ -25,7 +25,7 @@ THE SOFTWARE. --> + xmlns:x="jelly:xml"> , , and @@ -172,53 +172,27 @@ THE SOFTWARE. logout="${%logout}"/> -
+ +
+
+
+ + +
-
+
- -
- -
+ + + + diff --git a/core/src/main/resources/lib/layout/main-panel.jelly b/core/src/main/resources/lib/layout/main-panel.jelly index 6b16dd6f55..a097d8d743 100644 --- a/core/src/main/resources/lib/layout/main-panel.jelly +++ b/core/src/main/resources/lib/layout/main-panel.jelly @@ -26,40 +26,47 @@ THE SOFTWARE. Generates the body as the main content part of a Jenkins page. + + Optional, sets the width of the page. Available options are 'default', 'narrow', or 'wide'. + - - - -
- - - ${app.getQuietDownReason()} - - - ${%saferestart} - - -
-
- -
- - - ${app.getQuietDownReason()} - - - ${%shutdown} - - -
-
-
-
- - start of main content ⇒ - - ⇐ end of main content +
diff --git a/core/src/main/resources/lib/layout/settings-subpage.jelly b/core/src/main/resources/lib/layout/settings-subpage.jelly index ca1e91058e..f497383df2 100644 --- a/core/src/main/resources/lib/layout/settings-subpage.jelly +++ b/core/src/main/resources/lib/layout/settings-subpage.jelly @@ -65,7 +65,7 @@ THE SOFTWARE. Defaults to false if not set. - Optional, sets the width of the page. Available options are 'default' or 'wide'. + Optional, sets the width of the page. Available options are 'narrow' or 'wide'. @@ -78,8 +78,7 @@ THE SOFTWARE. + permissions="${attrs.permissions}"> @@ -120,23 +119,19 @@ THE SOFTWARE. - -
-
- - - -
- ${description} -
-
-
+ + + + +
+ ${description} +
+
+
- + - -
-
+
@@ -144,7 +139,7 @@ THE SOFTWARE.
- + diff --git a/core/src/main/resources/lib/layout/side-panel.jelly b/core/src/main/resources/lib/layout/side-panel.jelly index 02afd6e7ba..6972c1efc2 100644 --- a/core/src/main/resources/lib/layout/side-panel.jelly +++ b/core/src/main/resources/lib/layout/side-panel.jelly @@ -26,17 +26,11 @@ THE SOFTWARE. Generates a left side content as part of a Jenkins page. Typically known as two-column or left-side + main-content layouts - - Make the side panel sticky. Should not be used on pages that include widgets in the sidepanel and when plugins can - add their own tasks, so the number of tasks is not fixed. - - -
+
- diff --git a/src/main/js/components/header/index.js b/src/main/js/components/header/index.js index 695f5058e9..b63e971a50 100644 --- a/src/main/js/components/header/index.js +++ b/src/main/js/components/header/index.js @@ -17,44 +17,9 @@ function init() { computeOverflow(); }); - // Fade in the page header on scroll, increasing opacity and intensity of the backdrop blur - window.addEventListener("scroll", () => { - const navigation = document.querySelector("#page-header"); - const scrollY = Math.max(0, window.scrollY); - navigation.style.setProperty( - "--background-opacity", - Math.min(70, scrollY) + "%", - ); - navigation.style.setProperty( - "--background-blur", - Math.min(40, scrollY) + "px", - ); - if ( - !document.querySelector("#main-panel > .jenkins-search--app-bar") && - !document.querySelector(".app-page-body__sidebar--sticky") - ) { - const prefersContrast = window.matchMedia( - "(prefers-contrast: more)", - ).matches; - navigation.style.setProperty( - "--border-opacity", - Math.min( - prefersContrast ? 100 : 15, - prefersContrast ? scrollY * 3 : scrollY, - ) + "%", - ); - } - }); - window.addEventListener("load", () => { // We can't use :has due to HtmlUnit CSS Parser not supporting it, so // these are workarounds for that same behaviour - if (document.querySelector("#main-panel > .jenkins-app-bar--sticky")) { - document - .querySelector(".jenkins-header") - .classList.add("jenkins-header--has-sticky-app-bar"); - } - if (!document.querySelector(".jenkins-breadcrumbs__list-item")) { document .querySelector(".jenkins-header") diff --git a/src/main/scss/abstracts/_theme.scss b/src/main/scss/abstracts/_theme.scss index 7dc2ae2873..b7ff9d14a1 100644 --- a/src/main/scss/abstracts/_theme.scss +++ b/src/main/scss/abstracts/_theme.scss @@ -78,11 +78,21 @@ $semantics: ( // Background colors --background: rgb(254 254 254); + // Body + --body-background-top: color-mix( + in oklch, + white 1.5%, + var(--card-background) + ); + // Header - --header-background: var(--background); - --header-border: var(--text-color-secondary); + --header-background: color-mix( + in sRGB, + var(--background), + var(--text-color-secondary) 2.5% + ); --header-color: var(--text-color); - --header-height: 4.125rem; + --header-height: 3.5rem; // App bar --bottom-app-bar-padding: 0.875rem; @@ -152,7 +162,6 @@ $semantics: ( var(--jenkins-border-color--subtle); @media (prefers-contrast: more) { - --header-border: var(--text-color); --focus-input-border: var(--text-color); --jenkins-border-color: var(--text-color); --jenkins-border-color--subtle: var(--text-color); @@ -268,7 +277,7 @@ $semantics: ( --panel-border-color: var(--light-grey); // Form - --section-padding: 1.625rem; + --section-padding: 1.25rem; --input-color: var(--white); --input-border: color-mix( in sRGB, @@ -285,7 +294,7 @@ $semantics: ( --form-item-max-width--small: min(35vw, 1200px); @media screen and (max-width: breakpoints.$tablet-breakpoint) { - --section-padding: 1.25rem; + --section-padding: 1rem; --form-item-max-width: 100%; --form-item-max-width--medium: 100%; --form-item-max-width--small: 100%; @@ -293,7 +302,7 @@ $semantics: ( --form-label-font-weight: var(--font-bold-weight); --form-input-padding: 0.5rem 0.625rem; - --form-input-border-radius: 0.625rem; + --form-input-border-radius: 0.6875rem; --form-input-glow: 0 0 0 0.5rem transparent; --form-input-glow--focus: 0 0 0 0.25rem var(--focus-input-glow); --pre-background: var(--button-background); diff --git a/src/main/scss/base/_core.scss b/src/main/scss/base/_core.scss index 7cbad0dd53..5ed25b7d16 100644 --- a/src/main/scss/base/_core.scss +++ b/src/main/scss/base/_core.scss @@ -1,9 +1,9 @@ html { - height: 100%; box-sizing: border-box; -webkit-tap-highlight-color: transparent; color: var(--text-color); scroll-padding-top: calc(var(--header-height) + var(--section-padding)); + overscroll-behavior: none; scrollbar-color: oklch(from var(--text-color-secondary) l c h / 0.35) oklch(from var(--text-color-secondary) l c h / 0.05); } @@ -13,7 +13,8 @@ body { min-height: 100vh; display: flex; flex-direction: column; - background-color: var(--background); + background: var(--header-background); + background-attachment: fixed; } *, diff --git a/src/main/scss/base/_layout-commons.scss b/src/main/scss/base/_layout-commons.scss index 7c5060bc3d..ba653a9cfa 100644 --- a/src/main/scss/base/_layout-commons.scss +++ b/src/main/scss/base/_layout-commons.scss @@ -1,112 +1,194 @@ @use "../base/breakpoints"; -/* -------------------------------------- */ - .app-page-body { - display: flex; - justify-content: flex-start; + position: relative; + display: grid; align-items: stretch; - flex: 1 0 auto; -} + border: 1px solid transparent; + margin-inline: var(--page-inset); + isolation: isolate; + flex-grow: 1; -.app-page-body__sidebar { - @media (min-width: breakpoints.$tablet-breakpoint) { - &--sticky { - position: sticky; - top: var(--header-height); - align-self: flex-start; + &:has(> .app-page-body__sidebar) { + grid-template-columns: auto 1fr; + } + + @media (width < 740px) { + display: flex; + flex-direction: column; + } + + & > .app-page-body__sidebar { + padding: var(--section-padding); + + @media (width >= 740px) { + width: 280px; + + &:not(:has(.pane, .jenkins-card)) { + position: sticky; + top: calc(var(--header-height) + var(--jenkins-border-width)); + height: calc(100vh - var(--header-height) - var(--page-inset)); + overflow-y: auto; + border-right: var(--jenkins-border); + } + } + + @media (width > breakpoints.$tablet-breakpoint) { + width: 320px; + } + } + + & > .app-page-body__contents { + padding: var(--section-padding); + padding-bottom: 0; + } + + // Remove this when sidepanels no longer contain widgets + &:has(#side-panel .pane, #side-panel .jenkins-card) { + & > .app-page-body__sidebar { + padding-right: 0; + + #tasks { + margin-bottom: 1rem; + } + + .jenkins-card, + .pane-frame { + margin-inline: -0.5rem; + } } } } +.app-main-panel { + margin-bottom: var(--section-padding); + + &--narrow { + max-width: 900px; + margin-inline: auto; + } + + &--wide { + width: 70vw; + max-width: 1700px; + margin-inline: auto; + } +} + +.app-full-screen-page-body { + display: flex; + flex-direction: column; + flex-grow: 1; + + & > .app-page-body__contents { + display: flex; + flex-direction: column; + flex-grow: 1; + min-height: 0; + } + + .app-main-panel { + display: flex; + flex-direction: column; + flex-grow: 1; + min-height: 0; + margin-bottom: 0; + } +} + +// Page frame +.app-page-frame__background { + position: relative; + pointer-events: none; + + &::before, + &::after { + content: ""; + position: fixed; + inset: var(--header-height) var(--page-inset) var(--page-inset) + var(--page-inset); + border-radius: var(--form-input-border-radius); + } + + &::before { + --background: var(--card-background); + + background-color: var(--card-background); + background-image: linear-gradient( + in oklch, + var(--body-background-top), + var(--background) + ); + background-size: 100% 90vh; + background-repeat: no-repeat; + z-index: -1; + } + + &::after { + border: var(--jenkins-border); + z-index: 1; + } +} + +.app-page-frame__mask { + position: fixed; + inset: 0; + z-index: 1; + pointer-events: none; + mask: + linear-gradient(#000 0 0), + linear-gradient(#000 0 0) left calc(var(--page-inset) + 1px) top + calc(var(--header-height) + 1px) / + calc(100% - calc(var(--page-inset) + 1px) * 2) + calc( + 100% - calc(var(--header-height) + 1px) - calc(var(--page-inset) + 1px) + ) + no-repeat; + mask-composite: exclude; + + &::before { + content: ""; + position: absolute; + inset: 0; + background: var(--header-background); + background-attachment: fixed; + opacity: 0.75; + } +} + +.app-page-frame__blur { + position: fixed; + inset: 0; + z-index: 1; + pointer-events: none; + + &::before, + &::after { + content: ""; + position: absolute; + left: 0; + right: 0; + backdrop-filter: blur(15px); + } + + &::before { + top: 0; + height: calc(var(--header-height) + var(--jenkins-border-width)); + } + + &::after { + bottom: 0; + height: var(--page-inset); + } +} + +// Legacy #page-body.clear::after { clear: both; content: ""; display: table; } -#side-panel { - flex-shrink: 0; -} - -#main-panel { - padding: 0 var(--section-padding) var(--section-padding) - var(--section-padding); - display: inline-block; - width: 100%; -} - -.app-page-body--one-column { - --form-item-max-width: calc(85vw - 4rem); - - @media screen and (width <= 1200px) { - --form-item-max-width: 100%; - } - - max-width: 85vw; - - #main-panel { - width: 100vw; - } - - .jenkins-section { - max-width: unset; - } - - @media (width <= 1200px) { - max-width: unset; - } - - margin: auto; -} - -body.two-column #main-panel { - width: calc(100% - var(--side-panel-width)); - flex: 1; - display: block; -} - -body.full-screen { - padding: 0; -} - -body.full-screen #main-panel { - padding: 0; -} - -body.two-column #side-panel { - width: var(--side-panel-width); -} - -@media (max-width: breakpoints.$tablet-breakpoint) { - body.two-column #page-body { - flex-wrap: wrap; - } - - body.two-column #side-panel { - width: 100%; - border-right: none; - } - - body.two-column #main-panel { - margin-left: 0; - width: 100%; - } -} - -.app-project-status-table { - width: 100%; - overflow-x: auto; -} - -@media (width >= 1170px) { - body.two-column #main-panel { - width: calc( - 100% - calc(var(--side-panel-width) + calc(var(--section-padding) * 2)) - ); - } -} - // Clearfixes extracted from responsive-grid.css as they essential .clearfix::before, .clearfix::after, diff --git a/src/main/scss/base/_sticky.scss b/src/main/scss/base/_sticky.scss index 43b1eb0f8e..d32b0bfd29 100644 --- a/src/main/scss/base/_sticky.scss +++ b/src/main/scss/base/_sticky.scss @@ -3,13 +3,19 @@ // clicks. // Keep this list in sync with the `position: sticky` rules across the SCSS sources. :root[data-disable-sticky="true"] { + .app-page-frame__blur, + .app-page-frame__mask, + .app-page-frame__background { + display: none; + } + .jenkins-header, .jenkins-app-bar--sticky, .jenkins-bottom-app-bar__shadow, .bottom-sticker, #bottom-sticker, - .app-page-body__sidebar--sticky, - .app-plugin-manager__sidebar { + .app-build-bar, + .app-page-body__sidebar { position: relative !important; // Reset the sticky offsets too — with relative positioning these would otherwise @@ -19,6 +25,12 @@ bottom: auto !important; } + // Don't allow for sidebar scrolling + .app-page-body__sidebar { + overflow-y: visible !important; + height: unset !important; + } + // The bottom app bar shadow uses negative margins to overlap content; reset them so it // cannot intercept clicks when sticky positioning is disabled. .jenkins-bottom-app-bar__shadow { diff --git a/src/main/scss/components/_app-bar.scss b/src/main/scss/components/_app-bar.scss index ec6b35a389..50a9288b9e 100644 --- a/src/main/scss/components/_app-bar.scss +++ b/src/main/scss/components/_app-bar.scss @@ -3,7 +3,7 @@ align-items: center; justify-content: space-between; margin-bottom: 0.875rem; - min-height: 2.875rem; + min-height: 2.5rem; gap: var(--section-padding); flex-wrap: wrap; @@ -68,23 +68,23 @@ &--sticky { position: sticky; - top: var(--header-height); + top: calc(var(--header-height) + 1.3125rem); z-index: 2; &::before, &::after { content: ""; position: absolute; - inset: calc(var(--header-height) * -1) + inset: calc(var(--section-padding) * -1) calc(var(--section-padding) * -1) -0.875rem; z-index: -1; pointer-events: none; } &::before { - background: var(--background); + background: var(--body-background-top); mask-image: linear-gradient(black 70%, transparent); - opacity: 0.55; + opacity: 0.6; } &::after { @@ -106,7 +106,8 @@ h1, h2 { margin: 0; - font-size: 1.3125rem; + font-size: 1.1875rem; + font-weight: 550; } &__subtitle { @@ -126,6 +127,7 @@ } .jenkins-bottom-app-bar__shadow { + --background: var(--card-background); --semi-translucent: color-mix(in sRGB, var(--background) 70%, transparent); position: sticky; diff --git a/src/main/scss/components/_breadcrumbs.scss b/src/main/scss/components/_breadcrumbs.scss index 333eec8d5a..3e18a9187e 100644 --- a/src/main/scss/components/_breadcrumbs.scss +++ b/src/main/scss/components/_breadcrumbs.scss @@ -9,6 +9,7 @@ &__list { display: flex; + margin: 0; & > * { z-index: 1; diff --git a/src/main/scss/components/_page-footer.scss b/src/main/scss/components/_page-footer.scss index a450bd0bd2..88e01c5524 100644 --- a/src/main/scss/components/_page-footer.scss +++ b/src/main/scss/components/_page-footer.scss @@ -6,10 +6,6 @@ font-size: var(--font-size-sm); } -.page-footer .container-fluid { - padding: 0; -} - .page-footer__flex-row { display: flex; align-items: stretch; @@ -19,8 +15,6 @@ .page-footer__flex-row > *:not(script) { display: flex; align-items: center; - padding: calc(var(--section-padding) / 2) - calc(var(--section-padding) - 0.85rem); gap: calc(var(--section-padding) / 2); } @@ -32,16 +26,3 @@ .page-footer span { display: inline-block; } - -.page-footer .jenkins-button { - transition: var(--standard-transition); - - &:hover { - color: var(--text-color) !important; - } - - svg, - .jenkins-overflow-button__ellipsis { - display: none; - } -} diff --git a/src/main/scss/components/_page-header.scss b/src/main/scss/components/_page-header.scss index bad32ec98c..fe7377aca9 100644 --- a/src/main/scss/components/_page-header.scss +++ b/src/main/scss/components/_page-header.scss @@ -1,13 +1,9 @@ @use "../abstracts/mixins"; .jenkins-header { - --background-opacity: 0%; - --background-blur: 0; - --border-opacity: 0%; - position: sticky; top: 0; - z-index: 999; + z-index: 2; display: flex; align-items: center; gap: var(--section-padding); @@ -16,26 +12,6 @@ min-height: var(--header-height); color: var(--header-color); - &::before { - content: ""; - position: absolute; - inset: 0; - backdrop-filter: blur(var(--background-blur)); - background: color-mix( - in sRGB, - var(--header-background) var(--background-opacity), - transparent - ); - border-bottom: var(--jenkins-border-width) solid - color-mix( - in sRGB, - var(--header-border) var(--border-opacity), - transparent - ); - background-clip: padding-box; - z-index: -1; - } - .app-jenkins-logo { @include mixins.item($border: false); @@ -79,7 +55,7 @@ } #jenkins-head-icon { - width: 1.625rem; + width: 1.5rem; } } @@ -91,11 +67,6 @@ // For customizable-header-plugin color: inherit !important; - svg { - width: 1.25rem; - height: 1.25rem; - } - img { width: 1.625rem; height: 1.625rem; @@ -121,7 +92,7 @@ &__main { display: grid; grid-template-columns: 1fr auto; - padding-left: var(--section-padding); + padding-left: calc(var(--section-padding) + var(--page-inset)); width: 100%; } @@ -135,14 +106,8 @@ display: flex; align-items: center; justify-content: center; - gap: 0.5rem; - padding-right: var(--section-padding); - } - - &--has-sticky-app-bar { - &::before { - mask-image: linear-gradient(black 50%, transparent); - } + gap: 0.25rem; + padding-right: calc(var(--section-padding) + var(--page-inset)); } &--no-breadcrumbs { diff --git a/src/main/scss/components/_side-panel-tasks.scss b/src/main/scss/components/_side-panel-tasks.scss index 65cde1a935..ccf91ca756 100644 --- a/src/main/scss/components/_side-panel-tasks.scss +++ b/src/main/scss/components/_side-panel-tasks.scss @@ -1,19 +1,14 @@ @use "../abstracts/mixins"; @use "../base/breakpoints"; -$background-outset: 0.5rem; +$inset: 0.6875rem; #tasks, .tasks, .subtasks { display: flex; flex-direction: column; - margin: 0 var(--section-padding) var(--section-padding) var(--section-padding); gap: 0.125rem; - - @media (min-width: breakpoints.$tablet-breakpoint) { - margin-right: calc($background-outset); - } } .subtasks { @@ -27,12 +22,11 @@ $background-outset: 0.5rem; #side-panel { .jenkins-app-bar { - margin-left: var(--section-padding); - margin-right: var(--section-padding); + margin-inline: $inset; } & > #tasks > .jenkins-search-container { - margin: 0 0 1rem #{-$background-outset}; + margin: 0 0 1rem; .jenkins-search__icon { width: 2.625rem; @@ -45,10 +39,6 @@ $background-outset: 0.5rem; } } -#tasks .task { - margin: 0 0 0 calc($background-outset * -1); -} - #tasks, .tasks { .task .task-link { @@ -57,7 +47,7 @@ $background-outset: 0.5rem; display: flex; align-items: center; justify-content: flex-start; - padding: 0.5rem 0.6875rem; + padding: 0.5rem $inset; gap: 0.625rem; width: 100%; font-size: var(--font-size-sm); @@ -115,7 +105,7 @@ $background-outset: 0.5rem; font-size: var(--font-size-sm); color: var(--text-color-secondary); font-weight: var(--font-bold-weight); - margin: 1rem 0 0.375rem; + margin: 1rem 0 0.375rem $inset; &:first-of-type { margin-top: 0.375rem; diff --git a/src/main/scss/components/_side-panel-widgets.scss b/src/main/scss/components/_side-panel-widgets.scss index 91feb2e0d6..abd46f7077 100644 --- a/src/main/scss/components/_side-panel-widgets.scss +++ b/src/main/scss/components/_side-panel-widgets.scss @@ -8,10 +8,6 @@ overflow: hidden; } -#side-panel .pane-frame { - margin-left: 10px; -} - #side-panel .pane-header, .app-build-content .pane-header { font-size: var(--font-size-sm); diff --git a/src/main/scss/pages/_build.scss b/src/main/scss/pages/_build.scss index 9c8fe52b7f..434beab29f 100644 --- a/src/main/scss/pages/_build.scss +++ b/src/main/scss/pages/_build.scss @@ -12,7 +12,6 @@ .app-build__grid { gap: calc(var(--section-padding) / 2); - margin-bottom: calc(var(--section-padding) * -0.5); & > * { height: 350px; @@ -139,101 +138,74 @@ } } -body:has(.app-build-bar), -body:has(.app-settings-container) { - --top: color-mix(in sRGB, var(--text-color-secondary) 4%, var(--background)); - --bottom: color-mix( - in sRGB, - var(--text-color-secondary) 2%, - var(--background) - ); +@keyframes app-build-bar-border-on-scroll { + from { + border-bottom-color: color-mix( + in sRGB, + var(--jenkins-border-color), + transparent + ); + } - background: linear-gradient(var(--top), var(--bottom)); - background-attachment: fixed; -} - -.app-settings-container { - --background: var(--card-background); - --top: color-mix( - in sRGB, - var(--text-color-secondary) 2%, - var(--card-background) - ); - - position: relative; - background-color: var(--card-background); - background-image: linear-gradient(var(--top), var(--card-background)); - background-size: 100% 90vh; - background-repeat: no-repeat; - border: var(--jenkins-border); - border-radius: var(--form-input-border-radius); - z-index: 0; - min-height: calc(100vh - var(--header-height) - var(--section-padding)); - - &__inner { - max-width: 900px; - margin: auto; - padding: var(--section-padding); - display: flex; - flex-direction: column; - - &--wide { - width: 70vw; - max-width: 1700px; - } - - // TODO - this can be moved to app-bar.scss when Manage Jenkins flag is removed - .jenkins-app-bar--sticky { - padding: var(--section-padding) 0 !important; - margin: -1rem 0 0.5rem !important; - - &::before, - &::after { - inset: 0 !important; - } - - &::before { - background: color-mix( - in sRGB, - var(--text-color-secondary) 2%, - var(--card-background) - ) !important; - mask-image: linear-gradient(black 60%, transparent) !important; - opacity: 0.75 !important; - } - - &::after { - left: calc(var(--section-padding) * -1) !important; - right: calc(var(--section-padding) * -1) !important; - mask-image: linear-gradient(black 65%, transparent) !important; - } - } + to { + border-bottom-color: var(--jenkins-border-color); } } .app-build-bar { - position: relative; + --app-build-bar-border-color: color-mix( + in sRGB, + var(--jenkins-border-color), + transparent + ); + + position: sticky; + top: calc(var(--header-height) + var(--jenkins-border-width)); display: grid; grid-template-columns: 1fr auto 1fr; - align-items: start; + align-items: center; gap: 0.5rem 1.5rem; - min-height: 2.875rem; - margin-top: -5px; - padding-bottom: 0.75rem; + min-height: var(--header-height); + background-color: color-mix( + in oklch, + transparent 25%, + var(--body-background-top) + ); + padding-inline: var(--section-padding); z-index: 102; - - @media (width <= 1200px) { - display: flex; - flex-direction: column; - } + border-bottom: var(--jenkins-border-width) solid + var(--app-build-bar-border-color); + margin-left: calc(var(--section-padding) * -1); + margin-right: calc(var(--section-padding) * -1); + border-top-left-radius: var(--form-input-border-radius); + border-top-right-radius: var(--form-input-border-radius); &::before { content: ""; position: absolute; - inset: -1000px calc(var(--section-padding) * -1) 0; - background-color: var(--background); - border-bottom: var(--jenkins-border-width) solid var(--card-border-color); + inset: 0; z-index: -1; + pointer-events: none; + backdrop-filter: blur(15px); + border-radius: inherit; + } + + @supports (animation-timeline: scroll()) { + animation-name: app-build-bar-border-on-scroll; + animation-duration: 1s; + animation-fill-mode: both; + animation-timing-function: linear; + animation-timeline: scroll(nearest block); + animation-range: 0 var(--header-height); + } + + @media (width < 740px) { + position: relative; + display: flex; + flex-direction: column; + align-items: start; + padding-block: var(--section-padding); + top: 0; } .app-build-bar__content { @@ -247,6 +219,7 @@ body:has(.app-settings-container) { display: inline-flex; align-items: center; justify-content: center; + margin-block: 0.5rem; } .app-build-bar__controls { @@ -254,7 +227,7 @@ body:has(.app-settings-container) { align-items: center; justify-content: end; flex: 1; - gap: 0.75rem; + gap: 0.5rem; @media (width <= 600px) { justify-content: stretch; @@ -278,12 +251,14 @@ body:has(.app-settings-container) { svg { width: 2rem; height: 2rem; + flex-shrink: 0; } } h1 { margin-bottom: 0; - font-size: 1.25rem; + font-size: 1.1875rem; + font-weight: 550; } .jenkins-button { @@ -325,6 +300,10 @@ body:has(.app-settings-container) { } } +.app-page-body__contents:has(.app-build-bar) { + padding-top: 0; +} + .app-build-content { display: flex; flex-direction: column; diff --git a/src/main/scss/pages/_job.scss b/src/main/scss/pages/_job.scss index 34732c7df1..44b9ee0ce7 100644 --- a/src/main/scss/pages/_job.scss +++ b/src/main/scss/pages/_job.scss @@ -1,11 +1,5 @@ @use "../abstracts/mixins"; -#side-panel { - #buildHistoryPage { - margin: 10px 0 10px 10px; - } -} - #buildHistoryPage { .jenkins-search { margin-inline: -0.25rem; diff --git a/src/main/scss/pages/_plugin-manager.scss b/src/main/scss/pages/_plugin-manager.scss index a1b23e7831..f4601d8255 100644 --- a/src/main/scss/pages/_plugin-manager.scss +++ b/src/main/scss/pages/_plugin-manager.scss @@ -104,13 +104,3 @@ border-radius: 0.25em; } } - -.app-plugin-manager__sidebar { - position: sticky; - top: calc(40px + 1.6rem); - - h1 { - margin-top: 1rem !important; - margin-bottom: 0.1rem !important; - } -} diff --git a/test/src/test/java/jenkins/model/ErrorPageTest.java b/test/src/test/java/jenkins/model/ErrorPageTest.java index 51a76827e3..69897cadbc 100644 --- a/test/src/test/java/jenkins/model/ErrorPageTest.java +++ b/test/src/test/java/jenkins/model/ErrorPageTest.java @@ -57,7 +57,6 @@ class ErrorPageTest { final String content = ex.getResponse().getContentAsString(StandardCharsets.UTF_8); assertThat(content, not(containsString(j.contextPath + "/login?from="))); assertThat(content, containsString("This page does not exist.")); - assertThat(content, not(containsString("REST API"))); } { // paths are fine on error page even when nested @@ -66,7 +65,6 @@ class ErrorPageTest { final String content = ex.getResponse().getContentAsString(StandardCharsets.UTF_8); assertThat(content, not(containsString(j.contextPath + "/login?from="))); assertThat(content, containsString("This page does not exist.")); - assertThat(content, not(containsString("REST API"))); } { // resource root action have custom (less) error message content @@ -76,7 +74,6 @@ class ErrorPageTest { assertThat(content, not(containsString(j.contextPath + "/login?from="))); assertThat(content, not(containsString("This page does not exist."))); assertThat(content, not(containsString("This page may not exist, or you may not have permission to see it."))); - assertThat(content, not(containsString("REST API"))); } /* Set up security realm and request as anonymous, we expect login link and hedged response */ @@ -90,7 +87,6 @@ class ErrorPageTest { assertThat(content, containsString(j.contextPath + "/login?from=" + j.contextPath.replace("/", "%2F") + "%2Ffoo")); assertThat(content, not(containsString(j.contextPath + "/login?from=" + j.contextPath.replace("/", "%2F") + "%2F404"))); assertThat(content, containsString("This page may not exist, or you may not have permission to see it.")); - assertThat(content, not(containsString("REST API"))); } { // paths are fine on error page even when nested @@ -100,7 +96,6 @@ class ErrorPageTest { assertThat(content, containsString(j.contextPath + "/login?from=" + j.contextPath.replace("/", "%2F") + "%2Ffoo%2Fbar%2Fbaz%2F")); assertThat(content, not(containsString(j.contextPath + "/login?from=" + j.contextPath.replace("/", "%2F") + "%2F404"))); assertThat(content, containsString("This page may not exist, or you may not have permission to see it.")); - assertThat(content, not(containsString("REST API"))); } { // resource root action have custom (less) error message content @@ -111,7 +106,6 @@ class ErrorPageTest { assertThat(content, not(containsString(j.contextPath + "/login?from=" + j.contextPath.replace("/", "%2F") + "%2F404"))); assertThat(content, not(containsString("This page does not exist."))); assertThat(content, not(containsString("This page may not exist, or you may not have permission to see it."))); - assertThat(content, not(containsString("REST API"))); } /* With the security realm still set up, log in and expect the profile link to show */ @@ -124,7 +118,6 @@ class ErrorPageTest { assertThat(content, not(containsString(j.contextPath + "/login?from="))); assertThat(content, containsString("user/alice")); assertThat(content, containsString("This page may not exist, or you may not have permission to see it.")); - assertThat(content, not(containsString("REST API"))); } { // paths are fine on error page even when nested @@ -134,7 +127,6 @@ class ErrorPageTest { assertThat(content, not(containsString(j.contextPath + "/login?from="))); assertThat(content, containsString("user/alice")); assertThat(content, containsString("This page may not exist, or you may not have permission to see it.")); - assertThat(content, not(containsString("REST API"))); } { // resource root action have custom (less) error message content @@ -145,7 +137,6 @@ class ErrorPageTest { assertThat(content, containsString("user/alice")); assertThat(content, not(containsString("This page does not exist."))); assertThat(content, not(containsString("This page may not exist, or you may not have permission to see it."))); - assertThat(content, not(containsString("REST API"))); } } finally { Dispatcher.TRACE = true; @@ -176,13 +167,11 @@ class ErrorPageTest { final String content = page.getWebResponse().getContentAsString(StandardCharsets.UTF_8); assertThat(content, containsString("Back to Jenkins")); assertThat(content, containsString("Jenkins serves only static files on this domain.")); - assertThat(content, not(containsString("REST API"))); if (page.isHtmlPage()) { final HtmlPage htmlPage = (HtmlPage) page; final Page nextPage = htmlPage.getAnchorByText("Back to Jenkins").click(); final String nextContent = nextPage.getWebResponse().getContentAsString(StandardCharsets.UTF_8); assertThat(nextContent, containsString("Welcome to Jenkins")); - assertThat(nextContent, containsString("REST API")); // Rest API exists for Jenkins main page } } }