Skip to content

feat(DynamicPageTitle): add expandedContent & snappedContent props #4997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 24, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import exitFSIcon from '@ui5/webcomponents-icons/dist/exit-full-screen.js';
import fullscreenIcon from '@ui5/webcomponents-icons/dist/full-screen.js';
import menu2Icon from '@ui5/webcomponents-icons/dist/menu2';
import navDownArrowIcon from '@ui5/webcomponents-icons/dist/navigation-down-arrow.js';
import { useEffect, useReducer, useRef, useState } from 'react';
import { useReducer, useState } from 'react';
import {
Badge,
Bar,
Expand All @@ -23,6 +23,7 @@ import {
FlexBoxDirection,
FlexBoxWrap,
Label,
MessageStrip,
ObjectStatus,
PageBackgroundDesign,
Panel,
Expand Down Expand Up @@ -91,6 +92,8 @@ const meta = {
}
header={<Title>Header Title</Title>}
subHeader={<Label>This is a sub header</Label>}
expandedContent={<MessageStrip>Information (only visible if header content is expanded)</MessageStrip>}
snappedContent={<MessageStrip>Information (only visible if header content is collapsed/snapped)</MessageStrip>}
>
<Badge>Status: OK</Badge>
</DynamicPageTitle>
Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/components/DynamicPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
(!showHideHeaderButton && !headerContentPinnable),
ref: componentRefTopHeader,
className: clsx(classes.title, headerTitle?.props?.className),
onToggleHeaderContentVisibility: onToggleHeaderContentInternal
onToggleHeaderContentVisibility: onToggleHeaderContentInternal,
'data-header-content-visible': headerContent && headerCollapsed !== true
})}
{headerContent &&
cloneElement(headerContent, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
import { useRef, useState } from 'react';
import type { DynamicPageTitlePropTypes } from '../..';
import { Breadcrumbs, BreadcrumbsItem, DynamicPage, ObjectPage, Title } from '../..';
import type { DynamicPagePropTypes, DynamicPageTitlePropTypes, ObjectPagePropTypes } from '../..';
import {
Avatar,
Breadcrumbs,
BreadcrumbsItem,
DynamicPage,
DynamicPageHeader,
ObjectPage,
ObjectPageSection,
Title
} from '../..';
import { Button } from '../../webComponents/index.js';
import { DynamicPageTitle } from './';

interface PropTypes {
dynamicPageTitleProps?: DynamicPageTitlePropTypes;
pageProps?: ObjectPagePropTypes | DynamicPagePropTypes;
isObjectPage: boolean;
childrenScrollable?: boolean;
}

const PageComponent = ({ dynamicPageTitleProps = {}, isObjectPage }: PropTypes) => {
const PageComponent = ({ dynamicPageTitleProps = {}, isObjectPage, pageProps = {}, childrenScrollable }: PropTypes) => {
const actionsRef = useRef(null);
const navActionsRef = useRef(null);
const [actionsToolbarInstance, setActionsToolbarInstance] = useState();
const [navActionsToolbarInstance, setNavActionsToolbarInstance] = useState();
const pageProps = {
const childrenObjectPage = (
<ObjectPageSection id={'0'}>
<div style={{ height: '1600px', background: 'cadetblue' }}></div>
</ObjectPageSection>
);
const childrenDynamicPage = <div style={{ height: '1600px', background: 'cadetblue' }}></div>;
const localPageProps = {
headerTitle: (
<DynamicPageTitle
actions={new Array(10).fill(<Button>Test</Button>)}
Expand All @@ -23,7 +40,8 @@ const PageComponent = ({ dynamicPageTitleProps = {}, isObjectPage }: PropTypes)
navigationActionsToolbarProps={{ overflowPopoverRef: navActionsRef }}
{...dynamicPageTitleProps}
/>
)
),
...pageProps
};
return (
<>
Expand All @@ -41,7 +59,15 @@ const PageComponent = ({ dynamicPageTitleProps = {}, isObjectPage }: PropTypes)
>
Show navActionsRef
</Button>
{isObjectPage ? <ObjectPage {...pageProps} /> : <DynamicPage {...pageProps} />}
{isObjectPage ? (
<ObjectPage data-testid="page" {...localPageProps}>
{childrenScrollable && childrenObjectPage}
</ObjectPage>
) : (
<DynamicPage data-testid="page" {...localPageProps}>
{childrenScrollable && childrenDynamicPage}
</DynamicPage>
)}
<p data-testid="actionsInstance">{`${!!actionsToolbarInstance}`}</p>
<p data-testid="navActionsInstance">{`${!!navActionsToolbarInstance}`}</p>
</>
Expand Down Expand Up @@ -144,4 +170,44 @@ describe('DynamicPageTitle', () => {
cy.findByTestId('breadcrumbs').parent().should('have.css', 'width', '460px' /*50% (min-width)*/);
});
});

it('expandedContent & snappedContent', () => {
[true, 'withImage', false].forEach((isObjectPage) => {
const image = isObjectPage === 'withImage' && <Avatar />;
[
undefined,
<DynamicPageHeader key="headerContent" style={{ height: '100px', background: 'lightblue' }}>
Header Section
</DynamicPageHeader>
].forEach((headerContent) => {
cy.mount(
<PageComponent
childrenScrollable
isObjectPage={!!isObjectPage}
pageProps={{ image, headerContent, style: { height: '800px' } }}
dynamicPageTitleProps={{
expandedContent: <div data-testid="expandedContent">expandedContent</div>,
snappedContent: <div data-testid="snappedContent">snappedContent</div>
}}
/>
);
if (headerContent) {
cy.findByText('expandedContent').should('be.visible');
cy.findByTestId('snappedContent').should('not.exist');
} else {
cy.findByText('snappedContent').should('be.visible');
cy.findByTestId('expandedContent').should('not.exist');
}
cy.wait(50);
cy.findByTestId('page').scrollTo(0, 500);
cy.findByText('snappedContent').should('be.visible');
cy.findByTestId('expandedContent').should('not.exist');
if (headerContent && image) {
cy.get('[data-component-name="ATwithImageSnappedContentContainer"]').should('be.visible');
} else {
cy.get('[data-component-name="ATwithImageSnappedContentContainer"]').should('not.exist');
}
});
});
});
});
27 changes: 27 additions & 0 deletions packages/main/src/components/DynamicPageTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,33 @@ export interface DynamicPageTitlePropTypes extends CommonProps {
* __Note:__ It is possible to overwrite internal implementations. Please use with caution!
*/
navigationActionsToolbarProps?: Omit<ToolbarPropTypes, 'design' | 'toolbarStyle' | 'active'>;
/**
* The content displayed in the `DynamicPageTitle` in expanded state.
*/
expandedContent?: ReactNode | ReactNode[];
/**
* The content displayed in the `DynamicPageTitle` in collapsed (snapped) state.
*/
snappedContent?: ReactNode | ReactNode[];
}

interface InternalProps extends DynamicPageTitlePropTypes {
/**
* The onToggleHeaderContentVisibility show or hide the header section
*/
onToggleHeaderContentVisibility?: (e: any) => boolean;
/**
* Defines whether the content area can be toggled
*/
'data-not-clickable'?: boolean;
/**
* Defines whether the content area is visible
*/
'data-header-content-visible'?: boolean;
/**
* Defines if the `snappedContent` should be rendered by the `DynamicPageTitle`
*/
'data-is-snapped-rendered-outside'?: boolean;
}

const useStyles = createUseStyles(DynamicPageTitleStyles, { name: 'DynamicPageTitle' });
Expand Down Expand Up @@ -128,6 +148,8 @@ const DynamicPageTitle = forwardRef<HTMLDivElement, DynamicPageTitlePropTypes>((
onToggleHeaderContentVisibility,
actionsToolbarProps,
navigationActionsToolbarProps,
expandedContent,
snappedContent,
...rest
} = props as InternalProps;

Expand Down Expand Up @@ -300,6 +322,11 @@ const DynamicPageTitle = forwardRef<HTMLDivElement, DynamicPageTitlePropTypes>((
</div>
</FlexBox>
)}
{props?.['data-header-content-visible']
? expandedContent
: props['data-is-snapped-rendered-outside']
? undefined
: snappedContent}
</FlexBox>
);
});
Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/components/ObjectPage/ObjectPage.jss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,6 @@ export const styles = {
padding: 0
}
},
titleInHeader: { padding: 0 }
titleInHeader: { padding: 0 },
snappedContent: { gridColumn: '1 / span 2' }
};
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ const meta = {
<BreadcrumbsItem>Employee Details</BreadcrumbsItem>
</Breadcrumbs>
}
expandedContent={<MessageStrip>Information (only visible if header content is expanded)</MessageStrip>}
snappedContent={<MessageStrip>Information (only visible if header content is collapsed/snapped)</MessageStrip>}
>
<ObjectStatus state={ValueState.Success}>employed</ObjectStatus>
</DynamicPageTitle>
Expand Down
26 changes: 23 additions & 3 deletions packages/main/src/components/ObjectPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,8 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
[onToggleHeaderContentVisibility, headerCollapsed, titleHeaderNotClickable]
);

