Skip to content
Merged
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/strange-beans-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': minor
---

Added optional `captureOverscroll` prop to `Popover`
4 changes: 4 additions & 0 deletions polaris-react/src/components/Popover/Popover.scss
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ $vertical-motion-offset: -5px;
flex: 0 0 auto;
}

.Pane-captureOverscroll {
overscroll-behavior: contain;
}

.Section {
padding: var(--p-space-4);

Expand Down
5 changes: 5 additions & 0 deletions polaris-react/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export interface PopoverProps {
autofocusTarget?: PopoverAutofocusTarget;
/** Prevents closing the popover when other overlays are clicked */
preventCloseOnChildOverlayClick?: boolean;
/**
* Prevents page scrolling when the end of the scrollable Popover overlay content is reached - applied to Pane subcomponent
* @default false
*/
captureOverscroll?: boolean;
}

export interface PopoverPublicAPI {
Expand Down
12 changes: 11 additions & 1 deletion polaris-react/src/components/Popover/components/Pane/Pane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,26 @@ export interface PaneProps {
height?: string;
/** Callback when the bottom of the popover is reached by mouse or keyboard */
onScrolledToBottom?(): void;
/**
* Prevents page scrolling when the end of the scrollable Popover content is reached
* @default false
*/
captureOverscroll?: boolean;
}

export function Pane({
captureOverscroll = false,
fixed,
sectioned,
children,
height,
onScrolledToBottom,
}: PaneProps) {
const className = classNames(styles.Pane, fixed && styles['Pane-fixed']);
const className = classNames(
styles.Pane,
fixed && styles['Pane-fixed'],
captureOverscroll && styles['Pane-captureOverscroll'],
);
const content = sectioned
? wrapWithComponent(children, Section, {})
: children;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,54 @@ describe('<Pane />', () => {
});
});
});

describe('captureOverscroll', () => {
const Children = () => (
<TextContainer>
<p>Text</p>
</TextContainer>
);

describe('when not passed', () => {
it('does not apply the Pane-captureOverscroll class', () => {
const popoverPane = mountWithApp(
<Pane>
<Children />
</Pane>,
);

expect(popoverPane).toContainReactComponent(Scrollable, {
className: 'Pane',
});
});
});

describe('when passed as true', () => {
it('applies the Pane-captureOverscroll class', () => {
const popoverPane = mountWithApp(
<Pane captureOverscroll>
<Children />
</Pane>,
);

expect(popoverPane).toContainReactComponent(Scrollable, {
className: 'Pane Pane-captureOverscroll',
});
});
});

describe('when passed as false', () => {
it('does not apply the Pane-captureOverscroll class', () => {
const popoverPane = mountWithApp(
<Pane captureOverscroll={false}>
<Children />
</Pane>,
);

expect(popoverPane).toContainReactComponent(Scrollable, {
className: 'Pane',
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface PopoverOverlayProps {
onClose(source: PopoverCloseSource): void;
autofocusTarget?: PopoverAutofocusTarget;
preventCloseOnChildOverlayClick?: boolean;
captureOverscroll?: boolean;
}

interface State {
Expand Down Expand Up @@ -222,6 +223,7 @@ export class PopoverOverlay extends PureComponent<PopoverOverlayProps, State> {
fluidContent,
hideOnPrint,
autofocusTarget,
captureOverscroll,
} = this.props;

const className = classNames(
Expand All @@ -248,7 +250,7 @@ export class PopoverOverlay extends PureComponent<PopoverOverlayProps, State> {
style={contentStyles}
ref={this.contentNode}
>
{renderPopoverContent(children, {sectioned})}
{renderPopoverContent(children, {captureOverscroll, sectioned})}
</div>
);

Expand Down
61 changes: 60 additions & 1 deletion polaris-react/src/components/Popover/tests/Popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {Portal} from '../../Portal';
import {PositionedOverlay} from '../../PositionedOverlay';
import {Popover} from '../Popover';
import type {PopoverPublicAPI} from '../Popover';
import {PopoverOverlay} from '../components';
import {Pane, PopoverOverlay} from '../components';
import * as setActivatorAttributes from '../set-activator-attributes';
import {TextContainer} from '../../TextContainer';

describe('<Popover />', () => {
const spy = jest.fn();
Expand Down Expand Up @@ -368,6 +369,64 @@ describe('<Popover />', () => {
});
});
});

describe('captureOverscroll', () => {
const TestActivator = <button>Activator</button>;

const Children = () => (
<TextContainer>
<p>Text</p>
</TextContainer>
);

const defaultProps = {
active: true,
activator: TestActivator,
onClose: jest.fn(),
};

describe('when not passed', () => {
it('does not pass the prop as true to the Pane component', () => {
const popover = mountWithApp(
<Popover {...defaultProps}>
<Children />
</Popover>,
);

expect(popover).toContainReactComponent(Pane, {
captureOverscroll: undefined,
});
});
});

describe('when passed as true', () => {
it('passes the prop as true to the Pane component', () => {
const popover = mountWithApp(
<Popover {...defaultProps} captureOverscroll>
<Children />
</Popover>,
);

expect(popover).toContainReactComponent(Pane, {
captureOverscroll: true,
});
});
});

describe('when passed as false', () => {
it('passes the prop as false to the Pane component', () => {
const popover = mountWithApp(
<Popover {...defaultProps} captureOverscroll={false}>
<Children />
</Popover>,
);

expect(popover).toContainReactComponent(Pane, {
captureOverscroll: false,
});
});
});
});
});

function noop() {}