🐛(frontend) implement hysteresis band for the control bar layout

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.
This commit is contained in:
lebaudantoine
2026-08-13 10:39:01 +02:00
parent ac503b3ae5
commit c4335d2809
2 changed files with 20 additions and 6 deletions
+1
View File
@@ -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
@@ -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 = () => {
</DialogTrigger>
)
}
interface BreakpointObserverProps {
containerRef: RefObject<HTMLDivElement>
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<boolean | null>(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<HTMLDivElement>
}) => {
const [isWide, setIsWide] = useState(false)
const [isWide, setIsWide] = useState<boolean | null>(null)
return (
<nav
@@ -107,7 +120,7 @@ export const MoreOptions = ({
containerRef={parentElement}
onWideChange={setIsWide}
/>
{isWide ? <NavigationControls /> : <LateralMenu />}
{isWide !== null && (isWide ? <NavigationControls /> : <LateralMenu />)}
</nav>
)
}