From be000773cceda7ff9f09a8566d687425d7dce52c Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 7 Jun 2018 12:53:38 +1000 Subject: [PATCH] Add yAxis=middle support to Popover (#7038) * Add yAxis=middle support to Popover Add yAxis=middle support to the Popover component so that we can use it to implement NUX tips. * Update popover README --- components/popover/README.md | 10 ++++- components/popover/index.js | 3 +- components/popover/style.scss | 64 +++++++++++++++++++++++++++++--- components/popover/test/utils.js | 22 +++++++++++ components/popover/utils.js | 21 ++++++++++- 5 files changed, 111 insertions(+), 9 deletions(-) diff --git a/components/popover/README.md b/components/popover/README.md index 158c36f5eb7ed..5b00b95c2614e 100644 --- a/components/popover/README.md +++ b/components/popover/README.md @@ -61,7 +61,7 @@ By default, the popover will receive focus when it mounts. To suppress this beha ### position -The direction in which the popover should open relative to its parent node. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. +The direction in which the popover should open relative to its parent node. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"middle"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. - Type: `String` - Required: No @@ -109,3 +109,11 @@ Opt-in prop to show popovers fullscreen on mobile, pass `false` in this prop to - Type: `String` - Required: No + + ## noArrow + + Set this to hide the arrow which visually indicates what the popover is anchored to. Note that the arrow will not display if `position` is set to `"middle center"`. + + - Type: `Boolean` + - Required: No + - Default: `false` diff --git a/components/popover/index.js b/components/popover/index.js index 55167d806623e..01f7c2fb89e41 100644 --- a/components/popover/index.js +++ b/components/popover/index.js @@ -39,6 +39,7 @@ class Popover extends Component { this.focus = this.focus.bind( this ); this.getAnchorRect = this.getAnchorRect.bind( this ); + this.updatePopoverSize = this.updatePopoverSize.bind( this ); this.computePopoverPosition = this.computePopoverPosition.bind( this ); this.throttledComputePopoverPosition = this.throttledComputePopoverPosition.bind( this ); this.maybeClose = this.maybeClose.bind( this ); @@ -220,7 +221,7 @@ class Popover extends Component { 'is-' + xAxis, { 'is-mobile': isMobile, - 'no-arrow': noArrow, + 'no-arrow': noArrow || ( xAxis === 'center' && yAxis === 'middle' ), } ); diff --git a/components/popover/style.scss b/components/popover/style.scss index 5e30b0932d757..666abbcdf949b 100644 --- a/components/popover/style.scss +++ b/components/popover/style.scss @@ -27,11 +27,8 @@ $arrow-size: 8px; &:after { content: ""; position: absolute; - margin-left: -10px; height: 0; width: 0; - border-left-color: transparent; - border-right-color: transparent; line-height: 0; } @@ -48,13 +45,16 @@ $arrow-size: 8px; &:before, &:after { - border-top-style: solid; border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; + border-top-style: solid; + margin-left: -10px; } } &.is-bottom { - margin-top: 8px; + margin-top: $arrow-size; &:before { top: -$arrow-size; @@ -67,7 +67,50 @@ $arrow-size: 8px; &:before, &:after { border-bottom-style: solid; + border-left-color: transparent; + border-right-color: transparent; border-top: none; + margin-left: -10px; + } + } + + &.is-middle.is-left { + margin-left: -$arrow-size; + + &:before { + right: -$arrow-size; + } + + &:after { + right: -6px; + } + + &:before, + &:after { + border-bottom-color: transparent; + border-left-style: solid; + border-right: none; + border-top-color: transparent; + } + } + + &.is-middle.is-right { + margin-left: $arrow-size; + + &:before { + left: -$arrow-size; + } + + &:after { + left: -6px; + } + + &:before, + &:after { + border-bottom-color: transparent; + border-left: none; + border-right-style: solid; + border-top-color: transparent; } } } @@ -81,6 +124,11 @@ $arrow-size: 8px; top: 100%; z-index: z-index( ".components-popover.is-bottom" ); } + + &.is-middle { + align-items: center; + display: flex; + } } } @@ -114,12 +162,18 @@ $arrow-size: 8px; .components-popover:not(.is-mobile).is-right & { position: absolute; left: 100%; + } + + .components-popover:not(.is-mobile):not(.is-middle).is-right & { margin-left: -24px; } .components-popover:not(.is-mobile).is-left & { position: absolute; right: 100%; + } + + .components-popover:not(.is-mobile):not(.is-middle).is-left & { margin-right: -24px; } } diff --git a/components/popover/test/utils.js b/components/popover/test/utils.js index aff0974db2fad..aaf9a0ecd621b 100644 --- a/components/popover/test/utils.js +++ b/components/popover/test/utils.js @@ -73,6 +73,28 @@ describe( 'computePopoverYAxisPosition', () => { yAxis: 'top', } ); } ); + + it( 'should position a popover in the middle', () => { + const anchorRect = { + top: 400, + left: 10, + bottom: 30, + right: 30, + width: 20, + height: 20, + }; + + const contentSize = { + width: 200, + height: 300, + }; + + expect( computePopoverYAxisPosition( anchorRect, contentSize, 'middle' ) ).toEqual( { + contentHeight: null, + popoverTop: 410, + yAxis: 'middle', + } ); + } ); } ); describe( 'computePopoverXAxisPosition', () => { diff --git a/components/popover/utils.js b/components/popover/utils.js index 02c1faa56a52c..849f4202e68af 100644 --- a/components/popover/utils.js +++ b/components/popover/utils.js @@ -69,6 +69,14 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) { const { height } = contentSize; // y axis aligment choices + const anchorMidPoint = anchorRect.top + ( anchorRect.height / 2 ); + const middleAlignment = { + popoverTop: anchorMidPoint, + contentHeight: ( + ( anchorMidPoint - ( height / 2 ) > 0 ? ( height / 2 ) : anchorMidPoint ) + + ( anchorMidPoint + ( height / 2 ) > window.innerHeight ? window.innerHeight - anchorMidPoint : ( height / 2 ) ) + ), + }; const topAlignment = { popoverTop: anchorRect.top, contentHeight: anchorRect.top - HEIGHT_OFFSET - height > 0 ? height : anchorRect.top - HEIGHT_OFFSET, @@ -81,7 +89,9 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) { // Choosing the y axis let chosenYAxis; let contentHeight = null; - if ( yAxis === 'top' && topAlignment.contentHeight === height ) { + if ( yAxis === 'middle' && middleAlignment.contentHeight === height ) { + chosenYAxis = 'middle'; + } else if ( yAxis === 'top' && topAlignment.contentHeight === height ) { chosenYAxis = 'top'; } else if ( yAxis === 'bottom' && bottomAlignment.contentHeight === height ) { chosenYAxis = 'bottom'; @@ -91,7 +101,14 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) { contentHeight = chosenHeight !== height ? chosenHeight : null; } - const popoverTop = chosenYAxis === 'top' ? topAlignment.popoverTop : bottomAlignment.popoverTop; + let popoverTop; + if ( chosenYAxis === 'middle' ) { + popoverTop = middleAlignment.popoverTop; + } else if ( chosenYAxis === 'top' ) { + popoverTop = topAlignment.popoverTop; + } else { + popoverTop = bottomAlignment.popoverTop; + } return { yAxis: chosenYAxis,