mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-29 11:47:15 +00:00
🐛(frontend) clamp pip tooltips on right edge
prevent pip tooltips from overflowing the right side of the window
This commit is contained in:
@@ -2,7 +2,9 @@ import {
|
|||||||
type ReactElement,
|
type ReactElement,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
useMemo, useRef,
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
@@ -16,8 +18,6 @@ export type VisualOnlyTooltipProps = {
|
|||||||
tooltipPosition?: 'top' | 'bottom'
|
tooltipPosition?: 'top' | 'bottom'
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOOLTIP_VERTICAL_OFFSET_PX = 8
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper component that displays a tooltip visually only (not announced by screen readers).
|
* Wrapper component that displays a tooltip visually only (not announced by screen readers).
|
||||||
*
|
*
|
||||||
@@ -37,10 +37,15 @@ export const VisualOnlyTooltip = ({
|
|||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
const { getContainer } = useUNSAFE_PortalContext()
|
const { getContainer } = useUNSAFE_PortalContext()
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null)
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
||||||
|
const tooltipRef = useRef<HTMLDivElement>(null)
|
||||||
const [position, setPosition] = useState<{
|
const [position, setPosition] = useState<{
|
||||||
top: number
|
top: number
|
||||||
left: number
|
left: number
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
const [computedStyle, setComputedStyle] = useState<{
|
||||||
|
left: number
|
||||||
|
arrowLeft: number
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
const isBottom = tooltipPosition === 'bottom'
|
const isBottom = tooltipPosition === 'bottom'
|
||||||
|
|
||||||
@@ -57,9 +62,23 @@ export const VisualOnlyTooltip = ({
|
|||||||
const hideTooltip = () => {
|
const hideTooltip = () => {
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
setPosition(null)
|
setPosition(null)
|
||||||
|
setComputedStyle(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipData = isVisible && position ? { isVisible, position } : null
|
useLayoutEffect(() => {
|
||||||
|
if (!tooltipRef.current || !isVisible || !position) return
|
||||||
|
const tooltipWidth = tooltipRef.current.getBoundingClientRect().width
|
||||||
|
const doc = tooltipRef.current.ownerDocument
|
||||||
|
const viewportWidth = doc.defaultView?.innerWidth ?? window.innerWidth
|
||||||
|
const padding = 8
|
||||||
|
const desiredLeft = position.left - tooltipWidth / 2
|
||||||
|
const maxLeft = viewportWidth - padding - tooltipWidth
|
||||||
|
if (desiredLeft <= maxLeft) {
|
||||||
|
setComputedStyle(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setComputedStyle({ left: maxLeft, arrowLeft: position.left - maxLeft })
|
||||||
|
}, [isVisible, position])
|
||||||
|
|
||||||
const portalContainer = useMemo(() => {
|
const portalContainer = useMemo(() => {
|
||||||
if (getContainer) return getContainer()
|
if (getContainer) return getContainer()
|
||||||
@@ -82,12 +101,14 @@ export const VisualOnlyTooltip = ({
|
|||||||
>
|
>
|
||||||
{wrappedChild}
|
{wrappedChild}
|
||||||
</div>
|
</div>
|
||||||
{tooltipData &&
|
{isVisible &&
|
||||||
|
position &&
|
||||||
portalContainer &&
|
portalContainer &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
ref={tooltipRef}
|
||||||
className={css({
|
className={css({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
padding: '2px 8px',
|
padding: '2px 8px',
|
||||||
@@ -97,12 +118,12 @@ export const VisualOnlyTooltip = ({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
zIndex: 9999,
|
zIndex: 100001,
|
||||||
boxShadow: '0 8px 20px rgba(0 0 0 / 0.1)',
|
boxShadow: '0 8px 20px rgba(0 0 0 / 0.1)',
|
||||||
'&::after': {
|
'&::after': {
|
||||||
content: '""',
|
content: '""',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '50%',
|
left: 'var(--tooltip-arrow-left, 50%)',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
border: '4px solid transparent',
|
border: '4px solid transparent',
|
||||||
...(isBottom
|
...(isBottom
|
||||||
@@ -117,11 +138,22 @@ export const VisualOnlyTooltip = ({
|
|||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
style={{
|
style={{
|
||||||
top: `${tooltipData.position.top}px`,
|
top: `${position.top}px`,
|
||||||
left: `${tooltipData.position.left}px`,
|
left: computedStyle
|
||||||
transform: isBottom
|
? `${computedStyle.left}px`
|
||||||
? 'translate(-50%, 0)'
|
: `${position.left}px`,
|
||||||
: 'translate(-50%, -100%)',
|
transform: computedStyle
|
||||||
|
? isBottom
|
||||||
|
? 'translateY(0)'
|
||||||
|
: 'translateY(-100%)'
|
||||||
|
: isBottom
|
||||||
|
? 'translate(-50%, 0)'
|
||||||
|
: 'translate(-50%, -100%)',
|
||||||
|
...(computedStyle
|
||||||
|
? {
|
||||||
|
'--tooltip-arrow-left': `${computedStyle.arrowLeft}px`,
|
||||||
|
}
|
||||||
|
: null),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tooltip}
|
{tooltip}
|
||||||
|
|||||||
Reference in New Issue
Block a user