UNPKG

@hypothesis/frontend-shared

Version:

Shared components, styles and utilities for Hypothesis projects

155 lines 5.47 kB
var _jsxFileName = "/home/runner/work/frontend-shared/frontend-shared/src/components/feedback/ToastMessages.tsx"; import classnames from 'classnames'; import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks'; import Callout from './Callout'; import { jsxDEV as _jsxDEV } from "preact/jsx-dev-runtime"; /** * An individual toast message: a brief and transient success or error message. * The message may be dismissed by clicking on it. `visuallyHidden` toast * messages will not be visible but are still available to screen readers. */ function ToastMessageItem({ message, onDismiss }) { return _jsxDEV(Callout, { classes: classnames({ 'sr-only': message.visuallyHidden }), status: message.type, onClick: () => onDismiss(message.id), variant: "raised", children: message.message }, void 0, false, { fileName: _jsxFileName, lineNumber: 55, columnNumber: 5 }, this); } const ToastMessageTransition = ({ direction, onTransitionEnd, children, transitionClasses = {} }) => { const isDismissed = direction === 'out'; const containerRef = useRef(null); const handleAnimation = e => { // Ignore animations happening on child elements if (e.target !== containerRef.current) { return; } onTransitionEnd === null || onTransitionEnd === void 0 || onTransitionEnd(direction !== null && direction !== void 0 ? direction : 'in'); }; const classes = useMemo(() => { const { transitionIn = 'animate-fade-in', transitionOut = 'animate-fade-out' } = transitionClasses; return { [transitionIn]: !isDismissed, [transitionOut]: isDismissed }; }, [isDismissed, transitionClasses]); return _jsxDEV("div", { "data-testid": "animation-container", onAnimationEnd: handleAnimation, ref: containerRef, className: classnames('relative w-full container', classes), children: children }, void 0, false, { fileName: _jsxFileName, lineNumber: 103, columnNumber: 5 }, this); }; /** * A collection of toast messages. These are rendered within an `aria-live` * region for accessibility with screen readers. */ export default function ToastMessages({ messages, onMessageDismiss, transitionClasses, /* istanbul ignore next - test seam */ setTimeout_ = setTimeout }) { // List of IDs of toast messages that have been dismissed and have an // in-progress 'out' transition const [dismissedMessages, setDismissedMessages] = useState([]); // Tracks not finished timeouts for auto-dismiss toast messages const messageSchedules = useRef(new Map()); const dismissMessage = useCallback(id => setDismissedMessages(ids => [...ids, id]), []); const scheduleMessageDismiss = useCallback(id => { const timeout = setTimeout_(() => { dismissMessage(id); messageSchedules.current.delete(id); }, 5000); messageSchedules.current.set(id, timeout); }, [dismissMessage, setTimeout_]); const onTransitionEnd = useCallback((direction, message) => { var _message$autoDismiss; const autoDismiss = (_message$autoDismiss = message.autoDismiss) !== null && _message$autoDismiss !== void 0 ? _message$autoDismiss : true; if (direction === 'in' && autoDismiss) { scheduleMessageDismiss(message.id); } if (direction === 'out') { onMessageDismiss(message.id); setDismissedMessages(ids => ids.filter(id => id !== message.id)); } }, [scheduleMessageDismiss, onMessageDismiss]); useLayoutEffect(() => { // Clear all pending timeouts for not yet dismissed toast messages when the // component is unmounted const pendingTimeouts = messageSchedules.current; return () => { pendingTimeouts.forEach(timeout => clearTimeout(timeout)); }; }, []); return _jsxDEV("ul", { "aria-live": "polite", "aria-relevant": "additions", className: "w-full space-y-2", "data-component": "ToastMessages", children: messages.map(message => { const isDismissed = dismissedMessages.includes(message.id); return _jsxDEV("li", { className: classnames({ // Add a bottom margin to visible messages only. Typically, we'd // use a `space-y-2` class on the parent to space children. // Doing that here could cause an undesired top margin on // the first visible message in a list that contains (only) // visually-hidden messages before it. // See https://tailwindcss.com/docs/space#limitations 'mb-2': !message.visuallyHidden }), children: _jsxDEV(ToastMessageTransition, { direction: isDismissed ? 'out' : 'in', onTransitionEnd: direction => onTransitionEnd(direction, message), transitionClasses: transitionClasses, children: _jsxDEV(ToastMessageItem, { message: message, onDismiss: dismissMessage }, void 0, false, { fileName: _jsxFileName, lineNumber: 206, columnNumber: 15 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 201, columnNumber: 13 }, this) }, message.id, false, { fileName: _jsxFileName, lineNumber: 189, columnNumber: 11 }, this); }) }, void 0, false, { fileName: _jsxFileName, lineNumber: 180, columnNumber: 5 }, this); } //# sourceMappingURL=ToastMessages.js.map