diff --git a/packages/base/package.json b/packages/base/package.json index d90b1a1e1c26..4811b4b0cde9 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -44,8 +44,9 @@ "lit-html": "^2.0.1" }, "devDependencies": { - "@openui5/sap.ui.core": "1.120.3", - "@ui5/webcomponents-tools": "1.22.0", + "@buxlabs/amd-to-es6": "0.16.1", + "@openui5/sap.ui.core": "1.116.0", + "@ui5/webcomponents-tools": "1.22.0-rc.1", "chromedriver": "120.0.0", "clean-css": "^5.2.2", "copy-and-watch": "^0.1.5", diff --git a/packages/fiori/package.json b/packages/fiori/package.json index f5d2eb864aee..6dfaec46ac09 100644 --- a/packages/fiori/package.json +++ b/packages/fiori/package.json @@ -53,7 +53,7 @@ "@zxing/library": "^0.17.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "1.22.0", + "@ui5/webcomponents-tools": "1.22.0-rc.1", "chromedriver": "120.0.0" } } diff --git a/packages/fiori/src/DynamicPage.hbs b/packages/fiori/src/DynamicPage.hbs new file mode 100644 index 000000000000..2a7008023b0d --- /dev/null +++ b/packages/fiori/src/DynamicPage.hbs @@ -0,0 +1,51 @@ +
+
+
+ + {{#if headerInTitle}} + + {{/if}} + + {{#if actionsInTitle}} + {{> header-actions}} + {{/if}} + +
+ + {{#if headerInContent}} + + {{/if}} + + {{#unless actionsInTitle}} + {{> header-actions}} + {{/unless}} + +
+
+ + {{#if showFooter}} +
+ {{/if}} +
+
+
+ +
+ +
+
+{{#*inline "header-actions"}} + + {{/inline}} \ No newline at end of file diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts new file mode 100644 index 000000000000..f3b383dfa5bb --- /dev/null +++ b/packages/fiori/src/DynamicPage.ts @@ -0,0 +1,336 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; +import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import MediaRange from "@ui5/webcomponents-base/dist/MediaRange.js"; +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 debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; + +// Template +import DynamicPageTemplate from "./generated/templates/DynamicPageTemplate.lit.js"; + +// Styles +import DynamicPageCss from "./generated/themes/DynamicPage.css.js"; + +import DynamicPageHeader from "./DynamicPageHeader.js"; +import DynamicPageTitle from "./DynamicPageTitle.js"; +import DynamicPageHeaderActions from "./DynamicPageHeaderActions.js"; + +// Texts +import { + DYNAMIC_PAGE_ARIA_LABEL_EXPANDED_HEADER, + DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER, +} from "./generated/i18n/i18n-defaults.js"; + +const SCROLL_DEBOUNCE_RATE = 25; // ms + +/** + * @class + * + *

Overview

+ * + * A layout component, representing a web page, consisting of a title, header with dynamic behavior, a content area, and an optional floating footer. + * + * The component consist of several components: + * + * + * + *

Usage

+ * + * Use the DynamicPage if you need to have a title, that is always visible + * and a header, that has configurable Expanding/Snapping functionality. + * If you don't need the Expanding/Snapping functionality it is better to use the + * ui5-page as a lighter component. + * + * The app can add to the default slot of the ui5-dynamic-page either content that is designed to fit its container (e.g. has 100% height), + * or content with own height that may overflow its container. In the second case the DynamicPage will show a scrollbar that allows the user + * scroll through the content. + * + * + * + *

Responsive Behavior

+ * + * Dynamic page web component implements the resposive paddings design. + * + *

Keyboard Handling

+ * + *

Basic Navigation

+ * + * + *

Fast Navigation

+ * + * + *

ES6 Module Import

+ * + * import "@ui5/webcomponents-fiori/dist/DynamicPage.js"; + * + * @constructor + * @extends sap.ui.webc.base.UI5Element + * @since 1.122 + * @public + */ +@customElement({ + tag: "ui5-dynamic-page", + renderer: litRender, + styles: DynamicPageCss, + template: DynamicPageTemplate, + dependencies: [DynamicPageHeader, DynamicPageTitle, DynamicPageHeaderActions], +}) + +class DynamicPage extends UI5Element { + static i18nBundle: I18nBundle; + + constructor() { + super(); + + this._updateMediaRange = this.updateMediaRange.bind(this); + } + + static async onDefine() { + DynamicPage.i18nBundle = await getI18nBundle("@ui5/webcomponents-fiori"); + } + + /** + * Defines if the header is snapped. + * + * @default false + * @public + */ + @property({ type: Boolean }) + headerSnapped!: boolean; + + /** + * Defines if the header is pinned. + * + * @default false + * @public + */ + @property({ type: Boolean }) + headerPinned!: boolean; + + /** + * Defines if the footer is shown. + * + * @default false + * @public + */ + @property({ type: Boolean }) + showFooter!: boolean; + + /** + * Defines the current media query size. + * + * @private + */ + @property() + mediaRange!: string; + + /** + * Defines the content of the Dynamic Page. + * + * @public + */ + @slot({ "default": true, type: HTMLElement }) + content!: HTMLElement[]; + + /** + * Defines the title HTML Element. + * + * @public + */ + @slot({ type: DynamicPageTitle }) + titleArea!: HTMLElement[]; + + /** + * Defines the title HTML Element. + * + * @public + */ + @slot({ type: DynamicPageHeader }) + headerArea!: HTMLElement[]; + + /** + * Defines the title HTML Element. + * + * @public + */ + @slot({ type: HTMLElement }) + footer!: HTMLElement[]; + + skipSnapOnScroll = false; + showHeaderInStickArea = false; + _updateMediaRange: ResizeObserverCallback; + + onEnterDOM() { + ResizeHandler.register(this, this._updateMediaRange); + } + + onExitDOM() { + ResizeHandler.deregister(this, this._updateMediaRange); + } + + onBeforeRendering(): void { + if (this.dynamicPageTitle) { + this.dynamicPageTitle.snapped = this.headerSnapped; + } + } + + get classes() { + return { + root: { + "ui5-dynamic-page-root": true, + }, + scrollContainer: { + "ui5-dynamic-page-scroll-container": true, + }, + headerWrapper: { + "ui5-dynamic-page-title-header-wrapper": true, + }, + content: { + "ui5-dynamic-page-content": true, + }, + fitContent: { + "ui5-dynamic-page-fit-content": true, + }, + footer: { + "ui5-dynamic-page-footer": true, + }, + spacer: { + "ui5-dynamic-page-spacer": true, + }, + }; + } + + get dynamicPageTitle(): DynamicPageTitle | null { + return this.querySelector("[ui5-dynamic-page-title]"); + } + + get dynamicPageHeader(): DynamicPageHeader | null { + return this.querySelector("[ui5-dynamic-page-header]"); + } + + get scrollContainer(): HTMLElement | null | undefined { + return this.getDomRef()?.querySelector(".ui5-dynamic-page-scroll-container"); + } + + get headerActions(): DynamicPageHeaderActions | null | undefined { + return this.getDomRef()?.querySelector("ui5-dynamic-page-header-actions"); + } + + get actionsInTitle(): boolean { + return this.headerSnapped || this.showHeaderInStickArea || this.headerPinned; + } + get headerInTitle(): boolean { + return !this.headerSnapped && (this.showHeaderInStickArea || this.headerPinned); + } + get headerInContent(): boolean { + return !this.headerSnapped && !this.headerInTitle; + } + + get _headerLabel() { + return this.headerSnapped + ? DynamicPage.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER) + : DynamicPage.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_EXPANDED_HEADER); + } + + get _headerExpanded() { + return !this.headerSnapped; + } + + get _accAttributesForHeaderActions() { + return { + controls: `${this._id}-header`, + }; + } + + snapOnScroll() { + debounce(() => this.snapTitleByScroll(), SCROLL_DEBOUNCE_RATE); + } + + snapTitleByScroll() { + if (!this.dynamicPageTitle || !this.dynamicPageHeader || this.headerPinned) { + return; + } + + const scrollTop = this.scrollContainer!.scrollTop; + + if (this.skipSnapOnScroll) { + this.skipSnapOnScroll = false; + return; + } + + if (scrollTop > this.dynamicPageHeader.getBoundingClientRect().height) { + this.headerSnapped = true; + this.showHeaderInStickArea = false; + } else { + this.headerSnapped = false; + } + + this.dynamicPageTitle.snapped = this.headerSnapped; + } + + async onExpandClick() { + this._toggleHeader(); + await renderFinished(); + this.headerActions?.focusExpandButton(); + announce(this._headerLabel, InvisibleMessageMode.Polite); + } + + async onPinClick() { + this.headerPinned = !this.headerPinned; + await renderFinished(); + this.headerActions?.focusPinButton(); + } + + async onToggleTitle() { + this._toggleHeader(); + await renderFinished(); + this.dynamicPageTitle!.focus(); + } + + _toggleHeader() { + this.showHeaderInStickArea = !this.showHeaderInStickArea; + this.headerSnapped = !this.headerSnapped; + + this.skipSnapOnScroll = true; + this.headerPinned = false; + } + + updateMediaRange() { + this.mediaRange = MediaRange.getCurrentRange(MediaRange.RANGESETS.RANGE_4STEPS, this.getDomRef()!.offsetWidth); + } +} + +DynamicPage.define(); + +export default DynamicPage; diff --git a/packages/fiori/src/DynamicPageHeader.hbs b/packages/fiori/src/DynamicPageHeader.hbs new file mode 100644 index 000000000000..463ae1cc0e54 --- /dev/null +++ b/packages/fiori/src/DynamicPageHeader.hbs @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/packages/fiori/src/DynamicPageHeader.ts b/packages/fiori/src/DynamicPageHeader.ts new file mode 100644 index 000000000000..7940a0a716c5 --- /dev/null +++ b/packages/fiori/src/DynamicPageHeader.ts @@ -0,0 +1,75 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; + +// Template +import DynamicPageHeaderTemplate from "./generated/templates/DynamicPageHeaderTemplate.lit.js"; + +// Styles +import DynamicPageHeaderCss from "./generated/themes/DynamicPageHeader.css.js"; + +/** + * @class + * + * Header of the DynamicPage. + * + *

Overview

+ * + * The DynamicPageHeader ui5-dynamic-page-header is part of the DynamicPage family + * and is used to serve as header of the DynamicPage. + * + *

Usage

+ * + * The DynamicPageHeader can hold any layout control and has two states - expanded + * and collapsed (snapped). The switching between these states happens when: + * + * + * + *

Responsive Behavior

+ * + * The responsive behavior of the DynamicPageHeader depends on the behavior of the + * content that is displayed. + * + * + * @constructor + * @extends sap.ui.webc.base.UI5Element + * @public + * @since 1.122 + */ +@customElement({ + tag: "ui5-dynamic-page-header", + renderer: litRender, + styles: DynamicPageHeaderCss, + template: DynamicPageHeaderTemplate, +}) +class DynamicPageHeader extends UI5Element { + static i18nBundle: I18nBundle; + + static async onDefine() { + DynamicPageHeader.i18nBundle = await getI18nBundle("@ui5/webcomponents-fiori"); + } + /** + * Defines the content of the Dynamic Page Header. + * + * @public + */ + @slot({ "default": true, type: HTMLElement }) + content!: HTMLElement[]; + + get classes() { + return { + root: { + "ui5-dynamic-page-header-root": true, + }, + }; + } +} + +DynamicPageHeader.define(); + +export default DynamicPageHeader; diff --git a/packages/fiori/src/DynamicPageHeaderActions.hbs b/packages/fiori/src/DynamicPageHeaderActions.hbs new file mode 100644 index 000000000000..cab5e8a62644 --- /dev/null +++ b/packages/fiori/src/DynamicPageHeaderActions.hbs @@ -0,0 +1,21 @@ +
+
+ + {{#unless snapped}} + + {{/unless}} +
+
\ No newline at end of file diff --git a/packages/fiori/src/DynamicPageHeaderActions.ts b/packages/fiori/src/DynamicPageHeaderActions.ts new file mode 100644 index 000000000000..863477635687 --- /dev/null +++ b/packages/fiori/src/DynamicPageHeaderActions.ts @@ -0,0 +1,149 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import event from "@ui5/webcomponents-base/dist/decorators/event.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; + +// Template +import DynamicPageHeaderActionsTemplate from "./generated/templates/DynamicPageHeaderActionsTemplate.lit.js"; + +// Styles +import DynamicPageHeaderActionsCss from "./generated/themes/DynamicPageHeaderActions.css.js"; + +// Texts +import { + DYNAMIC_PAGE_ARIA_LABEL_EXPAND_HEADER, + DYNAMIC_PAGE_ARIA_LABEL_SNAP_HEADER, + DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER, +} from "./generated/i18n/i18n-defaults.js"; +/** + * @class + * + *

Overview

+ * + * The DynamicPageHeaderActions component is part of the DynamicPage + * family and is holding the action buttons behind the DynamicPageTitle and the DynamicPageHeader. + * + * The "pin" action is used to attach the header to a certain state (expanded/collapsed). + * The expand/collapse action is used to switch between the two states of DynamicPageHeader. + * + * + * @constructor + * @extends sap.ui.webc.base.UI5Element + * @private + * @since 1.122 + */ +@customElement({ + tag: "ui5-dynamic-page-header-actions", + renderer: litRender, + styles: DynamicPageHeaderActionsCss, + template: DynamicPageHeaderActionsTemplate, +}) + +/** + * Event that is being fired by clicking on the expand button. + * + * @public + */ +@event("expand-button-click") + +/** + * Event that is being fired by clicking on the pin button. + * + * @public + */ +@event("pin-button-click") + +class DynamicPageHeaderActions extends UI5Element { + static i18nBundle: I18nBundle; + + static async onDefine() { + DynamicPageHeaderActions.i18nBundle = await getI18nBundle("@ui5/webcomponents-fiori"); + } + + /** + * Defines whether the header is pinned. + * + * @public + * @default false + */ + @property({ type: Boolean }) + pinned!: boolean; + + /** + * Defines whether the header is snapped. + * + * @public + * @default false + */ + @property({ type: Boolean }) + snapped!: boolean; + + /** + * Contains attributes to be added to HTML to gain accessibility. + * + * @public + * @default {} + */ + @property({ type: Object }) + accessibilityAttributes!: { controls: string }; + + get classes() { + return { + root: { + "ui5-dynamic-page-header-actions-root": true, + }, + wrapper: { + "ui5-dynamic-page-header-actions--wrapper": true, + }, + }; + } + + get arrowButtonIcon() { + return this.snapped ? "slim-arrow-down" : "slim-arrow-up"; + } + + get pinButtonIcon() { + return this.pinned ? "pushpin-on" : "pushpin-off"; + } + + get expandButton(): HTMLElement | null | undefined { + return this.getDomRef()?.querySelector(".ui5-dynamic-page-header-action-expand"); + } + + get pinButton(): HTMLElement | null | undefined { + return this.getDomRef()?.querySelector(".ui5-dynamic-page-header-action-pin"); + } + + get pinLabel() { + return DynamicPageHeaderActions.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER); + } + + get expandLabel() { + return this.snapped + ? DynamicPageHeaderActions.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_EXPAND_HEADER) + : DynamicPageHeaderActions.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_SNAP_HEADER); + } + + focusExpandButton() { + this.expandButton?.focus(); + } + + focusPinButton() { + this.pinButton?.focus(); + } + + onExpandClick() { + this.fireEvent("expand-button-click"); + } + + onPinClick() { + this.fireEvent("pin-button-click"); + } +} + +DynamicPageHeaderActions.define(); + +export default DynamicPageHeaderActions; diff --git a/packages/fiori/src/DynamicPageTitle.hbs b/packages/fiori/src/DynamicPageTitle.hbs new file mode 100644 index 000000000000..acf3d0999141 --- /dev/null +++ b/packages/fiori/src/DynamicPageTitle.hbs @@ -0,0 +1,47 @@ +
+ + +
+ + + {{#if mobileNavigationActions}} + + {{/if}} +
+ +
+
+ +
+ + {{#if hasContent}} +
+ +
+ {{/if}} + +
+ + {{#unless mobileNavigationActions}} +
+ + {{/unless}} +
+
+ + + {{_ariaDescribedbyText}} +
\ No newline at end of file diff --git a/packages/fiori/src/DynamicPageTitle.ts b/packages/fiori/src/DynamicPageTitle.ts new file mode 100644 index 000000000000..2f9b2786aea4 --- /dev/null +++ b/packages/fiori/src/DynamicPageTitle.ts @@ -0,0 +1,313 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import { isEnter, isSpace } from "@ui5/webcomponents-base/dist/Keys.js"; +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"; + +// Template +import DynamicPageTitleTemplate from "./generated/templates/DynamicPageTitleTemplate.lit.js"; + +// Styles +import DynamicPageTitleCss from "./generated/themes/DynamicPageTitle.css.js"; + +// Texts +import { + DYNAMIC_PAGE_ARIA_DESCR_TOGGLE_HEADER, +} from "./generated/i18n/i18n-defaults.js"; + +/** + * @class + * + *

Overview

+ * + * Title of the DynamicPage. + * + * The DynamicPageTitle component is part of the DynamicPage + * family and is used to serve as title of the DynamicPage. + * + *

Usage

+ * + * The DynamicPageTitle can hold any component and displays the most important + * information regarding the object that will always remain visible while scrolling. + * + * Note: The actions slot accepts any UI5 web component, but it`s + * recommended to use ui5-toolbar. + * + * The user can switch between the expanded/collapsed states of the + * DynamicPage by clicking on the DynamicPageTitle + * or by using the expand/collapse visual indicators, positioned at the bottom of the + * DynamicPageTitle and the DynamicPageHeader inside ui5-dynamic-page-header-actions. + * + *

Responsive Behavior

+ * + * The responsive behavior of the DynamicPageTitle depends on the behavior of the + * content that is displayed. + * + * + * @constructor + * @extends sap.ui.webc.base.UI5Element + * @public + */ +@customElement({ + tag: "ui5-dynamic-page-title", + fastNavigation: true, + renderer: litRender, + styles: DynamicPageTitleCss, + template: DynamicPageTitleTemplate, +}) +class DynamicPageTitle extends UI5Element { + static i18nBundle: I18nBundle; + + static async onDefine() { + DynamicPageTitle.i18nBundle = await getI18nBundle("@ui5/webcomponents-fiori"); + } + + /** + * Defines the content of the Heading of the Dynamic Page. + * + * @public + */ + @slot({ type: HTMLElement }) + heading!: HTMLElement[]; + + /** + * Defines the heading that is shown only when the header is snapped. + * + * @public + */ + @slot({ type: HTMLElement }) + snappedHeading!: HTMLElement[]; + + /** + * Defines the heading that is shown only when the header is expanded. + * + * @public + */ + @slot({ type: HTMLElement }) + expandedHeading!: HTMLElement[]; + + /** + * Defines the actions in the Dynamic page title. + * + * @public + */ + @slot({ type: HTMLElement }) + actions!: HTMLElement[]; + + /** + * Defines the navigation actions in the Dynamic page title. + * + * @public + */ + @slot({ type: HTMLElement }) + navigationActions!: HTMLElement[]; + + /** + * Defines the content of the Dynamic page title. + * + * @public + */ + @slot({ "default": true, type: HTMLElement }) + content!: HTMLElement[]; + + /** + * Defines the content of the title that is shown only when the header is snapped. + * + * @public + */ + @slot({ type: HTMLElement }) + snappedContent!: HTMLElement[]; + + /** + * Defines the content of the title that is shown only when the header is expanded. + * + * @public + */ + @slot({ type: HTMLElement }) + expandedContent!: HTMLElement[]; + + /** + * Defines the content of the breadcrumbs inside Dynamic Page Title. + * + * @public + */ + @slot({ type: HTMLElement }) + breadcrumbs!: HTMLElement[]; + + /** + * Defines if the title is snapped. + * + * @public + * @default false + */ + @property({ type: Boolean }) + snapped!: boolean; + + /** + * Defines if the mobileNavigationActions are shown. + * + * @private + */ + @property({ type: Boolean }) + mobileNavigationActions!: boolean; + + /** + * Indicates if the elements is on focus + * @private + */ + @property({ type: Boolean }) + focused!: boolean; + + _handleResize: ResizeObserverCallback; + minContentWidth?: number; + minActionsWidth?: number; + + constructor() { + super(); + this._handleResize = this.handleResize.bind(this); + } + + get hasContent() { + return !!this.content.length; + } + + get hasHeading() { + return !!this.heading.length; + } + + get headingSlotName() { + if (this.hasHeading) { + return "heading"; + } + if (!this.snapped) { + return "expandedHeading"; + } + return "snappedHeading"; + } + + get contentSlotName() { + return !this.snapped ? "expandedContent" : "snappedContent"; + } + + get _headerExpanded() { + return !this.snapped; + } + + get _ariaDescribedbyText() { + return DynamicPageTitle.i18nBundle.getText(DYNAMIC_PAGE_ARIA_DESCR_TOGGLE_HEADER); + } + + get _ariaLabelledBy() { + const hasAnyHeading = this[this.headingSlotName].length; + if (hasAnyHeading) { + return `${this._id}-heading`; + } + } + + get classes() { + return { + root: { + "ui5-dynamic-page-title-root": true, + }, + focusArea: { + "ui5-dynamic-page-title-focus-area": true, + }, + topArea: { + "ui5-dynamic-page-title--top-area": true, + }, + wrapper: { + "ui5-dynamic-page-title--wrapper": true, + }, + heading: { + "ui5-dynamic-page-title--heading": true, + }, + content: { + "ui5-dynamic-page-title--content": true, + }, + actions: { + "ui5-dynamic-page-title--actions": true, + }, + actionsSeparator: { + "ui5-dynamic-page-title--actions-separator": true, + }, + }; + } + + get styles() { + return { + content: { + "min-width": `${this.minContentWidth || 0}px`, + }, + actions: { + "min-width": `${this.minActionsWidth || 0}px`, + }, + }; + } + + onEnterDOM() { + ResizeHandler.register(this, this._handleResize); + } + + onExitDOM() { + ResizeHandler.deregister(this, this._handleResize); + } + + onBeforeRendering(): void { + this.prepareLayoutActions(); + } + + prepareLayoutActions() { + // all navigation/layout actions should have the NeverOverflow behavior + const navigationActions = this.querySelector("[ui5-toolbar][slot='navigationActions']"); + if (!navigationActions) { + return; + } + navigationActions.items.forEach(action => { + action.overflowPriority = ToolbarItemOverflowBehavior.NeverOverflow; + }); + } + + handleResize() { + this.mobileNavigationActions = this.offsetWidth < 1280; + } + + onMinContentWidthChange(event: CustomEvent) { + const slotName = (event.target)?.assignedSlot?.name; + if (!slotName || slotName === "content") { + this.minContentWidth = event.detail.minWidth; + } else if (slotName === "actions") { + this.minActionsWidth = event.detail.minWidth; + } + } + + _onfocusout() { + this.focused = false; + } + + _onfocusin() { + this.focused = true; + } + + _onkeydown(e: KeyboardEvent) { + if (isEnter(e) || isSpace(e)) { + e.preventDefault(); + this.fireEvent("_toggle-title"); + } + } + + _onclick() { + this.fireEvent("_toggle-title"); + } +} + +DynamicPageTitle.define(); + +export default DynamicPageTitle; diff --git a/packages/fiori/src/bundle.esm.ts b/packages/fiori/src/bundle.esm.ts index d6cb5b6d4076..fd9c7eb5676a 100644 --- a/packages/fiori/src/bundle.esm.ts +++ b/packages/fiori/src/bundle.esm.ts @@ -14,6 +14,10 @@ import "./illustrations/AllIllustrations.js"; // FIORI components import Bar from "./Bar.js"; import BarcodeScannerDialog from "./BarcodeScannerDialog.js"; +import DynamicPage from "./DynamicPage.js"; +import DynamicPageHeader from "./DynamicPageHeader.js"; +import DynamicPageTitle from "./DynamicPageTitle.js"; +import DynamicPageHeaderActions from "./DynamicPageHeaderActions.js"; import DynamicSideContent from "./DynamicSideContent.js"; import FilterItem from "./FilterItem.js"; import FilterItemOption from "./FilterItemOption.js"; diff --git a/packages/fiori/src/i18n/messagebundle.properties b/packages/fiori/src/i18n/messagebundle.properties index 34db9c285ce1..5d2064e55b27 100644 --- a/packages/fiori/src/i18n/messagebundle.properties +++ b/packages/fiori/src/i18n/messagebundle.properties @@ -8,6 +8,24 @@ BARCODE_SCANNER_DIALOG_CANCEL_BUTTON_TXT=Cancel #XBUT: Text for the busy indicator in the BarcodeScannerDialog BARCODE_SCANNER_DIALOG_LOADING_TXT=Loading +#XACT: Aria label for the expanded header state +DYNAMIC_PAGE_ARIA_LABEL_EXPANDED_HEADER=Header Expanded + +#XACT: Aria label for the snapped header state +DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER=Header Snapped + +#XACT: Aria label for the button that expands the header +DYNAMIC_PAGE_ARIA_LABEL_EXPAND_HEADER=Expand Header + +#XACT: Aria label for the button that snaps the header +DYNAMIC_PAGE_ARIA_LABEL_SNAP_HEADER=Snap Header + +#XACT: Aria label for the button that pins the header +DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER=Pin Header + +#XACT: Aria description for the button that toggles the header +DYNAMIC_PAGE_ARIA_DESCR_TOGGLE_HEADER=Toggle Header + #XTXT: Text for the FlexibleColumnLayout start column FCL_START_COLUMN_TXT=First column diff --git a/packages/fiori/src/themes/DynamicPage.css b/packages/fiori/src/themes/DynamicPage.css new file mode 100644 index 000000000000..bc5af31763d8 --- /dev/null +++ b/packages/fiori/src/themes/DynamicPage.css @@ -0,0 +1,154 @@ +.ui5-dynamic-page-title-header-wrapper { + position: sticky; + top: 0; + z-index: 1; +} + +:host { + display: block; + height: 100%; + background-color: var(--ui5_dynamic_page_background); +} + +.ui5-dynamic-page-root { + height: inherit; + overflow-y: hidden; +} + +.ui5-dynamic-page-scroll-container { + overflow-y: auto; + height: 100%; +} + +/* Automatically fit content that has height: 100% */ +.ui5-dynamic-page-scroll-container { + display: flex; + flex-direction: column; +} + +.ui5-dynamic-page-content { + flex-grow: 1; + position: relative; +} + +.ui5-dynamic-page-fit-content { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +/* Footer */ +.ui5-dynamic-page-footer { + position: sticky; + bottom: 0.5rem; + box-sizing: border-box; + z-index: 1; + opacity: 1; +} + +.ui5-dynamic-page-spacer { + height: var(--_ui5_dynamic_page_footer_spacer); +} + +:host([show-footer]) .ui5-dynamic-page-fit-content { + bottom: var(--_ui5_dynamic_page_footer_spacer); +} + +:host([show-footer]) .ui5-dynamic-page-footer { + animation: bounceShow 0.35s forwards ease-in-out; +} + +:host(:not([show-footer])) .ui5-dynamic-page-footer { + animation: bounceHide 0.35s forwards ease-in-out; +} + +::slotted([slot="footer"]) { + /* TODO css vars? */ + border-radius: 0.25rem; + background-color: var(--_ui5_dynamic_page_header_background_color); + box-sizing: content-box; + width: auto; + margin: 0 0.5rem 0.5rem 0.5rem; +} + +/* Footer animation */ +@keyframes bounceShow { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes bounceHide { + 0% { + transform: translateY(-5%); + opacity: 1; + } + 100% { + transform: translateY(100%); + opacity: 0; + } +} + +::slotted([slot="titleArea"]) { + width: auto; +} + +/* Responsive paddings */ + +:host([media-range="S"]) .ui5-dynamic-page-content, +:host([media-range="S"]) .ui5-dynamic-page-fit-content { + padding: var(--_ui5_dynamic_page_content_padding_S); +} + +:host([media-range="S"]) ::slotted([slot="titleArea"]) { + padding: var(--_ui5_dynamic_page_title_padding_S); +} + +:host([media-range="S"]) ::slotted([slot="headerArea"]) { + padding: var(--_ui5_dynamic_page_header_padding_S); +} + +:host([media-range="M"]) .ui5-dynamic-page-content, +:host([media-range="M"]) .ui5-dynamic-page-fit-content { + padding: var(--_ui5_dynamic_page_content_padding_M); +} + +:host([media-range="M"]) ::slotted([slot="titleArea"]) { + padding: var(--_ui5_dynamic_page_title_padding_M); +} + +:host([media-range="M"]) ::slotted([slot="headerArea"]) { + padding: var(--_ui5_dynamic_page_header_padding_M); +} + +:host([media-range="L"]) .ui5-dynamic-page-content, +:host([media-range="L"]) .ui5-dynamic-page-fit-content { + padding: var(--_ui5_dynamic_page_content_padding_L); +} + +:host([media-range="L"]) ::slotted([slot="titleArea"]) { + padding: var(--_ui5_dynamic_page_title_padding_L); +} + +:host([media-range="L"]) ::slotted([slot="headerArea"]) { + padding: var(--_ui5_dynamic_page_header_padding_L); +} + +:host([media-range="XL"]) .ui5-dynamic-page-content, +:host([media-range="XL"]) .ui5-dynamic-page-fit-content { + padding: var(--_ui5_dynamic_page_content_padding_XL); +} + +:host([media-range="XL"]) ::slotted([slot="titleArea"]) { + padding: var(--_ui5_dynamic_page_title_padding_XL); +} + +:host([media-range="XL"]) ::slotted([slot="headerArea"]) { + padding: var(--_ui5_dynamic_page_header_padding_XL); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/DynamicPageHeader.css b/packages/fiori/src/themes/DynamicPageHeader.css new file mode 100644 index 000000000000..3534f9ccf81d --- /dev/null +++ b/packages/fiori/src/themes/DynamicPageHeader.css @@ -0,0 +1,11 @@ +:host { + background-color: var(--_ui5_dynamic_page_header_background_color); + display: block; + box-shadow: var(--_ui5_dynamic_page_header-boxshadow); +} + +.ui5-dynamic-page-header-root { + background: inherit; + padding-top: var(--_ui5_dynamic_page_header_padding_top); + padding-bottom: var(--_ui5_dynamic_page_header_padding_bottom); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/DynamicPageHeaderActions.css b/packages/fiori/src/themes/DynamicPageHeaderActions.css new file mode 100644 index 000000000000..add42bbe13bf --- /dev/null +++ b/packages/fiori/src/themes/DynamicPageHeaderActions.css @@ -0,0 +1,43 @@ +.ui5-dynamic-page-header-actions-root { + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + .ui5-dynamic-page-header-action { + position: relative; + z-index: 1; + min-width: 1.5rem; + height: 1.5rem; + background-color: var(--sapObjectHeader_Background); + border: 1px solid var(--sapButton_BorderColor); + box-shadow: var(--_ui5_dynamic_page_header-actions-boxshadow); + } + + .ui5-dynamic-page-header-actions--wrapper { + position: absolute; + display: flex; + gap: 0.5rem; + + &:before, + &:after { + content: ""; + position: absolute; + top: 50%; + transform: translate(0, -100%); + width: 6.125rem; + height: 0.0625rem; + z-index: 0; + } + + &:before { + right: 50%; + /*background: linear-gradient(to right, transparent, var(--sapObjectHeader_BorderColor));*/ + } + + &::after { + left: 50%; + /*background: linear-gradient(to left, transparent, var(--sapObjectHeader_BorderColor));*/ + } + } +} diff --git a/packages/fiori/src/themes/DynamicPageTitle.css b/packages/fiori/src/themes/DynamicPageTitle.css new file mode 100644 index 000000000000..d5bfb76dbc79 --- /dev/null +++ b/packages/fiori/src/themes/DynamicPageTitle.css @@ -0,0 +1,146 @@ +@import "./InvisibleTextStyles.css"; + +:host { + display: flex; + flex-direction: column; + width: 100%; + background-color: var(--_ui5_dynamic_page_header_background_color); + min-height: var(--_ui5_dynamic_page_title_min_height); + word-wrap: break-word; + padding-top: var(--_ui5_dynamic_page_title_padding_top); + padding-bottom: var(--_ui5_dynamic_page_title_padding_bottom); + border-bottom: var(--_ui5_dynamic_page_title_border); +} + +:host .ui5-dynamic-page-title-root { + display: inherit; + flex-direction: inherit; + width: inherit; + height: inherit; +} + +:host(:hover) { + background-color: var(--_ui5_dynamic_page_title_hover_background); + cursor: pointer; + border-bottom: var(--_ui5_dynamic_page_title_hover_border); +} + +:host([snapped]) { + box-shadow: var(--sapContent_HeaderShadow); +} + +/* breadcrumbs */ +::slotted([ui5-breadcrumbs][slot="breadcrumbs"]) { + padding: var(--_ui5_dynamic_page_title_breadcrumbs_padding_top) 0 + var(--_ui5_dynamic_page_title_breadcrumbs_padding_bottom) 0; +} + +/* heading */ +::slotted([ui5-title][slot="heading"]), +::slotted([ui5-title][slot="expandedHeading"]), +::slotted([ui5-title][slot="snappedHeading"]) { + font-family: var(--sapObjectHeader_Title_FontFamily); + color: var(--sapObjectHeader_Title_TextColor); + padding: var(--_ui5_dynamic_page_title_heading_padding_top) 0 0 0; + margin: var(--_ui5_dynamic_page_title_heading_margin); +} + +::slotted([ui5-title][slot="expandedHeading"]), +::slotted([ui5-title][slot="heading"]) { + font-size: var(--sapObjectHeader_Title_FontSize); +} + +::slotted([ui5-title][slot="snappedHeading"]), +:host([snapped]) ::slotted([ui5-title][slot="heading"]) { + font-size: var(--sapObjectHeader_Title_SnappedFontSize); + text-overflow: ellipsis; +} + +/* subheading / expanded & snapped content */ +::slotted([ui5-title][slot="expandedContent"]), +::slotted([ui5-title][slot="snappedContent"]) { + color: var(--sapObjectHeader_Subtitle_TextColor); + font-size: var(--sapObjectHeader_Subtitle_FontSize); + margin-top: var(--_ui5_dynamic_page_title_subheading_margin_top); +} + +/* actions */ +::slotted([ui5-toolbar][slot="actions"]) { + padding: 0 0 0 1rem; +} + +.ui5-dynamic-page-title--wrapper { + display: flex; + align-items: center; +} + +.ui5-dynamic-page-title--heading { + flex-shrink: 1; + min-width: 1px; +} + +.ui5-dynamic-page-title--content { + flex-shrink: 1.6; + min-width: 3rem; + flex-grow: 1; +} + +.ui5-dynamic-page-title--actions { + flex-shrink: 1.6; + min-width: 3rem; + flex-grow: 1; + display: flex; + align-items: center; +} + +.ui5-dynamic-page-title--actions ::slotted([slot="actions"]) { + border: none; + flex: 1; +} + +.ui5-dynamic-page-title--actions .ui5-dynamic-page-title--actions-separator { + flex: 0 1; + + &::before { + content: ""; + display: inline-block; + height: var(--_ui5_dynamic_page_title_actions_separator_height); + width: 0.0625rem; + vertical-align: middle; + background: var(--sapToolbar_SeparatorColor); + margin: 0 0.5rem 0 0.25rem; + } +} + +::slotted([slot="navigationActions"]) { + border: none; + flex: 0 1; + padding-left: 0; + padding-right: 0; +} + +::slotted([slot="breadcrumbs"]) { + margin-bottom: -0.5rem; +} + +.ui5-dynamic-page-title--top-area { + display: flex; + justify-content: space-between; + align-items: center; +} + +/* focus */ +:host([focused]) { +/* separate change adds proper parameters */ + outline: 0.0625rem dotted black; + outline-offset: -0.0625rem; +} + +.ui5-dynamic-page-title-focus-area { + outline: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} diff --git a/packages/fiori/src/themes/base/DynamicPage-parameters.css b/packages/fiori/src/themes/base/DynamicPage-parameters.css new file mode 100644 index 000000000000..d8415e68c1fc --- /dev/null +++ b/packages/fiori/src/themes/base/DynamicPage-parameters.css @@ -0,0 +1,18 @@ +:root { + --_ui5_dynamic_page_footer_spacer: 4rem; + + --_ui5_dynamic_page_title_padding_S: 0.5rem 1rem; + --_ui5_dynamic_page_title_padding_M: 0.5rem 2rem; + --_ui5_dynamic_page_title_padding_L: 0.5rem 2rem; + --_ui5_dynamic_page_title_padding_XL: 0.5rem 3rem; + --_ui5_dynamic_page_header_padding_S: 0.5rem 1rem 0; + --_ui5_dynamic_page_header_padding_M: 1rem 2rem; + --_ui5_dynamic_page_header_padding_L: 1rem 3rem; + --_ui5_dynamic_page_header_padding_XL: 1rem 3rem; + --_ui5_dynamic_page_content_padding_S: 2rem 1rem 0; + --_ui5_dynamic_page_content_padding_M: 2rem 2rem 0; + --_ui5_dynamic_page_content_padding_L: 1rem 2rem 0; + --_ui5_dynamic_page_content_padding_XL: 1rem 3rem 0; + + --ui5_dynamic_page_background: var(--sapBackgroundColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/base/DynamicPageHeader-parameters.css b/packages/fiori/src/themes/base/DynamicPageHeader-parameters.css new file mode 100644 index 000000000000..1077f95bde5c --- /dev/null +++ b/packages/fiori/src/themes/base/DynamicPageHeader-parameters.css @@ -0,0 +1,8 @@ +:root { + --_ui5_dynamic_page_header_padding_top: 1rem; + --_ui5_dynamic_page_header_padding_bottom: 1rem; + + --_ui5_dynamic_page_header_background_color: var(--sapObjectHeader_Background); + --_ui5_dynamic_page_header-actions-boxshadow: var(--sapContent_Shadow0); + --_ui5_dynamic_page_header-boxshadow: var(--sapContent_HeaderShadow); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..a59223bf3dfc --- /dev/null +++ b/packages/fiori/src/themes/base/DynamicPageTitle-parameters.css @@ -0,0 +1,17 @@ +:root { + --_ui5_dynamic_page_title_padding_top: 0.5rem; + --_ui5_dynamic_page_title_padding_bottom: 0.5rem; + --_ui5_dynamic_page_title_min_height: 4rem; + + --_ui5_dynamic_page_title_breadcrumbs_padding_top: 0.5rem; + --_ui5_dynamic_page_title_breadcrumbs_padding_bottom: 0.25rem; + + --_ui5_dynamic_page_title_heading_padding_top: 0.3125rem; + + --_ui5_dynamic_page_title_subheading_margin_top: 0.25rem; + + --_ui5_dynamic_page_title_content_padding_left: 1rem; + + --_ui5_dynamic_page_title_hover_background: var(--sapObjectHeader_Hover_Background); + +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_belize/DynamicPage-parameters.css b/packages/fiori/src/themes/sap_belize/DynamicPage-parameters.css new file mode 100644 index 000000000000..42c46db7b609 --- /dev/null +++ b/packages/fiori/src/themes/sap_belize/DynamicPage-parameters.css @@ -0,0 +1,16 @@ +:root { + /* paddings */ + --_ui5_dynamic_page_title_padding_S: 0.5rem 0rem 0.5rem 1rem; + --_ui5_dynamic_page_title_padding_M: 0.5rem 1rem 0.5rem 2rem; + --_ui5_dynamic_page_title_padding_L: 0.5rem 2rem 0.5rem 3rem; + --_ui5_dynamic_page_title_padding_XL: 0.5rem 2rem 0.5rem 3rem; + --_ui5_dynamic_page_header_padding_S: 0.5rem 1rem 1rem 1rem; + --_ui5_dynamic_page_header_padding_M: 1rem 2rem; + --_ui5_dynamic_page_header_padding_L: 1rem 3rem; + --_ui5_dynamic_page_header_padding_XL: 1rem 3rem; + --_ui5_dynamic_page_content_padding_S: 2rem 1rem 0 1rem; + --_ui5_dynamic_page_content_padding_M: 2rem 2rem 0 2rem; + --_ui5_dynamic_page_content_padding_L: 1rem 3rem 0 3rem; + --_ui5_dynamic_page_content_padding_XL: 1rem 3rem 0 3rem; + +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_belize/parameters-bundle.css b/packages/fiori/src/themes/sap_belize/parameters-bundle.css index d7ddadee3a99..cfe35a1711e5 100644 --- a/packages/fiori/src/themes/sap_belize/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_belize/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "./DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/MediaGallery-parameters.css"; diff --git a/packages/fiori/src/themes/sap_belize_hcb/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_belize_hcb/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..ec881c8c719d --- /dev/null +++ b/packages/fiori/src/themes/sap_belize_hcb/DynamicPageTitle-parameters.css @@ -0,0 +1,6 @@ +@import "../base/DynamicPageTitle-parameters.css"; + +:root { + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_belize_hcb/parameters-bundle.css b/packages/fiori/src/themes/sap_belize_hcb/parameters-bundle.css index 30004e7942cb..e2128c795535 100644 --- a/packages/fiori/src/themes/sap_belize_hcb/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_belize_hcb/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_belize_hcw/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_belize_hcw/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..ec881c8c719d --- /dev/null +++ b/packages/fiori/src/themes/sap_belize_hcw/DynamicPageTitle-parameters.css @@ -0,0 +1,6 @@ +@import "../base/DynamicPageTitle-parameters.css"; + +:root { + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_belize_hcw/parameters-bundle.css b/packages/fiori/src/themes/sap_belize_hcw/parameters-bundle.css index 30004e7942cb..e2128c795535 100644 --- a/packages/fiori/src/themes/sap_belize_hcw/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_belize_hcw/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_fiori_3/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_fiori_3/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..9bb9aea9f221 --- /dev/null +++ b/packages/fiori/src/themes/sap_fiori_3/DynamicPageTitle-parameters.css @@ -0,0 +1,3 @@ +:root { + --_ui5_dynamic_page_title_min_height: 3rem; +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_fiori_3/parameters-bundle.css b/packages/fiori/src/themes/sap_fiori_3/parameters-bundle.css index 0ab99d77ccc5..2da5386cbf6c 100644 --- a/packages/fiori/src/themes/sap_fiori_3/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_fiori_3/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_fiori_3_dark/parameters-bundle.css b/packages/fiori/src/themes/sap_fiori_3_dark/parameters-bundle.css index 0ab99d77ccc5..67ebf4269608 100644 --- a/packages/fiori/src/themes/sap_fiori_3_dark/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_fiori_3_dark/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_fiori_3_hcb/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_fiori_3_hcb/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..b154c618635d --- /dev/null +++ b/packages/fiori/src/themes/sap_fiori_3_hcb/DynamicPageTitle-parameters.css @@ -0,0 +1,6 @@ +@import "../sap_fiori_3/DynamicPageTitle-parameters.css"; + +:root { + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_fiori_3_hcb/parameters-bundle.css b/packages/fiori/src/themes/sap_fiori_3_hcb/parameters-bundle.css index 30004e7942cb..e2128c795535 100644 --- a/packages/fiori/src/themes/sap_fiori_3_hcb/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_fiori_3_hcb/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_fiori_3_hcw/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_fiori_3_hcw/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..b154c618635d --- /dev/null +++ b/packages/fiori/src/themes/sap_fiori_3_hcw/DynamicPageTitle-parameters.css @@ -0,0 +1,6 @@ +@import "../sap_fiori_3/DynamicPageTitle-parameters.css"; + +:root { + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_fiori_3_hcw/parameters-bundle.css b/packages/fiori/src/themes/sap_fiori_3_hcw/parameters-bundle.css index 30004e7942cb..e2128c795535 100644 --- a/packages/fiori/src/themes/sap_fiori_3_hcw/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_fiori_3_hcw/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon/parameters-bundle.css index b9966910d906..a682ab7f37a0 100644 --- a/packages/fiori/src/themes/sap_horizon/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_dark/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_horizon_dark/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..8da5b5fd1d37 --- /dev/null +++ b/packages/fiori/src/themes/sap_horizon_dark/DynamicPageTitle-parameters.css @@ -0,0 +1,5 @@ +@import "../base/DynamicPageTitle-parameters.css"; + +:root { + --_ui5_dynamic_page_title_hover_background: transparent; +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_dark/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_dark/parameters-bundle.css index b9966910d906..9077ec22d4d7 100644 --- a/packages/fiori/src/themes/sap_horizon_dark/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_dark/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_dark_exp/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_dark_exp/parameters-bundle.css index 17242073e914..9b2d12fa832d 100644 --- a/packages/fiori/src/themes/sap_horizon_dark_exp/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_dark_exp/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_exp/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_exp/parameters-bundle.css index 17242073e914..9b2d12fa832d 100644 --- a/packages/fiori/src/themes/sap_horizon_exp/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_exp/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "../base/FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_hcb/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_horizon_hcb/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..0889d694e55e --- /dev/null +++ b/packages/fiori/src/themes/sap_horizon_hcb/DynamicPageTitle-parameters.css @@ -0,0 +1,9 @@ +@import "../base/DynamicPageTitle-parameters.css"; + +:root { + /*text-shadow of titles, breadcrumbs*/ + --sapContent_TextShadow: none; + + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_hcb/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_hcb/parameters-bundle.css index 546625c59346..e504c3e7ff96 100644 --- a/packages/fiori/src/themes/sap_horizon_hcb/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_hcb/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_hcb_exp/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_hcb_exp/parameters-bundle.css index 546625c59346..94e705a03bb1 100644 --- a/packages/fiori/src/themes/sap_horizon_hcb_exp/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_hcb_exp/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_hcw/DynamicPageTitle-parameters.css b/packages/fiori/src/themes/sap_horizon_hcw/DynamicPageTitle-parameters.css new file mode 100644 index 000000000000..0889d694e55e --- /dev/null +++ b/packages/fiori/src/themes/sap_horizon_hcw/DynamicPageTitle-parameters.css @@ -0,0 +1,9 @@ +@import "../base/DynamicPageTitle-parameters.css"; + +:root { + /*text-shadow of titles, breadcrumbs*/ + --sapContent_TextShadow: none; + + --_ui5_dynamic_page_title_border: 0.0625rem solid transparent; + --_ui5_dynamic_page_title_hover_border: 0.0625rem solid var(--sapObjectHeader_BorderColor); +} \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_hcw/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_hcw/parameters-bundle.css index 47d7c10d4bc8..4505d1e7d36d 100644 --- a/packages/fiori/src/themes/sap_horizon_hcw/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_hcw/parameters-bundle.css @@ -1,5 +1,9 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; +@import "./DynamicPageTitle-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; @import "./MediaGallery-parameters.css"; diff --git a/packages/fiori/src/themes/sap_horizon_hcw_exp/parameters-bundle.css b/packages/fiori/src/themes/sap_horizon_hcw_exp/parameters-bundle.css index 47d7c10d4bc8..60b43b5e0f3d 100644 --- a/packages/fiori/src/themes/sap_horizon_hcw_exp/parameters-bundle.css +++ b/packages/fiori/src/themes/sap_horizon_hcw_exp/parameters-bundle.css @@ -1,4 +1,7 @@ @import "../base/sizes-parameters.css"; +@import "../base/DynamicPage-parameters.css"; +@import "../base/DynamicPageTitle-parameters.css"; +@import "../base/DynamicPageHeader-parameters.css"; @import "../base/Bar-parameters.css"; @import "./FlexibleColumnLayout-parameters.css"; @import "../base/IllustratedMessage-parameters.css"; diff --git a/packages/fiori/test/pages/DynamicPage.html b/packages/fiori/test/pages/DynamicPage.html new file mode 100644 index 000000000000..1758a7ad753d --- /dev/null +++ b/packages/fiori/test/pages/DynamicPage.html @@ -0,0 +1,852 @@ + + + + + + + Dynamic page + + + + + + + + + + + + + + Link1 + + Link2 + Link3 + Link4 + Link5 + Link6 + Link7 + Location + + + Expanded Heading + + Snapped Heading + +
+ This is an expanded subheading +
+ +
+ This is a snapped subheading +
+ + + + + + + + + + + + + + + + + +
+ +
+
+ + +
+
+ Product: + +
+
+
+ Description: + +
+
+
+ Supplier: + Titanium + Scroll down + Scroll to bottom +
+
+
+
+ Progress: + +
+ +
+
+ + + 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 + + + + Edit + Close + +
+ + + + diff --git a/packages/fiori/test/pages/DynamicPageWithFullscreenContent.html b/packages/fiori/test/pages/DynamicPageWithFullscreenContent.html new file mode 100644 index 000000000000..5bf89c64e49c --- /dev/null +++ b/packages/fiori/test/pages/DynamicPageWithFullscreenContent.html @@ -0,0 +1,524 @@ + + + + + + + Dynamic page + + + + + + + + + + + + + + Link1 + + Link2 + Link3 + Link4 + Link5 + Link6 + Link7 + Location + + + Select page content: + + +
+ Select content type from the dropdown +
+ +
+ Select content type from the dropdown +
+ + + div with height="100%" + IllustratedMessage + DynamicSideContent + MediaGallery + ProductSwitch + Carrousel + Table + Table growing with button + Table growing with scroll + + + + + + + + + + +
+ +
+
+ + +
+
+ Product: + +
+
+
+ Description: + +
+
+
+ Supplier: + Titanium +
+
+
+
+ Rating: + +
+ +
+
+ + + + Edit + Close + +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fiori/test/pages/styles/DynamicPage.css b/packages/fiori/test/pages/styles/DynamicPage.css new file mode 100644 index 000000000000..d4f20921f136 --- /dev/null +++ b/packages/fiori/test/pages/styles/DynamicPage.css @@ -0,0 +1,9 @@ +html, body { + height: 100%; + padding: 0; + margin: 0; +} + +.content-padding > * { + padding: 0.5rem; +} \ No newline at end of file diff --git a/packages/fiori/test/specs/DynamicPage.spec.js b/packages/fiori/test/specs/DynamicPage.spec.js new file mode 100644 index 000000000000..59b1c3f4bcda --- /dev/null +++ b/packages/fiori/test/specs/DynamicPage.spec.js @@ -0,0 +1,417 @@ +import { assert } from "chai"; + +describe("API", () => { + before(async () => { + await browser.url(`test/pages/DynamicPage.html`); + }); + + it("toggles the header-snapped state with 'headerSnapped' property", async () => { + const page = await browser.$("#page"); + const headerSlot = await page.shadow$("slot[name=headerArea]"); + + // assert init state + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is initially expanded"); + assert.ok(await headerSlot.isExisting(), "Header slot is rendered"); + + // act + await page.setProperty("headerSnapped", true); + + // assert header is no longer rendered + assert.strictEqual(await headerSlot.isExisting(), false, "Header is not rendered"); + }); + + it("propagates-down the 'headerSnapped' property", async () => { + const page = await browser.$("#page"), + headerActions = page.shadow$('ui5-dynamic-page-header-actions'), + title = await browser.$("#page ui5-dynamic-page-title"); + + // assert init state + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + assert.strictEqual(await headerActions.getProperty("snapped"), true, "Header action is snapped"); + assert.strictEqual(await title.getProperty("snapped"), true, "Title is snapped"); + + // act + await page.setProperty("headerSnapped", false); + + // assert property is propagated + assert.strictEqual(await headerActions.getProperty("snapped"), false, "Header action is expanded"); + assert.strictEqual(await title.getProperty("snapped"), false, "Title is expanded"); + }); + + it("toggles the header-pinned state with 'headerPinned' property", async () => { + const page = await browser.$("#page"), + headerInStickyArea = await page.shadow$('header.ui5-dynamic-page-title-header-wrapper > slot[name=headerArea]'), + headerInScrollableArea = await page.shadow$('.ui5-dynamic-page-scroll-container > slot[name=headerArea]');; + + // assert init state + assert.strictEqual(await page.getProperty("headerPinned"), false, "Header is initially not pinned"); + assert.strictEqual( await headerInStickyArea.isExisting(), false, "Header is not in the sticky area"); + assert.strictEqual( await headerInScrollableArea.isExisting(), true, "Header is inside the scrollable area"); + + // act + await page.setProperty("headerPinned", true); + + // assert header is pinned in DOM + assert.strictEqual(await headerInStickyArea.isExisting(), true, "Header is inside the sticky area"); + assert.strictEqual(await headerInScrollableArea.isExisting(), false, "Header is not in the scrollable area"); + }); + + it("propagates-down the 'headerPinned' property", async () => { + const page = await browser.$("#page"), + headerActions = page.shadow$('ui5-dynamic-page-header-actions'); + + // assert init state + assert.strictEqual(await page.getProperty("headerPinned"), true, "Header is pinned"); + assert.strictEqual(await headerActions.getProperty("pinned"), true, "Pin header action is initially on"); + + // act + await page.setProperty("headerPinned", false); + + // assert property is propagated + assert.strictEqual(await headerActions.getProperty("pinned"), false, "Pin header action is off"); + }); +}); + +describe("Page general interaction", () => { + before(async () => { + await browser.url(`test/pages/DynamicPage.html`); + }); + + it("allows toggle the footer", async () => { + const footer = await browser.$("#page").shadow$(".ui5-dynamic-page-footer"); + const toggleFooterButton = await browser.$("#actionsToolbar").shadow$("#toggleFooterBtn"); + + assert.ok(await footer.isDisplayedInViewport(), "Footer should be visible."); + + // act: click to hide the footer + await toggleFooterButton.click(); + + await browser.waitUntil(async () => !(await footer.isDisplayedInViewport()), { + timeout: 1000, + timeoutMsg: "expected footer to not be visible after 1000ms" + }); + }); + + it("snaps the header upon pressing the snap button", async () => { + const dynamicPage = await browser.$("#page"); + const snapButton = await dynamicPage.shadow$("ui5-dynamic-page-header-actions").shadow$(".ui5-dynamic-page-header-action"); + + // check initial state + assert.strictEqual(await dynamicPage.getProperty("headerSnapped"), false, "Header is initially expanded"); + assert.ok(await snapButton.isExisting(), "Arrow button is found"); + + // act: click to snap the header + await snapButton.click(); + + assert.strictEqual(await dynamicPage.getProperty("headerSnapped"), true, "Header is snapped"); + }); + + it("expands the header upon pressing the expand button", async () => { + const dynamicPage = await browser.$("#page"); + const headerSlot = await dynamicPage.shadow$("slot[name=headerArea]"); + const expandButton = await dynamicPage.shadow$("ui5-dynamic-page-header-actions").shadow$(".ui5-dynamic-page-header-action"); + + assert.strictEqual(await dynamicPage.getProperty("headerSnapped"), true, "Header is snapped"); + assert.strictEqual(await dynamicPage.shadow$("slot[name=headerArea]").isExisting(), false, "Header slot is not rendered"); + + // act: click to expand the header + await expandButton.click(); + + assert.strictEqual(await dynamicPage.getProperty("headerSnapped"), false, "Header is expanded"); + assert.ok(await headerSlot.isExisting(), "Header slot is rendered"); + }); + + it("snaps the header upon scroll", async () => { + const dynamicPage = await browser.$("#page"); + const scrollButton = await browser.$("#scrollDownBtn"); + + // Act: scroll to hide the header + await dynamicPage.setProperty("isExpanding", false); + await scrollButton.click(); + + await browser.waitUntil(async () => (await browser.$("#page").getProperty("headerSnapped")), { + timeout: 2000, + timeoutMsg: "The header must be snapped." + }); + }); + + it("expands the header in the sticky area", async () => { + const page = await browser.$("#page"); + const expandButton = await page.shadow$("ui5-dynamic-page-header-actions").shadow$(".ui5-dynamic-page-header-action"); + + assert.strictEqual(await page.shadow$("slot[name=headerArea]").isExisting(), false, "Header slot is not rendered"); + + // act: click to expand the header + await expandButton.click(); + + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + assert.ok(await page.shadow$(".ui5-dynamic-page-title-header-wrapper slot[name=headerArea]").isExisting(), + "Header slot is rendered in the sticky area"); + }); + + it("pins the header", async () => { + const page = await browser.$("#page"); + const pinButton = await page.shadow$("ui5-dynamic-page-header-actions") + .shadow$$(".ui5-dynamic-page-header-action")[1]; + + // act: pin the header + await pinButton.click(); + + assert.strictEqual(await page.getProperty("headerPinned"), true, "Header is pinned"); + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is still expanded"); + }); + + it("keeps the pinned header expanded during scroll", async () => { + const page = await browser.$("#page"); + const scrollTopBeforeScroll = await page.getProperty("iPreviousScrollAmount"); + const scrollButton = await browser.$("#scrollDownBtn"); + + // act: scroll the page down + await scrollButton.click(); + + // wait untill the page processes the scroll event + await browser.waitUntil(async () => (await browser.$("#page").getProperty("iPreviousScrollAmount")) > scrollTopBeforeScroll, { + timeout: 2000, + timeoutMsg: "The scroll handler must me called." + }); + + // check: the header state should remained unchanged + assert.strictEqual(await page.getProperty("headerPinned"), true, "Header is still pinned"); + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is still expanded"); + }); + + it("unpins the header upon press of snap button", async () => { + const page = await browser.$("#page"); + const snaoButton = await page.shadow$("ui5-dynamic-page-header-actions") + .shadow$(".ui5-dynamic-page-header-action"); + + // act: snap the header + await snaoButton.click(); + + assert.strictEqual(await page.getProperty("headerPinned"), false, "Header is not pinned"); + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + }); + + it("expands the title with click", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // assert initial state + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + + // act: click to expand + await title.click(); + + assert.strictEqual(await title.getProperty("focused"), true, "title is focused"); + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + }); + + it("snaps the title with click", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // assert initial state + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + + // act: click to snap + await title.click(); + + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + }); + + it("expands the title using keyboard", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // assert init state + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + + // act: toggle the title + await title.keys("Enter") + + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + }); + + it("snaps the title using keyboard", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + + // assert init state + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + + // act: toggle the title + await title.keys("Enter") + + assert.strictEqual(await page.getProperty("headerSnapped"), true, "Header is snapped"); + }); +}); + +describe("Page layout when content has 100% height", () => { + before(async () => { + await browser.url(`test/pages/DynamicPageWithFullscreenContent.html`); + }); + + it("footer does not hide the content", async () => { + const page = await browser.$("#page"); + const content = await browser.$("#content"); + const footer = await browser.$("#footer"); + + assert.ok(await page.getProperty("showFooter"), "Footer is shown"); + + const contentBottom = await browser.execute((el) => { + return el.getBoundingClientRect().bottom; + }, content); + const footerTop = await browser.execute((el) => { + return el.getBoundingClientRect().top; + }, footer); + + assert.ok(contentBottom <= footerTop, "No overlap"); + }); + + it("content expands to fill the space between header and footer", async () => { + const page = await browser.$("#page"); + const content = await page.shadow$(".ui5-dynamic-page-fit-content"); + const header = await browser.$("#page ui5-dynamic-page-header"); + const footerSpacer = await page.shadow$(".ui5-dynamic-page-spacer"); + + assert.ok(await page.getProperty("showFooter"), "Footer is shown"); + + const headerBottom = await browser.execute((el) => { + return el.getBoundingClientRect().bottom; + }, header); + + const footerTop = await browser.execute((el) => { + return el.getBoundingClientRect().top; + }, footerSpacer); + + const contentTop = await browser.execute((el) => { + return el.getBoundingClientRect().top; + }, content); + + const contentBottom = await browser.execute((el) => { + return el.getBoundingClientRect().bottom; + }, content); + + assert.strictEqual(contentTop, headerBottom, "Content is rendered right below header"); + assert.strictEqual(contentBottom, footerTop, "Content is rendered right above footer"); + }); + +}); + +describe("Page layout when content oveflows", () => { + before(async () => { + await browser.url(`test/pages/DynamicPage.html`); + }); + + it("footer does not hide the content", async () => { + const page = await browser.$("#page"); + const content = await browser.$("#col1list"); + const footer = await browser.$("#footer"); + const scrollTopBeforeScroll = await page.getProperty("iPreviousScrollAmount"); + const scrollButton = await browser.$("#scrollToBottomBtn"); + + assert.ok(await page.getProperty("showFooter"), "Footer is shown"); + + // act: scroll the page down + await scrollButton.click(); + + // wait untill the page processes the scroll event + await browser.waitUntil(async () => (await browser.$("#page").getProperty("iPreviousScrollAmount")) > scrollTopBeforeScroll, { + timeout: 2000, + timeoutMsg: "The scroll handler must me called." + }); + + // check if footer overlaps content + const contentBottom = await browser.execute((el) => { + return el.getBoundingClientRect().bottom; + }, content); + const footerTop = await browser.execute((el) => { + return el.getBoundingClientRect().top; + }, footer); + + assert.ok(contentBottom <= footerTop, "No overlap"); + }); +}); + +describe("ARIA attributes", () => { + before(async () => { + await browser.url(`test/pages/DynamicPage.html`); + }); + + it("sets expanded state attributes", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + const titleFocusArea = await title.shadow$(".ui5-dynamic-page-title-focus-area"); + const headerWrapper = await page.shadow$(".ui5-dynamic-page-title-header-wrapper"); + const headerActions = await page.shadow$("ui5-dynamic-page-header-actions"); + const expandButton = await headerActions.shadow$("ui5-button.ui5-dynamic-page-header-action-expand"); + const pinButton = await headerActions.shadow$("ui5-toggle-button.ui5-dynamic-page-header-action-pin"); + + // assert init state + assert.strictEqual(await page.getProperty("headerSnapped"), false, "Header is expanded"); + + // check ARIA attribute values + assert.strictEqual(await headerWrapper.getAttribute("aria-label"), "Header Expanded", + "aria-label value is correct"); + assert.strictEqual(await headerWrapper.getAttribute("aria-expanded"), "true", + "aria-expanded value is correct"); + assert.strictEqual(await headerWrapper.getAttribute("role"), "region", + "header role is correct"); + + assert.strictEqual(await titleFocusArea.getAttribute("aria-expanded"), "true", + "aria-expanded value is correct"); + assert.strictEqual(await titleFocusArea.getAttribute("aria-describedby"), "toggle-description", + "aria-describedby is correct"); + assert.strictEqual(await titleFocusArea.getAttribute("role"), "button", + "title focus area role is correct"); + + assert.strictEqual(await expandButton.getProperty("accessibleName"), "Snap Header", + "expand button accessible-name is correct"); + assert.strictEqual(await expandButton.getProperty("title"), "Snap Header", + "expand button accessible-name is correct"); + + assert.strictEqual(await pinButton.getProperty("accessibleName"), "Pin Header", + "pin button accessible-name is correct"); + assert.strictEqual(await pinButton.getProperty("title"), "Pin Header", + "pin button accessible-name is correct"); + + assert.exists(await title.shadow$("#toggle-description")); + }); + + it("sets snapped state attributes", async () => { + const page = await browser.$("#page"); + const title = await browser.$("#page ui5-dynamic-page-title"); + const titleFocusArea = await title.shadow$(".ui5-dynamic-page-title-focus-area"); + const headerWrapper = await page.shadow$(".ui5-dynamic-page-title-header-wrapper"); + + // snap the header + await page.setProperty("headerSnapped", true); + + // check ARIA attribute values + assert.strictEqual(await headerWrapper.getAttribute("aria-label"), "Header Snapped", + "aria-label value is correct"); + assert.strictEqual(await headerWrapper.getAttribute("aria-expanded"), "false", + "aria-expanded value is correct"); + assert.strictEqual(await headerWrapper.getAttribute("role"), "region", + "role is correct"); + + assert.strictEqual(await titleFocusArea.getAttribute("aria-expanded"), "false", + "aria-expanded value is correct"); + assert.strictEqual(await titleFocusArea.getAttribute("aria-describedby"), "toggle-description", + "aria-describedby is correct"); + assert.strictEqual(await titleFocusArea.getAttribute("role"), "button", + "title focus area role is correct"); + + const headerActions = await page.shadow$("ui5-dynamic-page-header-actions"); + const expandButton = await headerActions.shadow$("ui5-button"); + + assert.ok(await expandButton.isExisting(), "expand button is rendered"); + assert.strictEqual(await expandButton.getProperty("accessibleName"), "Expand Header", + "expand button accessible-name is correct"); + assert.strictEqual(await expandButton.getProperty("title"), "Expand Header", + "expand button tooltip is correct"); + + assert.exists(await title.shadow$("#toggle-description")); + }); + +}); \ No newline at end of file diff --git a/packages/localization/package.json b/packages/localization/package.json index 842df10f1005..d0069f9c694a 100644 --- a/packages/localization/package.json +++ b/packages/localization/package.json @@ -29,12 +29,8 @@ "prepublishOnly": "tsc -b" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/generator": "^7.23.6", - "@babel/parser": "^7.23.6", - "@openui5/sap.ui.core": "1.120.3", - "@ui5/webcomponents-tools": "1.22.0", - "babel-plugin-amd-to-esm": "^2.0.3", + "@openui5/sap.ui.core": "1.116.0", + "@ui5/webcomponents-tools": "1.22.0-rc.1", "chromedriver": "120.0.0", "mkdirp": "^1.0.4", "resolve": "^1.20.0" diff --git a/packages/main/package.json b/packages/main/package.json index 06a0bb118060..f8f5bea6762e 100644 --- a/packages/main/package.json +++ b/packages/main/package.json @@ -56,7 +56,7 @@ "@ui5/webcomponents-theming": "1.22.0" }, "devDependencies": { - "@ui5/webcomponents-tools": "1.22.0", + "@ui5/webcomponents-tools": "1.22.0-rc.1", "chromedriver": "120.0.0" } } diff --git a/packages/main/src/Breadcrumbs.ts b/packages/main/src/Breadcrumbs.ts index 7352bd0613ac..3f21ee360e83 100644 --- a/packages/main/src/Breadcrumbs.ts +++ b/packages/main/src/Breadcrumbs.ts @@ -7,6 +7,7 @@ import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import { isSpace, isShow, @@ -221,7 +222,9 @@ class Breadcrumbs extends UI5Element { this._preprocessItems(); } - onAfterRendering() { + async onAfterRendering() { + await renderFinished(); + this._cacheWidths(); this._updateOverflow(); } diff --git a/packages/main/src/Toolbar.ts b/packages/main/src/Toolbar.ts index d24f9d83ee04..22b77ddb3437 100644 --- a/packages/main/src/Toolbar.ts +++ b/packages/main/src/Toolbar.ts @@ -41,6 +41,10 @@ import { import Button from "./Button.js"; import Popover from "./Popover.js"; +type ToolbarMinWidthChangeEventDetail = { + minWidth: number, +}; + function calculateCSSREMValue(styleSet: CSSStyleDeclaration, propertyName: string): number { return Number(styleSet.getPropertyValue(propertyName).replace("rem", "")) * parseInt(getComputedStyle(document.body).getPropertyValue("font-size")); } @@ -147,6 +151,7 @@ class Toolbar extends UI5Element { _onInteract!: EventListener; itemsToOverflow: Array = []; itemsWidth = 0; + minContentWidth = 0; popoverOpen = false; itemsWidthMeasured = false; @@ -389,15 +394,31 @@ class Toolbar extends UI5Element { } storeItemsWidth() { - let totalWidth = 0; + let totalWidth = 0, + minWidth = 0; this.items.forEach((item: ToolbarItem) => { const itemWidth = this.getItemWidth(item); totalWidth += itemWidth; + if (item.overflowPriority === ToolbarItemOverflowBehavior.NeverOverflow) { + minWidth += itemWidth; + } this.ITEMS_WIDTH_MAP.set(item._id, itemWidth); }); + if (minWidth && totalWidth > minWidth) { + minWidth += this.overflowButtonSize; + } + + if (minWidth !== this.minContentWidth) { + const spaceAroundContent = this.offsetWidth - this.getDomRef()!.offsetWidth; + this.fireEvent("_min-content-width-change", { + minWidth: minWidth + spaceAroundContent, + }); + } + this.itemsWidth = totalWidth; + this.minContentWidth = minWidth; } distributeItems(overflowSpace = 0) { @@ -600,3 +621,6 @@ class Toolbar extends UI5Element { Toolbar.define(); export default Toolbar; +export type { + ToolbarMinWidthChangeEventDetail, +}; diff --git a/packages/main/src/themes/base/sizes-parameters.css b/packages/main/src/themes/base/sizes-parameters.css index aa6dcaf41970..3e252f4cca72 100644 --- a/packages/main/src/themes/base/sizes-parameters.css +++ b/packages/main/src/themes/base/sizes-parameters.css @@ -141,6 +141,9 @@ --_ui5-toolbar-separator-height: 2rem; --_ui5-toolbar-height: 2.75rem; --_ui5_toolbar_overflow_padding: 0.25rem 0.5rem; + + /* DynamicPageTitle */ + --_ui5_dynamic_page_title_actions_separator_height: var(--_ui5-toolbar-separator-height); } [data-ui5-compact-size], @@ -343,4 +346,7 @@ --_ui5-toolbar-separator-height: 1.5rem; --_ui5-toolbar-height: 2rem; --_ui5_toolbar_overflow_padding: 0.1875rem 0.375rem; + + /* DynamicPageTitle */ + --_ui5_dynamic_page_title_actions_separator_height: var(--_ui5-toolbar-separator-height); } diff --git a/packages/playground/_stories/fiori/DynamicPage/DynamicPage.stories.ts b/packages/playground/_stories/fiori/DynamicPage/DynamicPage.stories.ts new file mode 100644 index 000000000000..ea9e4329c5dd --- /dev/null +++ b/packages/playground/_stories/fiori/DynamicPage/DynamicPage.stories.ts @@ -0,0 +1,763 @@ +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import type { Meta } from "@storybook/web-components"; +import type { PartialStoryFn } from "@storybook/types"; + +import argTypes from "./argTypes.js"; +import type { StoryArgsSlots } from "./argTypes.js"; +import type { UI5StoryArgs } from "../../../types.js"; + +import type DynamicPage from "@ui5/webcomponents-fiori/dist/DynamicPage.js"; + +import DynamicPageTitleTemplate from "./DynamicPageTitleTemplate.js" +import DynamicPageHeaderTemplate from "./DynamicPageHeaderTemplate.js" + +const stylesDecorator = (storyFn: PartialStoryFn) => html` + + ${storyFn()} +`; + +export default { + title: "Fiori/Dynamic page", + component: "DynamicPage", + decorators: [stylesDecorator], + argTypes, +} as Meta; + + +const Template: UI5StoryArgs = (args) => { + return html` + + ${unsafeHTML(args.headerArea)} + ${unsafeHTML(args.titleArea)} + ${unsafeHTML(args.default)} + ${unsafeHTML(args.footer)} + +`; +}; + +export const Basic = Template.bind({}); +Basic.args = { + showFooter: true, + titleArea: DynamicPageTitleTemplate, + headerArea: DynamicPageHeaderTemplate, + default: ` + + 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 + `, + footer: ` + Edit + Close + `, + +}; + +Basic.decorators = [ + (story) => html` + ${story()}`, +]; + +Basic.parameters = { + docs: { + story: { + // Opt-out of inline rendering + inline: false, + iframeHeight: 600, + } + }, +}; \ No newline at end of file diff --git a/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeader/DynamicPageHeader.stories.ts b/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeader/DynamicPageHeader.stories.ts new file mode 100644 index 000000000000..9d5409d18c87 --- /dev/null +++ b/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeader/DynamicPageHeader.stories.ts @@ -0,0 +1,23 @@ +import { html } from "lit"; +import type { Meta } from "@storybook/web-components"; +import type { PartialStoryFn } from "@storybook/types"; + +import argTypes from "./argTypes.js"; +import type { StoryArgsSlots } from "../argTypes.js"; +import type { UI5StoryArgs } from "../../../../types.js"; + +import type DynamicPageHeader from "@ui5/webcomponents-fiori/dist/DynamicPageHeader.js"; +import DynamicPageHeaderTemplate from "../DynamicPageHeaderTemplate.js"; + +export default { + title: "Fiori/Dynamic Page/ Dynamic Page Header", + component: "DynamicPageHeader", + argTypes, +} as Meta; + +const Template: UI5StoryArgs = (args) => { + return DynamicPageHeaderTemplate; +}; + +export const Basic = Template.bind({}); +Basic.tags = ["_hidden_"]; diff --git a/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeaderTemplate.ts b/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeaderTemplate.ts new file mode 100644 index 000000000000..7aec29d82026 --- /dev/null +++ b/packages/playground/_stories/fiori/DynamicPage/DynamicPageHeaderTemplate.ts @@ -0,0 +1,55 @@ +export default ` +
+
+ + +
+
+ Product: + +
+
+
+ Description: + +
+
+
+ Supplier: + Titanium +
+
+
+
+ Progress: + +
+ +
+
`; diff --git a/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitle/DynamicPageTitle.stories.ts b/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitle/DynamicPageTitle.stories.ts new file mode 100644 index 000000000000..a0888daffdb5 --- /dev/null +++ b/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitle/DynamicPageTitle.stories.ts @@ -0,0 +1,21 @@ +import type { Meta } from "@storybook/web-components"; + +import argTypes from "./argTypes.js"; +import type { StoryArgsSlots } from "../argTypes.js"; +import type { UI5StoryArgs } from "../../../../types.js"; + +import type DynamicPageTitle from "@ui5/webcomponents-fiori/dist/DynamicPageTitle.js"; +import DynamicPageTitleTemplate from "../DynamicPageTitleTemplate.js"; + +export default { + title: "Fiori/Dynamic Page/ Dynamic Page Title", + component: "DynamicPageTitle", + argTypes, +} as Meta; + +const Template: UI5StoryArgs = (args) => { + return DynamicPageTitleTemplate; +}; + +export const Basic = Template.bind({}); +Basic.tags = ["_hidden_"]; diff --git a/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitleTemplate.ts b/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitleTemplate.ts new file mode 100644 index 000000000000..5209e2613686 --- /dev/null +++ b/packages/playground/_stories/fiori/DynamicPage/DynamicPageTitleTemplate.ts @@ -0,0 +1,52 @@ +export default ` + + Link1 + + Link2 + Link3 + Link4 + Link5 + Link6 + Link7 + Location + + + Expanded Heading + + Snapped Heading + +
+ This is an expanded subheading +
+ +
+ This is a snapped subheading +
+ + + + + + + + + + + + + + + + +
` \ No newline at end of file diff --git a/packages/theming/css-vars-usage.json b/packages/theming/css-vars-usage.json index 4d2043e68a38..7802922a9f3b 100644 --- a/packages/theming/css-vars-usage.json +++ b/packages/theming/css-vars-usage.json @@ -506,6 +506,13 @@ "--sapNeutralTextColor", "--sapObjectHeader_Background", "--sapObjectHeader_BorderColor", + "--sapObjectHeader_Hover_Background", + "--sapObjectHeader_Subtitle_FontSize", + "--sapObjectHeader_Subtitle_TextColor", + "--sapObjectHeader_Title_FontFamily", + "--sapObjectHeader_Title_FontSize", + "--sapObjectHeader_Title_SnappedFontSize", + "--sapObjectHeader_Title_TextColor", "--sapPageFooter_Background", "--sapPageFooter_BorderColor", "--sapPageFooter_TextColor",