Skip to content

Commit cf24cd5

Browse files
authored
fix(DynamicPage): smooth header collapse (#5274)
Fixes #5269
1 parent 069313e commit cf24cd5

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

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

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ describe('DynamicPage', () => {
109109
});
110110

111111
it('programmatically pin header (`alwaysShowContentHeader`)', () => {
112+
document.body.style.margin = '0px';
112113
const TestComp = ({ onPinnedStateChange }: DynamicPagePropTypes) => {
113114
const [pinned, setPinned] = useState(false);
114115
const handlePinChange = (pinned) => {
@@ -117,16 +118,17 @@ describe('DynamicPage', () => {
117118
};
118119
return (
119120
<>
120-
<Button
121+
<button
122+
style={{ height: '40px' }}
121123
data-testid="btn"
122124
onClick={() => {
123125
setPinned((prev) => !prev);
124126
}}
125127
>
126128
toggle {`${!pinned}`}
127-
</Button>
129+
</button>
128130
<DynamicPage
129-
style={{ height: '100vh' }}
131+
style={{ height: 'calc(100vh - 40px)' }}
130132
headerTitle={<DynamicPageTitle header="Heading" subHeader="SubHeading" />}
131133
headerContent={<DynamicPageHeader>DynamicPageHeader</DynamicPageHeader>}
132134
headerContentPinnable
@@ -152,6 +154,7 @@ describe('DynamicPage', () => {
152154
cy.get('@onPinSpy').should('have.been.calledWith', true);
153155
cy.findByText('DynamicPageHeader').should('be.visible');
154156

157+
cy.wait(100);
155158
cy.findByTestId('op').scrollTo(0, 0);
156159
cy.findByText('DynamicPageHeader').should('be.visible');
157160

@@ -163,6 +166,7 @@ describe('DynamicPage', () => {
163166
cy.get('@onPinSpy').should('have.been.calledWith', false);
164167
cy.findByText('DynamicPageHeader').should('be.visible');
165168

169+
cy.wait(100);
166170
cy.findByTestId('op').scrollTo(0, 801);
167171
cy.findByText('DynamicPageHeader').should('not.be.visible');
168172

@@ -171,6 +175,7 @@ describe('DynamicPage', () => {
171175
cy.findByText('DynamicPageHeader').should('be.visible');
172176

173177
cy.findByTestId('btn').click();
178+
cy.wait(200);
174179
cy.findByTestId('op').scrollTo(0, 501);
175180
cy.findByText('DynamicPageHeader').should('not.be.visible');
176181

@@ -201,36 +206,54 @@ describe('DynamicPage', () => {
201206
cy.get('@onPinSpy').should('have.callCount', 7);
202207
});
203208

204-
it('collapse header when partially visible', () => {
209+
it('collapse header when not visible', () => {
210+
document.body.style.margin = '0px';
205211
cy.viewport(1440, 1080);
206212
cy.mount(
207213
<DynamicPage
208214
style={{ height: '100vh' }}
209215
headerTitle={<DynamicPageTitle header="Heading" subHeader="SubHeading" />}
210216
headerContent={
211-
<DynamicPageHeader>
212-
<div style={{ height: '400px', width: '100%', background: 'lightyellow' }}>DynamicPageHeader</div>
217+
<DynamicPageHeader style={{ height: '400px' }}>
218+
<div style={{ width: '100%', background: 'lightyellow' }}>DynamicPageHeader</div>
213219
</DynamicPageHeader>
214220
}
215221
headerContentPinnable
216222
showHideHeaderButton
217223
data-testid="op"
218224
>
219-
<div style={{ height: '2000px' }} />
225+
<div style={{ height: '2000px', background: 'lightblue' }}>
226+
<span>Content</span>
227+
</div>
220228
</DynamicPage>
221229
);
222230
cy.wait(50);
223231

224232
cy.findByTestId('op').scrollTo(0, 400);
233+
cy.findByText('Content').should('be.visible');
234+
// header content height + padding
235+
cy.findByTestId('op').scrollTo(0, 432);
236+
cy.findByText('Content').should('be.visible');
237+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist');
238+
225239
cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click();
226240
// wait for timeout of expand click
227-
cy.wait(500);
228-
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist');
241+
cy.wait(200);
242+
cy.findByText('DynamicPageHeader').should('be.visible');
243+
// expanded header is covering the Content, not detectable by Cypress
244+
// cy.findByText('Content').should('not.be.visible');
245+
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('exist');
229246

230-
cy.findByTestId('op').scrollTo(0, 1);
247+
// collapse when scrolling
248+
cy.findByTestId('op').scrollTo(0, 440);
249+
cy.wait(200);
231250
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist');
232-
cy.wait(50);
251+
cy.findByText('DynamicPageHeader').should('not.be.visible');
252+
253+
cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click();
254+
cy.wait(200);
233255
cy.findByTestId('op').scrollTo(0, 0);
256+
cy.wait(200);
234257
cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('be.visible');
235258
cy.findByText('DynamicPageHeader').should('be.visible');
236259
});

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,31 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
175175

176176
useEffect(() => {
177177
const dynamicPage = dynamicPageRef.current;
178-
const oneTimeScrollHandler = () => {
178+
const oneTimeScrollHandler = (e) => {
179179
setHeaderState(HEADER_STATES.AUTO);
180-
setHeaderCollapsedInternal(true);
180+
// only collapse the header after it was programmatically expanded, if the header shouldn't be visible
181+
if (
182+
e.target.scrollTop >
183+
(topHeaderRef?.current.offsetHeight ?? 0) +
184+
Math.max(0, headerContentRef.current.offsetHeight ?? 0 - topHeaderRef?.current.offsetHeight ?? 0)
185+
) {
186+
setHeaderCollapsedInternal(true);
187+
}
181188
};
182189
if (headerState === HEADER_STATES.VISIBLE || headerState === HEADER_STATES.HIDDEN) {
183-
dynamicPage?.addEventListener('scroll', oneTimeScrollHandler, { once: true });
190+
// only reset state after scroll if scroll isn't invoked by expanding the header
191+
const timeout = scrollTimeout.current - performance.now();
192+
if (timeout > 0) {
193+
setTimeout(() => {
194+
dynamicPage?.addEventListener('scroll', oneTimeScrollHandler, {
195+
once: true
196+
});
197+
}, timeout + 50);
198+
} else {
199+
dynamicPage?.addEventListener('scroll', oneTimeScrollHandler, {
200+
once: true
201+
});
202+
}
184203
}
185204
return () => {
186205
dynamicPage?.removeEventListener('scroll', oneTimeScrollHandler);
@@ -290,7 +309,10 @@ const DynamicPage = forwardRef<HTMLDivElement, DynamicPagePropTypes>((props, ref
290309
{headerContent &&
291310
cloneElement(headerContent, {
292311
ref: componentRefHeaderContent,
293-
style: headerCollapsed === true ? { position: 'absolute', visibility: 'hidden' } : headerContent.props.style,
312+
style:
313+
headerCollapsed === true
314+
? { ...headerContent.props.style, position: 'relative', visibility: 'hidden' }
315+
: headerContent.props.style,
294316
className: clsx(classes.header, headerContent?.props?.className),
295317
headerPinned: headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.VISIBLE,
296318
topHeaderHeight

0 commit comments

Comments
 (0)