@react-spectrum/toast 3.0.0-nightly.3114 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/Toast.main.js +6 -15
  2. package/dist/Toast.main.js.map +1 -1
  3. package/dist/Toast.mjs +6 -15
  4. package/dist/Toast.module.js +6 -15
  5. package/dist/Toast.module.js.map +1 -1
  6. package/dist/ToastContainer.main.js +37 -12
  7. package/dist/ToastContainer.main.js.map +1 -1
  8. package/dist/ToastContainer.mjs +38 -13
  9. package/dist/ToastContainer.module.js +38 -13
  10. package/dist/ToastContainer.module.js.map +1 -1
  11. package/dist/Toaster.main.js +14 -4
  12. package/dist/Toaster.main.js.map +1 -1
  13. package/dist/Toaster.mjs +15 -5
  14. package/dist/Toaster.module.js +15 -5
  15. package/dist/Toaster.module.js.map +1 -1
  16. package/dist/{toastContainer.da404f80.css → toastContainer.6706a095.css} +69 -45
  17. package/dist/toastContainer.6706a095.css.map +1 -0
  18. package/dist/toastContainer_css.main.js +33 -11
  19. package/dist/toastContainer_css.main.js.map +1 -1
  20. package/dist/toastContainer_css.mjs +33 -11
  21. package/dist/toastContainer_css.module.js +33 -11
  22. package/dist/toastContainer_css.module.js.map +1 -1
  23. package/dist/toast_vars_css.main.js +10 -10
  24. package/dist/toast_vars_css.mjs +10 -10
  25. package/dist/toast_vars_css.module.js +10 -10
  26. package/dist/types.d.ts +4 -2
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/{vars.c6e65b7e.css → vars.84eec607.css} +16 -16
  29. package/dist/{vars.c6e65b7e.css.map → vars.84eec607.css.map} +1 -1
  30. package/package.json +17 -17
  31. package/src/Toast.tsx +3 -17
  32. package/src/ToastContainer.tsx +52 -24
  33. package/src/Toaster.tsx +12 -5
  34. package/src/toastContainer.css +46 -24
  35. package/dist/toastContainer.da404f80.css.map +0 -1
@@ -14,16 +14,21 @@ import {AriaToastRegionProps} from '@react-aria/toast';
14
14
  import {classNames} from '@react-spectrum/utils';
15
15
  import {DOMProps} from '@react-types/shared';
16
16
  import {filterDOMProps} from '@react-aria/utils';
17
- import React, {ReactElement, useEffect, useRef} from 'react';
17
+ import {flushSync} from 'react-dom';
18
+ import React, {ReactElement, useEffect, useMemo, useRef} from 'react';
18
19
  import {SpectrumToastValue, Toast} from './Toast';
19
20
  import toastContainerStyles from './toastContainer.css';
20
21
  import {Toaster} from './Toaster';
21
22
  import {ToastOptions, ToastQueue, useToastQueue} from '@react-stately/toast';
22
23
  import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';
23
24
 
24
- export interface SpectrumToastContainerProps extends AriaToastRegionProps {}
25
+ export type ToastPlacement = 'top' | 'top end' | 'bottom' | 'bottom end';
25
26
 
