From c4335d2809314d6833a37d83c4e898847f718168 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 13 Aug 2026 10:39:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20implement=20hysteresis?= =?UTF-8?q?=20band=20for=20the=20control=20bar=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce dual thresholds (1100px wide, 1050px narrow) for switching the control bar between the expanded inline controls and the collapsed menu. The 50px deadband absorbs the width changes caused by rendering 5 buttons vs. 1 button, preventing an infinite layout oscillation and the resulting `ResizeObserver loop` errors. --- CHANGELOG.md | 1 + .../prefabs/ControlBar/MoreOptions.tsx | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaac76e9..6acf2696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to - 📈(frontend) downgrade unreachable external home URL from error to event - 🐛(frontend) handle 401 responses when syncing user preferences - 🐛(frontend) harden speaker test against missing sinks and play errors +- 🐛(frontend) implement hysteresis band for the control bar layout ## [1.26.0] - 2026-08-12 diff --git a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MoreOptions.tsx b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MoreOptions.tsx index 582d08c1..d7b5e064 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MoreOptions.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MoreOptions.tsx @@ -12,7 +12,8 @@ import type { ToggleButtonProps } from '@/primitives/ToggleButton' import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' -const CONTROL_BAR_BREAKPOINT = 1100 +const CONTROL_BAR_BREAKPOINT_WIDE = 1100 +const CONTROL_BAR_BREAKPOINT_NARROW = 1050 const NavigationControls = ({ onPress, @@ -65,10 +66,9 @@ export const LateralMenu = () => { ) } - interface BreakpointObserverProps { containerRef: RefObject - onWideChange: (isWide: boolean) => void + onWideChange: (isWide: boolean | null) => void } const BreakpointObserver = ({ @@ -76,7 +76,20 @@ const BreakpointObserver = ({ onWideChange, }: BreakpointObserverProps) => { const { width } = useSize(containerRef) - const isWide = width > CONTROL_BAR_BREAKPOINT + const [isWide, setIsWide] = useState(null) + + useEffect(() => { + if (!width) { + return + } + if (width > CONTROL_BAR_BREAKPOINT_WIDE) { + setIsWide(true) + } else if (width <= CONTROL_BAR_BREAKPOINT_NARROW) { + setIsWide(false) + } else { + setIsWide((prev) => (prev === null ? false : prev)) + } + }, [width]) useEffect(() => { onWideChange(isWide) @@ -90,7 +103,7 @@ export const MoreOptions = ({ }: { parentElement: RefObject }) => { - const [isWide, setIsWide] = useState(false) + const [isWide, setIsWide] = useState(null) return ( ) }