diff --git a/packages/fiori/src/DynamicPage.hbs b/packages/fiori/src/DynamicPage.hbs index 8354e83c5a1e..c037ba01571b 100644 --- a/packages/fiori/src/DynamicPage.hbs +++ b/packages/fiori/src/DynamicPage.hbs @@ -17,7 +17,7 @@ {{> header-actions}} {{/if}} - + {{#if headerInContent}} {{/if}} @@ -42,17 +42,19 @@ {{#*inline "header-actions"}} - {{#if hasHeading}} - - - {{/if}} + {{#unless hasSnappedTitleOnMobile}} + {{#if hasHeading}} + + + {{/if}} + {{/unless}} {{/inline}} diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index b9dbb5cfbef5..c197dad11d68 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -13,6 +13,7 @@ import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; import InvisibleMessageMode from "@ui5/webcomponents-base/dist/types/InvisibleMessageMode.js"; import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; import debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; @@ -223,6 +224,7 @@ class DynamicPage extends UI5Element { if (this.dynamicPageTitle) { this.dynamicPageTitle.snapped = this._headerSnapped; this.dynamicPageTitle.interactive = this.hasHeading; + this.dynamicPageTitle.hasSnappedTitleOnMobile = !!this.hasSnappedTitleOnMobile; } } @@ -278,6 +280,10 @@ class DynamicPage extends UI5Element { return this._headerSnapped; } + get hasSnappedTitleOnMobile() { + return isPhone() && this.headerSnapped && this.dynamicPageTitle?.snappedTitleOnMobile.length; + } + /** * Defines if the header is snapped. * @@ -327,6 +333,11 @@ class DynamicPage extends UI5Element { this.fireEvent("title-toggle"); await renderFinished(); this.headerActions?.focusExpandButton(); + + if (this.hasSnappedTitleOnMobile) { + this.dynamicPageTitle?.focus(); + } + announce(this._headerLabel, InvisibleMessageMode.Polite); } diff --git a/packages/fiori/src/DynamicPageTitle.hbs b/packages/fiori/src/DynamicPageTitle.hbs index d7e6de98ebfa..25aa2682b6ce 100644 --- a/packages/fiori/src/DynamicPageTitle.hbs +++ b/packages/fiori/src/DynamicPageTitle.hbs @@ -9,39 +9,48 @@ aria-labelledby="{{_ariaLabelledBy}}" aria-describedby="{{_id}}-toggle-description"> -
- - {{#if mobileNavigationActions}} - - {{/if}} -
+ {{#if hasSnappedTitleOnMobile}} +
+ + +
+ {{else}} +
+ -
-
- + {{#if mobileNavigationActions}} + + {{/if}}
- {{#if hasContent}} -
- +
+
+
- {{/if}} -
- - {{#unless mobileNavigationActions}} - {{#if _needsSeparator}} -
- {{/if}} - - {{/unless}} + {{#if hasContent}} +
+ +
+ {{/if}} + +
+ + {{#unless mobileNavigationActions}} + {{#if _needsSeparator}} +
+ {{/if}} + + {{/unless}} +
-
- + + {{/if}} + {{_ariaDescribedbyText}}
\ No newline at end of file diff --git a/packages/fiori/src/DynamicPageTitle.ts b/packages/fiori/src/DynamicPageTitle.ts index 4c25fa163740..acf37be2a2bf 100644 --- a/packages/fiori/src/DynamicPageTitle.ts +++ b/packages/fiori/src/DynamicPageTitle.ts @@ -13,6 +13,8 @@ import type Toolbar from "@ui5/webcomponents/dist/Toolbar.js"; import type { ToolbarMinWidthChangeEventDetail } from "@ui5/webcomponents/dist/Toolbar.js"; import ToolbarItemOverflowBehavior from "@ui5/webcomponents/dist/types/ToolbarItemOverflowBehavior.js"; import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; +import Icon from "@ui5/webcomponents/dist/Icon.js"; +import Title from "@ui5/webcomponents/dist/Title.js"; // Template import DynamicPageTitleTemplate from "./generated/templates/DynamicPageTitleTemplate.lit.js"; @@ -63,6 +65,7 @@ import { renderer: litRender, styles: DynamicPageTitleCss, template: DynamicPageTitleTemplate, + dependencies: [Title, Icon], }) /** @@ -110,6 +113,13 @@ class DynamicPageTitle extends UI5Element { @property({ type: Number }) minActionsWidth?: number; + /** + * Indicates whether the title has snapped on mobile devices. + * @private + */ + @property({ type: Boolean }) + hasSnappedTitleOnMobile = false; + /** * Defines the content of the Heading of the Dynamic Page. * @@ -132,6 +142,22 @@ class DynamicPageTitle extends UI5Element { @slot({ type: HTMLElement }) snappedHeading!: HTMLElement[]; + /** + * Defines the content of the snapped title on mobile devices. + * + * This slot is displayed only when the `DynamicPageTitle` is in the snapped state on mobile devices. + * It should be used to provide a simplified, single-line title that takes up less space on smaller screens. + * + * **Note:** + * - The content set in this slot **overrides** all other content set in the `DynamicPageTitle` slots when displayed. + * - The slot is intended for a single `ui5-title` component. + * + * @public + * @since 2.3.0 + */ + @slot({ type: HTMLElement }) + snappedTitleOnMobile!: Array; + /** * Defines the bar with actions in the Dynamic page title. * diff --git a/packages/fiori/src/themes/DynamicPage.css b/packages/fiori/src/themes/DynamicPage.css index 93db2137cae4..c4415e41418b 100644 --- a/packages/fiori/src/themes/DynamicPage.css +++ b/packages/fiori/src/themes/DynamicPage.css @@ -126,4 +126,11 @@ :host([media-range="XL"]) ::slotted([slot="headerArea"]) { padding: var(--_ui5_dynamic_page_header_padding_XL); +} + +/* snappedTitleOnMobile */ +:host([_header-snapped]) ::slotted([slot="headerArea"]) { + height: 0; + padding: 0; + visibility: hidden; } \ No newline at end of file diff --git a/packages/fiori/src/themes/DynamicPageTitle.css b/packages/fiori/src/themes/DynamicPageTitle.css index e1159a671714..3c2f875b9c27 100644 --- a/packages/fiori/src/themes/DynamicPageTitle.css +++ b/packages/fiori/src/themes/DynamicPageTitle.css @@ -35,6 +35,11 @@ box-shadow: var(--sapContent_HeaderShadow); } +:host([has-snapped-title-on-mobile]) { + min-height: var(--_ui5_dynamic_page_snapped_title_on_mobile_min_height); + line-height: var(--_ui5_dynamic_page_snapped_title_on_mobile_line_height); +} + /* breadcrumbs */ ::slotted([ui5-breadcrumbs][slot="breadcrumbs"]) { padding: var(--_ui5_dynamic_page_title_breadcrumbs_padding_top) 0 @@ -88,6 +93,13 @@ min-width: 1px; } +.ui5-dynamic-page--snapped-title-on-mobile { + display: flex; + justify-content: space-between; + align-items: center; + pointer-events: none; +} + .ui5-dynamic-page-title--content { padding: 0 0 0 1rem; flex-shrink: 1.6; diff --git a/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css index eebbce124b13..c6f0fabc51ab 100644 --- a/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css +++ b/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css @@ -16,4 +16,6 @@ --_ui5_dynamic_page_title_hover_background: var(--sapObjectHeader_Hover_Background); + --_ui5_dynamic_page_snapped_title_on_mobile_line_height: 2rem; + --_ui5_dynamic_page_snapped_title_on_mobile_min_height: 2rem; } \ No newline at end of file diff --git a/packages/fiori/test/pages/DynamicPageWithSnappedTitleOnMobile.html b/packages/fiori/test/pages/DynamicPageWithSnappedTitleOnMobile.html new file mode 100644 index 000000000000..0d30bc33fb3a --- /dev/null +++ b/packages/fiori/test/pages/DynamicPageWithSnappedTitleOnMobile.html @@ -0,0 +1,207 @@ +<!DOCTYPE html> +<html> + <head> + <meta + name="viewport" + content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" + /> + <meta charset="utf-8" /> + + <title>Dynamic page + + + + + + + + + + + + + + Man + Shoes + Running Shoes + + + Special Running Shoe + + Snapped Title On Mobile + +
+ + Special Running Shoe +
+ +

PO-48865

+

PO-48865

+ + Special 157.4M EUR + + + + + + + + + + + +
+ + +
+ +
+ Availability +

In Stock

+
+
+ Price +

379.99 USD

+
+
+ Product Description +

Super-lightweight cushioning propels you forward from landing to toe-off and has a fast, snappy feel.

+
+
+
+ + + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + 10 inch Portable DVD + 7 inch WidescreenPortable DVD Player w MP3 + Astro Laptop 1516 + Astro Phone 6 + Audio/Video Cable Kit - 4m + Beam Breaker B-1 + Beam Breaker B-2 + Beam Breaker B-3 + Beam Breaker B-4 + Camcorder View + Benda Laptop 1408 + Cepat Tablet 10.5 + Gladiator MX + + + + Save + Close + +
+ + + + diff --git a/packages/fiori/test/specs/DynamicPage.mobile.spec.js b/packages/fiori/test/specs/DynamicPage.mobile.spec.js new file mode 100644 index 000000000000..6407fb7a5dab --- /dev/null +++ b/packages/fiori/test/specs/DynamicPage.mobile.spec.js @@ -0,0 +1,111 @@ +import { assert } from "chai"; + +describe("DynamicPage Mobile Behaviour", () => { + beforeEach(async () => { + await browser.url("test/pages/DynamicPageWithSnappedTitleOnMobile.html"); + await browser.emulateDevice("iPhone X"); + }); + + it("should display snapped title on mobile when snappedTitleOnMobile slot has content and header is snapped", async () => { + const dynamicPage = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // Ensure the header is snapped + await dynamicPage.setProperty("headerSnapped", true); + + // Check if the snapped title on mobile is visible + const snappedTitleOnMobile = await title.shadow$(".ui5-dynamic-page--snapped-title-on-mobile"); + const isDisplayed = await snappedTitleOnMobile.isDisplayed(); + + assert.ok( + isDisplayed, + "Snapped title on mobile should be displayed when header is snapped and snappedTitleOnMobile slot has content." + ); + }); + + it("should not display snapped title on mobile when snappedTitleOnMobile slot is empty", async () => { + const dynamicPage = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // Remove content from the snappedTitleOnMobile slot + await browser.execute(() => { + const titleElement = document.querySelector("#page ui5-dynamic-page-title"); + const snappedTitleContent = titleElement.querySelector("[slot='snappedTitleOnMobile']"); + if (snappedTitleContent) { + titleElement.removeChild(snappedTitleContent); + } + }); + + // Snap the header + await dynamicPage.setProperty("headerSnapped", true); + + // Check if the snapped title on mobile is not visible + const snappedTitleOnMobile = await title.shadow$(".ui5-dynamic-page--snapped-title-on-mobile"); + const isDisplayed = await snappedTitleOnMobile.isDisplayed(); + + assert.strictEqual( + isDisplayed, + false, + "Snapped title on mobile should not be displayed when the snappedTitleOnMobile slot is empty." + ); + }); + + it("should expand the header when clicked on the snapped title on mobile", async () => { + const dynamicPage = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // Ensure the header is snapped + await dynamicPage.setProperty("headerSnapped", true); + + // Click on the title focus area to expand the header + const titleFocusArea = await title.shadow$(".ui5-dynamic-page-title-focus-area"); + await titleFocusArea.click(); + + // Check if the header is expanded + const headerSnapped = await dynamicPage.getProperty("headerSnapped"); + + assert.strictEqual( + headerSnapped, + false, + "Header should expand when snapped title on mobile is clicked." + ); + }); + + it("should not display snapped title on mobile when header is not snapped", async () => { + const dynamicPage = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // Ensure the header is not snapped + await dynamicPage.setProperty("headerSnapped", false); + + // Check if the snapped title on mobile is not visible + const snappedTitleOnMobile = await title.shadow$(".ui5-dynamic-page--snapped-title-on-mobile"); + const isDisplayed = await snappedTitleOnMobile.isDisplayed(); + + assert.strictEqual( + isDisplayed, + false, + "Snapped title on mobile should not be displayed when the header is not snapped." + ); + }); + + it("should focus the title focus area when header action is clicked to snap the header", async () => { + const dynamicPage = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // Ensure the header is expanded + await dynamicPage.setProperty("headerSnapped", false); + + // Click on the header action to snap the header + const snapButton = await dynamicPage.shadow$("ui5-dynamic-page-header-actions") + .shadow$(".ui5-dynamic-page-header-action"); + await snapButton.click(); + + // Check if the title focus area is focused + const isFocused = await title.isFocused(); + assert.ok( + isFocused, + "Focus should be applied to the title focus area after snapping the header via header action." + ); + }); +}); \ No newline at end of file