Retained tabs stay mounted under display:none while inactive, which reports a zero-width onLayout. ControlledAgentControls fed that straight into its density resolver, so a hidden tab's toolbar collapsed to its tightest density and painted one frame collapsed before re-expanding when the tab became visible again. Extract the zero-width guard that useContainerWidthBelow already had into a shared readMeasuredWidth helper, and use it in the composer toolbar's own layout handler so a display:none measurement can never drive its density. Also bump the chat outline rail's MIN_PANEL_WIDTH breakpoint (818 -> 918) so the rail disappears earlier under a narrower panel, and widen the chat-outline e2e spec's viewport so its panel keeps healthy margin over the new threshold.
58 lines
1.5 KiB
TypeScript
58 lines
1.5 KiB
TypeScript
import { useCallback, useState } from "react";
|
|
import type { LayoutChangeEvent } from "react-native";
|
|
|
|
/**
|
|
* Reads a real width out of a layout event. Retained tabs stay mounted under
|
|
* `display: none`, so an inactive panel emits width 0 — that is "not rendered",
|
|
* not "very narrow", and must never drive a width-based collapse.
|
|
*/
|
|
export function readMeasuredWidth(event: LayoutChangeEvent): number | null {
|
|
const width = event.nativeEvent.layout.width;
|
|
return width > 0 ? width : null;
|
|
}
|
|
|
|
/**
|
|
* Tracks the width of a container via onLayout.
|
|
*/
|
|
export function useContainerWidth(): {
|
|
onLayout: (e: LayoutChangeEvent) => void;
|
|
width: number;
|
|
} {
|
|
const [width, setWidth] = useState(0);
|
|
return {
|
|
onLayout: useCallback((e: LayoutChangeEvent) => {
|
|
setWidth(e.nativeEvent.layout.width);
|
|
}, []),
|
|
width,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Tracks only whether a container is narrower than a threshold.
|
|
*/
|
|
export function useContainerWidthBelow(
|
|
threshold: number,
|
|
options?: { initialIsBelow?: boolean },
|
|
): {
|
|
onLayout: (e: LayoutChangeEvent) => void;
|
|
isBelow: boolean;
|
|
} {
|
|
const [isBelow, setIsBelow] = useState(options?.initialIsBelow ?? true);
|
|
return {
|
|
onLayout: useCallback(
|
|
(e: LayoutChangeEvent) => {
|
|
const width = readMeasuredWidth(e);
|
|
if (width === null) {
|
|
return;
|
|
}
|
|
const nextIsBelow = width < threshold;
|
|
setIsBelow((currentIsBelow) =>
|
|
currentIsBelow === nextIsBelow ? currentIsBelow : nextIsBelow,
|
|
);
|
|
},
|
|
[threshold],
|
|
),
|
|
isBelow,
|
|
};
|
|
}
|