Skip to content

Commit 6bce2da

Browse files
authored
feat(ObjectPage & DynamicPage): add onPinnedStateChange, fix setting alwaysShowContentHeader in runtime (#3876)
1 parent 6b7b0aa commit 6bce2da

File tree

11 files changed

+466
-64
lines changed

11 files changed

+466
-64
lines changed

cypress.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineConfig } from 'cypress';
21
import codeCoverageTask from '@cypress/code-coverage/task';
2+
import { defineConfig } from 'cypress';
33

44
export default defineConfig({
55
component: {
@@ -16,5 +16,6 @@ export default defineConfig({
1616
viewportWidth: 1920,
1717
viewportHeight: 1080,
1818
video: false,
19-
screenshotOnRunFailure: false
19+
screenshotOnRunFailure: false,
20+
scrollBehavior: false
2021
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"license": "Apache-2.0",
88
"scripts": {
99
"start": "lerna run build:i18n && start-storybook -p 6006",
10-
"build:prepare": "lerna run build:i18n && node scripts/build-wrappers.js",
10+
"build:prepare": "lerna run build:i18n && node scripts/build-wrappers.js && rimraf node_modules/@types/mocha",
1111
"build:cleanup": "rimraf packages/main/tmp",
1212
"build": "yarn build:prepare && tsc --build && yarn build:cleanup",
1313
"build:storybook": "lerna run build:i18n && build-storybook -o .out",

packages/main/src/components/DynamicPage/DynamicPage.cy.tsx

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { DynamicPage, DynamicPageHeader, DynamicPageTitle } from '../..';
1+
import { useState } from 'react';
2+
import { Button, DynamicPage, DynamicPageHeader, DynamicPagePropTypes, DynamicPageTitle } from '../..';
23

34
describe('DynamicPage', () => {
45
it('toggle header', () => {
@@ -35,4 +36,164 @@ describe('DynamicPage', () => {
3536
cy.get('@toggleSpy').should('have.been.calledWith', true);
3637
cy.get('@toggleSpy').should('have.callCount', 4);
3738
});
39+
40+
it('pin header', () => {
41+
const pin = cy.spy().as('onPinSpy');
42+
cy.mount(
43+
<DynamicPage
44+
style={{ height: '100vh' }}
45+
headerTitle={<DynamicPageTitle header="Heading" subHeader="SubHeading" />}
46+
headerContent={<DynamicPageHeader>DynamicPageHeader</DynamicPageHeader>}
47+
headerContentPinnable
48+
showHideHeaderButton
49+
onPinnedStateChange={pin}
50+
data-testid="op"
51+
>
52+
<div style={{ height: '2000px' }} />
53+
</DynamicPage>
54+
);
55+
cy.wait(50);
56+
cy.findByTestId('op').scrollTo(0, 500);
57+
cy.findByText('DynamicPageHeader').should('not.be.visible');
58+
59+
cy.findByTestId('op').scrollTo('top');
60+
61+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click();
62+
cy.get('@onPinSpy').should('have.been.calledOnce');
63+
cy.get('@onPinSpy').should('have.been.calledWith', true);
64+
65+
cy.findByTestId('op').scrollTo(0, 500);
66+
cy.findByText('DynamicPageHeader').should('be.visible');
67+
68+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click();
69+
cy.get('@onPinSpy').should('have.been.calledTwice');
70+
cy.get('@onPinSpy').should('have.been.calledWith', false);
71+
cy.findByTestId('op').scrollTo(0, 501);
72+
cy.findByText('DynamicPageHeader').should('not.be.visible');
73+
});
74+
75+
it('programmatically pin header (`alwaysShowContentHeader`)', () => {
76+
const TestComp = ({ onPinnedStateChange }: DynamicPagePropTypes) => {
77+
const [pinned, setPinned] = useState(false);
78+
const handlePinChange = (pinned) => {
79+
onPinnedStateChange(pinned);
80+
setPinned(pinned);
81+
};
82+
return (
83+
<>
84+
<Button
85+
data-testid="btn"
86+
onClick={() => {
87+
setPinned((prev) => !prev);
88+
}}
89+
>
90+
toggle {`${!pinned}`}
91+
</Button>
92+
<DynamicPage
93+
style={{ height: '100vh' }}
94+
headerTitle={<DynamicPageTitle header="Heading" subHeader="SubHeading" />}
95+
headerContent={<DynamicPageHeader>DynamicPageHeader</DynamicPageHeader>}
96+
headerContentPinnable
97+
showHideHeaderButton
98+
alwaysShowContentHeader={pinned}
99+
onPinnedStateChange={handlePinChange}
100+
data-testid="op"
101+
>
102+
<div style={{ height: '2000px' }} />
103+
</DynamicPage>
104+
</>
105+
);
106+
};
107+
const pin = cy.spy().as('onPinSpy');
108+
cy.mount(<TestComp onPinnedStateChange={pin} />);
109+
cy.wait(50);
110+
111+
cy.findByTestId('op').scrollTo(0, 500);
112+
cy.findByText('DynamicPageHeader').should('not.be.visible');
113+
114+
cy.findByTestId('btn').click();
115+
cy.get('@onPinSpy').should('have.been.calledOnce');
116+
cy.get('@onPinSpy').should('have.been.calledWith', true);
117+
cy.findByText('DynamicPageHeader').should('be.visible');
118+
119+
cy.findByTestId('op').scrollTo(0, 0);
120+
cy.findByText('DynamicPageHeader').should('be.visible');
121+
122+
cy.findByTestId('op').scrollTo(0, 800);
123+
cy.findByText('DynamicPageHeader').should('be.visible');
124+
125+
cy.findByTestId('btn').click();
126+
cy.get('@onPinSpy').should('have.been.calledTwice');
127+
cy.get('@onPinSpy').should('have.been.calledWith', false);
128+
cy.findByText('DynamicPageHeader').should('be.visible');
129+
130+
cy.findByTestId('op').scrollTo(0, 801);
131+
cy.findByText('DynamicPageHeader').should('not.be.visible');
132+
133+
cy.findByTestId('btn').click();
134+
cy.findByTestId('op').scrollTo(0, 500);
135+
cy.findByText('DynamicPageHeader').should('be.visible');
136+
137+
cy.findByTestId('btn').click();
138+
cy.findByTestId('op').scrollTo(0, 501);
139+
cy.findByText('DynamicPageHeader').should('not.be.visible');
140+
141+
cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click();
142+
cy.findByText('DynamicPageHeader').should('be.visible');
143+
144+
// wait for timeout of expand click
145+
cy.wait(500);
146+
147+
cy.findByTestId('op').scrollTo(0, 502);
148+
cy.findByText('DynamicPageHeader').should('not.be.visible');
149+
150+
cy.findByTestId('op').scrollTo(0, 30);
151+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click();
152+
cy.get('@onPinSpy').should('have.callCount', 5);
153+
cy.findByTestId('btn').should('have.text', 'toggle false');
154+
cy.findByText('DynamicPageHeader').should('be.visible');
155+
156+
cy.findByTestId('op').scrollTo(0, 500);
157+
cy.findByTestId('btn').click();
158+
cy.findByTestId('op').scrollTo(0, 501);
159+
cy.findByText('DynamicPageHeader').should('not.be.visible');
160+
161+
cy.findByTestId('btn').click();
162+
cy.findByText('DynamicPageHeader').should('be.visible');
163+
cy.get('@onPinSpy').should('have.callCount', 7);
164+
});
165+
166+
it('collapse header when partially visible', () => {
167+
cy.viewport(1440, 1080);
168+
cy.mount(
169+
<DynamicPage
170+
style={{ height: '100vh' }}
171+
headerTitle={<DynamicPageTitle header="Heading" subHeader="SubHeading" />}
172+
headerContent={
173+
<DynamicPageHeader>
174+
<div style={{ height: '400px', width: '100%', background: 'lightyellow' }}>DynamicPageHeader</div>
175+
</DynamicPageHeader>
176+
}
177+
headerContentPinnable
178+
showHideHeaderButton
179+
data-testid="op"
180+
>
181+
<div style={{ height: '2000px' }} />
182+
</DynamicPage>
183+
);
184+
cy.wait(50);
185+
186+
cy.findByTestId('op').scrollTo(0, 400);
187+
cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click();
188+
// wait for timeout of expand click
189+
cy.wait(500);
190+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist');
191+
192+
cy.findByTestId('op').scrollTo(0, 1);
193+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist');
194+
cy.wait(50);
195+
cy.findByTestId('op').scrollTo(0, 0);
196+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('be.visible');
197+
cy.findByText('DynamicPageHeader').should('be.visible');
198+
});
38199
});

packages/main/src/components/DynamicPage/__snapshots__/DynamicPage.test.tsx.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ exports[`DynamicPage always show content header 1`] = `
264264
<ui5-button
265265
accessible-name="Collapse Header"
266266
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonExpandable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
267+
data-component-name="DynamicPageAnchorBarExpandBtn"
267268
data-ui5wcr-dynamic-page-header-action=""
268269
design="Default"
269270
icon="slim-arrow-up"
@@ -273,6 +274,7 @@ exports[`DynamicPage always show content header 1`] = `
273274
<ui5-toggle-button
274275
accessible-name="Unpin Header"
275276
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonPinnable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
277+
data-component-name="DynamicPageAnchorBarPinBtn"
276278
data-ui5wcr-dynamic-page-header-action=""
277279
design="Default"
278280
icon="pushpin-off"
@@ -460,6 +462,7 @@ exports[`DynamicPage hider header button 1`] = `
460462
<ui5-toggle-button
461463
accessible-name="Unpin Header"
462464
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonPinnable"
465+
data-component-name="DynamicPageAnchorBarPinBtn"
463466
data-ui5wcr-dynamic-page-header-action=""
464467
design="Default"
465468
icon="pushpin-off"
@@ -675,6 +678,7 @@ exports[`DynamicPage render footer 1`] = `
675678
<ui5-button
676679
accessible-name="Expand Header"
677680
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonExpandable"
681+
data-component-name="DynamicPageAnchorBarExpandBtn"
678682
data-ui5wcr-dynamic-page-header-action=""
679683
design="Default"
680684
icon="slim-arrow-down"
@@ -974,6 +978,7 @@ exports[`DynamicPage with content 1`] = `
974978
<ui5-button
975979
accessible-name="Collapse Header"
976980
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonExpandable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
981+
data-component-name="DynamicPageAnchorBarExpandBtn"
977982
data-ui5wcr-dynamic-page-header-action=""
978983
design="Default"
979984
icon="slim-arrow-up"
@@ -983,6 +988,7 @@ exports[`DynamicPage with content 1`] = `
983988
<ui5-toggle-button
984989
accessible-name="Pin Header"
985990
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonPinnable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
991+
data-component-name="DynamicPageAnchorBarPinBtn"
986992
data-ui5wcr-dynamic-page-header-action=""
987993
design="Default"
988994
icon="pushpin-off"
@@ -1976,6 +1982,7 @@ exports[`DynamicPage without content 1`] = `
19761982
<ui5-button
19771983
accessible-name="Collapse Header"
19781984
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonExpandable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
1985+
data-component-name="DynamicPageAnchorBarExpandBtn"
19791986
data-ui5wcr-dynamic-page-header-action=""
19801987
design="Default"
19811988
icon="slim-arrow-up"
@@ -1985,6 +1992,7 @@ exports[`DynamicPage without content 1`] = `
19851992
<ui5-toggle-button
19861993
accessible-name="Pin Header"
19871994
class="DynamicPageAnchorBar-anchorBarActionButton DynamicPageAnchorBar-anchorBarActionButtonPinnable DynamicPageAnchorBar-anchorBarActionPinnableAndExpandable"
1995+
data-component-name="DynamicPageAnchorBarPinBtn"
19881996
data-ui5wcr-dynamic-page-header-action=""
19891997
design="Default"
19901998
icon="pushpin-off"

packages/main/src/components/DynamicPage/index.tsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export interface DynamicPagePropTypes extends Omit<CommonProps, 'title'> {
7272
* Fired when the `headerContent` is expanded or collapsed.
7373
*/
7474
onToggleHeaderContent?: (visible: boolean) => void;
75+
/**
76+
* Fired when the `headerContent` changes its `pinned` state.
77+
*/
78+
onPinnedStateChange?: (pinned: boolean) => void;
7579
}
7680

7781
/**
@@ -107,6 +111,7 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
107111
footer,
108112
a11yConfig,
109113
onToggleHeaderContent,
114+
onPinnedStateChange,
110115
...rest
111116
} = props;
112117
const { onScroll: _1, ...propsWithoutOmitted } = rest;
@@ -117,6 +122,7 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
117122

118123
const [componentRefTopHeader, topHeaderRef] = useSyncRef<HTMLDivElement>((headerTitle as any)?.ref);
119124
const [componentRefHeaderContent, headerContentRef] = useSyncRef<HTMLDivElement>((headerContent as any)?.ref);
125+
const scrollTimeout = useRef(0);
120126

121127
const [headerState, setHeaderState] = useState<HEADER_STATES>(
122128
alwaysShowContentHeader ? HEADER_STATES.VISIBLE_PINNED : HEADER_STATES.AUTO
@@ -134,7 +140,8 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
134140
[headerCollapsedInternal, setHeaderCollapsedInternal],
135141
{
136142
noHeader: false,
137-
fixedHeader: headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED
143+
fixedHeader: headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED,
144+
scrollTimeout
138145
}
139146
);
140147

@@ -168,19 +175,21 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
168175
}, []);
169176

170177
useEffect(() => {
178+
const dynamicPage = dynamicPageRef.current;
171179
const oneTimeScrollHandler = () => {
172180
setHeaderState(HEADER_STATES.AUTO);
173181
setHeaderCollapsedInternal(true);
174182
};
175183
if (headerState === HEADER_STATES.VISIBLE || headerState === HEADER_STATES.HIDDEN) {
176-
dynamicPageRef.current?.addEventListener('scroll', oneTimeScrollHandler, { once: true });
184+
dynamicPage?.addEventListener('scroll', oneTimeScrollHandler, { once: true });
177185
}
178186
return () => {
179-
dynamicPageRef.current?.removeEventListener('scroll', oneTimeScrollHandler);
187+
dynamicPage?.removeEventListener('scroll', oneTimeScrollHandler);
180188
};
181189
}, [dynamicPageRef, headerState]);
182190

183191
const onToggleHeaderContentVisibility = (e) => {
192+
scrollTimeout.current = performance.now() + 500;
184193
const shouldHideHeader = !e.detail.visible;
185194
setHeaderState((oldState) => {
186195
if (oldState === HEADER_STATES.VISIBLE_PINNED || oldState === HEADER_STATES.HIDDEN_PINNED) {
@@ -222,10 +231,14 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
222231
};
223232

224233
useEffect(() => {
225-
if (alwaysShowContentHeader) {
226-
setHeaderState(HEADER_STATES.VISIBLE_PINNED);
234+
if (alwaysShowContentHeader !== undefined) {
235+
if (alwaysShowContentHeader) {
236+
setHeaderState(HEADER_STATES.VISIBLE_PINNED);
237+
} else {
238+
setHeaderState(HEADER_STATES.VISIBLE);
239+
}
227240
}
228-
}, [alwaysShowContentHeader, setHeaderState]);
241+
}, [alwaysShowContentHeader]);
229242

230243
const responsivePaddingClass = useResponsiveContentPadding(dynamicPageRef.current);
231244

@@ -297,11 +310,12 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
297310
headerContentPinnable={headerContentPinnable}
298311
showHideHeaderButton={showHideHeaderButton}
299312
headerContentVisible={headerContent && headerCollapsed !== true}
300-
onToggleHeaderContentVisibility={onToggleHeaderContentInternal}
301-
setHeaderPinned={handleHeaderPinnedChange}
302313
headerPinned={headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED}
303-
onHoverToggleButton={onHoverToggleButton}
304314
a11yConfig={a11yConfig}
315+
onHoverToggleButton={onHoverToggleButton}
316+
onToggleHeaderContentVisibility={onToggleHeaderContentInternal}
317+
onPinnedStateChange={onPinnedStateChange}
318+
setHeaderPinned={handleHeaderPinnedChange}
305319
/>
306320
</FlexBox>
307321
<div

0 commit comments

Comments
 (0)