@react-spectrum/s2
Version:
Spectrum 2 UI components in React
523 lines (499 loc) • 24.1 kB
JavaScript
import "./Toast.css";
import {ActionButton as $da878a05ab4a403e$export$cfc7921d29ef7b80} from "./ActionButton.mjs";
import $e17a4836f4b6cc28$export$2e2bcd8739ae039 from "../icons/AlertTriangle.mjs";
import {Button as $067ea9f64ccd4e8e$export$353f5b6fc5456de1} from "./Button.mjs";
import {CenterBaseline as $1f4b04be3f24aae3$export$768dac55bb57081d} from "./CenterBaseline.mjs";
import $22a7d72fc05e138d$export$2e2bcd8739ae039 from "../icons/CheckmarkCircle.mjs";
import $e59ae2a33cca2ebf$export$2e2bcd8739ae039 from "../icons/ChevronDown.mjs";
import {CloseButton as $a9cda54c4f47ce52$export$de65de8213222d10} from "./CloseButton.mjs";
import $4c8f17dac3ecefd9$export$2e2bcd8739ae039 from "../icons/InfoCircle.mjs";
import $jeXmD$intlStringsmjs from "./intlStrings.mjs";
import {Text as $8e847109a6ab556d$export$5f1af8db9871e1d6} from "./Content.mjs";
import "./Toast_module.css";
import $jeXmD$Toast_modulemjs from "./Toast_module.mjs";
import {jsx as $jeXmD$jsx, jsxs as $jeXmD$jsxs} from "react/jsx-runtime";
import {createContext as $jeXmD$createContext, useRef as $jeXmD$useRef, useMemo as $jeXmD$useMemo, useEffect as $jeXmD$useEffect, useContext as $jeXmD$useContext} from "react";
import {filterDOMProps as $jeXmD$filterDOMProps, useEvent as $jeXmD$useEvent} from "@react-aria/utils";
import {flushSync as $jeXmD$flushSync} from "react-dom";
import {useModalOverlay as $jeXmD$useModalOverlay, FocusScope as $jeXmD$FocusScope} from "react-aria";
import {UNSTABLE_ToastQueue as $jeXmD$UNSTABLE_ToastQueue, UNSTABLE_ToastRegion as $jeXmD$UNSTABLE_ToastRegion, UNSTABLE_ToastList as $jeXmD$UNSTABLE_ToastList, UNSTABLE_ToastStateContext as $jeXmD$UNSTABLE_ToastStateContext, UNSTABLE_Toast as $jeXmD$UNSTABLE_Toast, UNSTABLE_ToastContent as $jeXmD$UNSTABLE_ToastContent} from "react-aria-components";
import {useLocalizedStringFormatter as $jeXmD$useLocalizedStringFormatter} from "@react-aria/i18n";
import {useMediaQuery as $jeXmD$useMediaQuery} from "@react-spectrum/utils";
import {useOverlayTriggerState as $jeXmD$useOverlayTriggerState} from "react-stately";
function $parcel$interopDefault(a) {
return a && a.__esModule ? a.default : a;
}
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
function $6e462db325878d1a$var$startViewTransition(fn, type) {
if ('startViewTransition' in document) {
// Safari doesn't support :active-view-transition-type() yet, so we fall back to a class on the html element.
document.documentElement.classList.add((0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[type]);
let viewTransition = document.startViewTransition({
update: ()=>(0, $jeXmD$flushSync)(fn),
types: [
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[type]
]
});
viewTransition.ready.catch(()=>{});
viewTransition.finished.then(()=>{
document.documentElement.classList.remove((0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[type]);
});
} else fn();
}
// There is a single global toast queue instance for the whole app, initialized lazily.
let $6e462db325878d1a$var$globalToastQueue = null;
function $6e462db325878d1a$var$getGlobalToastQueue() {
if (!$6e462db325878d1a$var$globalToastQueue) $6e462db325878d1a$var$globalToastQueue = new (0, $jeXmD$UNSTABLE_ToastQueue)({
maxVisibleToasts: Infinity,
wrapUpdate (fn, action) {
$6e462db325878d1a$var$startViewTransition(fn, `toast-${action}`);
}
});
return $6e462db325878d1a$var$globalToastQueue;
}
function $6e462db325878d1a$var$addToast(children, variant, options = {}) {
let value = {
children: children,
variant: variant,
actionLabel: options.actionLabel,
onAction: options.onAction,
shouldCloseOnAction: options.shouldCloseOnAction,
...(0, $jeXmD$filterDOMProps)(options)
};
// Minimum time of 5s from https://spectrum.adobe.com/page/toast/#Auto-dismissible
// Actionable toasts cannot be auto dismissed. That would fail WCAG SC 2.2.1.
// It is debatable whether non-actionable toasts would also fail.
let timeout = options.timeout && !options.actionLabel ? Math.max(options.timeout, 5000) : undefined;
let queue = $6e462db325878d1a$var$getGlobalToastQueue();
let key = queue.add(value, {
timeout: timeout,
onClose: options.onClose
});
return ()=>queue.close(key);
}
const $6e462db325878d1a$export$f1f8569633bbbec4 = {
/** Queues a neutral toast. */ neutral (children, options = {}) {
return $6e462db325878d1a$var$addToast(children, 'neutral', options);
},
/** Queues a positive toast. */ positive (children, options = {}) {
return $6e462db325878d1a$var$addToast(children, 'positive', options);
},
/** Queues a negative toast. */ negative (children, options = {}) {
return $6e462db325878d1a$var$addToast(children, 'negative', options);
},
/** Queues an informational toast. */ info (children, options = {}) {
return $6e462db325878d1a$var$addToast(children, 'info', options);
}
};
const $6e462db325878d1a$var$toastRegion = function anonymous(props) {
let rules = " ";
if (props.isFocusVisible) rules += ' _Lf91';
else rules += ' _Le91';
rules += ' Oh91';
rules += ' _Mc91';
rules += ' _Kd91';
rules += ' sd91';
if (props.placement === "bottom") rules += ' _tb91';
else if (props.placement === "top") rules += ' _ta91';
rules += ' _Pb91';
rules += ' _Ar91';
rules += ' _zr91';
rules += ' ZJ91';
if (props.placement === "top") {
if (props.isExpanded) rules += ' Wr91';
else rules += ' Wu91';
}
if (props.placement === "bottom") {
if (props.isExpanded) rules += ' _lr91';
else rules += ' _lu91';
}
if (props.align === "end") rules += ' IM91';
else if (props.align === "center") rules += ' IM91';
else if (props.align === "start") rules += ' IC91';
if (props.align === "end") rules += ' HC91';
else if (props.align === "center") rules += ' HM91';
else if (props.align === "start") rules += ' HM91';
rules += ' _oa91';
rules += ' Kb91';
rules += ' oc91';
rules += ' nc91';
rules += ' kc91';
rules += ' jc91';
return rules;
};
const $6e462db325878d1a$var$toastList = function anonymous(props) {
let rules = " ";
rules += ' _Pc91';
rules += ' _ub91';
rules += ' sd91';
rules += ' Ue91';
rules += ' qe91';
if (props.placement === "bottom") rules += ' _tb91';
else if (props.placement === "top") rules += ' _ta91';
rules += ' _oa91';
rules += ' Jy91';
rules += ' Gy91';
if (props.isExpanded) {
if (props.placement === "bottom") rules += ' Tt91';
else if (props.placement === "top") rules += ' Tf91';
}
if (props.isExpanded) {
if (props.placement === "bottom") rules += ' Qf91';
else if (props.placement === "top") rules += ' Qt91';
}
if (props.isExpanded) rules += ' St91';
else rules += ' Sd91';
if (props.isExpanded) rules += ' Rt91';
else rules += ' Rd91';
if (props.isExpanded) rules += ' Ic91';
else rules += ' Iy91';
if (props.isExpanded) rules += ' Hc91';
else rules += ' Hy91';
if (props.isExpanded) rules += ' _Na91';
if (props.isExpanded) rules += ' Pa91';
return rules;
};
const $6e462db325878d1a$var$toastStyle = function anonymous(props) {
let rules = " ";
if (props.isFocusVisible) rules += ' _Lf91';
else rules += ' _Le91';
if (props.isExpanded) rules += ' Ok91';
else rules += ' Oh91';
rules += ' _Mc91';
rules += ' _Kd91';
rules += ' sd91';
rules += ' Ul91';
rules += ' ql91';
rules += ' Sf91';
rules += ' Rt91';
rules += ' Te91';
rules += ' Qe91';
rules += ' oc91';
rules += ' nc91';
rules += ' kc91';
rules += ' jc91';
rules += ' Mj91';
rules += ' -Zn3gsb-L0uB6Qb91';
rules += ' LksArhc91';
rules += ' _oa91';
rules += ' _va91';
rules += ' ug91';
rules += ' uch91';
rules += ' udi91';
rules += ' uea91';
rules += ' ugb91';
rules += ' uhd91';
rules += ' uje91';
rules += ' uic91';
rules += ' vd91';
rules += ' vsf91';
rules += ' wb91';
rules += ' xb91';
rules += ' _xa91';
rules += ' _Fa91';
rules += ' _Ffb91';
rules += ' px91';
if (props.variant === "negative") rules += ' gB91';
else if (props.variant === "positive") rules += ' g191';
else if (props.variant === "info") rules += ' g291';
else if (props.variant === "neutral") rules += ' g691';
rules += ' -_8sjo0b-t5ZbAob91';
if (props.isExpanded) rules += ' _nd91';
else rules += ' _nb91';
return rules;
};
const $6e462db325878d1a$var$toastBody = function anonymous(props) {
let rules = " ";
if (props.isSingle) rules += ' sd91';
else rules += ' se91';
rules += ' D4w1sLc91';
rules += ' CWLL0Xb91';
rules += ' _ub91';
rules += ' _wb91';
rules += ' eb91';
rules += ' qj91';
rules += ' Ue91';
return rules;
};
const $6e462db325878d1a$var$toastContent = " sd91 Ue91 qe91 ea91 zk52g2d91 yk52g2d91 Bk52g2d91 Ak52g2d91 ri91 ZJ91";
const $6e462db325878d1a$var$controls = function anonymous(props) {
let rules = " ";
rules += ' _pb91';
if (props.isExpanded) rules += ' sd91';
else rules += ' sk91';
rules += ' _Cb91';
rules += ' Ue91';
rules += ' qe91';
if (props.isExpanded) rules += ' _Ib91';
else rules += ' _Ia91';
return rules;
};
const $6e462db325878d1a$var$ICONS = {
info: (0, $4c8f17dac3ecefd9$export$2e2bcd8739ae039),
negative: (0, $e17a4836f4b6cc28$export$2e2bcd8739ae039),
positive: (0, $22a7d72fc05e138d$export$2e2bcd8739ae039)
};
const $6e462db325878d1a$var$ToastContainerContext = /*#__PURE__*/ (0, $jeXmD$createContext)(null);
function $6e462db325878d1a$export$f2815235e76a62b9(props) {
let { placement: placement = 'bottom' } = props;
let queue = $6e462db325878d1a$var$getGlobalToastQueue();
let align = 'center';
[placement, align = 'center'] = placement.split(' ');
let stringFormatter = (0, $jeXmD$useLocalizedStringFormatter)((0, ($parcel$interopDefault($jeXmD$intlStringsmjs))), '-spectrum/s2');
let regionRef = (0, $jeXmD$useRef)(null);
let state = (0, $jeXmD$useOverlayTriggerState)({});
let { isOpen: isExpanded, close: close, toggle: toggle } = state;
let ctx = (0, $jeXmD$useMemo)(()=>({
isExpanded: isExpanded,
toggleExpanded () {
if (!isExpanded && queue.visibleToasts.length <= 1) return;
$6e462db325878d1a$var$startViewTransition(()=>toggle(), isExpanded ? 'toast-collapse' : 'toast-expand');
}
}), [
isExpanded,
toggle,
queue
]);
// Set the state to collapsed whenever the queue is emptied.
(0, $jeXmD$useEffect)(()=>{
return queue.subscribe(()=>{
if (queue.visibleToasts.length === 0 && isExpanded) close();
});
}, [
queue,
isExpanded,
close
]);
let collapse = ()=>{
regionRef.current?.focus();
ctx.toggleExpanded();
};
// Prevent scroll, aria hide outside, and contain focus when expanded, since we take over the whole screen.
// Attach event handler to the ref since ToastRegion doesn't pass through onKeyDown.
(0, $jeXmD$useModalOverlay)({}, state, regionRef);
(0, $jeXmD$useEvent)(regionRef, 'keydown', isExpanded ? (e)=>{
if (e.key === 'Escape') collapse();
} : undefined);
return /*#__PURE__*/ (0, $jeXmD$jsx)((0, $jeXmD$UNSTABLE_ToastRegion), {
...props,
ref: regionRef,
queue: queue,
className: (renderProps)=>$6e462db325878d1a$var$toastRegion({
...renderProps,
placement: placement,
align: align,
isExpanded: isExpanded
}),
children: /*#__PURE__*/ (0, $jeXmD$jsx)((0, $jeXmD$FocusScope), {
contain: isExpanded,
children: /*#__PURE__*/ (0, $jeXmD$jsxs)($6e462db325878d1a$var$ToastContainerContext.Provider, {
value: ctx,
children: [
isExpanded && // eslint-disable-next-line
/*#__PURE__*/ (0, $jeXmD$jsx)("div", {
className: (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-background'] + " _Pb91 Wr91 _lr91 _Ar91 _zr91 g891",
onClick: collapse
}),
/*#__PURE__*/ (0, $jeXmD$jsx)($6e462db325878d1a$var$SpectrumToastList, {
placement: placement,
align: align
}),
/*#__PURE__*/ (0, $jeXmD$jsxs)("div", {
className: (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-controls'] + $6e462db325878d1a$var$controls({
isExpanded: isExpanded
}),
children: [
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $da878a05ab4a403e$export$cfc7921d29ef7b80), {
size: "S",
onPress: ()=>queue.clear(),
// Default focus ring does not have enough contrast against gray background.
UNSAFE_style: {
outlineColor: 'white'
},
children: stringFormatter.format('toast.clearAll')
}),
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $da878a05ab4a403e$export$cfc7921d29ef7b80), {
size: "S",
onPress: collapse,
UNSAFE_style: {
outlineColor: 'white'
},
children: stringFormatter.format('toast.collapse')
})
]
})
]
})
})
});
}
function $6e462db325878d1a$var$SpectrumToastList({ placement: placement, align: align }) {
let { isExpanded: isExpanded, toggleExpanded: toggleExpanded } = (0, $jeXmD$useContext)($6e462db325878d1a$var$ToastContainerContext);
// Attach click handler to ref since ToastList doesn't pass through onClick/onPress.
let toastListRef = (0, $jeXmD$useRef)(null);
(0, $jeXmD$useEvent)(toastListRef, 'click', (e)=>{
// Have to check if this is a button because stopPropagation in react events doesn't affect native events.
if (!isExpanded && !e.target?.closest('button')) toggleExpanded();
});
let reduceMotion = (0, $jeXmD$useMediaQuery)('(prefers-reduced-motion)');
return /*#__PURE__*/ (0, $jeXmD$jsx)((0, $jeXmD$UNSTABLE_ToastList), {
ref: toastListRef,
style: ({ isHovered: isHovered })=>{
let origin = isHovered ? 95 : 55;
return {
perspective: 80,
perspectiveOrigin: 'center ' + (placement === 'top' ? `calc(100% + ${origin}px)` : `${-origin}px`),
transition: 'perspective-origin 400ms'
};
},
className: (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[isExpanded ? 'toast-list-expanded' : 'toast-list-collapsed'] + $6e462db325878d1a$var$toastList({
placement: placement,
align: align,
isExpanded: isExpanded
}),
children: ({ toast: toast })=>/*#__PURE__*/ (0, $jeXmD$jsx)($6e462db325878d1a$export$f9b1e1c88454fa08, {
toast: toast,
placement: placement,
align: align,
reduceMotion: reduceMotion
})
});
}
function $6e462db325878d1a$export$f9b1e1c88454fa08(props) {
let { toast: toast, placement: placement = 'bottom', align: align = 'center' } = props;
let variant = toast.content.variant || 'info';
let Icon = $6e462db325878d1a$var$ICONS[variant];
let state = (0, $jeXmD$useContext)((0, $jeXmD$UNSTABLE_ToastStateContext));
let visibleToasts = state.visibleToasts;
let index = visibleToasts.indexOf(toast);
let isMain = index <= 0;
let ctx = (0, $jeXmD$useContext)($6e462db325878d1a$var$ToastContainerContext);
let isExpanded = ctx?.isExpanded || false;
let toastRef = (0, $jeXmD$useRef)(null);
let stringFormatter = (0, $jeXmD$useLocalizedStringFormatter)((0, ($parcel$interopDefault($jeXmD$intlStringsmjs))), '-spectrum/s2');
// When not expanded, use a presentational div for the toasts behind the top one.
// The content is invisible, all we show is the background color.
if (!isMain && ctx && !ctx.isExpanded) return /*#__PURE__*/ (0, $jeXmD$jsx)("div", {
role: "presentation",
style: {
position: 'absolute',
[placement === 'top' ? 'bottom' : 'top']: 0,
left: 0,
width: '100%',
translate: `0 0 ${-12 * index / 16}rem`,
// Only 3 toasts are visible in the stack at once, but all toasts are in the DOM.
// This allows view transitions to smoothly animate them from where they would be
// in the collapsed stack to their final position in the expanded list.
opacity: index >= 3 ? 0 : 1,
zIndex: visibleToasts.length - index - 1,
// When reduced motion is enabled, use append index to view-transition-name
// so that adding/removing a toast cross fades instead of transitioning the position.
// This works because the toasts are seen as separate elements instead of the same one when their index changes.
viewTransitionName: toast.key + (props.reduceMotion ? '-' + index : ''),
viewTransitionClass: [
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs))).toast,
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['background-toast']
].map((c)=>CSS.escape(c)).join(' ')
},
className: (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs))).toast + $6e462db325878d1a$var$toastStyle({
variant: toast.content.variant || 'info',
index: index,
isExpanded: isExpanded
})
});
return /*#__PURE__*/ (0, $jeXmD$jsxs)((0, $jeXmD$UNSTABLE_Toast), {
ref: toastRef,
toast: toast,
style: {
zIndex: visibleToasts.length - index - 1,
viewTransitionName: toast.key,
viewTransitionClass: [
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs))).toast,
!isMain ? (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['background-toast'] : '',
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[placement],
(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))[align]
].filter(Boolean).map((c)=>CSS.escape(c)).join(' ')
},
className: (renderProps)=>(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs))).toast + $6e462db325878d1a$var$toastStyle({
...renderProps,
variant: toast.content.variant || 'info',
index: index,
isExpanded: isExpanded
}),
children: [
/*#__PURE__*/ (0, $jeXmD$jsxs)("div", {
role: "presentation",
className: $6e462db325878d1a$var$toastBody({
isSingle: !isMain || visibleToasts.length <= 1 || isExpanded
}),
children: [
/*#__PURE__*/ (0, $jeXmD$jsxs)((0, $jeXmD$UNSTABLE_ToastContent), {
className: $6e462db325878d1a$var$toastContent + (ctx && isMain ? ` ${(0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-content']}` : null),
children: [
Icon && /*#__PURE__*/ (0, $jeXmD$jsx)((0, $1f4b04be3f24aae3$export$768dac55bb57081d), {
children: /*#__PURE__*/ (0, $jeXmD$jsx)(Icon, {})
}),
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $8e847109a6ab556d$export$5f1af8db9871e1d6), {
slot: "title",
children: toast.content.children
})
]
}),
!isExpanded && visibleToasts.length > 1 && /*#__PURE__*/ (0, $jeXmD$jsxs)((0, $da878a05ab4a403e$export$cfc7921d29ef7b80), {
isQuiet: true,
staticColor: "white",
styles: " zPgKvMe91 yPgKvMe91 BPgKvMe91 APgKvMe91",
// Make the chevron line up with the toast text, even though there is padding within the button.
UNSAFE_style: {
marginInlineStart: variant === 'neutral' ? -10 : 14
},
UNSAFE_className: ctx && isMain ? (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-expand'] : undefined,
onPress: ()=>{
// This button disappears when clicked, so move focus to the toast.
toastRef.current?.focus();
ctx?.toggleExpanded();
},
children: [
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $8e847109a6ab556d$export$5f1af8db9871e1d6), {
children: stringFormatter.format('toast.showAll')
}),
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $e59ae2a33cca2ebf$export$2e2bcd8739ae039), {
UNSAFE_style: {
rotate: placement === 'bottom' ? '180deg' : undefined
}
})
]
}),
toast.content.actionLabel && /*#__PURE__*/ (0, $jeXmD$jsx)((0, $067ea9f64ccd4e8e$export$353f5b6fc5456de1), {
variant: "secondary",
fillStyle: "outline",
staticColor: "white",
onPress: ()=>{
toast.content.onAction?.();
if (toast.content.shouldCloseOnAction) state.close(toast.key);
},
UNSAFE_className: ctx && isMain ? (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-action'] : undefined,
styles: " IM91 zzz3eAe91 yzz3eAe91 Bzz3eAe91 Azz3eAe91",
children: toast.content.actionLabel
})
]
}),
/*#__PURE__*/ (0, $jeXmD$jsx)((0, $a9cda54c4f47ce52$export$de65de8213222d10), {
staticColor: "white",
UNSAFE_className: ctx && isMain ? (0, ($parcel$interopDefault($jeXmD$Toast_modulemjs)))['toast-close'] : undefined
})
]
});
}
export {$6e462db325878d1a$export$f1f8569633bbbec4 as ToastQueue, $6e462db325878d1a$export$f2815235e76a62b9 as ToastContainer, $6e462db325878d1a$export$f9b1e1c88454fa08 as SpectrumToast};
//# sourceMappingURL=Toast.mjs.map