diff --git a/app/views/warning_bar/_warning_bar.html.erb b/app/views/warning_bar/_warning_bar.html.erb
index 0c92614aa61a..0ef1422cabbf 100644
--- a/app/views/warning_bar/_warning_bar.html.erb
+++ b/app/views/warning_bar/_warning_bar.html.erb
@@ -1,8 +1,8 @@
-<% if show_warning_bar? %>
-
- <%= render partial: "warning_bar/host_and_protocol_mismatch" if render_host_and_protocol_mismatch? %>
- <%= render partial: "warning_bar/pending_migrations" if render_pending_migrations_warning? %>
- <%= render partial: "warning_bar/unsupported_browser" if unsupported_browser? %>
- <%= render partial: "warning_bar/missing_wp_edit_role_workflows" if render_workflow_missing_warning? %>
+
+ <% if show_warning_bar? %>
+ <%= render partial: "warning_bar/host_and_protocol_mismatch" if render_host_and_protocol_mismatch? %>
+ <%= render partial: "warning_bar/pending_migrations" if render_pending_migrations_warning? %>
+ <%= render partial: "warning_bar/unsupported_browser" if unsupported_browser? %>
+ <%= render partial: "warning_bar/missing_wp_edit_role_workflows" if render_workflow_missing_warning? %>
+ <% end %>
-<% end %>
diff --git a/frontend/src/global_styles/layout/_base.sass b/frontend/src/global_styles/layout/_base.sass
index 1261bf796b8b..48f2fc0168a8 100644
--- a/frontend/src/global_styles/layout/_base.sass
+++ b/frontend/src/global_styles/layout/_base.sass
@@ -48,7 +48,7 @@
margin: 0 0 0 0
padding: 0
// Needed for Safari
- height: calc(100dvh - var(--header-height))
+ height: calc(100dvh - var(--header-height) - var(--warning-bar-height, 0px))
overflow-y: auto
overflow-x: hidden
background-color: var(--body-background)
diff --git a/frontend/src/global_styles/layout/_base_mobile.sass b/frontend/src/global_styles/layout/_base_mobile.sass
index 81046d14d272..ac759e1defcd 100644
--- a/frontend/src/global_styles/layout/_base_mobile.sass
+++ b/frontend/src/global_styles/layout/_base_mobile.sass
@@ -40,7 +40,7 @@
#main,
#content-wrapper,
#main-menu #menu-sidebar
- height: 100dvh
+ height: calc(100dvh - var(--warning-bar-height, 0px))
#main .content-overlay
top: 0 !important
diff --git a/frontend/src/global_styles/layout/_main_menu.sass b/frontend/src/global_styles/layout/_main_menu.sass
index 599a9fb7582e..2cb55bc9c2b7 100644
--- a/frontend/src/global_styles/layout/_main_menu.sass
+++ b/frontend/src/global_styles/layout/_main_menu.sass
@@ -61,7 +61,7 @@ $arrow-left-width: 36px
display: flex
flex-direction: column
+allow-vertical-scrolling
- height: calc(100dvh - var(--header-height))
+ height: calc(100dvh - var(--header-height) - var(--warning-bar-height, 0px))
position: relative
@include styled-scroll-bar
padding: 0 var(--main-menu-x-spacing) var(--main-menu-x-spacing) var(--main-menu-x-spacing)
diff --git a/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.spec.ts b/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.spec.ts
new file mode 100644
index 000000000000..93b2b5458fb6
--- /dev/null
+++ b/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.spec.ts
@@ -0,0 +1,95 @@
+//-- copyright
+// OpenProject is an open source project management software.
+// Copyright (C) the OpenProject GmbH
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License version 3.
+//
+// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+// Copyright (C) 2006-2013 Jean-Philippe Lang
+// Copyright (C) 2010-2013 the ChiliProject Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see
.
+//
+// See COPYRIGHT and LICENSE files for more details.
+//++
+
+import { waitFor } from '@testing-library/dom';
+
+import WarningBarController from './warning-bar.controller';
+import { setupStimulusTest, type StimulusTestContext } from 'core-stimulus/test-helpers';
+
+describe('WarningBarController', () => {
+ let ctx:StimulusTestContext;
+
+ const warningBarHeight = () => document.documentElement.style.getPropertyValue('--warning-bar-height');
+
+ beforeEach(async () => {
+ ctx = await setupStimulusTest({
+ controllers: { 'warning-bar': WarningBarController },
+ });
+ });
+
+ afterEach(() => ctx.dispose());
+
+ it('publishes the height of the bar', async () => {
+ await ctx.mount(`
+
+ `);
+
+ await waitFor(() => {
+ expect(warningBarHeight()).toBe('42px');
+ });
+ });
+
+ it('publishes the new height when the bar grows', async () => {
+ await ctx.mount(`
+
+ `);
+ await waitFor(() => expect(warningBarHeight()).toBe('42px'));
+
+ document.getElementById('banner')!.style.height = '80px';
+
+ await waitFor(() => {
+ expect(warningBarHeight()).toBe('80px');
+ });
+ });
+
+ it('publishes no height for an empty bar', async () => {
+ await ctx.mount('
');
+
+ await waitFor(() => {
+ expect(warningBarHeight()).toBe('0px');
+ });
+ });
+
+ it('removes the height when the bar is removed', async () => {
+ await ctx.mount(`
+
+ `);
+ await waitFor(() => expect(warningBarHeight()).toBe('42px'));
+
+ document.querySelector('[data-controller="warning-bar"]')!.remove();
+
+ await waitFor(() => {
+ expect(warningBarHeight()).toBe('');
+ });
+ });
+});
diff --git a/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.ts b/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.ts
new file mode 100644
index 000000000000..0d3775bc342c
--- /dev/null
+++ b/frontend/src/stimulus/controllers/dynamic/warning-bar.controller.ts
@@ -0,0 +1,46 @@
+//-- copyright
+// OpenProject is an open source project management software.
+// Copyright (C) the OpenProject GmbH
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License version 3.
+//
+// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+// Copyright (C) 2006-2013 Jean-Philippe Lang
+// Copyright (C) 2010-2013 the ChiliProject Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see
.
+//
+// See COPYRIGHT and LICENSE files for more details.
+//++
+
+import { Controller } from '@hotwired/stimulus';
+
+export default class WarningBarController extends Controller
{
+ private resizeObserver:ResizeObserver;
+
+ connect() {
+ this.resizeObserver = new ResizeObserver(() => this.publishHeight());
+ this.resizeObserver.observe(this.element);
+ }
+
+ disconnect() {
+ this.resizeObserver.disconnect();
+ document.documentElement.style.removeProperty('--warning-bar-height');
+ }
+
+ private publishHeight() {
+ document.documentElement.style.setProperty('--warning-bar-height', `${this.element.offsetHeight}px`);
+ }
+}
diff --git a/frontend/src/turbo/turbo-global-listeners.ts b/frontend/src/turbo/turbo-global-listeners.ts
index 95370fc7cd4b..d115e98da22d 100644
--- a/frontend/src/turbo/turbo-global-listeners.ts
+++ b/frontend/src/turbo/turbo-global-listeners.ts
@@ -42,14 +42,6 @@ import { isOpenProjectCustomElement } from './openproject-custom-element';
export function addTurboGlobalListeners(target:Document = document, signal?:AbortSignal) {
const runOnRenderAndLoad = () => {
- // Add to content if warnings displayed
- if (target.querySelector('.warning-bar--item')) {
- const content = target.querySelector('#content');
- if (content) {
- content.style.marginBottom = '100px';
- }
- }
-
const deviceService:DeviceService = new DeviceService();
// Register scroll handler on mobile header
if (deviceService.isMobile) {