diff --git a/.changeset/strange-beans-grow.md b/.changeset/strange-beans-grow.md
new file mode 100644
index 00000000000..032e8ed5752
--- /dev/null
+++ b/.changeset/strange-beans-grow.md
@@ -0,0 +1,5 @@
+---
+'@shopify/polaris': minor
+---
+
+Added optional `captureOverscroll` prop to `Popover`
diff --git a/polaris-react/src/components/Popover/Popover.scss b/polaris-react/src/components/Popover/Popover.scss
index 1bae452adf8..399e73a8cc1 100644
--- a/polaris-react/src/components/Popover/Popover.scss
+++ b/polaris-react/src/components/Popover/Popover.scss
@@ -106,6 +106,10 @@ $vertical-motion-offset: -5px;
flex: 0 0 auto;
}
+.Pane-captureOverscroll {
+ overscroll-behavior: contain;
+}
+
.Section {
padding: var(--p-space-4);
diff --git a/polaris-react/src/components/Popover/Popover.tsx b/polaris-react/src/components/Popover/Popover.tsx
index be376dc87f1..dfe7e7efc9c 100644
--- a/polaris-react/src/components/Popover/Popover.tsx
+++ b/polaris-react/src/components/Popover/Popover.tsx
@@ -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 {
diff --git a/polaris-react/src/components/Popover/components/Pane/Pane.tsx b/polaris-react/src/components/Popover/components/Pane/Pane.tsx
index 491ae742e2a..2ee7d084097 100644
--- a/polaris-react/src/components/Popover/components/Pane/Pane.tsx
+++ b/polaris-react/src/components/Popover/components/Pane/Pane.tsx
@@ -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;
diff --git a/polaris-react/src/components/Popover/components/Pane/tests/Pane.test.tsx b/polaris-react/src/components/Popover/components/Pane/tests/Pane.test.tsx
index 8fbde51822f..83ebd471195 100644
--- a/polaris-react/src/components/Popover/components/Pane/tests/Pane.test.tsx
+++ b/polaris-react/src/components/Popover/components/Pane/tests/Pane.test.tsx
@@ -153,4 +153,54 @@ describe('', () => {
});
});
});
+
+ describe('captureOverscroll', () => {
+ const Children = () => (
+
+ Text
+
+ );
+
+ describe('when not passed', () => {
+ it('does not apply the Pane-captureOverscroll class', () => {
+ const popoverPane = mountWithApp(
+
+
+ ,
+ );
+
+ expect(popoverPane).toContainReactComponent(Scrollable, {
+ className: 'Pane',
+ });
+ });
+ });
+
+ describe('when passed as true', () => {
+ it('applies the Pane-captureOverscroll class', () => {
+ const popoverPane = mountWithApp(
+
+
+ ,
+ );
+
+ expect(popoverPane).toContainReactComponent(Scrollable, {
+ className: 'Pane Pane-captureOverscroll',
+ });
+ });
+ });
+
+ describe('when passed as false', () => {
+ it('does not apply the Pane-captureOverscroll class', () => {
+ const popoverPane = mountWithApp(
+
+
+ ,
+ );
+
+ expect(popoverPane).toContainReactComponent(Scrollable, {
+ className: 'Pane',
+ });
+ });
+ });
+ });
});
diff --git a/polaris-react/src/components/Popover/components/PopoverOverlay/PopoverOverlay.tsx b/polaris-react/src/components/Popover/components/PopoverOverlay/PopoverOverlay.tsx
index 862a076fc91..5fd3fb9a1f6 100644
--- a/polaris-react/src/components/Popover/components/PopoverOverlay/PopoverOverlay.tsx
+++ b/polaris-react/src/components/Popover/components/PopoverOverlay/PopoverOverlay.tsx
@@ -57,6 +57,7 @@ export interface PopoverOverlayProps {
onClose(source: PopoverCloseSource): void;
autofocusTarget?: PopoverAutofocusTarget;
preventCloseOnChildOverlayClick?: boolean;
+ captureOverscroll?: boolean;
}
interface State {
@@ -222,6 +223,7 @@ export class PopoverOverlay extends PureComponent {
fluidContent,
hideOnPrint,
autofocusTarget,
+ captureOverscroll,
} = this.props;
const className = classNames(
@@ -248,7 +250,7 @@ export class PopoverOverlay extends PureComponent {
style={contentStyles}
ref={this.contentNode}
>
- {renderPopoverContent(children, {sectioned})}
+ {renderPopoverContent(children, {captureOverscroll, sectioned})}
);
diff --git a/polaris-react/src/components/Popover/tests/Popover.test.tsx b/polaris-react/src/components/Popover/tests/Popover.test.tsx
index 56be705a1cb..46e9ae3ee34 100644
--- a/polaris-react/src/components/Popover/tests/Popover.test.tsx
+++ b/polaris-react/src/components/Popover/tests/Popover.test.tsx
@@ -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('', () => {
const spy = jest.fn();
@@ -368,6 +369,64 @@ describe('', () => {
});
});
});
+
+ describe('captureOverscroll', () => {
+ const TestActivator = ;
+
+ const Children = () => (
+
+ Text
+
+ );
+
+ 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(
+
+
+ ,
+ );
+
+ expect(popover).toContainReactComponent(Pane, {
+ captureOverscroll: undefined,
+ });
+ });
+ });
+
+ describe('when passed as true', () => {
+ it('passes the prop as true to the Pane component', () => {
+ const popover = mountWithApp(
+
+
+ ,
+ );
+
+ expect(popover).toContainReactComponent(Pane, {
+ captureOverscroll: true,
+ });
+ });
+ });
+
+ describe('when passed as false', () => {
+ it('passes the prop as false to the Pane component', () => {
+ const popover = mountWithApp(
+
+
+ ,
+ );
+
+ expect(popover).toContainReactComponent(Pane, {
+ captureOverscroll: false,
+ });
+ });
+ });
+ });
});
function noop() {}