mirror of https://github.com/acid-info/lsd.git
fix: consider scroll in portal elements and handle stale portal containers
This commit is contained in:
parent
85b148a8c9
commit
55d4bda014
|
@ -16,6 +16,7 @@ import {
|
|||
omitCommonProps,
|
||||
} from '../../utils/useCommonProps'
|
||||
import { TooltipBase } from '../TooltipBase'
|
||||
import { useUpdatePositionStyle } from '../../utils/useUpdatePositionStyle'
|
||||
|
||||
export const CALENDAR_MIN_YEAR = 1850
|
||||
export const CALENDAR_MAX_YEAR = 2100
|
||||
|
@ -64,7 +65,6 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
}) => {
|
||||
const commonProps = useCommonProps(props)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [style, setStyle] = useState<React.CSSProperties>({})
|
||||
const [startDate, setStartDate] = useState<Date | null>(
|
||||
startDateProp
|
||||
? safeConvertDate(startDateProp, minDate, maxDate).date
|
||||
|
@ -147,19 +147,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
}
|
||||
}, [endDate])
|
||||
|
||||
const updateStyle = () => {
|
||||
const { width, height, top, left } =
|
||||
handleRef.current!.getBoundingClientRect()
|
||||
setStyle({
|
||||
left,
|
||||
width,
|
||||
top: top + height,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateStyle()
|
||||
}, [open])
|
||||
const positionStyle = useUpdatePositionStyle(handleRef, open)
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider
|
||||
|
@ -189,7 +177,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
disabled && calendarClasses.disabled,
|
||||
)}
|
||||
rootRef={ref}
|
||||
style={{ ...style, ...(props.style ?? {}) }}
|
||||
style={{ ...positionStyle, ...(props.style ?? {}) }}
|
||||
arrowOffset={tooltipArrowOffset}
|
||||
>
|
||||
<div className={clsx(calendarClasses.container)}>
|
||||
|
|
|
@ -70,7 +70,6 @@ export const DatePicker: React.FC<DatePickerProps> & {
|
|||
<div
|
||||
id={inputId}
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={clsx(
|
||||
{ ...omitCommonProps(props) },
|
||||
props.className,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
useCommonProps,
|
||||
} from '../../utils/useCommonProps'
|
||||
import { dropdownMenuClasses } from './DropdownMenu.classes'
|
||||
import { useUpdatePositionStyle } from '../../utils/useUpdatePositionStyle'
|
||||
|
||||
export type DropdownMenuProps = CommonProps &
|
||||
Omit<React.HTMLAttributes<HTMLUListElement>, 'label'> & {
|
||||
|
@ -30,7 +31,6 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
|||
}) => {
|
||||
const commonProps = useCommonProps(props)
|
||||
const ref = useRef<HTMLUListElement>(null)
|
||||
const [style, setStyle] = useState<React.CSSProperties>({})
|
||||
|
||||
useClickAway(ref, (event) => {
|
||||
if (!open || event.composedPath().includes(handleRef.current!)) return
|
||||
|
@ -38,20 +38,7 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
|||
onClose && onClose()
|
||||
})
|
||||
|
||||
const updateStyle = () => {
|
||||
const { width, height, top, left } =
|
||||
handleRef.current!.getBoundingClientRect()
|
||||
|
||||
setStyle({
|
||||
left,
|
||||
width,
|
||||
top: top + height,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateStyle()
|
||||
}, [open])
|
||||
const positionStyle = useUpdatePositionStyle(handleRef, open)
|
||||
|
||||
return (
|
||||
<ul
|
||||
|
@ -59,7 +46,7 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
|||
ref={ref}
|
||||
role="listbox"
|
||||
aria-label={label}
|
||||
style={{ ...style, ...(props.style ?? {}) }}
|
||||
style={{ ...positionStyle, ...(props.style ?? {}) }}
|
||||
className={clsx(
|
||||
commonProps.className,
|
||||
props.className,
|
||||
|
|
|
@ -13,7 +13,14 @@ export const usePortal = ({ parentId }: Props) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || !elementRef.current) return
|
||||
document.getElementById(parentId)?.appendChild(elementRef.current)
|
||||
|
||||
const parentElements = document.querySelectorAll(`#${parentId}`)
|
||||
|
||||
// In some places (e.g. storybook), there may be multiple portal containers when a component
|
||||
// is rendered multiple times. Here, we append only to the last found parent element.
|
||||
// This is because usually the last parent is the most recently rendered one.
|
||||
// Without this, we may append to a parent that is about to be removed from the DOM.
|
||||
parentElements[parentElements.length - 1]?.appendChild(elementRef.current)
|
||||
|
||||
return () => {
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const useUpdatePositionStyle = (
|
||||
handleRef: React.RefObject<HTMLElement>,
|
||||
tiggerUpdate: boolean | undefined,
|
||||
): React.CSSProperties => {
|
||||
const [style, setStyle] = useState<React.CSSProperties>({})
|
||||
|
||||
useEffect(() => {
|
||||
const { width, height, top, left } =
|
||||
handleRef.current!.getBoundingClientRect()
|
||||
|
||||
setStyle({
|
||||
left: left + window.scrollX,
|
||||
width,
|
||||
top: top + height + window.scrollY,
|
||||
})
|
||||
}, [tiggerUpdate])
|
||||
|
||||
return style
|
||||
}
|
Loading…
Reference in New Issue