fix(gui): address review — stale smoke case, tooltip trigger overlap, collapse contract docs
- The real-browser collapse smoke tracked the old chrome: visible HARNESS text (the wordmark svg is aria-hidden now), an 'Expand sidebar' label (renamed 'Open sidebar'), a 300px settle (default is 280), and an immediate focus assert (rail search defers focus past the slide). The case now tracks the brand span, polls the deferred focus, and uses the current labels and width. - Tooltip treated hover and focus as one trigger: leaving with the mouse dropped the bubble of a still-focused anchor (and vice versa). The two triggers are tracked independently; the bubble hides only after both clear. Spec pins both orders. - The ui-sidebar README and the bilingual collapse note still described the retired geometry morph; both now state the slide + crossfade contract, the fixed-width (never-conceding) sidebar, and the rail's whale-mark/tooltip chrome.
This commit is contained in:
@@ -34,11 +34,14 @@ interface AnchorProps {
|
||||
export function Tooltip({ label, side = 'right', disabled = false, children }: { label: string; side?: TooltipSide; disabled?: boolean; children: ReactElement<AnchorProps> }) {
|
||||
const anchor = useRef<HTMLElement | null>(null)
|
||||
const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
|
||||
// Hover and focus are independent triggers: the bubble hides only after
|
||||
// BOTH clear (hovering away from a focused anchor must not drop it).
|
||||
const triggers = useRef({ hover: false, focus: false })
|
||||
|
||||
// Disabling mid-hover (e.g. clicking a rail control expands the sidebar)
|
||||
// must drop an already-visible bubble: no mouseleave fires.
|
||||
useEffect(() => {
|
||||
if (disabled) setPos(null)
|
||||
if (disabled) { triggers.current = { hover: false, focus: false }; setPos(null) }
|
||||
}, [disabled])
|
||||
|
||||
const show = () => {
|
||||
@@ -51,16 +54,18 @@ export function Tooltip({ label, side = 'right', disabled = false, children }: {
|
||||
? { x: r.right + 10, y: r.top + r.height / 2 }
|
||||
: { x: r.left + r.width / 2, y: r.bottom + 8 })
|
||||
}
|
||||
const hide = () => { setPos(null) }
|
||||
const hide = () => {
|
||||
if (!triggers.current.hover && !triggers.current.focus) setPos(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(children, {
|
||||
ref: anchor,
|
||||
onMouseEnter: (e) => { children.props.onMouseEnter?.(e); show() },
|
||||
onMouseLeave: (e) => { children.props.onMouseLeave?.(e); hide() },
|
||||
onFocus: (e) => { children.props.onFocus?.(e); show() },
|
||||
onBlur: (e) => { children.props.onBlur?.(e); hide() },
|
||||
onMouseEnter: (e) => { children.props.onMouseEnter?.(e); triggers.current.hover = true; show() },
|
||||
onMouseLeave: (e) => { children.props.onMouseLeave?.(e); triggers.current.hover = false; hide() },
|
||||
onFocus: (e) => { children.props.onFocus?.(e); triggers.current.focus = true; show() },
|
||||
onBlur: (e) => { children.props.onBlur?.(e); triggers.current.focus = false; hide() },
|
||||
})}
|
||||
{pos !== null && (
|
||||
<span className={css.bubble} data-side={side} style={{ left: pos.x, top: pos.y }} role="tooltip">
|
||||
|
||||
Reference in New Issue
Block a user