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,
|
omitCommonProps,
|
||||||
} from '../../utils/useCommonProps'
|
} from '../../utils/useCommonProps'
|
||||||
import { TooltipBase } from '../TooltipBase'
|
import { TooltipBase } from '../TooltipBase'
|
||||||
|
import { useUpdatePositionStyle } from '../../utils/useUpdatePositionStyle'
|
||||||
|
|
||||||
export const CALENDAR_MIN_YEAR = 1850
|
export const CALENDAR_MIN_YEAR = 1850
|
||||||
export const CALENDAR_MAX_YEAR = 2100
|
export const CALENDAR_MAX_YEAR = 2100
|
||||||
|
@ -64,7 +65,6 @@ export const Calendar: React.FC<CalendarProps> & {
|
||||||
}) => {
|
}) => {
|
||||||
const commonProps = useCommonProps(props)
|
const commonProps = useCommonProps(props)
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
const [style, setStyle] = useState<React.CSSProperties>({})
|
|
||||||
const [startDate, setStartDate] = useState<Date | null>(
|
const [startDate, setStartDate] = useState<Date | null>(
|
||||||
startDateProp
|
startDateProp
|
||||||
? safeConvertDate(startDateProp, minDate, maxDate).date
|
? safeConvertDate(startDateProp, minDate, maxDate).date
|
||||||
|
@ -147,19 +147,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
||||||
}
|
}
|
||||||
}, [endDate])
|
}, [endDate])
|
||||||
|
|
||||||
const updateStyle = () => {
|
const positionStyle = useUpdatePositionStyle(handleRef, open)
|
||||||
const { width, height, top, left } =
|
|
||||||
handleRef.current!.getBoundingClientRect()
|
|
||||||
setStyle({
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
top: top + height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateStyle()
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarContext.Provider
|
<CalendarContext.Provider
|
||||||
|
@ -189,7 +177,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
||||||
disabled && calendarClasses.disabled,
|
disabled && calendarClasses.disabled,
|
||||||
)}
|
)}
|
||||||
rootRef={ref}
|
rootRef={ref}
|
||||||
style={{ ...style, ...(props.style ?? {}) }}
|
style={{ ...positionStyle, ...(props.style ?? {}) }}
|
||||||
arrowOffset={tooltipArrowOffset}
|
arrowOffset={tooltipArrowOffset}
|
||||||
>
|
>
|
||||||
<div className={clsx(calendarClasses.container)}>
|
<div className={clsx(calendarClasses.container)}>
|
||||||
|
|
|
@ -70,7 +70,6 @@ export const DatePicker: React.FC<DatePickerProps> & {
|
||||||
<div
|
<div
|
||||||
id={inputId}
|
id={inputId}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
{ ...omitCommonProps(props) },
|
{ ...omitCommonProps(props) },
|
||||||
props.className,
|
props.className,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
useCommonProps,
|
useCommonProps,
|
||||||
} from '../../utils/useCommonProps'
|
} from '../../utils/useCommonProps'
|
||||||
import { dropdownMenuClasses } from './DropdownMenu.classes'
|
import { dropdownMenuClasses } from './DropdownMenu.classes'
|
||||||
|
import { useUpdatePositionStyle } from '../../utils/useUpdatePositionStyle'
|
||||||
|
|
||||||
export type DropdownMenuProps = CommonProps &
|
export type DropdownMenuProps = CommonProps &
|
||||||
Omit<React.HTMLAttributes<HTMLUListElement>, 'label'> & {
|
Omit<React.HTMLAttributes<HTMLUListElement>, 'label'> & {
|
||||||
|
@ -30,7 +31,6 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
||||||
}) => {
|
}) => {
|
||||||
const commonProps = useCommonProps(props)
|
const commonProps = useCommonProps(props)
|
||||||
const ref = useRef<HTMLUListElement>(null)
|
const ref = useRef<HTMLUListElement>(null)
|
||||||
const [style, setStyle] = useState<React.CSSProperties>({})
|
|
||||||
|
|
||||||
useClickAway(ref, (event) => {
|
useClickAway(ref, (event) => {
|
||||||
if (!open || event.composedPath().includes(handleRef.current!)) return
|
if (!open || event.composedPath().includes(handleRef.current!)) return
|
||||||
|
@ -38,20 +38,7 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
||||||
onClose && onClose()
|
onClose && onClose()
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateStyle = () => {
|
const positionStyle = useUpdatePositionStyle(handleRef, open)
|
||||||
const { width, height, top, left } =
|
|
||||||
handleRef.current!.getBoundingClientRect()
|
|
||||||
|
|
||||||
setStyle({
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
top: top + height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateStyle()
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
|
@ -59,7 +46,7 @@ export const DropdownMenu: React.FC<DropdownMenuProps> & {
|
||||||
ref={ref}
|
ref={ref}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
style={{ ...style, ...(props.style ?? {}) }}
|
style={{ ...positionStyle, ...(props.style ?? {}) }}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
commonProps.className,
|
commonProps.className,
|
||||||
props.className,
|
props.className,
|
||||||
|
|
|
@ -13,7 +13,14 @@ export const usePortal = ({ parentId }: Props) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined' || !elementRef.current) return
|
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 () => {
|
return () => {
|
||||||
try {
|
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