@react-spectrum/s2
Version:
Spectrum 2 UI components in React
529 lines (503 loc) • 24.1 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.
*/
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]);
let viewTransition = document.startViewTransition({
update: ()=>(0, $fI0QS$reactdom.flushSync)(fn),
types: [
(0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))[type]
]
});
viewTransition.ready.catch(()=>{});
viewTransition.finished.then(()=>{
document.documentElement.classList.remove((0, ($parcel$interopDefault($aa16d99d2c9699f6$exports)))[type]);
});
} 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 += ' _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 $f1992060af7549d9$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 $f1992060af7549d9$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 $f1992060af7549d9$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 $f1992060af7549d9$var$toastContent = " sd91 Ue91 qe91 ea91 zk52g2d91 yk52g2d91 Bk52g2d91 Ak52g2d91 ri91 ZJ91";
const $f1992060af7549d9$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 $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);
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'] + " _Pb91 Wr91 _lr91 _Ar91 _zr91 g891",
onClick: collapse
}),
/*#__PURE__*/ (0, $fI0QS$reactjsxruntime.jsx)($f1992060af7549d9$var$SpectrumToastList, {
placement: placement,
align: align
}),
/*#__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 }) {
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();
});
let reduceMotion = (0, $fI0QS$reactspectrumutils.useMediaQuery)('(prefers-reduced-motion)');
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
})
});
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: " 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($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: " IM91 zzz3eAe91 yzz3eAe91 Bzz3eAe91 Azz3eAe91",
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
})
]
});
}
//# sourceMappingURL=Toast.cjs.map