From c165164bb87c0475780ca6ec894249afad8319a4 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Thu, 15 Oct 2026 11:06:20 +0200 Subject: [PATCH] Dynamically calculate the space the warning bar will take, instead of hard-coding 100px --- app/views/warning_bar/_warning_bar.html.erb | 14 +-- frontend/src/global_styles/layout/_base.sass | 2 +- .../global_styles/layout/_base_mobile.sass | 2 +- .../src/global_styles/layout/_main_menu.sass | 2 +- .../dynamic/warning-bar.controller.spec.ts | 95 +++++++++++++++++++ .../dynamic/warning-bar.controller.ts | 46 +++++++++ frontend/src/turbo/turbo-global-listeners.ts | 8 -- 7 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 frontend/src/stimulus/controllers/dynamic/warning-bar.controller.spec.ts create mode 100644 frontend/src/stimulus/controllers/dynamic/warning-bar.controller.ts 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) {