Skip to content

Commit

Permalink
fix: consider scroll in portal elements and handle stale portal conta…
Browse files Browse the repository at this point in the history
…iners
  • Loading branch information
jongomez committed Oct 25, 2023
1 parent 85b148a commit 55d4bda
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 33 deletions.
18 changes: 3 additions & 15 deletions packages/lsd-react/src/components/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export const DatePicker: React.FC<DatePickerProps> & {
<div
id={inputId}
ref={ref}
{...props}
className={clsx(
{ ...omitCommonProps(props) },
props.className,
Expand Down
19 changes: 3 additions & 16 deletions packages/lsd-react/src/components/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'> & {
Expand All @@ -30,36 +31,22 @@ 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

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
{...omitCommonProps(props)}
ref={ref}
role="listbox"
aria-label={label}
style={{ ...style, ...(props.style ?? {}) }}
style={{ ...positionStyle, ...(props.style ?? {}) }}
className={clsx(
commonProps.className,
props.className,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions packages/lsd-react/src/utils/useUpdatePositionStyle.ts
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 55d4bda

Please sign in to comment.