@react-spectrum/s2
Version:
Spectrum 2 UI components in React
550 lines (524 loc) • 25.2 kB
JavaScript
require("./Toast.css");
var $6e265ff388155b91$exports = require("./ActionButton.cjs");
var $7e7cdbd2b8ae2467$exports = require("../icons/AlertTriangle.cjs");
var $25d06cf8d4e72761$exports = require("./Button.cjs");
var $e991cbcdf82ced71$exports = require("./CenterBaseline.cjs");
var $6391be254c189366$exports = require("../icons/CheckmarkCircle.cjs");
var $d88edd0de6bc65f6$exports = require("../icons/ChevronDown.cjs");
var $2f907cb84c6e9e75$exports = require("./CloseButton.cjs");
var $0ed6e07b499b9797$exports = require("../icons/InfoCircle.cjs");
var $4526404114e78c80$exports = require("./intlStrings.cjs");
var $6367bc87eb7d24ad$exports = require("./Content.cjs");
require("./Toast_module.css");
var $aa16d99d2c9699f6$exports = require("./Toast_module.cjs");
var $fI0QS$reactjsxruntime = require("react/jsx-runtime");
var $fI0QS$react = require("react");
var $fI0QS$reactariautils = require("@react-aria/utils");
var $fI0QS$reactdom = require("react-dom");
var $fI0QS$reactaria = require("react-aria");
var $fI0QS$reactariacomponents = require("react-aria-components");
var $fI0QS$reactariai18n = require("@react-aria/i18n");
var $fI0QS$reactspectrumutils = require("@react-spectrum/utils");
var $fI0QS$reactstately = require("react-stately");
function $parcel$interopDefault(a) {
return a && a.__esModule ? a.default : a;
}
function $parcel$export(e, n, v, s) {
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
}
$parcel$export(module.exports, "ToastQueue", () => $f1992060af7549d9$export$f1f8569633bbbec4);
$parcel$export(module.exports, "ToastContainer", () => $f1992060af7549d9$export$f2815235e76a62b9);
/*
* 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.
*/
let $f1992060af7549d9$var$globalReduceMotion = false;
function $f1992060af7549d9$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($aa16d99d2c9699f6$exports)))[type]);
if ($f1992060af7549d9$var$globalReduceMotion) document.documentElement.classList.add((0, ($parcel$interopDefault($aa16d99d2c9699f6$exports))).reduceMotion);
let viewTransition = document.startViewTransition(()=>(0, $fI0QS$reactdom.flushSync)(fn));
viewTransition.ready.catch(()=>{});
viewTransition.finished.then(()=>{
document.documentElement.classList.remove((0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))[type], (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports))).reduceMotion);
});
} else fn();
}
// There is a single global toast queue instance for the whole app, initialized lazily.
let $f1992060af7549d9$var$globalToastQueue = null;
function $f1992060af7549d9$var$getGlobalToastQueue() {
if (!$f1992060af7549d9$var$globalToastQueue) $f1992060af7549d9$var$globalToastQueue = new (0, $fI0QS$reactariacomponents.UNSTABLE_ToastQueue)({
maxVisibleToasts: Infinity,
wrapUpdate (fn, action) {
$f1992060af7549d9$var$startViewTransition(fn, `toast-${action}`);
}
});
return $f1992060af7549d9$var$globalToastQueue;
}
function $f1992060af7549d9$var$addToast(children, variant, options = {}) {
let value = {
children: children,
variant: variant,
actionLabel: options.actionLabel,
onAction: options.onAction,
shouldCloseOnAction: options.shouldCloseOnAction,
...(0, $fI0QS$reactariautils.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 = $f1992060af7549d9$var$getGlobalToastQueue();
let key = queue.add(value, {
timeout: timeout,
onClose: options.onClose
});
return ()=>queue.close(key);
}
const $f1992060af7549d9$export$f1f8569633bbbec4 = {
/** Queues a neutral toast. */ neutral (children, options = {}) {
return $f1992060af7549d9$var$addToast(children, 'neutral', options);
},
/** Queues a positive toast. */ positive (children, options = {}) {
return $f1992060af7549d9$var$addToast(children, 'positive', options);
},
/** Queues a negative toast. */ negative (children, options = {}) {
return $f1992060af7549d9$var$addToast(children, 'negative', options);
},
/** Queues an informational toast. */ info (children, options = {}) {
return $f1992060af7549d9$var$addToast(children, 'info', options);
}
};
const $f1992060af7549d9$var$toastRegion = function anonymous(props) {
let rules = " ";
if (props.isFocusVisible) rules += ' _Lf1';
else rules += ' _Le1';
rules += ' Oh1';
rules += ' Olc1';
rules += ' _Mc1';
rules += ' _Kd1';
rules += ' sd1';
if (props.placement === "bottom") rules += ' _tb1';
else if (props.placement === "top") rules += ' _ta1';
rules += ' _Pb1';
rules += ' _Ar1';
rules += ' _zr1';
rules += ' ZJ1';
if (props.placement === "top") {
if (props.isExpanded) rules += ' Wr1';
else rules += ' Wu1';
}
if (props.placement === "bottom") {
if (props.isExpanded) rules += ' _lr1';
else rules += ' _lu1';
}
if (props.align === "end") rules += ' IM1';
else if (props.align === "center") rules += ' IM1';
else if (props.align === "start") rules += ' IC1';
if (props.align === "end") rules += ' HC1';
else if (props.align === "center") rules += ' HM1';
else if (props.align === "start") rules += ' HM1';
rules += ' _oa1';
rules += ' Ka1';
rules += ' oc1';
rules += ' nc1';
rules += ' kc1';
rules += ' jc1';
return rules;
};
const $f1992060af7549d9$var$toastList = function anonymous(props) {
let rules = " ";
rules += ' _Pc1';
rules += ' _ub1';
rules += ' sd1';
rules += ' Ue1';
rules += ' qe1';
if (props.placement === "bottom") rules += ' _tb1';
else if (props.placement === "top") rules += ' _ta1';
rules += ' _oa1';
rules += ' Jy1';
rules += ' Gy1';
if (props.isExpanded) {
if (props.placement === "bottom") rules += ' Tt1';
else if (props.placement === "top") rules += ' Tf1';
}
if (props.isExpanded) {
if (props.placement === "bottom") rules += ' Qf1';
else if (props.placement === "top") rules += ' Qt1';
}
if (props.isExpanded) rules += ' St1';
else rules += ' Sd1';
if (props.isExpanded) rules += ' Rt1';
else rules += ' Rd1';
if (props.isExpanded) rules += ' Ic1';
else rules += ' Iy1';
if (props.isExpanded) rules += ' Hc1';
else rules += ' Hy1';
if (props.isExpanded) rules += ' _Na1';
if (props.isExpanded) rules += ' Pa1';
return rules;
};
const $f1992060af7549d9$var$toastStyle = function anonymous(props) {
let rules = " ";
if (props.isFocusVisible) rules += ' _Lf1';
else rules += ' _Le1';
if (props.isExpanded) rules += ' Ok1';
else rules += ' Oh1';
rules += ' _Mc1';
rules += ' _Kd1';
rules += ' sd1';
rules += ' Ul1';
rules += ' ql1';
rules += ' Sf1';
rules += ' Rt1';
rules += ' Te1';
rules += ' Qe1';
rules += ' oc1';
rules += ' nc1';
rules += ' kc1';
rules += ' jc1';
rules += ' Mj1';
rules += ' -Zn3gsb-L0uB6Qb1';
rules += ' LksArhc1';
rules += ' _oa1';
rules += ' _va1';
rules += ' uk1';
rules += ' ucJ9TBTb1';
rules += ' ud3Euai1';
rules += ' uea1';
rules += ' ugb1';
rules += ' uhd1';
rules += ' uje1';
rules += ' u2NhKxcl1';
rules += ' uic1';
rules += ' -_6BNtrc-c1';
rules += ' vx1';
rules += ' xb1';
rules += ' _xa1';
rules += ' _Fd1';
rules += ' _FnuYUweb1';
rules += ' px1';
if (props.variant === "negative") rules += ' gB1';
else if (props.variant === "positive") rules += ' g11';
else if (props.variant === "info") rules += ' g21';
else if (props.variant === "neutral") rules += ' g61';
rules += ' -_8sjo0b-t5ZbAob1';
if (props.isExpanded) rules += ' _nd1';
else rules += ' _nLeasBb1';
rules += ' _8d1';
return rules;
};
const $f1992060af7549d9$var$toastBody = function anonymous(props) {
let rules = " ";
if (props.isSingle) rules += ' sd1';
else rules += ' se1';
rules += ' D4w1sLc1';
rules += ' CWLL0Xb1';
rules += ' _ub1';
rules += ' _wb1';
rules += ' eb1';
rules += ' qj1';
rules += ' Ue1';
return rules;
};
const $f1992060af7549d9$var$toastContent = " sd1 Ue1 qe1 ea1 zk52g2d1 yk52g2d1 Bk52g2d1 Ak52g2d1 ri1 ZJ1 -nTSCV-LTAEPe1 __ZLTAEPe1 Na1";
const $f1992060af7549d9$var$controls = function anonymous(props) {
let rules = " ";
rules += ' _pb1';
if (props.isExpanded) rules += ' sd1';
else rules += ' sk1';
rules += ' _Cb1';
rules += ' Ue1';
rules += ' qe1';
if (props.isExpanded) rules += ' _Ib1';
else rules += ' _Ia1';
return rules;
};
const $f1992060af7549d9$var$ICONS = {
info: (0, $0ed6e07b499b9797$exports.default),
negative: (0, $7e7cdbd2b8ae2467$exports.default),
positive: (0, $6391be254c189366$exports.default)
};
const $f1992060af7549d9$var$ToastContainerContext = /*#__PURE__*/ (0, $fI0QS$react.createContext)(null);
function $f1992060af7549d9$export$f2815235e76a62b9(props) {
let { placement: placement = 'bottom' } = props;
let queue = $f1992060af7549d9$var$getGlobalToastQueue();
let align = 'center';
[placement, align = 'center'] = placement.split(' ');
let stringFormatter = (0, $fI0QS$reactariai18n.useLocalizedStringFormatter)((0, ($parcel$interopDefault($4526404114e78c80$exports))), '-spectrum/s2');
let regionRef = (0, $fI0QS$react.useRef)(null);
let state = (0, $fI0QS$reactstately.useOverlayTriggerState)({});
let { isOpen: isExpanded, close: close, toggle: toggle } = state;
let ctx = (0, $fI0QS$react.useMemo)(()=>({
isExpanded: isExpanded,
toggleExpanded () {
if (!isExpanded && queue.visibleToasts.length <= 1) return;
$f1992060af7549d9$var$startViewTransition(()=>toggle(), isExpanded ? 'toast-collapse' : 'toast-expand');
}
}), [
isExpanded,
toggle,
queue
]);
// Set the state to collapsed whenever the queue is emptied.
(0, $fI0QS$react.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, $fI0QS$reactaria.useModalOverlay)({}, state, regionRef);
(0, $fI0QS$reactariautils.useEvent)(regionRef, 'keydown', isExpanded ? (e)=>{
if (e.key === 'Escape') collapse();
} : undefined);
let prefersReducedMotion = (0, $fI0QS$reactspectrumutils.useMediaQuery)('(prefers-reduced-motion)');
let reduceMotion = props['PRIVATE_forceReducedMotion'] ?? prefersReducedMotion;
(0, $fI0QS$react.useEffect)(()=>{
let oldGlobalReduceMotion = $f1992060af7549d9$var$globalReduceMotion;
$f1992060af7549d9$var$globalReduceMotion = reduceMotion;
return ()=>{
$f1992060af7549d9$var$globalReduceMotion = oldGlobalReduceMotion;
};
}, [
reduceMotion
]);
return /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $fI0QS$reactariacomponents.UNSTABLE_ToastRegion), {
...props,
ref: regionRef,
queue: queue,
className: (renderProps)=>$f1992060af7549d9$var$toastRegion({
...renderProps,
placement: placement,
align: align,
isExpanded: isExpanded
}),
children: /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $fI0QS$reactaria.FocusScope), {
contain: isExpanded,
children: /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)($f1992060af7549d9$var$ToastContainerContext.Provider, {
value: ctx,
children: [
isExpanded && // eslint-disable-next-line
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)("div", {
className: (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['toast-background'] + " _Pb1 Wr1 _lr1 _Ar1 _zr1 g81",
onClick: collapse
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)($f1992060af7549d9$var$SpectrumToastList, {
placement: placement,
align: align,
reduceMotion: reduceMotion
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)("div", {
className: (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['toast-controls'] + $f1992060af7549d9$var$controls({
isExpanded: isExpanded
}),
children: [
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $6e265ff388155b91$exports.ActionButton), {
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, $fI0QS$reactjsxruntime.jsx)((0, $6e265ff388155b91$exports.ActionButton), {
size: "S",
onPress: collapse,
UNSAFE_style: {
outlineColor: 'white'
},
children: stringFormatter.format('toast.collapse')
})
]
})
]
})
})
});
}
function $f1992060af7549d9$var$SpectrumToastList({ placement: placement, align: align, reduceMotion: reduceMotion }) {
let { isExpanded: isExpanded, toggleExpanded: toggleExpanded } = (0, $fI0QS$react.useContext)($f1992060af7549d9$var$ToastContainerContext);
// Attach click handler to ref since ToastList doesn't pass through onClick/onPress.
let toastListRef = (0, $fI0QS$react.useRef)(null);
(0, $fI0QS$reactariautils.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();
});
return /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $fI0QS$reactariacomponents.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($aa16d99d2c9699f6$exports)))[isExpanded ? 'toast-list-expanded' : 'toast-list-collapsed'] + $f1992060af7549d9$var$toastList({
placement: placement,
align: align,
isExpanded: isExpanded
}),
children: ({ toast: toast })=>/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)($f1992060af7549d9$export$f9b1e1c88454fa08, {
toast: toast,
placement: placement,
align: align,
reduceMotion: reduceMotion
})
});
}
function $f1992060af7549d9$export$f9b1e1c88454fa08(props) {
let { toast: toast, placement: placement = 'bottom', align: align = 'center' } = props;
let variant = toast.content.variant || 'info';
let Icon = $f1992060af7549d9$var$ICONS[variant];
let state = (0, $fI0QS$react.useContext)((0, $fI0QS$reactariacomponents.UNSTABLE_ToastStateContext));
let visibleToasts = state.visibleToasts;
let index = visibleToasts.indexOf(toast);
let isMain = index <= 0;
let ctx = (0, $fI0QS$react.useContext)($f1992060af7549d9$var$ToastContainerContext);
let isExpanded = ctx?.isExpanded || false;
let toastRef = (0, $fI0QS$react.useRef)(null);
let stringFormatter = (0, $fI0QS$reactariai18n.useLocalizedStringFormatter)((0, ($parcel$interopDefault($4526404114e78c80$exports))), '-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, $fI0QS$reactjsxruntime.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($aa16d99d2c9699f6$exports))).toast,
(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['background-toast']
].map((c)=>CSS.escape(c)).join(' ')
},
className: (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports))).toast + $f1992060af7549d9$var$toastStyle({
variant: toast.content.variant || 'info',
index: index,
isExpanded: isExpanded
}),
ref: $f1992060af7549d9$var$fixSafariTransform
});
return /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)((0, $fI0QS$reactariacomponents.UNSTABLE_Toast), {
ref: toastRef,
toast: toast,
style: {
zIndex: visibleToasts.length - index - 1,
viewTransitionName: toast.key,
viewTransitionClass: [
(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports))).toast,
!isMain ? (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['background-toast'] : '',
(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))[placement],
(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))[align]
].filter(Boolean).map((c)=>CSS.escape(c)).join(' ')
},
className: (renderProps)=>(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports))).toast + $f1992060af7549d9$var$toastStyle({
...renderProps,
variant: toast.content.variant || 'info',
index: index,
isExpanded: isExpanded
}),
children: [
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)("div", {
role: "presentation",
className: $f1992060af7549d9$var$toastBody({
isSingle: !isMain || visibleToasts.length <= 1 || isExpanded
}),
children: [
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)((0, $fI0QS$reactariacomponents.UNSTABLE_ToastContent), {
className: $f1992060af7549d9$var$toastContent + (ctx && isMain ? ` ${(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['toast-content']}` : null),
children: [
Icon && /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $e991cbcdf82ced71$exports.CenterBaseline), {
children: /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)(Icon, {})
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $6367bc87eb7d24ad$exports.Text), {
slot: "title",
children: toast.content.children
})
]
}),
!isExpanded && visibleToasts.length > 1 && /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsxs)((0, $6e265ff388155b91$exports.ActionButton), {
isQuiet: true,
staticColor: "white",
styles: " zPgKvMe1 yPgKvMe1 BPgKvMe1 APgKvMe1",
// 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($aa16d99d2c9699f6$exports)))['toast-expand'] : undefined,
onPress: ()=>{
// This button disappears when clicked, so move focus to the toast.
toastRef.current?.focus();
ctx?.toggleExpanded();
},
children: [
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $6367bc87eb7d24ad$exports.Text), {
children: stringFormatter.format('toast.showAll')
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $d88edd0de6bc65f6$exports.default), {
UNSAFE_style: {
rotate: placement === 'bottom' ? '180deg' : undefined
}
})
]
}),
toast.content.actionLabel && /*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $25d06cf8d4e72761$exports.Button), {
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($aa16d99d2c9699f6$exports)))['toast-action'] : undefined,
styles: " IM1 zzz3eAe1 yzz3eAe1 Bzz3eAe1 Azz3eAe1",
children: toast.content.actionLabel
})
]
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)((0, $2f907cb84c6e9e75$exports.CloseButton), {
staticColor: "white",
UNSAFE_className: ctx && isMain ? (0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))['toast-close'] : undefined
})
]
});
}
function $f1992060af7549d9$var$fixSafariTransform(el) {
// Safari has a bug where the toasts display in the wrong position (CSS transform is not applied correctly).
// Work around this by removing it, forcing a reflow, and re-applying.
if (el && (0, $fI0QS$reactariautils.isWebKit)()) {
let translate = el.style.translate;
el.style.translate = '';
el.offsetHeight;
el.style.translate = translate;
}
}
//# sourceMappingURL=Toast.cjs.map