-
Notifications
You must be signed in to change notification settings - Fork 724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(tooltip): Enable custom portal container for tooltip #1567
base: master
Are you sure you want to change the base?
Changes from 1 commit
ac66f31
ae56a47
3fbf8a9
7a5e3b3
99066ae
6aeb835
0ec63f5
ec94a27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,7 +51,8 @@ export default function useTooltipInPortal({ | |
...tooltipProps | ||
}: TooltipInPortalProps) { | ||
const detectBounds = detectBoundsProp == null ? detectBoundsOption : detectBoundsProp; | ||
const portalContainer = portalContainerProp == null ? portalContainerOption : portalContainerProp; | ||
const portalContainer = | ||
portalContainerProp == null ? portalContainerOption : portalContainerProp; | ||
const portalContainerRect = portalContainer?.getBoundingClientRect(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be quite expensive to call each render which is why we opted to use Is it necessary to support the if this simplifies things we could leave it for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh that's a good catch. 🤔 I don't think we necessarily need to support the override at the component level since typically a portal container doesn't move in the screen. I'll drop this prop at the tooltip level and just keep it on However, I'll keep passing in the |
||
const zIndex = zIndexProp == null ? zIndexOption : zIndexProp; | ||
const TooltipComponent = detectBounds ? TooltipWithBounds : Tooltip; | ||
|
@@ -67,11 +68,22 @@ export default function useTooltipInPortal({ | |
|
||
return ( | ||
<Portal container={portalContainer} zIndex={zIndex}> | ||
<TooltipComponent left={portalLeft} top={portalTop} {...tooltipProps} {...additionalTooltipProps} /> | ||
<TooltipComponent | ||
left={portalLeft} | ||
top={portalTop} | ||
{...tooltipProps} | ||
{...additionalTooltipProps} | ||
/> | ||
</Portal> | ||
); | ||
}, | ||
[detectBoundsOption, portalContainerOption, zIndexOption, containerBounds.left, containerBounds.top], | ||
[ | ||
detectBoundsOption, | ||
portalContainerOption, | ||
zIndexOption, | ||
containerBounds.left, | ||
containerBounds.top, | ||
], | ||
); | ||
|
||
return { | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,22 +6,19 @@ import { Portal } from '../src'; | |||||||||||||
const PortalWithContainer = () => { | ||||||||||||||
// useState rather than useRef so it will react to the ref being available for testing purposes | ||||||||||||||
const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null); | ||||||||||||||
const onRefChange = React.useCallback( | ||||||||||||||
(node) => { | ||||||||||||||
setPortalContainer(node); | ||||||||||||||
}, | ||||||||||||||
[] | ||||||||||||||
); | ||||||||||||||
const onRefChange = React.useCallback((node) => { | ||||||||||||||
setPortalContainer(node); | ||||||||||||||
}, []); | ||||||||||||||
return ( | ||||||||||||||
<> | ||||||||||||||
<div data-testid='inner-div' ref={onRefChange} /> | ||||||||||||||
<div data-testid="inner-div" ref={onRefChange} /> | ||||||||||||||
{portalContainer && ( | ||||||||||||||
<Portal container={portalContainer}> | ||||||||||||||
<div data-testid='element-in-portal'></div> | ||||||||||||||
<div data-testid="element-in-portal"></div> | ||||||||||||||
</Portal> | ||||||||||||||
)} | ||||||||||||||
</> | ||||||||||||||
) | ||||||||||||||
); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
describe('Portal', () => { | ||||||||||||||
|
@@ -32,21 +29,25 @@ describe('Portal', () => { | |||||||||||||
it('should render children at the document root', async () => { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for adding such great tests! 🙌 |
||||||||||||||
render( | ||||||||||||||
<> | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would this make more sense as a test setup since you wouldn't expect to find
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a great point! |
||||||||||||||
<div data-testid='inner-div' /> | ||||||||||||||
<div data-testid="inner-div" /> | ||||||||||||||
<Portal> | ||||||||||||||
<div data-testid='element-in-portal'></div> | ||||||||||||||
<div data-testid="element-in-portal"></div> | ||||||||||||||
</Portal> | ||||||||||||||
</>, | ||||||||||||||
); | ||||||||||||||
const elementInPortal = await screen.findByTestId('element-in-portal'); | ||||||||||||||
expect(elementInPortal).toBeInTheDocument(); | ||||||||||||||
expect(within(screen.getByTestId('inner-div')).queryByTestId('element-in-portal')).not.toBeInTheDocument(); | ||||||||||||||
expect( | ||||||||||||||
within(screen.getByTestId('inner-div')).queryByTestId('element-in-portal'), | ||||||||||||||
).not.toBeInTheDocument(); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should render children in the provided portal container', async () => { | ||||||||||||||
render(<PortalWithContainer />); | ||||||||||||||
const elementInPortal = await screen.findByTestId('element-in-portal'); | ||||||||||||||
expect(elementInPortal).toBeInTheDocument(); | ||||||||||||||
expect(within(screen.getByTestId('inner-div')).getByTestId('element-in-portal')).toBeInTheDocument(); | ||||||||||||||
expect( | ||||||||||||||
within(screen.getByTestId('inner-div')).getByTestId('element-in-portal'), | ||||||||||||||
).toBeInTheDocument(); | ||||||||||||||
}); | ||||||||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,31 +23,28 @@ interface TooltipWithPortalContainerProps { | |
shouldUsePortalContainer: boolean; | ||
} | ||
|
||
const TooltipWithPortalContainer = ({ shouldUsePortalContainer }: TooltipWithPortalContainerProps) => { | ||
const TooltipWithPortalContainer = ({ | ||
shouldUsePortalContainer, | ||
}: TooltipWithPortalContainerProps) => { | ||
// useState rather than useRef so it will react to the ref being available for testing purposes | ||
const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null); | ||
const onRefChange = React.useCallback( | ||
(node) => { | ||
setPortalContainer(node); | ||
}, | ||
[] | ||
); | ||
const onRefChange = React.useCallback((node) => { | ||
setPortalContainer(node); | ||
}, []); | ||
|
||
const { TooltipInPortal } = useTooltipInPortal( | ||
{ | ||
polyfill: ResizeObserver, | ||
portalContainer: shouldUsePortalContainer ? portalContainer ?? undefined : undefined, | ||
} | ||
); | ||
const { TooltipInPortal } = useTooltipInPortal({ | ||
polyfill: ResizeObserver, | ||
portalContainer: shouldUsePortalContainer ? portalContainer ?? undefined : undefined, | ||
}); | ||
|
||
return ( | ||
<> | ||
<div data-testid='inner-div' ref={shouldUsePortalContainer ? onRefChange : undefined} /> | ||
<div data-testid="inner-div" ref={shouldUsePortalContainer ? onRefChange : undefined} /> | ||
<TooltipInPortal> | ||
<div data-testid='element-in-tooltip'></div> | ||
<div data-testid="element-in-tooltip"></div> | ||
</TooltipInPortal> | ||
</> | ||
) | ||
); | ||
}; | ||
|
||
describe('useTooltipInPortal()', () => { | ||
|
@@ -63,7 +60,7 @@ describe('useTooltipInPortal()', () => { | |
const zIndex = wrapper.find('Portal').prop('zIndex'); | ||
expect(zIndex).toBe(1); | ||
}); | ||
|
||
it('should pass zIndex prop from component to Portal', () => { | ||
const wrapper = shallow( | ||
<TooltipWithZIndex zIndexOption={1} zIndexProp="var(--tooltip-zindex)" />, | ||
|
@@ -81,14 +78,18 @@ describe('useTooltipInPortal()', () => { | |
render(<TooltipWithPortalContainer shouldUsePortalContainer={false} />); | ||
const elementInPortal = await screen.findByTestId('element-in-tooltip'); | ||
expect(elementInPortal).toBeInTheDocument(); | ||
expect(within(screen.getByTestId('inner-div')).queryByTestId('element-in-tooltip')).not.toBeInTheDocument(); | ||
expect( | ||
within(screen.getByTestId('inner-div')).queryByTestId('element-in-tooltip'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess same question on this test since |
||
).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('it should render tooltip in the provided portal container', async () => { | ||
render(<TooltipWithPortalContainer shouldUsePortalContainer={true} />); | ||
const elementInPortal = await screen.findByTestId('element-in-tooltip'); | ||
expect(elementInPortal).toBeInTheDocument(); | ||
expect(within(screen.getByTestId('inner-div')).getByTestId('element-in-tooltip')).toBeInTheDocument(); | ||
expect( | ||
within(screen.getByTestId('inner-div')).getByTestId('element-in-tooltip'), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simplifying a bit