const snappedHeaderInObjPage = headerTitle && headerTitle.props.snappedContent && headerCollapsed === true && !!image;

const renderTitleSection = useCallback(
(inHeader = false) => {
const titleInHeaderClass = inHeader ? classes.titleInHeader : undefined;
Expand All @@ -620,18 +622,31 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
showSubHeaderRight: true,
className: clsx(titleInHeaderClass, headerTitle?.props?.className),
'data-not-clickable': titleHeaderNotClickable,
onToggleHeaderContentVisibility: onTitleClick
onToggleHeaderContentVisibility: onTitleClick,
'data-header-content-visible': headerContent && headerCollapsed !== true,
'data-is-snapped-rendered-outside': snappedHeaderInObjPage
});
}
return cloneElement(headerTitle, {
className: clsx(titleInHeaderClass, headerTitle?.props?.className),
'data-not-clickable': titleHeaderNotClickable,
onToggleHeaderContentVisibility: onTitleClick
onToggleHeaderContentVisibility: onTitleClick,
'data-header-content-visible': headerContent && headerCollapsed !== true,
'data-is-snapped-rendered-outside': snappedHeaderInObjPage
});
},
[headerTitle, titleHeaderNotClickable, onTitleClick]
[headerTitle, titleHeaderNotClickable, onTitleClick, headerCollapsed, snappedHeaderInObjPage, !!headerContent]
);

const isInitial = useRef(true);
useEffect(() => {
if (!isInitial.current) {
scrollTimeout.current = performance.now() + 200;
} else {
isInitial.current = false;
}
}, [snappedHeaderInObjPage]);

const renderHeaderContentSection = useCallback(() => {
if (headerContent?.props) {
return cloneElement(headerContent, {
Expand Down Expand Up @@ -763,6 +778,11 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
<CollapsedAvatar image={image} imageShapeCircle={imageShapeCircle} />
)}
{headerTitle && renderTitleSection()}
{snappedHeaderInObjPage && (
<div className={classes.snappedContent} data-component-name="ATwithImageSnappedContentContainer">
{headerTitle.props.snappedContent}
</div>
)}
</header>
{renderHeaderContentSection()}
{headerContent && headerTitle && (
Expand Down