Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Banner] Measure contentNode on change rather than window.onResize #11974

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/weak-crabs-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': patch
---

Fixed `Banner` not accurately detecting content changes
54 changes: 54 additions & 0 deletions polaris-react/src/components/Banner/Banner.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,60 @@ export const CustomIcon = {
},
};

export const WithDynamicContent = {
render() {
const [contentExpanded, setContentExpanded] = useState(false);

const toggleContent = () => setContentExpanded(!contentExpanded);

return (
<Banner tone="info">
<Text as="p" variant="bodyMd">
Here is the first line of content.{' '}
<Button variant="plain" onClick={toggleContent}>
{!contentExpanded
? 'Expand for more content.'
: 'Collapse content.'}
</Button>
</Text>
{contentExpanded ? (
<>
<Text as="p" variant="bodyMd">
Here is more content.
</Text>
<Text as="p" variant="bodyMd">
And here is even more.
</Text>
</>
) : null}
</Banner>
);
},
};

export const WithHiddenIcon = {
render() {
return (
<AllBanners
title={undefined}
hideIcon
onDismiss={() => {}}
children={
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</>
}
/>
);
},
};

export const All = {
render() {
return (
Expand Down
33 changes: 18 additions & 15 deletions polaris-react/src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, {
forwardRef,
useContext,
useEffect,
useRef,
useState,
useEffect,
useCallback,
} from 'react';
import type {PropsWithChildren} from 'react';
import type {ColorTextAlias} from '@shopify/polaris-tokens';
Expand All @@ -25,7 +24,6 @@ import {WithinContentContext} from '../../utilities/within-content-context';
import {classNames} from '../../utilities/css';
import {useBreakpoints} from '../../utilities/breakpoints';
import {useI18n} from '../../utilities/i18n';
import {useEventListener} from '../../utilities/use-event-listener';
import {BlockStack} from '../BlockStack';

import styles from './Banner.module.css';
Expand Down Expand Up @@ -248,24 +246,29 @@ export function InlineIconBanner({
}: PropsWithChildren<Omit<BannerLayoutProps, 'textColor' | 'bannerTitle'>>) {
const [blockAlign, setBlockAlign] =
useState<InlineStackProps['blockAlign']>('center');
const contentNode = useRef<HTMLDivElement>(null);
const iconNode = useRef<HTMLDivElement>(null);
const dismissIconNode = useRef<HTMLDivElement>(null);
const contentNode = useRef<HTMLDivElement>(null);

const handleResize = useCallback(() => {
const contentHeight = contentNode.current?.offsetHeight;
const iconBoxHeight =
iconNode.current?.offsetHeight || dismissIconNode.current?.offsetHeight;
useEffect(() => {
const updateLayout = () => {
const contentHeight = contentNode.current?.offsetHeight ?? 0;
const iconBoxHeight =
iconNode.current?.offsetHeight || dismissIconNode.current?.offsetHeight;

if (!contentHeight || !iconBoxHeight) return;
if (contentHeight && iconBoxHeight) {
setBlockAlign(contentHeight > iconBoxHeight ? 'start' : 'center');
}
};

contentHeight > iconBoxHeight
? setBlockAlign('start')
: setBlockAlign('center');
}, []);
const observer = new ResizeObserver(updateLayout);

if (iconNode.current) observer.observe(iconNode.current);
if (dismissIconNode.current) observer.observe(dismissIconNode.current);
if (contentNode.current) observer.observe(contentNode.current);

useEffect(() => handleResize(), [handleResize]);
useEventListener('resize', handleResize);
return () => observer.disconnect();
}, []);

return (
<Box width="100%" padding="300" borderRadius="300">
Expand Down
59 changes: 59 additions & 0 deletions polaris-react/tests/setup/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
export {};

interface ResizeObserverMockType {
callback: ResizeObserverCallback;
observations: Element[];
observe: (target: Element) => void;
unobserve: (target: Element) => void;
disconnect: () => void;
}

class ResizeObserverMock implements ResizeObserverMockType {
callback: ResizeObserverCallback;
observations: Element[];

constructor(callback: ResizeObserverCallback) {
this.callback = callback;
this.observations = [];
}

observe(target: Element): void {
this.observations.push(target);
this.callback(
[
{
target,
contentRect: target.getBoundingClientRect(),
borderBoxSize: [
{
blockSize: target.clientHeight,
inlineSize: target.clientWidth,
},
],
contentBoxSize: [
{
blockSize: target.clientHeight,
inlineSize: target.clientWidth,
},
],
devicePixelContentBoxSize: [
{
blockSize: target.clientHeight,
inlineSize: target.clientWidth,
},
],
},
],
this,
);
}

unobserve(target: Element): void {
this.observations = this.observations.filter((obs) => obs !== target);
}

disconnect(): void {
this.observations = [];
}
}

if (typeof window !== 'undefined') {
// Mocks for scrolling
window.scroll = () => {};
Expand All @@ -10,6 +67,8 @@ if (typeof window !== 'undefined') {
_features?: string,
_replace?: boolean,
) => null;

window.ResizeObserver = ResizeObserverMock;
}

const IGNORE_ERROR_REGEXES = [
Expand Down
Loading