From bfc912cd7e986f85bbb5788830a490d6b4b0eea1 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Tue, 1 Jul 2025 14:43:53 +0200 Subject: [PATCH] feat(ObjectPageTitle): introduce `snappedHeader` & `snappedSubHeader` prop --- .../ObjectPage/ObjectPage.stories.tsx | 4 ++ .../ObjectPageTitle/ObjectPageTitle.cy.tsx | 58 +++++++++++++++++++ .../src/components/ObjectPageTitle/index.tsx | 12 ++-- .../components/ObjectPageTitle/types/index.ts | 26 +++++++-- 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx index d1ad5383b28..25e2e438eb9 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx @@ -1,6 +1,7 @@ import '@ui5/webcomponents-fiori/dist/illustrations/UnableToLoad.js'; import SampleImage from '@sb/demoImages/Person.png'; import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; import BarDesign from '@ui5/webcomponents/dist/types/BarDesign.js'; import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js'; import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js'; @@ -59,6 +60,7 @@ const meta = { imageShapeCircle: true, image: SampleImage, style: { height: '700px', maxHeight: '90vh' }, + onToggleHeaderArea: fn(), footerArea: ( Denise Smith} + snappedHeader={Denise Smith (snapped header)} subHeader="Senior UI Developer" + snappedSubHeader={'Senior UI Developer (snapped header)'} actionsBar={ diff --git a/packages/main/src/components/ObjectPageTitle/ObjectPageTitle.cy.tsx b/packages/main/src/components/ObjectPageTitle/ObjectPageTitle.cy.tsx index 0c37a8353cf..0ef431cac15 100644 --- a/packages/main/src/components/ObjectPageTitle/ObjectPageTitle.cy.tsx +++ b/packages/main/src/components/ObjectPageTitle/ObjectPageTitle.cy.tsx @@ -129,4 +129,62 @@ describe('ObjectPageTitle', () => { cy.findByTestId('expandedContent').should('not.exist'); }); }); + + [ + { snappedHeader: 'Snapped Header', header: 'Header' }, + { snappedHeader: undefined, header: 'Header' }, + { snappedHeader: undefined, header: undefined }, + { snappedHeader: 'Snapped Header', header: undefined }, + ].forEach(({ snappedHeader, header }) => { + const titleParts = []; + if (snappedHeader) { + titleParts.push('snappedHeader'); + } + if (header) { + titleParts.push('header'); + } + const title = titleParts.length ? titleParts.join(' & ') : 'no headers'; + + it(`renders with ${title}`, () => { + cy.mount( + + Header Section + + ), + style: { height: '800px' }, + }} + titleProps={{ + snappedHeader, + header, + }} + />, + ); + + // not snapped - always show header + cy.findByText('Snapped Header').should('not.exist'); + if (header) { + cy.findByText('Header').should('exist'); + } else { + cy.findByText('Header').should('not.exist'); + } + + cy.findByTestId('page').scrollTo('bottom'); + + // snapped - show snapped header if defined otherwise show header + if (snappedHeader) { + cy.findByText('Snapped Header').should('exist'); + cy.findByText('Header').should('not.exist'); + } else if (header) { + cy.findByText('Snapped Header').should('not.exist'); + cy.findByText('Header').should('exist'); + } else { + cy.findByText('Snapped Header').should('not.exist'); + cy.findByText('Header').should('not.exist'); + } + }); + }); }); diff --git a/packages/main/src/components/ObjectPageTitle/index.tsx b/packages/main/src/components/ObjectPageTitle/index.tsx index a44abca0fe3..373111ca5e0 100644 --- a/packages/main/src/components/ObjectPageTitle/index.tsx +++ b/packages/main/src/components/ObjectPageTitle/index.tsx @@ -28,6 +28,8 @@ const ObjectPageTitle = forwardRef((pr onToggleHeaderContentVisibility, expandedContent, snappedContent, + snappedHeader, + snappedSubHeader, _snappedAvatar, ...rest } = props as InternalProps; @@ -41,6 +43,8 @@ const ObjectPageTitle = forwardRef((pr ); const containerClasses = clsx(classNames.container, isPhone && classNames.phone, className); const toolbarContainerRef = useRef(null); + const _header = !props?.['data-header-content-visible'] && snappedHeader ? snappedHeader : header; + const _subHeader = !props?.['data-header-content-visible'] && snappedSubHeader ? snappedSubHeader : subHeader; useEffect(() => { isMounted.current = true; @@ -157,9 +161,9 @@ const ObjectPageTitle = forwardRef((pr data-component-name="ObjectPageTitleMiddleSection" > - {header && ( + {_header && (
- {header} + {_header}
)} {children && ( @@ -182,13 +186,13 @@ const ObjectPageTitle = forwardRef((pr )}
- {subHeader && ( + {_subHeader && (
- {subHeader} + {_subHeader}
)} diff --git a/packages/main/src/components/ObjectPageTitle/types/index.ts b/packages/main/src/components/ObjectPageTitle/types/index.ts index 288c59e5e4b..f2864363568 100644 --- a/packages/main/src/components/ObjectPageTitle/types/index.ts +++ b/packages/main/src/components/ObjectPageTitle/types/index.ts @@ -23,15 +23,33 @@ export interface ObjectPageTitlePropTypes extends CommonProps { children?: ReactNode | ReactNode[]; /** - * The `header` is positioned in the `ObjectPageTitle` left area. - * Use this prop to display a `Title` (or any other component that serves as a heading). + * The `header` is displayed in the left area of the `ObjectPageTitle`. + * Use this prop to render a `Title` or any other component that serves as a heading. + * + * __Note:__ If the header is snapped (collapsed), the `snappedHeader` prop is used instead (if defined). */ header?: ReactNode; /** - * The `subHeader` is positioned in the `ObjectPageTitle` left area below the `header`. - * Use this aggregation to display a component like `Label` or any other component that serves as sub header. + * The `snappedHeader` is displayed in the left area of the `ObjectPageTitle` when the header is snapped (collapsed). + * Use this prop to render a `Title` or any other component that serves as a heading in the snapped view. + * + * @since 2.12.0 + */ + snappedHeader?: ReactNode; + /** + * The `subHeader` is displayed in the left area of the `ObjectPageTitle`, below the `header`. + * Use this prop to render a `Label` or any other component that serves as a sub-header. + * + * __Note:__ If the header is snapped (collapsed), the `snappedSubHeader` prop is used instead (if defined). */ subHeader?: ReactNode; + /** + * The `snappedSubHeader` is displayed in the left area of the `ObjectPageTitle` when the header is snapped (collapsed). + * Use this prop to render a `Label` or any other component that serves as a sub-header in the snapped view. + * + * @since 2.12.0 + */ + snappedSubHeader?: ReactNode; /** * Defines navigation-actions bar of the `ObjectPageTitle`. *