26
- export interface SpectrumToastOptions extends Omit<ToastOptions, 'priority'>, DOMProps {
27
+ export interface SpectrumToastContainerProps extends AriaToastRegionProps {
28
+ placement?: ToastPlacement
29
+ }
30
+
31
+ export interface SpectrumToastOptions extends ToastOptions, DOMProps {
27
32
  /** A label for the action button within the toast. */
28
33
  actionLabel?: string,
29
34
  /** Handler that is called when the action button is pressed. */
@@ -34,13 +39,23 @@ export interface SpectrumToastOptions extends Omit<ToastOptions, 'priority'>, DO
34
39
 
35
40
  type CloseFunction = () => void;
36
41
 
42
+ function wrapInViewTransition(fn: () => void): void {
43
+ if ('startViewTransition' in document) {
44
+ document.startViewTransition(() => {
45
+ flushSync(fn);
46
+ }).ready.catch(() => {});
47
+ } else {
48
+ fn();
49
+ }
50
+ }
51
+
37
52
  // There is a single global toast queue instance for the whole app, initialized lazily.
38
53
  let globalToastQueue: ToastQueue<SpectrumToastValue> | null = null;
39
54
  function getGlobalToastQueue() {
40
55
  if (!globalToastQueue) {
41
56
  globalToastQueue = new ToastQueue({
42
57
  maxVisibleToasts: Infinity,
43
- hasExitAnimation: true
58
+ wrapUpdate: wrapInViewTransition
44
59
  });
45
60
  }
46
61
 
@@ -77,25 +92,19 @@ function useActiveToastContainer() {
77
92
  * A ToastContainer renders the queued toasts in an application. It should be placed
78
93
  * at the root of the app.
79
94
  */
80
- export function ToastContainer(props: SpectrumToastContainerProps): ReactElement {
95
+ export function ToastContainer(props: SpectrumToastContainerProps): ReactElement | null {
81
96
  // Track all toast provider instances in a set.
82
97
  // Only the first one will actually render.
83
98
  // We use a ref to do this, since it will have a stable identity
84
99
  // over the lifetime of the component.
85
- let ref = useRef(undefined);
100
+ let ref = useRef(null);
101
+
86
102
 
87
- // eslint-disable-next-line arrow-body-style
88
103
  useEffect(() => {
89
104
  toastProviders.add(ref);
90
105
  triggerSubscriptions();
91
106
 
92
107
  return () => {
93
- // When this toast provider unmounts, reset all animations so that
94
- // when the new toast provider renders, it is seamless.
95
- for (let toast of getGlobalToastQueue().visibleToasts) {
96
- toast.animation = null;
97
- }
98
-
99
108
  // Remove this toast provider, and call subscriptions.
100
109
  // This will cause all other instances to re-render,
101
110
  // and the first one to become the new active toast provider.
@@ -108,19 +117,38 @@ export function ToastContainer(props: SpectrumToastContainerProps): ReactElement
108
117
  let activeToastContainer = useActiveToastContainer();
109
118
  let state = useToastQueue(getGlobalToastQueue());
110
119
 
120
+ let {placement, isCentered} = useMemo(() => {
121
+ let placements = (props.placement ?? 'bottom').split(' ');
122
+ let placement = placements[placements.length - 1];
123
+ let isCentered = placements.length === 1;
124
+ return {placement, isCentered};
125
+ }, [props.placement]);
126
+
111
127
  if (ref === activeToastContainer && state.visibleToasts.length > 0) {
112
128
  return (
113
129
  <Toaster state={state} {...props}>
114
130
  <ol className={classNames(toastContainerStyles, 'spectrum-ToastContainer-list')}>
115
- {state.visibleToasts.slice().reverse().map((toast) => (
116
- <li
117
- key={toast.key}
118
- className={classNames(toastContainerStyles, 'spectrum-ToastContainer-listitem')}>
119
- <Toast
120
- toast={toast}
121
- state={state} />
122
- </li>
123
- ))}
131
+ {state.visibleToasts.map((toast, index) => {
132
+ let shouldFade = isCentered && index !== 0;
133
+ return (
134
+ <li
135
+ key={toast.key}
136
+ className={classNames(toastContainerStyles, 'spectrum-ToastContainer-listitem')}
137
+ style={{
138
+ viewTransitionName: toast.key,
139
+ viewTransitionClass: classNames(
140
+ toastContainerStyles,
141
+ 'toast',
142
+ placement,
143
+ {'fadeOnly': shouldFade}
144
+ )
145
+ }}>
146
+ <Toast
147
+ toast={toast}
148
+ state={state} />
149
+ </li>
150
+ );
151
+ })}
124
152
  </ol>
125
153
  </Toaster>
126
154
  );
@@ -144,7 +172,7 @@ function addToast(children: string, variant: SpectrumToastValue['variant'], opti
144
172
 
145
173
  let shouldContinue = window.dispatchEvent(event);
146
174
  if (!shouldContinue) {
147
- return;
175
+ return () => {};
148
176
  }
149
177
  }
150
178
 
@@ -160,7 +188,7 @@ function addToast(children: string, variant: SpectrumToastValue['variant'], opti
160
188
  // Minimum time of 5s from https://spectrum.adobe.com/page/toast/#Auto-dismissible
161
189
  // Actionable toasts cannot be auto dismissed. That would fail WCAG SC 2.2.1.
162
190
  // It is debatable whether non-actionable toasts would also fail.
163
- let timeout = options.timeout && !options.onAction ? Math.max(options.timeout, 5000) : null;
191
+ let timeout = options.timeout && !options.onAction ? Math.max(options.timeout, 5000) : undefined;
164
192
  let queue = getGlobalToastQueue();
165
193
  let key = queue.add(value, {timeout, onClose: options.onClose});
166
194
  return () => queue.close(key);
package/src/Toaster.tsx CHANGED
@@ -15,15 +15,17 @@ import {classNames} from '@react-spectrum/utils';
15
15
  import {FocusScope, useFocusRing} from '@react-aria/focus';
16
16
  import {mergeProps} from '@react-aria/utils';
17
17
  import {Provider} from '@react-spectrum/provider';
18
- import React, {createContext, ReactElement, ReactNode, useRef} from 'react';
18
+ import React, {createContext, ReactElement, ReactNode, useMemo, useRef} from 'react';
19
19
  import ReactDOM from 'react-dom';
20
20
  import toastContainerStyles from './toastContainer.css';
21
+ import type {ToastPlacement} from './ToastContainer';
21
22
  import {ToastState} from '@react-stately/toast';
22
23
  import {useUNSTABLE_PortalContext} from '@react-aria/overlays';
23
24
 
24
25
  interface ToastContainerProps extends AriaToastRegionProps {
25
26
  children: ReactNode,
26
- state: ToastState<unknown>
27
+ state: ToastState<unknown>,
28
+ placement?: ToastPlacement
27
29
  }
28
30
 
29
31
  export const ToasterContext = createContext(false);
@@ -34,11 +36,16 @@ export function Toaster(props: ToastContainerProps): ReactElement {
34
36
  state
35
37
  } = props;
36
38
 
37
- let ref = useRef(undefined);
39
+ let ref = useRef(null);
38
40
  let {regionProps} = useToastRegion(props, state, ref);
39
41
  let {focusProps, isFocusVisible} = useFocusRing();
40
42
  let {getContainer} = useUNSTABLE_PortalContext();
41
43
 
44
+ let [position, placement] = useMemo(() => {
45
+ let [pos = 'bottom', place = 'center'] = props.placement?.split(' ') || [];
46
+ return [pos, place];
47
+ }, [props.placement]);
48
+
42
49
  let contents = (
43
50
  <Provider UNSAFE_style={{background: 'transparent'}}>
44
51
  <FocusScope>
@@ -46,8 +53,8 @@ export function Toaster(props: ToastContainerProps): ReactElement {
46
53
  <div
47
54
  {...mergeProps(regionProps, focusProps)}
48
55
  ref={ref}
49
- data-position="bottom"
50
- data-placement="center"
56
+ data-position={position}
57
+ data-placement={placement}
51
58
  className={classNames(
52
59
  toastContainerStyles,
53
60
  'react-spectrum-ToastContainer',
@@ -38,20 +38,17 @@
38
38
  top: 0;
39
39
  flex-direction: column;
40
40
  --slide-from: translateY(-100%);
41
- --slide-to: translateY(0);
42
41
  }
43
42
 
44
43
  &[data-position=bottom] {
45
44
  bottom: 0;
46
45
  flex-direction: column-reverse;
47
46
  --slide-from: translateY(100%);
48
- --slide-to: translateY(0);
49
47
  }
50
48
 
51
- &[data-placement=left] {
49
+ &[data-placement=start] {
52
50
  align-items: flex-start;
53
51
  --slide-from: translateX(-100%);
54
- --slide-to: translateX(0);
55
52
 
56
53
  &:dir(rtl) {
57
54
  --slide-from: translateX(100%);
@@ -62,10 +59,9 @@
62
59
  align-items: center;
63
60
  }
64
61
 
65
- &[data-placement=right] {
62
+ &[data-placement=end] {
66
63
  align-items: flex-end;
67
64
  --slide-from: translateX(100%);
68
- --slide-to: translateX(0);
69
65
 
70
66
  &:dir(rtl) {
71
67
  --slide-from: translateX(-100%);
@@ -82,14 +78,6 @@
82
78
  position: relative;
83
79
  outline: none;
84
80
 
85
- &[data-animation=entering] {
86
- animation: slide-in 300ms;
87
- }
88
-
89
- &[data-animation=exiting] {
90
- animation: fade-out 300ms forwards;
91
- }
92
-
93
81
  .spectrum-Toast-contentWrapper {
94
82
  display: inline-flex;
95
83
  }
@@ -100,33 +88,67 @@
100
88
  flex-direction: inherit;
101
89
  align-items: inherit;
102
90
  list-style-type: none;
103
- margin-block-start: 0;
104
- margin-block-end: 0;
105
- margin-inline-start: 0;
106
- margin-inline-end: 0;
91
+ margin: 0;
107
92
  padding-inline-start: 0;
108
93
  }
109
94
 
110
95
  .spectrum-ToastContainer-listitem {
111
96
  display: inline-block;
97
+ view-transition-class: toast;
112
98
  }
113
99
 
114
100
  @keyframes slide-in {
115
101
  from {
116
- transform: var(--slide-from);
102
+ translate: var(--slideX) var(--slideY);
103
+ opacity: 0;
117
104
  }
105
+ }
118
106
 
107
+ @keyframes slide-out {
119
108
  to {
120
- transform: var(--slide-to);
109
+ translate: var(--slideX) var(--slideY);
110
+ opacity: 0;
121
111
  }
122
112
  }
123
113
 
124
114
  @keyframes fade-out {
125
- from {
126
- opacity: 1;
127
- }
128
-
129
115
  to {
130
116
  opacity: 0;
131
117
  }
132
118
  }
119
+
120
+ ::view-transition-group(.toast) {
121
+ animation-duration: 300ms;
122
+ }
123
+
124
+ ::view-transition-group(.toast.bottom) {
125
+ --slideX: 0;
126
+ --slideY: calc(100% + 12px);
127
+ }
128
+
129
+ ::view-transition-group(.toast.top) {
130
+ --slideX: 0;
131
+ --slideY: calc(-100% - 12px);
132
+ }
133
+
134
+ ::view-transition-group(.toast.start) {
135
+ --slideX: calc(-100% - 12px);
136
+ --slideY: 0;
137
+ }
138
+
139
+ ::view-transition-group(.toast.end) {
140
+ --slideX: calc(100% + 12px);
141
+ --slideY: 0;
142
+ }
143
+
144
+ ::view-transition-new(.toast):only-child {
145
+ animation-name: slide-in;
146
+ }
147
+
148
+ ::view-transition-old(.toast):only-child {
149
+ animation-name: slide-out;
150
+ }
151
+
152
+ ::view-transition-old(.toast.fadeOnly):only-child {
153
+ animation-name: fade-out;
154
+ }
@@ -1 +0,0 @@
1
- {"mappings":"AAYA;;;;;;;;AAOE;;;;;;;;;;;AAqBE;;;;;AAQF;;;;AAKE;;;;;AAOJ;EACE;;;;EAIE;;;;;AAMJ;;;;;;;;;;;;;;;AAiBE;;;;;AAKA;;;;;;;AAOA;;;;;;;AAOA;;;;;;AAKE;;;;AAAA;;;;AAKF;;;;AAIA;;;;;;AAKE;;;;AAAA;;;;AAMJ;;;;;;;;AASE;;;;AAIA;;;;AAIA;;;;AAKF;;;;;;;;;;AAYA;;;;AAIA;;;;;;;;;;AAUA","sources":["packages/@react-spectrum/toast/src/toastContainer.css"],"sourcesContent":["/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n@import \"../../../@adobe/spectrum-css-temp/components/commons/focus-ring.css\";\n\n.react-spectrum-ToastContainer {\n composes: spectrum-FocusRing;\n --spectrum-focus-ring-border-radius: var(--spectrum-toast-border-radius);\n --spectrum-focus-ring-gap: var(--spectrum-alias-focus-ring-gap);\n --spectrum-focus-ring-size: var(--spectrum-alias-focus-ring-size);\n\n position: fixed;\n inset-inline-start: 0;\n inset-inline-end: 0;\n z-index: 100050; /* above modals */\n display: flex;\n pointer-events: none;\n outline: none;\n /* keep focus ring inside the viewport */\n margin-block-end: 8px;\n margin-inline: 8px;\n\n .spectrum-Toast {\n margin: 8px;\n pointer-events: all;\n }\n\n &[data-position=top] {\n top: 0;\n flex-direction: column;\n --slide-from: translateY(-100%);\n --slide-to: translateY(0);\n }\n\n &[data-position=bottom] {\n bottom: 0;\n flex-direction: column-reverse;\n --slide-from: translateY(100%);\n --slide-to: translateY(0);\n }\n\n &[data-placement=left] {\n align-items: flex-start;\n --slide-from: translateX(-100%);\n --slide-to: translateX(0);\n\n &:dir(rtl) {\n --slide-from: translateX(100%);\n }\n }\n\n &[data-placement=center] {\n align-items: center;\n }\n\n &[data-placement=right] {\n align-items: flex-end;\n --slide-from: translateX(100%);\n --slide-to: translateX(0);\n\n &:dir(rtl) {\n --slide-from: translateX(-100%);\n }\n }\n}\n\n.spectrum-Toast {\n composes: spectrum-FocusRing;\n --spectrum-focus-ring-border-radius: var(--spectrum-toast-border-radius);\n --spectrum-focus-ring-gap: var(--spectrum-alias-focus-ring-gap);\n --spectrum-focus-ring-size: var(--spectrum-alias-focus-ring-size);\n\n position: relative;\n outline: none;\n\n &[data-animation=entering] {\n animation: slide-in 300ms;\n }\n\n &[data-animation=exiting] {\n animation: fade-out 300ms forwards;\n }\n\n .spectrum-Toast-contentWrapper {\n display: inline-flex;\n }\n}\n\n.spectrum-ToastContainer-list {\n display: inherit;\n flex-direction: inherit;\n align-items: inherit;\n list-style-type: none;\n margin-block-start: 0;\n margin-block-end: 0;\n margin-inline-start: 0;\n margin-inline-end: 0;\n padding-inline-start: 0;\n}\n\n.spectrum-ToastContainer-listitem {\n display: inline-block;\n}\n\n@keyframes slide-in {\n from {\n transform: var(--slide-from);\n }\n\n to {\n transform: var(--slide-to);\n }\n}\n\n@keyframes fade-out {\n from {\n opacity: 1;\n }\n\n to {\n opacity: 0;\n }\n}\n"],"names":[],"version":3,"file":"toastContainer.da404f80.css.map"}