feat(dropdown): allow dropdown to set the specified container (#344)

* feat(dropdown): allow dropdown to set the specified container

* test(modal): update snapshots

* docs(select): add example for custom popup container

* fix(dropdown): fix type of getPopupContainer

* test(dropdown): add testcase for specified container rendering
This commit is contained in:
witt
2020-07-21 21:38:17 +08:00
committed by GitHub
parent 6e97f89540
commit 4125e6f0a3
9 changed files with 162 additions and 46 deletions

View File

@@ -164,4 +164,24 @@ describe('Dropdown', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should render to specified container', () => {
const Mock: React.FC<{}> = () => {
const ref = useRef<HTMLDivElement>(null)
const customContainer = useRef<HTMLDivElement>(null)
return (
<div>
<div ref={customContainer} id="custom" />
<div ref={ref}>
<Dropdown parent={ref} visible getPopupContainer={() => customContainer.current}>
<span>test-value</span>
</Dropdown>
</div>
</div>
)
}
const wrapper = mount(<Mock />)
const customContainer = wrapper.find('#custom')
expect(customContainer.html()).toContain('dropdown')
})
})

View File

@@ -10,6 +10,7 @@ interface Props {
parent?: MutableRefObject<HTMLElement | null> | undefined
visible: boolean
disableMatchWidth?: boolean
getPopupContainer?: () => HTMLElement | null
}
interface ReactiveDomReact {
@@ -26,31 +27,48 @@ const defaultRect: ReactiveDomReact = {
width: 0,
}
const getRect = (ref: MutableRefObject<HTMLElement | null>): ReactiveDomReact => {
const getOffset = (el?: HTMLElement | null | undefined) => {
if (!el)
return {
top: 0,
left: 0,
}
const { top, left } = el.getBoundingClientRect()
return { top, left }
}
const getRect = (
ref: MutableRefObject<HTMLElement | null>,
getContainer?: () => HTMLElement | null,
): ReactiveDomReact => {
if (!ref || !ref.current) return defaultRect
const rect = ref.current.getBoundingClientRect()
const container = getContainer ? getContainer() : null
const scrollElement = container || document.documentElement
const { top: offsetTop, left: offsetLeft } = getOffset(container)
return {
...rect,
width: rect.width || rect.right - rect.left,
top: rect.bottom + document.documentElement.scrollTop,
left: rect.left + document.documentElement.scrollLeft,
top: rect.bottom + scrollElement.scrollTop - offsetTop,
left: rect.left + scrollElement.scrollLeft - offsetLeft,
}
}
const Dropdown: React.FC<React.PropsWithChildren<Props>> = React.memo(
({ children, parent, visible, disableMatchWidth }) => {
const el = usePortal('dropdown')
({ children, parent, visible, disableMatchWidth, getPopupContainer }) => {
const el = usePortal('dropdown', getPopupContainer)
const [rect, setRect] = useState<ReactiveDomReact>(defaultRect)
if (!parent) return null
const updateRect = () => {
const { top, left, right, width: nativeWidth } = getRect(parent)
const { top, left, right, width: nativeWidth } = getRect(parent, getPopupContainer)
setRect({ top, left, right, width: nativeWidth })
}
useResize(updateRect)
useClickAnyWhere(() => {
const { top, left } = getRect(parent)
const { top, left } = getRect(parent, getPopupContainer)
const shouldUpdatePosition = top !== rect.top || left !== rect.left
if (!shouldUpdatePosition) return
updateRect()