From 0ce5d4ece0a17b1b1a990e9dbf60bd9d6ab0cf51 Mon Sep 17 00:00:00 2001 From: Cheton Wu <447801+cheton@users.noreply.github.com> Date: Thu, 18 May 2023 13:33:55 +0800 Subject: [PATCH] fix: correct arrow position when enabling popper's flip modifier (#761) * fix: correct arrow position when enabling popper's flip modifier * feat: combining default and user-defined modifiers * docs: update the commonly asked questions in popover and tooltip --- .../react-docs/pages/components/popover.mdx | 84 ++++++++++++++++++- .../react-docs/pages/components/tooltip.mdx | 76 ++++++++++++++++- packages/react/src/popover/PopoverArrow.js | 32 +++---- packages/react/src/popover/PopoverContent.js | 7 +- .../__snapshots__/Popover.test.js.snap | 68 ++++++++++++++- packages/react/src/popover/styles.js | 44 ++++------ packages/react/src/popper/Popper.js | 14 ++-- packages/react/src/tooltip/TooltipArrow.js | 32 +++---- packages/react/src/tooltip/TooltipContent.js | 7 +- .../__snapshots__/Tooltip.test.js.snap | 65 +++++++++++++- packages/react/src/tooltip/styles.js | 44 ++++------ 11 files changed, 369 insertions(+), 104 deletions(-) diff --git a/packages/react-docs/pages/components/popover.mdx b/packages/react-docs/pages/components/popover.mdx index 9ab0743696..2a73e3337b 100644 --- a/packages/react-docs/pages/components/popover.mdx +++ b/packages/react-docs/pages/components/popover.mdx @@ -459,23 +459,99 @@ Use the `placement` prop to control the placement of the popover. ## Commonly Asked Questions -### Preventing popover content cut-off with `PopperProps` +### Resolving popover content cut-off with `PopperProps` By default, the `Popover` component positions the popover relative to its parent container. In some cases, the popover content might be cut off when it extends outside the container that holds it. -To mitigate this issue, you can pass `PopperProps={{ usePortal: true }}` to `PopoverContent` to make it positioned on the document root. +To address this issue, you can pass `PopperProps={{ usePortal: true }}` to `PopoverContent` to make it positioned on the document root. ```jsx - + Popover ``` +### Automatically adjusting popover placement with the `flip` modifier + +The `flip` modifier is a useful feature that allows for automatic adjustment of popover placement when it is at risk of overflowing the specified boundary. To learn more about utilizing the `flip` modifier, please refer to [Popper.js documentation](https://popper.js.org/docs/v2/modifiers/flip/). + +In the following example, the popover's placement is initially set to `top`. However, if the placement is not suitable due to space constraints, the opposite `bottom` placement will be used instead. + +```jsx noInline +const FormGroup = (props) => ( + +); + +render(() => { + const [colorMode] = useColorMode(); + const [colorStyle] = useColorStyle({ colorMode }); + const [isFlipModifierEnabled, toggleIsFlipModifierEnabled] = useToggle(true); + + return ( + <> + + + Modifiers + + + + + toggleIsFlipModifierEnabled()} + /> + + Enable flip modifier + + + + + + + + + Reference + + + + Popover + + + + + + ); +}); +``` + ## Accessibility ### Keyboard and focus @@ -554,7 +630,7 @@ The `Popover` component includes several accessibility features to ensure that i | Name | Type | Default | Description | | :--- | :--- | :------ | :---------- | -| PopperComponent | ElementType | Popper | The component used for the popover. | +| PopperComponent | ElementType | Popper | The component used for the popper. | | PopperProps | object | | Props applied to the Popper component. | | PopoverArrowComponent | ElementType | PopoverArrow | The component used for the popover arrow. | | PopoverArrowProps | object | | Props applied to the `PopoverArrow` component. | diff --git a/packages/react-docs/pages/components/tooltip.mdx b/packages/react-docs/pages/components/tooltip.mdx index 64c505fad0..b403505272 100644 --- a/packages/react-docs/pages/components/tooltip.mdx +++ b/packages/react-docs/pages/components/tooltip.mdx @@ -374,11 +374,11 @@ function Example() { ## Commonly Asked Questions -### Preventing tooltip cut-off with `PopperProps` +### Resolving tooltip content cut-off with `PopperProps` By default, the `Tooltip` component positions the tooltip relative to its parent container. In some cases, the tooltip content might be cut off when it extends outside the container that holds it. -To mitigate this issue, you can pass `PopperProps={{ usePortal: true }}` to `Tooltip` to make it positioned on the document root. +To address this issue, you can pass `PopperProps={{ usePortal: true }}` to `Tooltip` to make it positioned on the document root. ```jsx ``` +### Automatically adjusting tooltip placement with the `flip` modifier + +The `flip` modifier is a useful feature that allows for automatic adjustment of tooltip placement when it is at risk of overflowing the specified boundary. To learn more about utilizing the `flip` modifier, please refer to [Popper.js documentation](https://popper.js.org/docs/v2/modifiers/flip/). + +In the following example, the tooltip's placement is initially set to `top`. However, if the placement is not suitable due to space constraints, the opposite `bottom` placement will be used instead. + +```jsx noInline +const FormGroup = (props) => ( + +); + +render(() => { + const [colorMode] = useColorMode(); + const [colorStyle] = useColorStyle({ colorMode }); + const [isFlipModifierEnabled, toggleIsFlipModifierEnabled] = useToggle(true); + + return ( + <> + + + Modifiers + + + + + toggleIsFlipModifierEnabled()} + /> + + Enable flip modifier + + + + + + + + Reference + + + + + + ); +}); +``` + ## Props ### Tooltip | Name | Type | Default | Description | | :--- | :--- | :------ | :---------- | -| PopperComponent | ElementType | Popper | The component used for the popover. | +| PopperComponent | ElementType | Popper | The component used for the popper. | | PopperProps | object | | Props applied to the Popper component. | | TooltipArrowComponent | ElementType | TooltipArrow | The component used for the tooltip arrow. | | TooltipArrowProps | object | | Props applied to the `TooltipArrow` component. | diff --git a/packages/react/src/popover/PopoverArrow.js b/packages/react/src/popover/PopoverArrow.js index 6b761f3a93..8e87338e6b 100644 --- a/packages/react/src/popover/PopoverArrow.js +++ b/packages/react/src/popover/PopoverArrow.js @@ -8,6 +8,7 @@ const PopoverArrow = forwardRef(( { arrowHeight = 8, arrowWidth = 12, + sx, ...rest }, ref, @@ -16,26 +17,27 @@ const PopoverArrow = forwardRef(( placement, popoverContentRef, } = usePopover(); - const styleProps = usePopoverArrowStyle({ arrowHeight, arrowWidth, placement }); - const colorStyleProps = (() => { - const popoverContentEl = popoverContentRef?.current; - if (isHTMLElement(popoverContentEl)) { - // Compute the background color of the first direct child of the popover content and apply it to the popover arrow - const computedStyle = getComputedStyle(popoverContentEl.firstChild); - return { - color: computedStyle?.backgroundColor, - }; - } - return {}; - })(); + const popoverContentEl = popoverContentRef?.current; + const styleProps = usePopoverArrowStyle({ arrowHeight, arrowWidth }); + + if (isHTMLElement(popoverContentEl)) { + // Compute the background color of the first direct child of the popover content and apply it to the popover arrow + const computedStyle = getComputedStyle(popoverContentEl.firstChild); + styleProps.color = computedStyle?.backgroundColor; + } return ( ); diff --git a/packages/react/src/popover/PopoverContent.js b/packages/react/src/popover/PopoverContent.js index 99df282e57..de888490b7 100644 --- a/packages/react/src/popover/PopoverContent.js +++ b/packages/react/src/popover/PopoverContent.js @@ -209,7 +209,6 @@ const PopoverContent = forwardRef(( anchorEl={popoverTriggerRef.current} id={popoverId} isOpen={isOpen} - modifiers={popperModifiers} placement={placement} ref={popoverContentRef} role={role} @@ -218,6 +217,12 @@ const PopoverContent = forwardRef(( willUseTransition={true} zIndex="popover" {...PopperProps} + modifiers={[ + // Default modifiers + ...popperModifiers, + // User-defined modifiers + ...ensureArray(PopperProps?.modifiers), + ]} > {({ placement, transition }) => { const { in: inProp, onEnter, onExited } = { ...transition }; diff --git a/packages/react/src/popover/__tests__/__snapshots__/Popover.test.js.snap b/packages/react/src/popover/__tests__/__snapshots__/Popover.test.js.snap index 256cdffdfd..3f074373fb 100644 --- a/packages/react/src/popover/__tests__/__snapshots__/Popover.test.js.snap +++ b/packages/react/src/popover/__tests__/__snapshots__/Popover.test.js.snap @@ -117,12 +117,35 @@ exports[`Popover should render correctly 1`] = ` } .emotion-6 { + color: white; +} + +.emotion-6[data-popper-placement^="top"] { + position: absolute; + bottom: 0; +} + +.emotion-6[data-popper-placement^="top"]::before { + content: ""; + border-top: 8px solid; + border-left: calc(12px/2) solid transparent; + border-right: calc(12px/2) solid transparent; + -webkit-filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.08)); + filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.08)); + position: absolute; + bottom: calc(8px * -1); + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); +} + +.emotion-6[data-popper-placement^="bottom"] { position: absolute; top: 0; - color: white; } -.emotion-6::before { +.emotion-6[data-popper-placement^="bottom"]::before { content: ""; border-bottom: 8px solid; border-left: calc(12px/2) solid transparent; @@ -137,6 +160,46 @@ exports[`Popover should render correctly 1`] = ` transform: translateX(-50%); } +.emotion-6[data-popper-placement^="left"] { + position: absolute; + right: 0; +} + +.emotion-6[data-popper-placement^="left"]::before { + content: ""; + border-left: 8px solid; + border-top: calc(12px/2) solid transparent; + border-bottom: calc(12px/2) solid transparent; + -webkit-filter: drop-shadow(1px 0px 1px rgba(0, 0, 0, 0.08)); + filter: drop-shadow(1px 0px 1px rgba(0, 0, 0, 0.08)); + position: absolute; + right: calc(8px * -1); + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +.emotion-6[data-popper-placement^="right"] { + position: absolute; + left: 0; +} + +.emotion-6[data-popper-placement^="right"]::before { + content: ""; + border-right: 8px solid; + border-top: calc(12px/2) solid transparent; + border-bottom: calc(12px/2) solid transparent; + -webkit-filter: drop-shadow(-1px 0px 1px rgba(0, 0, 0, 0.08)); + filter: drop-shadow(-1px 0px 1px rgba(0, 0, 0, 0.08)); + position: absolute; + left: calc(8px * -1); + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +