-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Render header background height in sync with content (#1387)
- Loading branch information
Showing
10 changed files
with
261 additions
and
47 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
pages/app-layout/full-page-table-with-variable-header-height.page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import React from 'react'; | ||
import AppLayout from '~components/app-layout'; | ||
import labels from './utils/labels'; | ||
import Table from '~components/table'; | ||
import { generateItems, Instance } from '../table/generate-data'; | ||
import { columnsConfig } from '../table/shared-configs'; | ||
import ExpandableSection from '~components/expandable-section'; | ||
import Header from '~components/header'; | ||
|
||
const items = generateItems(20); | ||
|
||
export default function () { | ||
return ( | ||
<AppLayout | ||
ariaLabels={labels} | ||
contentType="table" | ||
navigationHide={true} | ||
content={ | ||
<Table<Instance> | ||
header={ | ||
<> | ||
<Header variant="awsui-h1-sticky">Header that changes size when scrolling</Header> | ||
<ExpandableSection headerText="Click to expand header area"> | ||
<div style={{ height: '300px' }}>Content</div> | ||
</ExpandableSection> | ||
</> | ||
} | ||
stickyHeader={true} | ||
variant="full-page" | ||
columnDefinitions={columnsConfig} | ||
items={items} | ||
/> | ||
} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import AppLayout, { AppLayoutProps } from '../../../lib/components/app-layout'; | ||
import { useDynamicOverlap } from '../../../lib/components/internal/hooks/use-dynamic-overlap'; | ||
import { useAppLayoutInternals } from '../../../lib/components/app-layout/visual-refresh/context'; | ||
|
||
jest.mock('../../../lib/components/internal/hooks/use-visual-mode', () => ({ | ||
...jest.requireActual('../../../lib/components/internal/hooks/use-visual-mode'), | ||
useVisualRefresh: jest.fn().mockReturnValue(true), | ||
})); | ||
|
||
let positiveHeight = true; | ||
|
||
jest.mock('../../../lib/components/internal/hooks/container-queries/utils', () => ({ | ||
...jest.requireActual('../../../lib/components/internal/hooks/container-queries/utils'), | ||
convertResizeObserverEntry: () => ({ contentBoxHeight: positiveHeight ? 800 : 0 }), | ||
})); | ||
|
||
describe('Background overlap', () => { | ||
function ComponentWithDynamicOverlap() { | ||
const ref = useDynamicOverlap(); | ||
const { hasBackgroundOverlap, isBackgroundOverlapDisabled } = useAppLayoutInternals(); | ||
return ( | ||
<> | ||
<div ref={ref} /> | ||
<div data-testid="has-background-overlap">{hasBackgroundOverlap.toString()}</div> | ||
<div data-testid="is-background-overlap-disabled">{isBackgroundOverlapDisabled.toString()}</div> | ||
</> | ||
); | ||
} | ||
|
||
function ComponentWithoutDynamicOverlap() { | ||
const { hasBackgroundOverlap, isBackgroundOverlapDisabled } = useAppLayoutInternals(); | ||
return ( | ||
<> | ||
<div data-testid="has-background-overlap">{hasBackgroundOverlap.toString()}</div> | ||
<div data-testid="is-background-overlap-disabled">{isBackgroundOverlapDisabled.toString()}</div> | ||
</> | ||
); | ||
} | ||
|
||
function renderApp(appLayoutProps?: AppLayoutProps) { | ||
const { rerender } = render(<AppLayout {...appLayoutProps} />); | ||
return { | ||
hasBackgroundOverlap: () => screen.getByTestId('has-background-overlap').textContent, | ||
isOverlapDisabled: () => screen.getByTestId('is-background-overlap-disabled').textContent, | ||
rerender: (appLayoutProps?: AppLayoutProps) => rerender(<AppLayout {...appLayoutProps} />), | ||
}; | ||
} | ||
|
||
beforeEach(() => { | ||
positiveHeight = true; | ||
}); | ||
|
||
describe('is applied', () => { | ||
test('when a child component sets the height dynamically with a height higher than 0', () => { | ||
const { hasBackgroundOverlap, isOverlapDisabled } = renderApp({ | ||
content: <ComponentWithDynamicOverlap />, | ||
}); | ||
expect(hasBackgroundOverlap()).toBe('true'); | ||
expect(isOverlapDisabled()).toBe('false'); | ||
}); | ||
|
||
test('when content header is present', () => { | ||
const { hasBackgroundOverlap, isOverlapDisabled } = renderApp({ | ||
content: <ComponentWithoutDynamicOverlap />, | ||
contentHeader: 'Content header', | ||
}); | ||
expect(hasBackgroundOverlap()).toBe('true'); | ||
expect(isOverlapDisabled()).toBe('false'); | ||
}); | ||
}); | ||
|
||
describe('is not applied', () => { | ||
test('when no content header is present and height is 0', () => { | ||
positiveHeight = false; | ||
const { hasBackgroundOverlap, isOverlapDisabled } = renderApp({ | ||
content: <ComponentWithDynamicOverlap />, | ||
}); | ||
expect(hasBackgroundOverlap()).toBe('false'); | ||
expect(isOverlapDisabled()).toBe('true'); | ||
}); | ||
|
||
test('when no content header is present and no child component sets the height dynamically', () => { | ||
const { hasBackgroundOverlap, isOverlapDisabled } = renderApp({ | ||
content: <ComponentWithoutDynamicOverlap />, | ||
}); | ||
expect(hasBackgroundOverlap()).toBe('false'); | ||
expect(isOverlapDisabled()).toBe('true'); | ||
}); | ||
}); | ||
|
||
test('is disabled when explicitly specified in the app layout props', () => { | ||
const { isOverlapDisabled } = renderApp({ | ||
content: <ComponentWithDynamicOverlap />, | ||
disableContentHeaderOverlap: true, | ||
}); | ||
expect(isOverlapDisabled()).toBe('true'); | ||
}); | ||
|
||
test('is updated accordingly when re-rendering', () => { | ||
const { hasBackgroundOverlap, isOverlapDisabled, rerender } = renderApp({ | ||
content: <ComponentWithDynamicOverlap />, | ||
}); | ||
expect(hasBackgroundOverlap()).toBe('true'); | ||
expect(isOverlapDisabled()).toBe('false'); | ||
rerender({ content: <ComponentWithoutDynamicOverlap /> }); | ||
expect(hasBackgroundOverlap()).toBe('false'); | ||
expect(isOverlapDisabled()).toBe('true'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import React, { useCallback, useState } from 'react'; | ||
import customCssProps from '../../internal/generated/custom-css-properties'; | ||
|
||
/** | ||
* The overlap height has a default set in CSS but can also be dynamically overridden | ||
* for content types (such as Table and Wizard) that have variable size content in the overlap. | ||
* If a child component utilizes a sticky header the hasStickyBackground property will determine | ||
* if the background remains in the same vertical position. | ||
*/ | ||
export default function useBackgroundOverlap({ | ||
contentHeader, | ||
disableContentHeaderOverlap, | ||
layoutElement, | ||
}: { | ||
contentHeader: React.ReactNode; | ||
disableContentHeaderOverlap?: boolean; | ||
layoutElement: React.Ref<HTMLElement>; | ||
}) { | ||
const hasContentHeader = !!contentHeader; | ||
|
||
const [hasBackgroundOverlap, setHasBackgroundOverlap] = useState(hasContentHeader); | ||
|
||
const updateBackgroundOverlapHeight = useCallback( | ||
(height: number) => { | ||
const hasOverlap = hasContentHeader || height > 0; | ||
setHasBackgroundOverlap(hasOverlap); | ||
|
||
/** | ||
* React 18 will trigger a paint before the state is correctly updated | ||
* (see https://github.com/facebook/react/issues/24331). | ||
* To work around this, we bypass React state updates and imperatively update the custom property on the DOM. | ||
* An alternative would be to use `queueMicrotask` and `flushSync` in the ResizeObserver callback, | ||
* but that would have some performance impact as it would delay the render. | ||
*/ | ||
// Layout component uses RefObject, we don't expect a RefCallback | ||
const element = typeof layoutElement !== 'function' && layoutElement?.current; | ||
if (!element) { | ||
return; | ||
} | ||
if (disableContentHeaderOverlap || !hasOverlap || height <= 0) { | ||
element.style.removeProperty(customCssProps.overlapHeight); | ||
} else { | ||
element.style.setProperty(customCssProps.overlapHeight, `${height}px`); | ||
} | ||
}, | ||
[hasContentHeader, layoutElement, disableContentHeaderOverlap] | ||
); | ||
|
||
return { | ||
hasBackgroundOverlap, | ||
updateBackgroundOverlapHeight, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.