Skip to content

feat(Toast): add placement prop to ToastContainer #7609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/@react-spectrum/toast/docs/Toast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ function Example() {
}
```

## Placement

By default, toasts are displayed at the bottom center of the screen. This can be changed by setting the `placement` prop on the `ToastContainer` to `'top start'`, `'top'`, `'top end'`, `'bottom start'`, `'bottom'`, or `'bottom end'`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the team feel about a blurb for mfe's that may have multiple ToastContainers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I checked and we currently don't talk about multiple ToastContainers in the docs. Do we need a general section on that or continue not documenting it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to be editing more docs soon, so I'll do this in a follow up!


```tsx example render=false hidden
<ToastContainer placement="bottom end" />
```

## API

### ToastQueue
Expand Down
6 changes: 5 additions & 1 deletion packages/@react-spectrum/toast/src/ToastContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import {Toaster} from './Toaster';
import {ToastOptions, ToastQueue, useToastQueue} from '@react-stately/toast';
import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';

export interface SpectrumToastContainerProps extends AriaToastRegionProps {}
export type ToastPlacement = 'top start' | 'top' | 'top end' | 'bottom start' | 'bottom' | 'bottom end';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that we specified in the ticket that we'd only support this set of placement, but it just occurred to me that we support left and right in additional to start and end in our other placement props. I don't quite remember if there was a use case for that or if it was carryover from v2 or not...


export interface SpectrumToastContainerProps extends AriaToastRegionProps {
placement?: ToastPlacement
}

export interface SpectrumToastOptions extends Omit<ToastOptions, 'priority'>, DOMProps {
/** A label for the action button within the toast. */
Expand Down
15 changes: 11 additions & 4 deletions packages/@react-spectrum/toast/src/Toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ import {classNames} from '@react-spectrum/utils';
import {FocusScope, useFocusRing} from '@react-aria/focus';
import {mergeProps} from '@react-aria/utils';
import {Provider} from '@react-spectrum/provider';
import React, {createContext, ReactElement, ReactNode, useRef} from 'react';
import React, {createContext, ReactElement, ReactNode, useMemo, useRef} from 'react';
import ReactDOM from 'react-dom';
import toastContainerStyles from './toastContainer.css';
import type {ToastPlacement} from './ToastContainer';
import {ToastState} from '@react-stately/toast';
import {useUNSTABLE_PortalContext} from '@react-aria/overlays';

interface ToastContainerProps extends AriaToastRegionProps {
children: ReactNode,
state: ToastState<unknown>
state: ToastState<unknown>,
placement?: ToastPlacement
}

export const ToasterContext = createContext(false);
Expand All @@ -39,15 +41,20 @@ export function Toaster(props: ToastContainerProps): ReactElement {
let {focusProps, isFocusVisible} = useFocusRing();
let {getContainer} = useUNSTABLE_PortalContext();

let [position, placement] = useMemo(() => {
let [pos = 'bottom', place = 'center'] = props.placement?.split(' ') || [];
return [pos, place];
}, [props.placement]);

let contents = (
<Provider UNSAFE_style={{background: 'transparent'}}>
<FocusScope>
<ToasterContext.Provider value={isFocusVisible}>
<div
{...mergeProps(regionProps, focusProps)}
ref={ref}
data-position="bottom"
data-placement="center"
data-position={position}
data-placement={placement}
className={classNames(
toastContainerStyles,
'react-spectrum-ToastContainer',
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/toast/src/toastContainer.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
--slide-to: translateY(0);
}

&[data-placement=left] {
&[data-placement=start] {
align-items: flex-start;
--slide-from: translateX(-100%);
--slide-to: translateX(0);
Expand All @@ -62,7 +62,7 @@
align-items: center;
}

&[data-placement=right] {
&[data-placement=end] {
align-items: flex-end;
--slide-from: translateX(100%);
--slide-to: translateX(0);
Expand Down
11 changes: 8 additions & 3 deletions packages/@react-spectrum/toast/stories/Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,26 @@ import {UNSTABLE_PortalProvider} from '@react-aria/overlays';
export default {
title: 'Toast',
decorators: [
(story, {parameters}) => (
(story, {parameters, args}) => (
<>
{!parameters.disableToastContainer && <ToastContainer />}
{!parameters.disableToastContainer && <ToastContainer placement={args.placement} />}
<MainLandmark>{story()}</MainLandmark>
</>
)
],
args: {
shouldCloseOnAction: false,
timeout: null
timeout: null,
placement: undefined
},
argTypes: {
timeout: {
control: 'radio',
options: [null, 5000]
},
placement: {
control: 'select',
options: [undefined, 'top start', 'top', 'top end', 'bottom start', 'bottom', 'bottom end']
}
}
};
Expand Down
Loading