mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 20:08:24 +00:00
🩹(frontend) improve reaction toolbar centering with dynamic positioning
Replace the previous hardcoded offset approach with a dedicated hook that dynamically centers the reaction toolbar relative to the reaction toggle. This improves layout accuracy and removes reliance on fixed values. Note: ResizeObserver is used for positioning updates and may not be supported in older browsers. Additional testing will be performed before production release. Replace direct DOM queries using hardcoded IDs with proper constants.
This commit is contained in:
committed by
aleb_the_flash
parent
b4ced74b1f
commit
d47d13f041
@@ -18,6 +18,7 @@ and this project adheres to
|
||||
|
||||
- ♻️(fullstack) simplify source serialization
|
||||
- ✨(backend) expose room configuration to all API consumers
|
||||
- 🩹(frontend) improve reaction toolbar centering with dynamic positioning
|
||||
|
||||
## [1.16.0] - 2026-05-13
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ const controlBarRegion = cva({
|
||||
},
|
||||
})
|
||||
|
||||
export const CONTROL_BAR_REGION_ID = 'control-bar-region'
|
||||
|
||||
export type ControlBarRegionProps = React.HTMLAttributes<HTMLDivElement> &
|
||||
RecipeVariantProps<typeof controlBarRegion>
|
||||
|
||||
@@ -34,6 +36,7 @@ export function ControlBarRegion({
|
||||
return (
|
||||
<div
|
||||
role="region"
|
||||
id={CONTROL_BAR_REGION_ID}
|
||||
aria-label={t('controls.region')}
|
||||
className={controlBarRegion({ mobile })}
|
||||
{...props}
|
||||
|
||||
@@ -15,6 +15,8 @@ const focusReactionsToolbar = () => {
|
||||
?.focus()
|
||||
}
|
||||
|
||||
export const REACTIONS_TOGGLE_ID = 'reactions-toggle'
|
||||
|
||||
export const ReactionsToggle = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
|
||||
|
||||
@@ -35,7 +37,7 @@ export const ReactionsToggle = () => {
|
||||
|
||||
return (
|
||||
<ToggleButton
|
||||
id="reactions-toggle"
|
||||
id={REACTIONS_TOGGLE_ID}
|
||||
data-attr="reactions-toggle"
|
||||
square
|
||||
variant="primaryDark"
|
||||
|
||||
+87
-11
@@ -1,4 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
@@ -7,6 +13,9 @@ import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { Button } from '@/primitives'
|
||||
|
||||
import { CONTROL_BAR_REGION_ID } from '@/features/layout/components/ControlBarRegion'
|
||||
import { REACTIONS_TOGGLE_ID } from '../ReactionsToggle'
|
||||
|
||||
const StyledContainer = styled('div', {
|
||||
base: {
|
||||
display: 'flex',
|
||||
@@ -30,15 +39,6 @@ const StyledContainer = styled('div', {
|
||||
pointerEvents: 'auto',
|
||||
},
|
||||
},
|
||||
desktopOffset: {
|
||||
true: {
|
||||
// Ideally this value should be calculated dynamically in JavaScript to keep
|
||||
// the reaction toolbar perfectly centered relative to the reaction toggle.
|
||||
// However, for simplicity and to follow a pragmatic 80/20 approach,
|
||||
// this value is currently hardcoded in CSS.
|
||||
marginRight: '30px',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -67,6 +67,7 @@ export const ReactionButtonsContainer = ({
|
||||
const { isOpen } = useReactionsToolbar()
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const { width } = useSize(scrollRef)
|
||||
|
||||
@@ -74,6 +75,11 @@ export const ReactionButtonsContainer = ({
|
||||
const [overflowing, setOverflowing] = useState(false)
|
||||
const [atStart, setAtStart] = useState(true)
|
||||
const [atEnd, setAtEnd] = useState(false)
|
||||
const [
|
||||
shouldBeCenteredWithToggleButton,
|
||||
setShouldBeCenteredWithToggleButton,
|
||||
] = useState(false)
|
||||
const [rightOffset, setRightOffset] = useState(0)
|
||||
|
||||
const updateArrows = useCallback(() => {
|
||||
const el = scrollRef.current
|
||||
@@ -83,6 +89,71 @@ export const ReactionButtonsContainer = ({
|
||||
setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) return
|
||||
const region = document.getElementById(CONTROL_BAR_REGION_ID)
|
||||
if (!region) return
|
||||
const check = () => {
|
||||
setShouldBeCenteredWithToggleButton(
|
||||
window.innerWidth > region.clientWidth
|
||||
)
|
||||
}
|
||||
check()
|
||||
const ro = new ResizeObserver(check)
|
||||
ro.observe(region)
|
||||
window.addEventListener('resize', check)
|
||||
return () => {
|
||||
ro.disconnect()
|
||||
window.removeEventListener('resize', check)
|
||||
}
|
||||
}, [isMobile])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!shouldBeCenteredWithToggleButton || isMobile) {
|
||||
setRightOffset(0)
|
||||
return
|
||||
}
|
||||
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
let frame = 0
|
||||
|
||||
const align = () => {
|
||||
const toggle = document.getElementById(REACTIONS_TOGGLE_ID)
|
||||
if (!toggle) return
|
||||
const toggleRect = toggle.getBoundingClientRect()
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
const toggleCenterX = toggleRect.left + toggleRect.width / 2
|
||||
const containerCenterX = containerRect.left + containerRect.width / 2
|
||||
const shift = toggleCenterX - containerCenterX
|
||||
if (Math.abs(shift) < 0.5) return
|
||||
setRightOffset((prev) => prev - shift * 2)
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
cancelAnimationFrame(frame)
|
||||
frame = requestAnimationFrame(align)
|
||||
}
|
||||
|
||||
schedule()
|
||||
|
||||
const ro = new ResizeObserver(schedule)
|
||||
ro.observe(container)
|
||||
const region = document.getElementById(CONTROL_BAR_REGION_ID)
|
||||
if (region) ro.observe(region)
|
||||
const toggle = document.getElementById(REACTIONS_TOGGLE_ID)
|
||||
if (toggle) ro.observe(toggle)
|
||||
|
||||
window.addEventListener('resize', schedule)
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame)
|
||||
ro.disconnect()
|
||||
window.removeEventListener('resize', schedule)
|
||||
}
|
||||
}, [shouldBeCenteredWithToggleButton, isMobile, isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const id = requestAnimationFrame(() => setIsVisible(true))
|
||||
@@ -101,9 +172,14 @@ export const ReactionButtonsContainer = ({
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
ref={containerRef}
|
||||
aria-hidden={!isOpen}
|
||||
isVisible={isVisible}
|
||||
desktopOffset={!isMobile}
|
||||
style={
|
||||
shouldBeCenteredWithToggleButton && !isMobile
|
||||
? { marginRight: `${rightOffset}px` }
|
||||
: { margin: '0 15px' }
|
||||
}
|
||||
>
|
||||
{overflowing && (
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user