diff --git a/UNRELEASED.md b/UNRELEASED.md index b30ecb4d5ec..422d9909b3f 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -21,6 +21,7 @@ - Fixed an issue with the `Filters` component where the `aria-expanded` attribute was `undefined` on mount ([#2589]https://github.com/Shopify/polaris-react/pull/2589) - Fixed `TrapFocus` from tabbing out of the container ([#2555](https://github.com/Shopify/polaris-react/pull/2555)) +- Fixed `PositionedOverlay` not correctly getting its position when aligned to the right of the activator ([#2587](https://github.com/Shopify/polaris-react/pull/2587)) ### Documentation diff --git a/src/components/Popover/Popover.scss b/src/components/Popover/Popover.scss index 83a8767f0b3..caaa62e2daf 100644 --- a/src/components/Popover/Popover.scss +++ b/src/components/Popover/Popover.scss @@ -52,7 +52,7 @@ $content-max-width: rem(400px); } .positionedAbove { - margin: spacing() 0 $visible-portion-of-arrow spacing(tight); + margin: spacing() spacing(tight) $visible-portion-of-arrow; &.fullWidth { margin: 0 0 $visible-portion-of-arrow; diff --git a/src/components/PositionedOverlay/PositionedOverlay.tsx b/src/components/PositionedOverlay/PositionedOverlay.tsx index ca250d7c79b..e88138c43dd 100644 --- a/src/components/PositionedOverlay/PositionedOverlay.tsx +++ b/src/components/PositionedOverlay/PositionedOverlay.tsx @@ -22,7 +22,8 @@ export {PreferredPosition, PreferredAlignment}; export type Positioning = 'above' | 'below'; export interface OverlayDetails { - left: number; + left?: number; + right?: number; desiredHeight: number; positioning: Positioning; measuring: boolean; @@ -44,7 +45,8 @@ export interface PositionedOverlayProps { interface State { measuring: boolean; activatorRect: Rect; - left: number; + left?: number; + right?: number; top: number; height: number; width: number | null; @@ -63,7 +65,8 @@ export class PositionedOverlay extends React.PureComponent< state: State = { measuring: true, activatorRect: getRectForNode(this.props.activator), - left: 0, + right: undefined, + left: undefined, top: 0, height: 0, width: null, @@ -118,12 +121,13 @@ export class PositionedOverlay extends React.PureComponent< } render() { - const {left, top, zIndex, width} = this.state; + const {left, right, top, zIndex, width} = this.state; const {render, fixed, classNames: propClassNames} = this.props; const style = { top: top == null || isNaN(top) ? undefined : top, left: left == null || isNaN(left) ? undefined : left, + right: right == null || isNaN(right) ? undefined : right, width: width == null || isNaN(width) ? undefined : width, zIndex: zIndex == null || isNaN(zIndex) ? undefined : zIndex, }; @@ -143,11 +147,19 @@ export class PositionedOverlay extends React.PureComponent< } private overlayDetails = (): OverlayDetails => { - const {measuring, left, positioning, height, activatorRect} = this.state; + const { + measuring, + left, + right, + positioning, + height, + activatorRect, + } = this.state; return { measuring, left, + right, desiredHeight: height, positioning, activatorRect, @@ -164,8 +176,9 @@ export class PositionedOverlay extends React.PureComponent< this.observer.disconnect(); this.setState( - ({left, top}) => ({ + ({left, top, right}) => ({ left, + right, top, height: 0, positioning: 'below', @@ -209,6 +222,7 @@ export class PositionedOverlay extends React.PureComponent< const overlayMargins = this.overlay.firstElementChild ? getMarginsForNode(this.overlay.firstElementChild as HTMLElement) : {activator: 0, container: 0, horizontal: 0}; + const containerRect = windowRect(); const zIndexForLayer = getZIndexForLayerFromNode(activator); const zIndex = @@ -234,7 +248,10 @@ export class PositionedOverlay extends React.PureComponent< { measuring: false, activatorRect: getRectForNode(activator), - left: horizontalPosition, + left: + preferredAlignment !== 'right' ? horizontalPosition : undefined, + right: + preferredAlignment === 'right' ? horizontalPosition : undefined, top: lockPosition ? top : verticalPosition.top, lockPosition: Boolean(fixed), height: verticalPosition.height || 0, @@ -278,9 +295,9 @@ export function intersectionWithViewport( function getMarginsForNode(node: HTMLElement) { const nodeStyles = window.getComputedStyle(node); return { - activator: parseFloat(nodeStyles.marginTop || ''), - container: parseFloat(nodeStyles.marginBottom || ''), - horizontal: parseFloat(nodeStyles.marginLeft || ''), + activator: parseFloat(nodeStyles.marginTop || '0'), + container: parseFloat(nodeStyles.marginBottom || '0'), + horizontal: parseFloat(nodeStyles.marginLeft || '0'), }; } @@ -298,7 +315,7 @@ function windowRect() { top: window.scrollY, left: window.scrollX, height: window.innerHeight, - width: window.innerWidth, + width: document.body.clientWidth, }); } diff --git a/src/components/PositionedOverlay/tests/PositionedOverlay.test.tsx b/src/components/PositionedOverlay/tests/PositionedOverlay.test.tsx index 679754e2777..bc8eb803e10 100644 --- a/src/components/PositionedOverlay/tests/PositionedOverlay.test.tsx +++ b/src/components/PositionedOverlay/tests/PositionedOverlay.test.tsx @@ -70,6 +70,20 @@ describe('', () => { , ); + expect((positionedOverlay.find('div').prop('style') as any).left).toBe(0); + expect( + (positionedOverlay.find('div').prop('style') as any).right, + ).toBeUndefined(); + }); + + it('aligns right if preferredAlignment is given', () => { + const positionedOverlay = mountWithAppProvider( + , + ); + + expect((positionedOverlay.find('div').prop('style') as any).right).toBe( + 0, + ); expect( (positionedOverlay.find('div').prop('style') as any).left, ).toBeUndefined(); diff --git a/src/components/PositionedOverlay/utilities/math.ts b/src/components/PositionedOverlay/utilities/math.ts index 61c3be592fb..90a5132928b 100644 --- a/src/components/PositionedOverlay/utilities/math.ts +++ b/src/components/PositionedOverlay/utilities/math.ts @@ -98,13 +98,12 @@ export function calculateHorizontalPosition( Math.max(0, activatorRect.left - overlayMargins.horizontal), ); } else if (preferredAlignment === 'right') { - const activatorRight = activatorRect.left + activatorRect.width; + const activatorRight = + containerRect.width - (activatorRect.left + activatorRect.width); + return Math.min( maximum, - Math.max( - 0, - activatorRight - overlayRect.width + overlayMargins.horizontal, - ), + Math.max(0, activatorRight - overlayMargins.horizontal), ); } diff --git a/src/components/Scrollable/Scrollable.scss b/src/components/Scrollable/Scrollable.scss index f4435de5f58..81c76b9e3ed 100644 --- a/src/components/Scrollable/Scrollable.scss +++ b/src/components/Scrollable/Scrollable.scss @@ -20,6 +20,10 @@ $shadow-top: inset 0 $shadow-size $shadow-size (-1 * $shadow-size) overflow-y: auto; } +.verticalHasScrolling { + overflow-y: scroll; +} + .hasTopShadow { box-shadow: $shadow-top; } diff --git a/src/components/Scrollable/Scrollable.tsx b/src/components/Scrollable/Scrollable.tsx index 5273928a1c7..ee91cd46939 100644 --- a/src/components/Scrollable/Scrollable.tsx +++ b/src/components/Scrollable/Scrollable.tsx @@ -42,6 +42,7 @@ interface State { topShadow: boolean; bottomShadow: boolean; scrollPosition: number; + canScroll: boolean; } export class Scrollable extends React.Component { @@ -56,6 +57,7 @@ export class Scrollable extends React.Component { topShadow: false, bottomShadow: false, scrollPosition: 0, + canScroll: false, }; private stickyManager = new StickyManager(); @@ -104,7 +106,7 @@ export class Scrollable extends React.Component { } render() { - const {topShadow, bottomShadow} = this.state; + const {topShadow, bottomShadow, canScroll} = this.state; const { children, className, @@ -123,6 +125,7 @@ export class Scrollable extends React.Component { horizontal && styles.horizontal, topShadow && styles.hasTopShadow, bottomShadow && styles.hasBottomShadow, + vertical && canScroll && styles.verticalHasScrolling, ); return ( @@ -168,6 +171,7 @@ export class Scrollable extends React.Component { topShadow: shouldTopShadow, bottomShadow: shouldBottomShadow, scrollPosition: scrollTop, + canScroll, }); }; diff --git a/src/components/TopBar/components/Menu/Menu.tsx b/src/components/TopBar/components/Menu/Menu.tsx index 0a36adc697b..4f3c0edc128 100644 --- a/src/components/TopBar/components/Menu/Menu.tsx +++ b/src/components/TopBar/components/Menu/Menu.tsx @@ -57,6 +57,7 @@ export function Menu(props: MenuProps) { onClose={onClose} fixed fullHeight={isFullHeight} + preferredAlignment="right" > {messageMarkup}