fix(web): review-round attachment refinements
Body-portal the lightbox and toast so transformed ancestors cannot trap their fixed positioning (a lightbox opened from a chat message covered only the chat column); make the toast pointer-transparent; observe the rail element's own size instead of window resizes; consume vertical wheel ticks exclusively via a non-passive listener with LINE/PAGE delta normalization; keep the start position when the rail mounts over an existing draft; honor prefers-reduced-motion for the toast, remove-control, and paging; retry loads through the guarded load effect; note the deliberate promptError re-announce; pin the intake toast in the assembled snapshot; sync the superseded multimodal note and package docs.
This commit is contained in:
@@ -11,6 +11,10 @@
|
||||
/* Above the 1000 the image lightbox backdrop uses: a failure reported while
|
||||
a preview is open must stay readable. */
|
||||
z-index: 1100;
|
||||
/* Purely an announcement: it must never intercept clicks — in particular
|
||||
after the CSS fade finished while a throttled background-tab timer has
|
||||
not yet unmounted the still-hit-testable fixed element. */
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -56,3 +60,11 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduced motion drops the slide-in; the delayed fade (an opacity change,
|
||||
not movement) still ends the banner before the timed unmount. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.toast {
|
||||
animation: dsh-toast-fade 1000ms ease 3000ms forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import css from './Toast.module.css'
|
||||
|
||||
/** Full-opacity hold before the fade starts. Must agree with the stylesheet's
|
||||
@@ -12,7 +13,9 @@ const FADE_MS = 1000
|
||||
* Transient top-center banner: slides in, holds at full opacity, fades out,
|
||||
* then reports done so the owner can unmount it. Re-showing the same text
|
||||
* restarts the cycle when the owner remounts the component (key it by a
|
||||
* per-show sequence).
|
||||
* per-show sequence). Rendered through a body portal so an owner inside a
|
||||
* transformed or filtered ancestor cannot trap the fixed banner in that
|
||||
* ancestor's box.
|
||||
*
|
||||
* @param props.text - resolved banner copy; the owner passes localized text.
|
||||
* @param props.icon - optional leading glyph (e.g. a warning icon).
|
||||
@@ -28,10 +31,11 @@ export function Toast({ text, icon, onDone }: {
|
||||
const timer = setTimeout(onDone, HOLD_MS + FADE_MS)
|
||||
return () => { clearTimeout(timer) }
|
||||
}, [onDone])
|
||||
return (
|
||||
return createPortal(
|
||||
<div className={css.toast} role="alert">
|
||||
{icon !== undefined && <span className={css.icon} aria-hidden>{icon}</span>}
|
||||
<span className={css.text}>{text}</span>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user