mirror of
https://github.com/zhigang1992/react.git
synced 2026-04-24 04:15:54 +08:00
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:
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user