diff --git a/packages/main/src/Panel.hbs b/packages/main/src/Panel.hbs
index d167315fc3c6..d7c3a05aa826 100644
--- a/packages/main/src/Panel.hbs
+++ b/packages/main/src/Panel.hbs
@@ -8,7 +8,7 @@
{{#if hasHeaderOrHeaderText}}
{{! header: either header or h1 with header text}}
diff --git a/packages/main/src/Panel.ts b/packages/main/src/Panel.ts
index 1ec38591982c..8d9d584bf050 100644
--- a/packages/main/src/Panel.ts
+++ b/packages/main/src/Panel.ts
@@ -196,6 +196,20 @@ class Panel extends UI5Element {
@property()
accessibleName!: string;
+ /**
+ * Indicates whether the Panel header is sticky or not.
+ * If stickyHeader is set to true, then whenever you scroll the content or
+ * the application, the header of the panel will be always visible and
+ * a solid color will be used for its design.
+ * @type {boolean}
+ * @name sap.ui.webc.main.Panel.prototype.stickyHeader
+ * @defaultvalue false
+ * @public
+ * @since 1.16.0-rc.1
+ */
+ @property({ type: Boolean })
+ stickyHeader!: boolean;
+
/**
* When set to
true
, the
accessibleName
property will be
* applied not only on the panel root itself, but on its toggle button too.
@@ -348,6 +362,9 @@ class Panel extends UI5Element {
headerBtn: {
"ui5-panel-header-button-animated": !this.shouldNotAnimate(),
},
+ stickyHeaderClass: {
+ "ui5-panel-heading-wrapper-sticky": this.stickyHeader,
+ },
};
}
diff --git a/packages/main/src/themes/Panel.css b/packages/main/src/themes/Panel.css
index bdb409713e0a..b5b2f7f62968 100644
--- a/packages/main/src/themes/Panel.css
+++ b/packages/main/src/themes/Panel.css
@@ -68,6 +68,14 @@
width: 100%;
}
+.ui5-panel-heading-wrapper.ui5-panel-heading-wrapper-sticky {
+ position: sticky;
+ top: 0;
+ background-color: var(--_ui5_panel_header_background_color);
+ z-index: 100; /* The z-index of the table header is 99 therefore to have table in the panel and panel header to be on top we need 100 */
+ border-radius: var(--_ui5_panel_border_radius);
+}
+
.ui5-panel-header-title {
width: calc(100% - var(--_ui5_panel_button_root_width));
overflow: hidden;
@@ -86,6 +94,7 @@
outline: none;
border-bottom-left-radius: var(--_ui5_panel_border_radius);
border-bottom-right-radius: var(--_ui5_panel_border_radius);
+ overflow: auto;
}
.ui5-panel-header-button-root {
@@ -136,4 +145,10 @@
.ui5-panel-header-icon-wrapper,
[ui5-button].ui5-panel-header-button-with-icon [ui5-icon] {
pointer-events: none;
+}
+
+.ui5-panel-root {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
}
\ No newline at end of file
diff --git a/packages/main/src/themes/base/Panel-parameters.css b/packages/main/src/themes/base/Panel-parameters.css
index 9898e72c372a..16f01e0e08f9 100644
--- a/packages/main/src/themes/base/Panel-parameters.css
+++ b/packages/main/src/themes/base/Panel-parameters.css
@@ -10,11 +10,12 @@
--_ui5_panel_header_padding_right: 0.5rem;
--_ui5_panel_header_button_wrapper_padding: .25rem;
--_ui5_panel_focus_offset: 1px;
- --_ui5_panel_content_padding: 0.625rem 1rem 1.375rem 1rem
+ --_ui5_panel_content_padding: 0.625rem 1rem 1.375rem 1rem;
+ --_ui5_panel_header_background_color: var(--sapBackgroundColor);
}
[data-ui5-compact-size],
.ui5-content-density-compact,
.sapUiSizeCompact {
--_ui5_panel_header_button_wrapper_padding: .5625rem .375rem;
-}
\ No newline at end of file
+}
diff --git a/packages/main/src/themes/sap_horizon/Panel-parameters.css b/packages/main/src/themes/sap_horizon/Panel-parameters.css
index 91ee8a14b2f6..dbc312c15d2a 100644
--- a/packages/main/src/themes/sap_horizon/Panel-parameters.css
+++ b/packages/main/src/themes/sap_horizon/Panel-parameters.css
@@ -10,4 +10,5 @@
--_ui5_panel_icon_color: var(--sapButton_Lite_TextColor);
--_ui5_panel_focus_offset: -1px;
--_ui5_panel_content_padding: 0.625rem 1rem;
+ --_ui5_panel_header_background_color: var(--sapGroup_TitleBackground);
}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_horizon_dark/Panel-parameters.css b/packages/main/src/themes/sap_horizon_dark/Panel-parameters.css
index 91ee8a14b2f6..dbc312c15d2a 100644
--- a/packages/main/src/themes/sap_horizon_dark/Panel-parameters.css
+++ b/packages/main/src/themes/sap_horizon_dark/Panel-parameters.css
@@ -10,4 +10,5 @@
--_ui5_panel_icon_color: var(--sapButton_Lite_TextColor);
--_ui5_panel_focus_offset: -1px;
--_ui5_panel_content_padding: 0.625rem 1rem;
+ --_ui5_panel_header_background_color: var(--sapGroup_TitleBackground);
}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_horizon_hcb/Panel-parameters.css b/packages/main/src/themes/sap_horizon_hcb/Panel-parameters.css
index 6bb582c00a34..4025c896d49b 100644
--- a/packages/main/src/themes/sap_horizon_hcb/Panel-parameters.css
+++ b/packages/main/src/themes/sap_horizon_hcb/Panel-parameters.css
@@ -6,4 +6,5 @@
--_ui5_panel_outline_offset: -0.125rem;
--_ui5_panel_icon_color: var(--sapButton_Lite_TextColor);
--_ui5_panel_content_padding: 0.625rem 1rem;
+ --_ui5_panel_header_background_color: var(--sapGroup_TitleBackground);
}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_horizon_hcw/Panel-parameters.css b/packages/main/src/themes/sap_horizon_hcw/Panel-parameters.css
index b5b0c48ed8b5..fa3ae99cd4b0 100644
--- a/packages/main/src/themes/sap_horizon_hcw/Panel-parameters.css
+++ b/packages/main/src/themes/sap_horizon_hcw/Panel-parameters.css
@@ -7,4 +7,5 @@
--_ui5_panel_outline_offset: -0.125rem;
--_ui5_panel_icon_color: var(--sapButton_Lite_TextColor);
--_ui5_panel_content_padding: 0.625rem 1rem;
+ --_ui5_panel_header_background_color: var(--sapGroup_TitleBackground);
}
\ No newline at end of file
diff --git a/packages/main/test/pages/Panel.html b/packages/main/test/pages/Panel.html
index 2a50f42861d2..14f97453f0fe 100644
--- a/packages/main/test/pages/Panel.html
+++ b/packages/main/test/pages/Panel.html
@@ -154,6 +154,28 @@
+
+
+
diff --git a/packages/main/test/pages/styles/Panel.css b/packages/main/test/pages/styles/Panel.css
index 71d2b28691a3..826ca9890d09 100644
--- a/packages/main/test/pages/styles/Panel.css
+++ b/packages/main/test/pages/styles/Panel.css
@@ -7,3 +7,7 @@
.panel1auto {
background-color: var(--sapBackgroundColor);
}
+
+#panel-stickyHeader::part(content) {
+ max-height: 70px;
+}
diff --git a/packages/main/test/specs/Panel.spec.js b/packages/main/test/specs/Panel.spec.js
index 9c1ec8ebaa33..5258bca39f66 100644
--- a/packages/main/test/specs/Panel.spec.js
+++ b/packages/main/test/specs/Panel.spec.js
@@ -124,6 +124,44 @@ describe("Panel general interaction", () => {
assert.notOk(await panelWithoutAnimationIcon.hasClass("ui5-panel-header-button-animated"), "Animation is turn off");
});
+ it("Test that the header is sticky when inner content scrollable", async () => {
+ const panel = await browser.$("#panel-stickyHeader");
+ const content = await panel.shadow$(".ui5-panel-content");
+ const title = await panel.shadow$(".ui5-panel-header-title");
+ const sExpected = "Panel with sticky header";
+ const panelHeader = panel.shadow$(".ui5-panel-heading-wrapper");
+
+ assert.strictEqual(await title.getText(), sExpected, "Initially the text is the expected one");
+ assert.strictEqual((await content.getCSSProperty('overflow')).value, "auto", "Check if the overflow property is set to 'auto'");
+
+ const initialScrollPosition = await browser.execute("return document.querySelector('#panel-stickyHeader').shadowRoot.querySelector('.ui5-panel-content').scrollTop");
+ await browser.execute("document.querySelector('#panel-stickyHeader').shadowRoot.querySelector('.ui5-panel-content').scrollBy(0, 200)");
+ const finalScrollPosition = await browser.execute("return document.querySelector('#panel-stickyHeader').shadowRoot.querySelector('.ui5-panel-content').scrollTop");
+ assert.ok(initialScrollPosition < finalScrollPosition, "Initial scroll position of the inner div should be less than the final");
+ assert.strictEqual(await panelHeader.isDisplayedInViewport(), true, "Assert that the header is still visible after scroll - it's sticky");
+ });
+
+ it("Test that the header is sticky", async () => {
+ const panel = await browser.$("#panel-stickyHeader");
+ const title = await panel.shadow$(".ui5-panel-header-title");
+ const sExpected = "Panel with sticky header";
+
+ const panelHeader = panel.shadow$(".ui5-panel-heading-wrapper");
+ const isStickyCssPosition = await browser.execute("return window.getComputedStyle(document.querySelector('#panel-stickyHeader').shadowRoot.querySelector('.ui5-panel-heading-wrapper')).position");
+
+ await browser.setWindowSize(1000, 1200);
+
+ assert.strictEqual(await title.getText(), sExpected, "Initially the text is the expected one");
+ assert.ok(await panelHeader.hasClass("ui5-panel-heading-wrapper-sticky"), "Assert that sticky css class is available");
+ assert.strictEqual(isStickyCssPosition, "sticky", "Assert that the header has a sticky position");
+
+ let isPanelHeaderDisplayed = await panelHeader.isDisplayedInViewport();
+ assert.strictEqual(isPanelHeaderDisplayed, true, "Initially the panel header should be visible");
+ await browser.execute("window.scrollBy(0, 500)");
+ isPanelHeaderDisplayed = await panelHeader.isDisplayedInViewport();
+ assert.strictEqual(isPanelHeaderDisplayed, true, "Assert that the header is still visible after scroll - it's sticky");
+ });
+
describe("Accessibility", async () => {
it("tests whether aria attributes are set correctly with native header", async () => {
diff --git a/packages/playground/_stories/main/Panel/Panel.stories.ts b/packages/playground/_stories/main/Panel/Panel.stories.ts
index 09569a809173..ee18915ba5d1 100644
--- a/packages/playground/_stories/main/Panel/Panel.stories.ts
+++ b/packages/playground/_stories/main/Panel/Panel.stories.ts
@@ -15,6 +15,7 @@ import TitleLevel from "@ui5/webcomponents/dist/types/TitleLevel.js";
const component = "ui5-panel";
+let index = 0;
export default {
title: "Main/Panel",
@@ -29,6 +30,7 @@ export default {
const Template : UI5StoryArgs = (args) => html`
= (args) => html`
?no-animation="${ifDefined(args.noAnimation)}"
header-level="${ifDefined(args.headerLevel)}"
accessible-name="${ifDefined(args.accessibleName)}"
+ ?sticky-header="${ifDefined(args.stickyHeader)}"
>
${unsafeHTML(args.header)}
${unsafeHTML(args.default)}
@@ -88,6 +91,97 @@ FixedPanel.args = {
};
FixedPanel.storyName = "Fixed Panel (Can't be Collapsed/Expanded)";
+export const StickyHeader = Template.bind({});
+StickyHeader.decorators = [
+ (story) => {
+ return html`
+
+
+ ${story()}
+
+
+
`
+ }
+]
+StickyHeader.args = {
+ default: `
+ Lorem ipsum!
+
+
+ Lorem ipsum dolor sit amet, tamquam invidunt cu sed, unum regione mel ea, quo ea alia novum. Ne qui illud zril
+ nostrum, vel ea sint dicant postea. Vel ne facete tritani, neglegentur concludaturque sed te. His animal dolorum ut.
+ Aeterno appareat ei mei, cu sed elit scripserit, an quodsi oportere accusamus quo. Pri ea probo corpora rationibus,
+ soluta incorrupte ex his.
+ Mei ei brute cetero, id duo magna aeque torquatos. Quodsi erroribus mediocritatem his ut, ad pri legere iracundia
+ democritum. Menandri intellegam in mea, ex vero movet qualisque sed. Maiorum verterem perfecto nec ea, est velit
+ elaboraret consequuntur eu, eam ad reque postea admodum. Ne inimicus convenire pri, doctus vidisse te ius.
+ Percipitur contentiones in vis, cu vim propriae phaedrum. Has ad magna errem honestatis, duo vero graeco epicurei
+ no, populo semper sit ne. Vulputate dissentiunt interpretaris ea vis, nec civibus moderatius at. Cu vim stet
+ dissentias, no vidit saperet indoctum nec, et pro magna prima nobis. Vis consul feugiat qualisque in, regione
+ persecuti cotidieque id eos, id ius omnesque vituperata.
+ Lorem ipsum dolor sit amet, tamquam invidunt cu sed, unum regione mel ea, quo ea alia novum. Ne qui illud zril
+ nostrum, vel ea sint dicant postea. Vel ne facete tritani, neglegentur concludaturque sed te. His animal dolorum ut.
+ Aeterno appareat ei mei, cu sed elit scripserit, an quodsi oportere accusamus quo. Pri ea probo corpora rationibus,
+ soluta incorrupte ex his.
+ Mei ei brute cetero, id duo magna aeque torquatos. Quodsi erroribus mediocritatem his ut, ad pri legere iracundia
+ democritum. Menandri intellegam in mea, ex vero movet qualisque sed. Maiorum verterem perfecto nec ea, est velit
+ elaboraret consequuntur eu, eam ad reque postea admodum. Ne inimicus convenire pri, doctus vidisse te ius.
+ Percipitur contentiones in vis, cu vim propriae phaedrum. Has ad magna errem honestatis, duo vero graeco epicurei
+ no, populo semper sit ne. Vulputate dissentiunt interpretaris ea vis, nec civibus moderatius at. Cu vim stet
+ dissentias, no vidit saperet indoctum nec, et pro magna prima nobis. Vis consul feugiat qualisque in, regione
+ persecuti cotidieque id eos, id ius omnesque vituperata.
+
+ `,
+ headerText: "Sticky header",
+ stickyHeader: true
+};
export const PanelCustomHeader = Template.bind({});
PanelCustomHeader.decorators = [