({ toast: t, geometry, reduceMotion, onDismiss, onMeasure }: ToastItemProps)
| 269 | } |
| 270 | |
| 271 | function ToastItem({ toast: t, geometry, reduceMotion, onDismiss, onMeasure }: ToastItemProps) { |
| 272 | const contentRef = useRef<HTMLDivElement>(null) |
| 273 | const [hovered, setHovered] = useState(false) |
| 274 | const Icon = VARIANT_ICON[t.variant] |
| 275 | |
| 276 | /** |
| 277 | * Report the natural height — measured on the unconstrained inner content so |
| 278 | * the outer `<li>` clamp can't feed back — so the provider lays the fan and |
| 279 | * collapsed clamp against real heights. `useLayoutEffect` avoids a paint jump. |
| 280 | */ |
| 281 | useLayoutEffect(() => { |
| 282 | const el = contentRef.current |
| 283 | if (!el) return |
| 284 | const report = () => onMeasure(t.id, el.offsetHeight + CARD_BORDER_PX) |
| 285 | report() |
| 286 | const observer = new ResizeObserver(report) |
| 287 | observer.observe(el) |
| 288 | return () => observer.disconnect() |
| 289 | }, [t.id, onMeasure]) |
| 290 | |
| 291 | const dismiss = useCallback(() => onDismiss(t.id), [onDismiss, t.id]) |
| 292 | |
| 293 | const { y, scale, height, zIndex } = geometry |
| 294 | const cornerRadius = height <= COMPACT_CARD_HEIGHT_PX ? COMPACT_RADIUS_PX : CONCENTRIC_RADIUS_PX |
| 295 | /** One fixed-duration tween so all cards reshuffle in unison; height tracks content instantly. */ |
| 296 | const transition = reduceMotion |
| 297 | ? { duration: 0 } |
| 298 | : { |
| 299 | duration: STACK_DURATION, |
| 300 | ease: TOAST_EASE, |
| 301 | height: { duration: 0 }, |
| 302 | } |
| 303 | |
| 304 | return ( |
| 305 | <motion.li |
| 306 | onMouseEnter={() => setHovered(true)} |
| 307 | onMouseLeave={() => setHovered(false)} |
| 308 | initial={reduceMotion ? false : { opacity: 0, y: height }} |
| 309 | animate={{ opacity: 1, y, scale, height }} |
| 310 | exit={{ |
| 311 | opacity: 0, |
| 312 | scale: 0.95, |
| 313 | transition: reduceMotion ? { duration: 0 } : { duration: 0.15, ease: 'easeIn' }, |
| 314 | }} |
| 315 | transition={transition} |
| 316 | style={{ zIndex, transformOrigin: 'bottom', width: TOAST_WIDTH, borderRadius: cornerRadius }} |
| 317 | className='pointer-events-auto absolute right-0 bottom-0 m-0 overflow-hidden border border-[var(--border-1)] bg-[var(--bg)] shadow-[var(--shadow-overlay)]' |
| 318 | > |
| 319 | <div ref={contentRef} className='flex flex-col gap-2 p-2'> |
| 320 | <div className='flex items-start gap-2'> |
| 321 | <div className='min-w-0 flex-1'> |
| 322 | <RevealText |
| 323 | text={t.message} |
| 324 | expanded={hovered} |
| 325 | clampLines={2} |
| 326 | lineHeightPx={20} |
| 327 | leadingIcon={ |
| 328 | <span className='mr-1.5 inline-flex h-5 items-center align-top'> |
nothing calls this directly
no test coverage detected