From bf12cfe2930d4e4e1aab9241c059eacf9fe760cc Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Mon, 13 Oct 2025 10:01:24 +0200 Subject: [PATCH 1/9] Refactor panel height calc to use resizeObserver --- app/assets/javascripts/panel_height_calc.js | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/panel_height_calc.js b/app/assets/javascripts/panel_height_calc.js index 2711ce3a967fca..e80fa018848056 100644 --- a/app/assets/javascripts/panel_height_calc.js +++ b/app/assets/javascripts/panel_height_calc.js @@ -1,28 +1,37 @@ -import { debounce } from 'lodash'; - const PANEL_SELECTOR = '.panel-content-inner'; const CSS_VARIABLE = '--panel-content-inner-height'; -const measurePanel = () => { - const panel = document.querySelector(PANEL_SELECTOR); +const measurePanel = (panel) => { return panel?.getBoundingClientRect().height ?? ''; }; -const setCSSVar = debounce(() => { - const height = measurePanel(); +const setCSSVar = (height) => { if (height) { document.documentElement.style.setProperty(CSS_VARIABLE, `${height}px`); } else { document.documentElement.style.setProperty(CSS_VARIABLE, null); } -}, 200); +}; export default () => { - setCSSVar(); + const panel = document.querySelector(PANEL_SELECTOR); + + if (!panel) { + return () => {}; + } + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const { height } = entry.contentRect; + setCSSVar(height); + } + }); + + resizeObserver.observe(panel); - window.addEventListener('resize', setCSSVar); + setCSSVar(measurePanel(panel)); return () => { - window.removeEventListener('resize', setCSSVar); + resizeObserver.disconnect(); }; }; -- GitLab From 7bfc23531e25dfccddef38a7660d56490f849d52 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Mon, 13 Oct 2025 10:18:58 +0200 Subject: [PATCH 2/9] Use entries[0] instead of looping --- app/assets/javascripts/panel_height_calc.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/panel_height_calc.js b/app/assets/javascripts/panel_height_calc.js index e80fa018848056..94fb371c953893 100644 --- a/app/assets/javascripts/panel_height_calc.js +++ b/app/assets/javascripts/panel_height_calc.js @@ -21,10 +21,8 @@ export default () => { } const resizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const { height } = entry.contentRect; - setCSSVar(height); - } + const { height } = entries[0].contentRect; + setCSSVar(height); }); resizeObserver.observe(panel); -- GitLab From 3c070e3d23e7d4e115914cf77a70e933163f638e Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Mon, 13 Oct 2025 11:20:05 +0200 Subject: [PATCH 3/9] Add debounce --- app/assets/javascripts/panel_height_calc.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/panel_height_calc.js b/app/assets/javascripts/panel_height_calc.js index 94fb371c953893..7557dee88701de 100644 --- a/app/assets/javascripts/panel_height_calc.js +++ b/app/assets/javascripts/panel_height_calc.js @@ -1,3 +1,5 @@ +import { debounce } from 'lodash'; + const PANEL_SELECTOR = '.panel-content-inner'; const CSS_VARIABLE = '--panel-content-inner-height'; @@ -5,13 +7,13 @@ const measurePanel = (panel) => { return panel?.getBoundingClientRect().height ?? ''; }; -const setCSSVar = (height) => { +const setCSSVar = debounce((height) => { if (height) { document.documentElement.style.setProperty(CSS_VARIABLE, `${height}px`); } else { document.documentElement.style.setProperty(CSS_VARIABLE, null); } -}; +}, 200); export default () => { const panel = document.querySelector(PANEL_SELECTOR); -- GitLab From cacbb10e1e2f3fd30982e0fcd4f95460db75def0 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Tue, 14 Oct 2025 10:52:22 +0200 Subject: [PATCH 4/9] CSS only solution --- app/assets/javascripts/main.js | 2 - app/assets/javascripts/panel_height_calc.js | 37 ------------------- .../framework/application-chrome.scss | 32 +++++++++++----- .../stylesheets/page_bundles/work_items.scss | 5 --- 4 files changed, 22 insertions(+), 54 deletions(-) delete mode 100644 app/assets/javascripts/panel_height_calc.js diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index ee4a9fc74e596d..62206098d32a21 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -34,7 +34,6 @@ import initBroadcastNotifications from './broadcast_notification'; import { initCopyCodeButton } from './behaviors/copy_code'; import initGitlabVersionCheck from './gitlab_version_check'; import { initExpireSessionModal } from './authentication/sessions'; -import initPanelHeightCalc from './panel_height_calc'; import 'ee_else_ce/main_ee'; import 'jh_else_ce/main_jh'; @@ -98,7 +97,6 @@ function deferredInitialisation() { initCopyCodeButton(); initGitlabVersionCheck(); initExpireSessionModal(); - initPanelHeightCalc(); addSelectOnFocusBehaviour('.js-select-on-focus'); diff --git a/app/assets/javascripts/panel_height_calc.js b/app/assets/javascripts/panel_height_calc.js deleted file mode 100644 index 7557dee88701de..00000000000000 --- a/app/assets/javascripts/panel_height_calc.js +++ /dev/null @@ -1,37 +0,0 @@ -import { debounce } from 'lodash'; - -const PANEL_SELECTOR = '.panel-content-inner'; -const CSS_VARIABLE = '--panel-content-inner-height'; - -const measurePanel = (panel) => { - return panel?.getBoundingClientRect().height ?? ''; -}; - -const setCSSVar = debounce((height) => { - if (height) { - document.documentElement.style.setProperty(CSS_VARIABLE, `${height}px`); - } else { - document.documentElement.style.setProperty(CSS_VARIABLE, null); - } -}, 200); - -export default () => { - const panel = document.querySelector(PANEL_SELECTOR); - - if (!panel) { - return () => {}; - } - - const resizeObserver = new ResizeObserver((entries) => { - const { height } = entries[0].contentRect; - setCSSVar(height); - }); - - resizeObserver.observe(panel); - - setCSSVar(measurePanel(panel)); - - return () => { - resizeObserver.disconnect(); - }; -}; diff --git a/app/assets/stylesheets/framework/application-chrome.scss b/app/assets/stylesheets/framework/application-chrome.scss index 44f9f43ddf36ab..136a5426f7fb43 100644 --- a/app/assets/stylesheets/framework/application-chrome.scss +++ b/app/assets/stylesheets/framework/application-chrome.scss @@ -425,6 +425,12 @@ @apply gl-px-3 gl-mx-0; } + .panel-content, + .vue-portal-target .work-item-drawer-content { + container-name: panel-content; + container-type: size; + } + // Panel content .panel-content { height: calc(100% - #{$header-height} - #{$gl-spacing-scale-3}); @@ -487,9 +493,10 @@ } // Issue boards height - .boards-app { - height: var(--panel-content-inner-height); - @apply gl-overflow-y-hidden; + .boards-app, + .issue-boards-content, + .issue-boards-content > .content { + @apply gl-h-full; } .boards-list { @@ -576,11 +583,13 @@ @apply gl-hidden #{!important}; &.right-sidebar-expanded { - @apply gl-block #{!important}; + @apply gl-flex #{!important}; + @apply gl-flex-col; } @container content-panels (width >= #{map.get($breakpoints, 'sm')}) { - @apply gl-block #{!important}; + @apply gl-flex #{!important}; + @apply gl-flex-col; } } @@ -898,14 +907,17 @@ transform .25s $gl-easing-out-cubic !important; } } -} -// Issue sidebar -@include gl-container-width-up(md) { - &:where(.application-chrome) { + // Issue sidebar + @container panel-content (width >= #{map.get($breakpoints, 'md')}) { .work-item-attributes-wrapper { top: calc(#{$work-item-sticky-header-height} + #{$gl-spacing-scale-3}) !important; - height: calc(var(--panel-content-inner-height) - var(--work-item-sticky-header-height) - 1.5rem + 1px) !important; // 1.5rem is to account for padding, the +1px is to account for a border + height: calc(100cqh - var(--work-item-sticky-header-height) - 1.5rem - #{$gl-spacing-scale-5}) !important; // Account for padding + } + + // Is sticky + :has(.issue-sticky-header) .work-item-attributes-wrapper { + height: calc(100cqh - var(--work-item-sticky-header-height) - #{$gl-spacing-scale-5} - #{$gl-spacing-scale-3}) !important; // Account for padding } } diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss index 94ed5496a2cc9c..8045b0699cbec8 100644 --- a/app/assets/stylesheets/page_bundles/work_items.scss +++ b/app/assets/stylesheets/page_bundles/work_items.scss @@ -487,8 +487,3 @@ ul.content-list { .work-item-drawer [data-testid="work-item-detail"]:has(.issue-sticky-header) .flash-container.sticky { top: calc(var(--work-item-sticky-header-height, 3rem) + $gl-spacing-scale-3); } - -.work-item-bulk-edit-sidebar-wrapper { - /* 57px – bulk actions header height */ - height: calc(var(--panel-content-inner-height, 100%) - 57px); -} -- GitLab From 8135f74479dd917db124a1e4bdd90178fcd249b2 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Thu, 16 Oct 2025 10:35:41 +0200 Subject: [PATCH 5/9] Add comment for meta --- app/assets/stylesheets/framework/application-chrome.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/application-chrome.scss b/app/assets/stylesheets/framework/application-chrome.scss index 136a5426f7fb43..da325ee3ba51a2 100644 --- a/app/assets/stylesheets/framework/application-chrome.scss +++ b/app/assets/stylesheets/framework/application-chrome.scss @@ -912,12 +912,12 @@ @container panel-content (width >= #{map.get($breakpoints, 'md')}) { .work-item-attributes-wrapper { top: calc(#{$work-item-sticky-header-height} + #{$gl-spacing-scale-3}) !important; - height: calc(100cqh - var(--work-item-sticky-header-height) - 1.5rem - #{$gl-spacing-scale-5}) !important; // Account for padding + height: calc(100cqh - #{$work-item-sticky-header-height} - 1.5rem - #{$gl-spacing-scale-5}) !important; // Account for padding and meta information } // Is sticky :has(.issue-sticky-header) .work-item-attributes-wrapper { - height: calc(100cqh - var(--work-item-sticky-header-height) - #{$gl-spacing-scale-5} - #{$gl-spacing-scale-3}) !important; // Account for padding + height: calc(100cqh - #{$work-item-sticky-header-height} - #{$gl-spacing-scale-5} - #{$gl-spacing-scale-3}) !important; // Account for padding } } -- GitLab From 076a764a43b29e81592a3fc4aa02ff2e46a7a7a7 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Thu, 16 Oct 2025 11:54:35 +0200 Subject: [PATCH 6/9] Fix MR sidebar --- .../framework/application-chrome.scss | 1 + app/assets/stylesheets/framework/sidebar.scss | 7 +- app/views/shared/issuable/_sidebar.html.haml | 91 ++++++++++--------- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/app/assets/stylesheets/framework/application-chrome.scss b/app/assets/stylesheets/framework/application-chrome.scss index da325ee3ba51a2..4523f8388caf2f 100644 --- a/app/assets/stylesheets/framework/application-chrome.scss +++ b/app/assets/stylesheets/framework/application-chrome.scss @@ -913,6 +913,7 @@ .work-item-attributes-wrapper { top: calc(#{$work-item-sticky-header-height} + #{$gl-spacing-scale-3}) !important; height: calc(100cqh - #{$work-item-sticky-header-height} - 1.5rem - #{$gl-spacing-scale-5}) !important; // Account for padding and meta information + overscroll-behavior: contain; } // Is sticky diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 417c3e23093e94..014b022c77db33 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -481,14 +481,17 @@ @include gl-container-width-up(lg, panel) { padding: 0; - .issuable-context-form { + .issuable-context-form-wrapper { top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height}); - height: calc(#{$calc-application-viewport-height} - #{$mr-sticky-header-height}); position: sticky; padding: 0 $gl-spacing-scale-3; margin-bottom: calc((#{$content-wrapper-padding} * -1)); overflow-y: auto; overscroll-behavior: contain; + + .issuable-context-form { + height: calc(#{$calc-application-viewport-height} - #{$mr-sticky-header-height}); + } } } } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index bb1258a43424a6..4bbde76aea007a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -22,65 +22,66 @@ - if notifications_todos_buttons_enabled? .js-sidebar-subscriptions-widget-root - = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'!gl-pr-2' if is_merge_request}" } do |f| - .block.assignee{ class: "#{'gl-mt-3' if !signed_in}", data: { testid: 'assignee-block-container' } } - = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in + .issuable-context-form-wrapper + = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'!gl-pr-2' if is_merge_request}" } do |f| + .block.assignee{ class: "#{'gl-mt-3' if !signed_in}", data: { testid: 'assignee-block-container' } } + = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - - if issuable_sidebar[:supports_severity] - .js-sidebar-severity-widget-root + - if issuable_sidebar[:supports_severity] + .js-sidebar-severity-widget-root - - if reviewers - .block.reviewer{ data: { testid: 'reviewers-block-container' } } - = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in + - if reviewers + .block.reviewer{ data: { testid: 'reviewers-block-container' } } + = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in - - if issuable_sidebar[:supports_escalation] - .block.escalation-status{ data: { testid: 'escalation_status_container' } } - .js-sidebar-escalation-status-root{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - = render_if_exists 'shared/issuable/sidebar_escalation_policy', issuable_sidebar: issuable_sidebar + - if issuable_sidebar[:supports_escalation] + .block.escalation-status{ data: { testid: 'escalation_status_container' } } + .js-sidebar-escalation-status-root{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + = render_if_exists 'shared/issuable/sidebar_escalation_policy', issuable_sidebar: issuable_sidebar - - if @project.group.present? - = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type + - if @project.group.present? + = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type - .js-sidebar-labels-widget-root{ data: sidebar_labels_data(issuable_sidebar, @project) } + .js-sidebar-labels-widget-root{ data: sidebar_labels_data(issuable_sidebar, @project) } - - if issuable_sidebar[:supports_milestone] - .block.milestone{ data: { testid: 'sidebar-milestones' } } - .js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + - if issuable_sidebar[:supports_milestone] + .block.milestone{ data: { testid: 'sidebar-milestones' } } + .js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - - if in_group_context_with_iterations && @project.group.licensed_feature_available?(:iterations) - .block{ data: { testid: 'iteration-container' } }< - = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type, issue_id: issuable_sidebar[:id] + - if in_group_context_with_iterations && @project.group.licensed_feature_available?(:iterations) + .block{ data: { testid: 'iteration-container' } }< + = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type, issue_id: issuable_sidebar[:id] - - if issuable_sidebar[:show_crm_contacts] - .block.contact - .js-sidebar-crm-contacts-root{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } + - if issuable_sidebar[:show_crm_contacts] + .block.contact + .js-sidebar-crm-contacts-root{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } - = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar, can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] + = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar, can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] - - if issuable_sidebar.has_key?(:due_date) - .js-sidebar-due-date-widget-root + - if issuable_sidebar.has_key?(:due_date) + .js-sidebar-due-date-widget-root - - if issuable_sidebar[:supports_time_tracking] - .js-sidebar-time-tracking-root.block - // Fallback while content is loading - .title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' } - %span.gl-font-bold= _('Time tracking') - = gl_loading_icon(inline: true) + - if issuable_sidebar[:supports_time_tracking] + .js-sidebar-time-tracking-root.block + // Fallback while content is loading + .title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' } + %span.gl-font-bold= _('Time tracking') + = gl_loading_icon(inline: true) - - if issuable_sidebar.dig(:features_available, :health_status) - .js-sidebar-health-status-widget-root{ data: sidebar_status_data(issuable_sidebar, @project) } + - if issuable_sidebar.dig(:features_available, :health_status) + .js-sidebar-health-status-widget-root{ data: sidebar_status_data(issuable_sidebar, @project) } - - if issuable_sidebar.has_key?(:confidential) - -# haml-lint:disable InlineJavaScript - %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe + - if issuable_sidebar.has_key?(:confidential) + -# haml-lint:disable InlineJavaScript + %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe - = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar + = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar - .js-sidebar-participants-widget-root + .js-sidebar-participants-widget-root - - if issuable_sidebar.dig(:current_user, :can_move) - .block - .js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + - if issuable_sidebar.dig(:current_user, :can_move) + .block + .js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - -# haml-lint:disable InlineJavaScript - %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar, @project).to_json.html_safe + -# haml-lint:disable InlineJavaScript + %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar, @project).to_json.html_safe -- GitLab From 4016522f6f3055aa4176a014e5cf7efacbff0538 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Fri, 17 Oct 2025 18:06:51 +0200 Subject: [PATCH 7/9] Remove sidebar overrides --- app/assets/javascripts/main.js | 2 + app/assets/javascripts/panel_height_calc.js | 28 ++++++ .../framework/application-chrome.scss | 15 ++- app/assets/stylesheets/framework/sidebar.scss | 7 +- app/views/shared/issuable/_sidebar.html.haml | 91 +++++++++---------- 5 files changed, 83 insertions(+), 60 deletions(-) create mode 100644 app/assets/javascripts/panel_height_calc.js diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 62206098d32a21..ee4a9fc74e596d 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -34,6 +34,7 @@ import initBroadcastNotifications from './broadcast_notification'; import { initCopyCodeButton } from './behaviors/copy_code'; import initGitlabVersionCheck from './gitlab_version_check'; import { initExpireSessionModal } from './authentication/sessions'; +import initPanelHeightCalc from './panel_height_calc'; import 'ee_else_ce/main_ee'; import 'jh_else_ce/main_jh'; @@ -97,6 +98,7 @@ function deferredInitialisation() { initCopyCodeButton(); initGitlabVersionCheck(); initExpireSessionModal(); + initPanelHeightCalc(); addSelectOnFocusBehaviour('.js-select-on-focus'); diff --git a/app/assets/javascripts/panel_height_calc.js b/app/assets/javascripts/panel_height_calc.js new file mode 100644 index 00000000000000..2711ce3a967fca --- /dev/null +++ b/app/assets/javascripts/panel_height_calc.js @@ -0,0 +1,28 @@ +import { debounce } from 'lodash'; + +const PANEL_SELECTOR = '.panel-content-inner'; +const CSS_VARIABLE = '--panel-content-inner-height'; + +const measurePanel = () => { + const panel = document.querySelector(PANEL_SELECTOR); + return panel?.getBoundingClientRect().height ?? ''; +}; + +const setCSSVar = debounce(() => { + const height = measurePanel(); + if (height) { + document.documentElement.style.setProperty(CSS_VARIABLE, `${height}px`); + } else { + document.documentElement.style.setProperty(CSS_VARIABLE, null); + } +}, 200); + +export default () => { + setCSSVar(); + + window.addEventListener('resize', setCSSVar); + + return () => { + window.removeEventListener('resize', setCSSVar); + }; +}; diff --git a/app/assets/stylesheets/framework/application-chrome.scss b/app/assets/stylesheets/framework/application-chrome.scss index 4523f8388caf2f..584ee209da8198 100644 --- a/app/assets/stylesheets/framework/application-chrome.scss +++ b/app/assets/stylesheets/framework/application-chrome.scss @@ -425,6 +425,7 @@ @apply gl-px-3 gl-mx-0; } + // For content size calculation via CSS .panel-content, .vue-portal-target .work-item-drawer-content { container-name: panel-content; @@ -907,18 +908,14 @@ transform .25s $gl-easing-out-cubic !important; } } +} - // Issue sidebar - @container panel-content (width >= #{map.get($breakpoints, 'md')}) { +// Issue sidebar +@include gl-container-width-up(md) { + &:where(.application-chrome) { .work-item-attributes-wrapper { top: calc(#{$work-item-sticky-header-height} + #{$gl-spacing-scale-3}) !important; - height: calc(100cqh - #{$work-item-sticky-header-height} - 1.5rem - #{$gl-spacing-scale-5}) !important; // Account for padding and meta information - overscroll-behavior: contain; - } - - // Is sticky - :has(.issue-sticky-header) .work-item-attributes-wrapper { - height: calc(100cqh - #{$work-item-sticky-header-height} - #{$gl-spacing-scale-5} - #{$gl-spacing-scale-3}) !important; // Account for padding + height: calc(var(--panel-content-inner-height) - var(--work-item-sticky-header-height) - 1.5rem + 1px) !important; // 1.5rem is to account for padding, the +1px is to account for a border } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 014b022c77db33..417c3e23093e94 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -481,17 +481,14 @@ @include gl-container-width-up(lg, panel) { padding: 0; - .issuable-context-form-wrapper { + .issuable-context-form { top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height}); + height: calc(#{$calc-application-viewport-height} - #{$mr-sticky-header-height}); position: sticky; padding: 0 $gl-spacing-scale-3; margin-bottom: calc((#{$content-wrapper-padding} * -1)); overflow-y: auto; overscroll-behavior: contain; - - .issuable-context-form { - height: calc(#{$calc-application-viewport-height} - #{$mr-sticky-header-height}); - } } } } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 4bbde76aea007a..bb1258a43424a6 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -22,66 +22,65 @@ - if notifications_todos_buttons_enabled? .js-sidebar-subscriptions-widget-root - .issuable-context-form-wrapper - = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'!gl-pr-2' if is_merge_request}" } do |f| - .block.assignee{ class: "#{'gl-mt-3' if !signed_in}", data: { testid: 'assignee-block-container' } } - = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in + = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'!gl-pr-2' if is_merge_request}" } do |f| + .block.assignee{ class: "#{'gl-mt-3' if !signed_in}", data: { testid: 'assignee-block-container' } } + = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - - if issuable_sidebar[:supports_severity] - .js-sidebar-severity-widget-root + - if issuable_sidebar[:supports_severity] + .js-sidebar-severity-widget-root - - if reviewers - .block.reviewer{ data: { testid: 'reviewers-block-container' } } - = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in + - if reviewers + .block.reviewer{ data: { testid: 'reviewers-block-container' } } + = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in - - if issuable_sidebar[:supports_escalation] - .block.escalation-status{ data: { testid: 'escalation_status_container' } } - .js-sidebar-escalation-status-root{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - = render_if_exists 'shared/issuable/sidebar_escalation_policy', issuable_sidebar: issuable_sidebar + - if issuable_sidebar[:supports_escalation] + .block.escalation-status{ data: { testid: 'escalation_status_container' } } + .js-sidebar-escalation-status-root{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + = render_if_exists 'shared/issuable/sidebar_escalation_policy', issuable_sidebar: issuable_sidebar - - if @project.group.present? - = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type + - if @project.group.present? + = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type - .js-sidebar-labels-widget-root{ data: sidebar_labels_data(issuable_sidebar, @project) } + .js-sidebar-labels-widget-root{ data: sidebar_labels_data(issuable_sidebar, @project) } - - if issuable_sidebar[:supports_milestone] - .block.milestone{ data: { testid: 'sidebar-milestones' } } - .js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + - if issuable_sidebar[:supports_milestone] + .block.milestone{ data: { testid: 'sidebar-milestones' } } + .js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - - if in_group_context_with_iterations && @project.group.licensed_feature_available?(:iterations) - .block{ data: { testid: 'iteration-container' } }< - = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type, issue_id: issuable_sidebar[:id] + - if in_group_context_with_iterations && @project.group.licensed_feature_available?(:iterations) + .block{ data: { testid: 'iteration-container' } }< + = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type, issue_id: issuable_sidebar[:id] - - if issuable_sidebar[:show_crm_contacts] - .block.contact - .js-sidebar-crm-contacts-root{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } + - if issuable_sidebar[:show_crm_contacts] + .block.contact + .js-sidebar-crm-contacts-root{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } - = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar, can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] + = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar, can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] - - if issuable_sidebar.has_key?(:due_date) - .js-sidebar-due-date-widget-root + - if issuable_sidebar.has_key?(:due_date) + .js-sidebar-due-date-widget-root - - if issuable_sidebar[:supports_time_tracking] - .js-sidebar-time-tracking-root.block - // Fallback while content is loading - .title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' } - %span.gl-font-bold= _('Time tracking') - = gl_loading_icon(inline: true) + - if issuable_sidebar[:supports_time_tracking] + .js-sidebar-time-tracking-root.block + // Fallback while content is loading + .title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' } + %span.gl-font-bold= _('Time tracking') + = gl_loading_icon(inline: true) - - if issuable_sidebar.dig(:features_available, :health_status) - .js-sidebar-health-status-widget-root{ data: sidebar_status_data(issuable_sidebar, @project) } + - if issuable_sidebar.dig(:features_available, :health_status) + .js-sidebar-health-status-widget-root{ data: sidebar_status_data(issuable_sidebar, @project) } - - if issuable_sidebar.has_key?(:confidential) - -# haml-lint:disable InlineJavaScript - %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe + - if issuable_sidebar.has_key?(:confidential) + -# haml-lint:disable InlineJavaScript + %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe - = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar + = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar - .js-sidebar-participants-widget-root + .js-sidebar-participants-widget-root - - if issuable_sidebar.dig(:current_user, :can_move) - .block - .js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + - if issuable_sidebar.dig(:current_user, :can_move) + .block + .js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - -# haml-lint:disable InlineJavaScript - %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar, @project).to_json.html_safe + -# haml-lint:disable InlineJavaScript + %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar, @project).to_json.html_safe -- GitLab From 59a842f5f8a90d34076b597f232cb4529303c912 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Wed, 22 Oct 2025 09:34:14 +0200 Subject: [PATCH 8/9] Simplify approach --- .../work_item_bulk_edit_sidebar.vue | 6 +++++- .../stylesheets/framework/application-chrome.scss | 15 --------------- app/assets/stylesheets/framework/sidebar.scss | 3 ++- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/work_items/components/work_item_bulk_edit/work_item_bulk_edit_sidebar.vue b/app/assets/javascripts/work_items/components/work_item_bulk_edit/work_item_bulk_edit_sidebar.vue index b798727973e909..c7c8cdb3cc68ec 100644 --- a/app/assets/javascripts/work_items/components/work_item_bulk_edit/work_item_bulk_edit_sidebar.vue +++ b/app/assets/javascripts/work_items/components/work_item_bulk_edit/work_item_bulk_edit_sidebar.vue @@ -272,7 +272,11 @@ export default {