-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Toast container #153
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
Toast container #153
Changes from all commits
a54d6b7
9cdee2d
b684d4e
d5fa497
7f98e8d
5e3367f
dcdf21f
850dec9
abcac35
aa58a1c
31331df
95c501a
c467bf7
c23a599
7c638f7
4c438ca
e6ef8b9
a23ce9c
f09cce4
930a70b
5a26d3b
78d951e
e2b6636
d535caf
0bfc8f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import {classNames} from '@react-spectrum/utils'; | ||
import React, {ReactElement} from 'react'; | ||
import {Toast} from './'; | ||
import toastContainerStyles from './toastContainer.css'; | ||
import {ToastStateBase} from '@react-types/toast'; | ||
|
||
interface ToastProviderProps { | ||
toasts?: ToastStateBase[], | ||
} | ||
|
||
export function ToastContainer(props: ToastProviderProps): ReactElement { | ||
let renderToasts = () => props.toasts.map((toast) => | ||
(<Toast {...toast.props}>{toast.content}</Toast>) | ||
); | ||
|
||
return ( | ||
<div className={classNames(toastContainerStyles, 'react-spectrum-ToastContainer')}> | ||
{renderToasts()} | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, {ReactElement, ReactNode, useContext} from 'react'; | ||
import {ToastContainer} from './'; | ||
import {ToastOptions, ToastStateBase} from '@react-types/toast'; | ||
import {useProviderProps} from '@react-spectrum/provider'; | ||
import {useToastState} from '@react-stately/toast'; | ||
|
||
interface ToastContextProps { | ||
setToasts?: (any) => void, | ||
toasts?: ToastStateBase[], | ||
positive?: (content: ReactNode, options: ToastOptions) => void, | ||
negative?: (content: ReactNode, options: ToastOptions) => void, | ||
neutral?: (content: ReactNode, options: ToastOptions) => void, | ||
info?: (content: ReactNode, options: ToastOptions) => void | ||
} | ||
|
||
interface ToastProviderProps { | ||
children: ReactNode | ||
} | ||
|
||
export const ToastContext = React.createContext<ToastContextProps | null>(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @snowystinger do you want this context to be it's own file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't particularly care, I had hoped originally that it would help with the circular dep, but since it doesn't affect it, you can leave it in here much like the Provider does |
||
|
||
export function useToastProvider() { | ||
return useContext(ToastContext); | ||
} | ||
|
||
export function ToastProvider(props: ToastProviderProps): ReactElement { | ||
let {onAdd, toasts} = useToastState(); | ||
let { | ||
children | ||
} = useProviderProps(props); | ||
|
||
let contextValue = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does someone add their own toast completely? is that not something we're going to support? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now sure what you mean? I believe we are only allowing them to use the functions from the context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in v2 people could use https://git.corp.adobe.com/React/react-spectrum-v2/blob/master/src/Toast/js/ToastContainer.js#L146 in addition to the predefined ones There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the Toast component is no longer public. |
||
neutral: (content: ReactNode, options: ToastOptions = {}) => { | ||
onAdd(content, options); | ||
LFDanLu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
positive: (content: ReactNode, options: ToastOptions = {}) => { | ||
onAdd(content, {...options, variant: 'positive'}); | ||
}, | ||
negative: (content: ReactNode, options: ToastOptions = {}) => { | ||
onAdd(content, {...options, variant: 'negative'}); | ||
}, | ||
info: (content: ReactNode, options: ToastOptions = {}) => { | ||
onAdd(content, {...options, variant: 'info'}); | ||
} | ||
}; | ||
|
||
return ( | ||
<ToastContext.Provider value={contextValue}> | ||
<ToastContainer toasts={toasts} /> | ||
{children} | ||
</ToastContext.Provider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export * from './Toast'; | ||
export * from './ToastContainer'; | ||
export * from './ToastProvider'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
.react-spectrum-ToastContainer { | ||
position: fixed; | ||
top: unset; | ||
bottom: 0; | ||
inset-inline-start: 0; | ||
inset-inline-end: 0; | ||
z-index: 100050; /* above modals */ | ||
display: flex; | ||
flex-direction: column-reverse; | ||
align-items: center; | ||
pointer-events: none; | ||
|
||
.spectrum-Toast { | ||
margin: 8px; | ||
pointer-events: all; | ||
} | ||
} | ||
.react-spectrum-ToastContainer--top { | ||
top: 0; | ||
flex-direction: column; | ||
bottom: unset; | ||
} | ||
.react-spectrum-ToastContainer--bottom { | ||
flex-direction: column-reverse; | ||
bottom: 0; | ||
} | ||
.react-spectrum-ToastContainer--left { | ||
align-items: flex-start; | ||
} | ||
.react-spectrum-ToastContainer--center { | ||
align-items: center; | ||
} | ||
.react-spectrum-ToastContainer--right { | ||
align-items: flex-end; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
import {action} from '@storybook/addon-actions'; | ||
import {Button} from '@react-spectrum/button'; | ||
import React from 'react'; | ||
import {storiesOf} from '@storybook/react'; | ||
import {Toast} from '../'; | ||
import {ToastProps} from '@react-types/toast'; | ||
import {useToastProvider} from '../'; | ||
|
||
storiesOf('Toast', module) | ||
.add( | ||
|
@@ -28,6 +30,9 @@ storiesOf('Toast', module) | |
.add( | ||
'action triggers close', | ||
() => render({actionLabel: 'Undo', onAction: action('onAction'), shouldCloseOnAction: true, onClose: action('onClose')}, 'Close on untoasting of the toast') | ||
).add( | ||
'add via provider', | ||
() => <RenderProvider /> | ||
); | ||
|
||
function render(props:ToastProps = {}, message:String) { | ||
|
@@ -37,3 +42,32 @@ function render(props:ToastProps = {}, message:String) { | |
</Toast> | ||
); | ||
} | ||
|
||
function RenderProvider() { | ||
let toastContext = useToastProvider(); | ||
|
||
return ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't' feel we needed a story for each type of toast since we have one of each type in the non-provider example stories. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds fine to me, curious on what chomatic would look like though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. chromatic would need a non-button triggered toast to the toast container story. I'm not sure that is useful beyond chromatic????? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah true... unless there is a way to have the button be triggered by default? Not super adamant on this though, the other Toast stories probably suffice |
||
<div> | ||
<Button | ||
onPress={() => toastContext.neutral('Toast is default', {onClose: action('onClose')})} | ||
variant="secondary"> | ||
Show Default Toast | ||
</Button> | ||
<Button | ||
onPress={() => toastContext.positive('Toast is positive', {onClose: action('onClose')})} | ||
variant="primary"> | ||
Show Primary Toast | ||
</Button> | ||
<Button | ||
onPress={() => toastContext.negative('Toast is negative', {onClose: action('onClose')})} | ||
variant="negative"> | ||
Show Negative Toast | ||
</Button> | ||
<Button | ||
onPress={() => toastContext.info('Toast is info', {onClose: action('onClose')})} | ||
variant="cta"> | ||
Show info Toast | ||
</Button> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given that a ToastProvider renders its own ToastContainer, will this be a problem if someone launches a toast from inside a form that has a Provider wrapping it for setting isQuiet?
There'd then be two ToastContainers on the page I think?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devongovett Can you remind me what the desired functionality is? Is it that toasts only render into the topmost provider or that a toast renders into whichever provider it is in? I can't recall, I think v2 was more like the first, but you wanted v3 to do the second option.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to add a followup to create a story and test for this. The goal will be all toasts show up in the top provider. https://jira.corp.adobe.com/browse/RSP-1491