From 89f6434d76e033ac8448e7a3f197420ff3b67edb Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 6 Jun 2023 17:44:23 +0200 Subject: [PATCH 1/8] docs: Add usable documentation Updates all the documentation examples for Toast so that they are human readable. Also adds docstring to public API --- .../react-components/react-toast/src/index.ts | 2 +- .../react-toast/src/state/types.ts | 48 +++++++++++++- .../src/state/useToastController.ts | 4 ++ .../stories/Toast/CustomAnnounce.stories.tsx | 45 +++++++------ .../stories/Toast/CustomTimeout.stories.tsx | 48 ++++++++++++-- .../stories/Toast/Default.stories.tsx | 4 +- .../Toast/DefaultToastOptions.stories.tsx | 15 ++++- .../stories/Toast/DismissAll.stories.tsx | 14 ++++- .../stories/Toast/DismissToast.stories.tsx | 19 +++++- .../Toast/DismissToastWithAction.stories.tsx | 15 ++++- .../Toast/MultipleToasters.stories.tsx | 53 +++++++++++----- .../stories/Toast/Offset.stories.tsx | 63 +++++++++++++++---- .../stories/Toast/PauseOnHover.stories.tsx | 15 ++++- .../Toast/PauseOnWindowBlur.stories.tsx | 15 ++++- .../stories/Toast/ToastDescription.md | 29 +++++++++ .../stories/Toast/ToastPositions.stories.tsx | 30 +++++++-- .../stories/Toast/ToasterLimit.stories.tsx | 35 ++++++++++- .../stories/Toast/UpdateToast.stories.tsx | 15 ++++- .../stories/Toast/index.stories.tsx | 11 +++- 19 files changed, 398 insertions(+), 82 deletions(-) create mode 100644 packages/react-components/react-toast/stories/Toast/ToastDescription.md diff --git a/packages/react-components/react-toast/src/index.ts b/packages/react-components/react-toast/src/index.ts index b72bb14ab99da0..2e0316bd0d1239 100644 --- a/packages/react-components/react-toast/src/index.ts +++ b/packages/react-components/react-toast/src/index.ts @@ -1,5 +1,5 @@ export { useToastController } from './state'; -export type { ToastPosition, ToastId, ToastOffset } from './state'; +export type { ToastPosition, ToastId, ToastOffset, ToastPoliteness } from './state'; export { ToastTrigger } from './ToastTrigger'; export type { ToastTriggerChildProps, ToastTriggerProps, ToastTriggerState } from './ToastTrigger'; diff --git a/packages/react-components/react-toast/src/state/types.ts b/packages/react-components/react-toast/src/state/types.ts index 7e4f6867661813..676d35e2c49202 100644 --- a/packages/react-components/react-toast/src/state/types.ts +++ b/packages/react-components/react-toast/src/state/types.ts @@ -5,17 +5,51 @@ export type ToastId = string; export type ToasterId = string; export type ToastPosition = 'top-end' | 'top-start' | 'bottom-end' | 'bottom-start'; +export type ToastPoliteness = 'assertive' | 'polite'; export interface ToastOptions { + /** + * Uniquely identifies a toast, used for update and dismiss operations + */ toastId: ToastId; + /** + * The position the toast should render to + */ position: ToastPosition; + /** + * Toast content + */ content: unknown; + /** + * Auto dismiss timeout in milliseconds + * @default 3000 + */ timeout: number; + /** + * Toast timeout pauses while focus is on another window + * @default false + */ pauseOnWindowBlur: boolean; + /** + * Toast timeout pauses while user cursor is on the toast + * @default false + */ pauseOnHover: boolean; + /** + * Toast belongs to a specific toaster + */ toasterId: ToasterId | undefined; + /** + * Higher priority toasts will be rendered before lower priority toasts + */ priority: number; - politeness: 'assertive' | 'polite'; + /** + * Used to determine [aria-live](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) narration + */ + politeness: ToastPoliteness; + /** + * Additional data that needs to be passed to the toast + */ data: TData; } @@ -34,9 +68,21 @@ export interface ToasterOptions } export interface Toast extends ToastOptions { + /** + * Determines the visiblity of a toast + */ close: () => void; + /** + * Removes a toast completely + */ remove: () => void; + /** + * A number used to track updates immutably + */ updateId: number; + /** + * Used to determine default priority when the user does not set one + */ dispatchedAt: number; } diff --git a/packages/react-components/react-toast/src/state/useToastController.ts b/packages/react-components/react-toast/src/state/useToastController.ts index f290ea7cc26826..5bb4d68cd24311 100644 --- a/packages/react-components/react-toast/src/state/useToastController.ts +++ b/packages/react-components/react-toast/src/state/useToastController.ts @@ -10,6 +10,10 @@ import { DispatchToastOptions, ToastId, ToasterId, UpdateToastOptions } from './ const noop = () => undefined; +/** + * @param toasterId - If an id is provided all imperative methods control that specific toaster + * @returns Imperative methods to control toasts + */ export function useToastController(toasterId?: ToasterId) { const { targetDocument } = useFluent(); diff --git a/packages/react-components/react-toast/stories/Toast/CustomAnnounce.stories.tsx b/packages/react-components/react-toast/stories/Toast/CustomAnnounce.stories.tsx index f9e93cf785f8cc..47c052d69953f2 100644 --- a/packages/react-components/react-toast/stories/Toast/CustomAnnounce.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/CustomAnnounce.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { Toaster, useToastController, Toast, ToastTitle, ToasterProps } from '@fluentui/react-toast'; -import { useId, Link, makeStyles, shorthands } from '@fluentui/react-components'; +import { Toaster, useToastController, Toast, ToastTitle, ToasterProps, ToastPoliteness } from '@fluentui/react-toast'; +import { useId, makeStyles, shorthands, Button, Field, RadioGroup, Radio } from '@fluentui/react-components'; const useStyles = makeStyles({ visuallyHidden: { @@ -20,26 +20,17 @@ export const CustomAnnounce = () => { const styles = useStyles(); const [alert, setAlert] = React.useState(''); const [status, setStatus] = React.useState(''); + const [politeness, setPoliteness] = React.useState('polite'); const toasterId = useId('toaster'); const { dispatchToast } = useToastController(toasterId); - const dispatchAlert = () => + const notify = () => dispatchToast( - Undo}> - Assertive toast {counter++} + + {politeness === 'polite' ? 'Polite' : 'Assertive'} toast {counter++} , - { politeness: 'assertive' }, - ); - - const dispatchStatus = () => - dispatchToast( - - Undo}> - Polite toast {counter++} - - , - { politeness: 'polite' }, + { politeness }, ); const announce: ToasterProps['announce'] = (msg, options) => { @@ -54,9 +45,27 @@ export const CustomAnnounce = () => {
{alert}
+ + setPoliteness(data.value as ToastPoliteness)}> + + + + +
- - + ); }; + +CustomAnnounce.parameters = { + docs: { + description: { + story: [ + 'The `Toaster` manages an [aria-live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions)', + "internally so that toasts are announced to screen readers on render. It's possible to opt-out of this default", + 'behaviour by providing a custom `announce` callback.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx b/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx index 9504287e88e721..6e9e0241b97ea2 100644 --- a/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx @@ -1,22 +1,60 @@ import * as React from 'react'; -import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { Toaster, useToastController, ToastTitle, Toast, ToastTrigger } from '@fluentui/react-toast'; +import { useId, Button, Link, SpinButton, Field } from '@fluentui/react-components'; export const CustomTimeout = () => { + const [timeout, setDismissTimeout] = React.useState(1000); const toasterId = useId('toaster'); const { dispatchToast } = useToastController(toasterId); const notify = () => dispatchToast( - Custom timeout 1000ms + + Dismiss + + } + intent="info" + > + {timeout >= 0 ? `Custom timeout ${timeout}ms` : `Dismiss manually`} + , - { timeout: 1000 }, + { timeout }, ); return ( <> + + { + console.log(data.value); + if (data.value) { + setDismissTimeout(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setDismissTimeout(newValue); + } + } + }} + /> + +
- + ); }; + +CustomTimeout.parameters = { + docs: { + description: { + story: [ + 'The timeout of toasts can be customized in milliseconds. Using a negative timeout value results in the toast', + 'never being auto-dismissed.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/Default.stories.tsx b/packages/react-components/react-toast/stories/Toast/Default.stories.tsx index 7f7ecf3cd34018..8b9f29825af982 100644 --- a/packages/react-components/react-toast/stories/Toast/Default.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, Toast, ToastTitle, ToastBody, ToastFooter } from '@fluentui/react-toast'; -import { useId, Link } from '@fluentui/react-components'; +import { useId, Link, Button } from '@fluentui/react-components'; export const Default = () => { const toasterId = useId('toaster'); @@ -22,7 +22,7 @@ export const Default = () => { return ( <> - + ); }; diff --git a/packages/react-components/react-toast/stories/Toast/DefaultToastOptions.stories.tsx b/packages/react-components/react-toast/stories/Toast/DefaultToastOptions.stories.tsx index 62584c9bb68366..32d10ea99273b9 100644 --- a/packages/react-components/react-toast/stories/Toast/DefaultToastOptions.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/DefaultToastOptions.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const DefaultToastOptions = () => { const toasterId = useId('toaster'); @@ -15,7 +15,18 @@ export const DefaultToastOptions = () => { return ( <> - + ); }; + +DefaultToastOptions.parameters = { + docs: { + description: { + story: [ + 'Default options for all toasts can be configured on the `Toaster`.', + 'These options are only defaults and can be overriden using `dispatchToast', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx index af71e386da1fc7..89da66b3319e04 100644 --- a/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const DismissAll = () => { const toasterId = useId('toaster'); @@ -16,8 +16,16 @@ export const DismissAll = () => { return ( <> - - + + ); }; + +DismissAll.parameters = { + docs: { + description: { + story: ['The `dismissAllToasts imperative API will dismiss all rendered Toasts.'].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx index 24185f579b2519..b408cda15374ff 100644 --- a/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const DismissToast = () => { const toasterId = useId('toaster'); @@ -18,8 +18,21 @@ export const DismissToast = () => { return ( <> - - + + ); }; + +DismissToast.parameters = { + docs: { + description: { + story: [ + 'Toasts can be dismissed imperatively using the `dismissToast` API. In order to imperatively dismiss a ', + "Toast, it's necessary to dispatch it with a user provided id. You can use the id to dismiss the toast.", + "**Don't** use this API to dismiss toats when clicking on an action inside the toast, use the `ToastTrigger`", + 'instead.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/DismissToastWithAction.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissToastWithAction.stories.tsx index 7b1f5c773d2575..83c3cc75791eb8 100644 --- a/packages/react-components/react-toast/stories/Toast/DismissToastWithAction.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/DismissToastWithAction.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, ToastTrigger, Toast } from '@fluentui/react-toast'; -import { useId, Link } from '@fluentui/react-components'; +import { useId, Link, Button } from '@fluentui/react-components'; export const DismissToastWithAction = () => { const toasterId = useId('toaster'); @@ -25,7 +25,18 @@ export const DismissToastWithAction = () => { return ( <> - + ); }; + +DismissToastWithAction.parameters = { + docs: { + description: { + story: [ + "By wrapping a button or link with a `ToastTrigger`, it's possible to make that actionable", + 'element dismiss the toast with a click.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/MultipleToasters.stories.tsx b/packages/react-components/react-toast/stories/Toast/MultipleToasters.stories.tsx index 921cc2b7d2f4af..7ab0f0b7ff73d2 100644 --- a/packages/react-components/react-toast/stories/Toast/MultipleToasters.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/MultipleToasters.stories.tsx @@ -1,31 +1,52 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button, Field, RadioGroup, Radio } from '@fluentui/react-components'; -export const MultipeToasters = () => { +export const MultipleToasters = () => { const first = useId('toaster-1'); const second = useId('toaster-2'); + const [toaster, setToaster] = React.useState(first); const { dispatchToast: dispatchFirstToast } = useToastController(first); const { dispatchToast: dispatchSecondToast } = useToastController(second); - const notifyFirst = () => - dispatchFirstToast( - - First toaster - , - ); - const notifySecond = () => - dispatchSecondToast( - - Second toaster - , - ); + const notify = () => { + if (toaster === first) { + dispatchFirstToast( + + First toaster + , + ); + } else { + dispatchSecondToast( + + Second toaster + , + ); + } + }; return ( <> + + setToaster(data.value)}> + + + + - - + ); }; + +MultipleToasters.parameters = { + docs: { + description: { + story: [ + '> ⚠️ This use case is **not recommended**', + '', + 'Toasters support a `toasterId` prop to support multiple Toasters in an app.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx index d7bded2be7e0e6..2f06e432cc0f37 100644 --- a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ToastPosition, Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button, Field, SpinButton } from '@fluentui/react-components'; export const Offset = () => { const toasterId = useId('toaster'); @@ -19,19 +19,56 @@ export const Offset = () => { return ( <> + + { + console.log(data.value); + if (data.value) { + setHorizontal(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setHorizontal(newValue); + } + } + }} + /> + + + { + console.log(data.value); + if (data.value) { + setVertical(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setVertical(newValue); + } + } + }} + /> + +
+ + + + - - - - -
- - setHorizontal(Number(e.target.value))} /> -
-
- - setVertical(Number(e.target.value))} /> -
); }; + +Offset.parameters = { + docs: { + description: { + story: [ + 'You can declare a static offset for toasts relative to the viewport. This offset can only be set on the', + "`Toaster` component, because it wouldn't make sense to have separate toast offsets for a toasts in a", + 'single position.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/PauseOnHover.stories.tsx b/packages/react-components/react-toast/stories/Toast/PauseOnHover.stories.tsx index d56a06b87e6071..f0e3b09ab57fe1 100644 --- a/packages/react-components/react-toast/stories/Toast/PauseOnHover.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/PauseOnHover.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const PauseOnHover = () => { const toasterId = useId('toaster'); @@ -16,7 +16,18 @@ export const PauseOnHover = () => { return ( <> - + ); }; + +PauseOnHover.parameters = { + docs: { + description: { + story: [ + 'The `pauseOnHover` option will enable users to pause the timeout of a toast while the mouse cursor', + 'is inside the toast. This option can also be set on the Toaster as a default.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/PauseOnWindowBlur.stories.tsx b/packages/react-components/react-toast/stories/Toast/PauseOnWindowBlur.stories.tsx index 6ddfd6a3dff7a3..448ab308dec9cc 100644 --- a/packages/react-components/react-toast/stories/Toast/PauseOnWindowBlur.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/PauseOnWindowBlur.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const PauseOnWindowBlur = () => { const toasterId = useId('toaster'); @@ -16,7 +16,18 @@ export const PauseOnWindowBlur = () => { return ( <> - + ); }; + +PauseOnWindowBlur.parameters = { + docs: { + description: { + story: [ + 'Use `pauseOnWindowBlur` option to pause the dismiss timeout of a Toast when the user moves focus', + 'to another window. This option can also be set on the Toaster as a default.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/ToastDescription.md b/packages/react-components/react-toast/stories/Toast/ToastDescription.md new file mode 100644 index 00000000000000..ee4d1a9ece48dc --- /dev/null +++ b/packages/react-components/react-toast/stories/Toast/ToastDescription.md @@ -0,0 +1,29 @@ +A Toasts displays temporary content to the user. Toasts are rendered as a separate surface that can be dismissed by +user action or a application timeout. Toasts are typically used in the following situations: + +- Update the user on the status of a task +- Display the progress of a task +- Notify the user to take an action +- Notify the user of an application update +- Warn the user of an error + +The Fluent UI Toast component uses an **imperative** API. Once a Toaster has been rendered, you can use the +`useToastController` hook to get access to imperative methods to dispatch a Toast. The Toast component itself +is simply a layout component. + +## Best practices + +### Do + +- Configure defaults on the Toaster +- Use the toast for non-critical messages +- Let the user view the toast content in the application after the toast dismissed +- Create a keyboard shortcut to move focus to actionable toasts +- Use `politeness` setting to differentiate urgent and non-urgent messages + +### Don't + +- Render too many toasts at once +- Use different positions for toasts +- Use more than one Toaster in an application +- Make every toast have `assertive` politeness diff --git a/packages/react-components/react-toast/stories/Toast/ToastPositions.stories.tsx b/packages/react-components/react-toast/stories/Toast/ToastPositions.stories.tsx index 7f4d6d06c791af..b3272836d5ca04 100644 --- a/packages/react-components/react-toast/stories/Toast/ToastPositions.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/ToastPositions.stories.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import { Toaster, useToastController, ToastPosition, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button, Field, RadioGroup, Radio } from '@fluentui/react-components'; export const ToastPositions = () => { const toasterId = useId('toaster'); const { dispatchToast } = useToastController(toasterId); - const notify = (position: ToastPosition) => + const [position, setPosition] = React.useState('bottom-end'); + const notify = () => dispatchToast( This toast is {position} @@ -15,11 +16,28 @@ export const ToastPositions = () => { return ( <> + + setPosition(data.value as ToastPosition)}> + + + + + + - - - - + ); }; + +ToastPositions.parameters = { + docs: { + description: { + story: [ + 'Toasts can be dispatched to all four corners of a page. We do not recommend to use more than one', + 'position for toasts in an application because that could be disorienting for users. Pick', + 'one desired position and configure it in the `Toaster`.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx b/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx index 0c20ea998408b7..fded7aa2984906 100644 --- a/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button, Field, SpinButton } from '@fluentui/react-components'; export const ToasterLimit = () => { const toasterId = useId('toaster'); const { dispatchToast } = useToastController(toasterId); + const [limit, setLimit] = React.useState(3); const notify = () => dispatchToast( @@ -14,8 +15,36 @@ export const ToasterLimit = () => { return ( <> - - + + { + console.log(data.value); + if (data.value) { + setLimit(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setLimit(newValue); + } + } + }} + /> + +
+ + ); }; + +ToasterLimit.parameters = { + docs: { + description: { + story: [ + 'Use the `limit` prop on the `Toaster` to define the maximum number of toasts that can be rendered', + 'at any one time. Any extra toasts will be queued and rendered when a toast has been dismissed.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx index 9bd15047953b16..cc1ca025be73aa 100644 --- a/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId } from '@fluentui/react-components'; +import { useId, Button } from '@fluentui/react-components'; export const UpdateToast = () => { const toastId = useId('toast'); @@ -39,7 +39,18 @@ export const UpdateToast = () => { return ( <> - + ); }; + +UpdateToast.parameters = { + docs: { + description: { + story: [ + 'Use the `updateToast` imperative API to make changes to a Toast that is already visible. To do this', + 'you **must** provide an id when dispatching the toast. Almost all options of a Toast can be updated.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-toast/stories/Toast/index.stories.tsx b/packages/react-components/react-toast/stories/Toast/index.stories.tsx index b384afafdea0f8..bfcbbbd0d74da2 100644 --- a/packages/react-components/react-toast/stories/Toast/index.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/index.stories.tsx @@ -9,10 +9,19 @@ export { DismissAll } from './DismissAll.stories'; export { PauseOnWindowBlur } from './PauseOnWindowBlur.stories'; export { PauseOnHover } from './PauseOnHover.stories'; export { UpdateToast } from './UpdateToast.stories'; -export { MultipeToasters } from './MultipleToasters.stories'; +export { MultipleToasters } from './MultipleToasters.stories'; export { ToasterLimit } from './ToasterLimit.stories'; export { CustomAnnounce } from './CustomAnnounce.stories'; +import descriptionMd from './ToastDescription.md'; + export default { title: 'Preview Components/Toast', + parameters: { + docs: { + description: { + component: [descriptionMd].join('\n'), + }, + }, + }, }; From d06e050f76ffab4fd7b4121afec645cddca55489 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 6 Jun 2023 17:58:26 +0200 Subject: [PATCH 2/8] update api md file --- packages/react-components/react-toast/etc/react-toast.api.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-components/react-toast/etc/react-toast.api.md b/packages/react-components/react-toast/etc/react-toast.api.md index 9c055682fb71dc..4b5fbce3c0cc4d 100644 --- a/packages/react-components/react-toast/etc/react-toast.api.md +++ b/packages/react-components/react-toast/etc/react-toast.api.md @@ -101,6 +101,9 @@ export type ToastId = string; // @public (undocumented) export type ToastOffset = Partial> | ToastOffsetObject; +// @public (undocumented) +export type ToastPoliteness = 'assertive' | 'polite'; + // @public (undocumented) export type ToastPosition = 'top-end' | 'top-start' | 'bottom-end' | 'bottom-start'; From 9c8dfad021a2a3944bb96bede4b527aa8a3c5c13 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Tue, 6 Jun 2023 17:59:38 +0200 Subject: [PATCH 3/8] Add unstable notice --- .../react-toast/stories/Toast/ToastDescription.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/react-components/react-toast/stories/Toast/ToastDescription.md b/packages/react-components/react-toast/stories/Toast/ToastDescription.md index ee4d1a9ece48dc..d8ca87dc8a90c4 100644 --- a/packages/react-components/react-toast/stories/Toast/ToastDescription.md +++ b/packages/react-components/react-toast/stories/Toast/ToastDescription.md @@ -1,3 +1,16 @@ + + +> **⚠️ Preview components are considered unstable:** +> +> ```jsx +> +> import { Toast } from '@fluentui/react-components/unstable'; +> +> ``` +> +> - Features and APIs may change before final release +> - Please contact us if you intend to use this in your product + A Toasts displays temporary content to the user. Toasts are rendered as a separate surface that can be dismissed by user action or a application timeout. Toasts are typically used in the following situations: From 0b81c79b53d8df447bd974a6c64085a0df94fa88 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 7 Jun 2023 17:45:18 +0200 Subject: [PATCH 4/8] props table, better offset story --- .../stories/Toast/Offset.stories.tsx | 110 ++++++++++++------ .../stories/Toast/index.stories.tsx | 8 ++ 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx index 2f06e432cc0f37..9c85e85392f384 100644 --- a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx @@ -1,11 +1,40 @@ import * as React from 'react'; import { ToastPosition, Toaster, useToastController, ToastTitle, Toast } from '@fluentui/react-toast'; -import { useId, Button, Field, SpinButton } from '@fluentui/react-components'; +import { useId, Button, Field, SpinButton, RadioGroup, Radio, makeStyles } from '@fluentui/react-components'; + +const useStyles = makeStyles({ + playground: { + display: 'grid', + gridTemplateColumns: '25% 75%', + columnGap: '20px', + rowGap: '20px', + }, + + horizontal: { + gridColumnEnd: 2, + }, + + vertical: { + gridRowStart: 2, + gridColumnEnd: 2, + }, + + positions: { + gridRowStart: 1, + gridRowEnd: 3, + gridColumnStart: 2, + }, +}); export const Offset = () => { + const styles = useStyles(); const toasterId = useId('toaster'); const { dispatchToast } = useToastController(toasterId); - const notify = (position: ToastPosition) => + const [horizontal, setHorizontal] = React.useState(10); + const [vertical, setVertical] = React.useState(10); + const [position, setPosition] = React.useState('bottom-end'); + + const notify = () => dispatchToast( @@ -14,48 +43,53 @@ export const Offset = () => { , { position }, ); - const [horizontal, setHorizontal] = React.useState(10); - const [vertical, setVertical] = React.useState(10); return ( <> - - { - console.log(data.value); - if (data.value) { - setHorizontal(data.value); - } else if (data.displayValue !== undefined) { - const newValue = parseFloat(data.displayValue); - if (!Number.isNaN(newValue)) { - setHorizontal(newValue); +
+ + { + console.log(data.value); + if (data.value) { + setHorizontal(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setHorizontal(newValue); + } } - } - }} - /> - - - { - console.log(data.value); - if (data.value) { - setVertical(data.value); - } else if (data.displayValue !== undefined) { - const newValue = parseFloat(data.displayValue); - if (!Number.isNaN(newValue)) { - setVertical(newValue); + }} + /> + + + { + console.log(data.value); + if (data.value) { + setVertical(data.value); + } else if (data.displayValue !== undefined) { + const newValue = parseFloat(data.displayValue); + if (!Number.isNaN(newValue)) { + setVertical(newValue); + } } - } - }} - /> - + }} + /> + + + setPosition(data.value as ToastPosition)}> + + + + + + +

- - - - + ); diff --git a/packages/react-components/react-toast/stories/Toast/index.stories.tsx b/packages/react-components/react-toast/stories/Toast/index.stories.tsx index bfcbbbd0d74da2..b2459ee2fcce8e 100644 --- a/packages/react-components/react-toast/stories/Toast/index.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/index.stories.tsx @@ -1,3 +1,4 @@ +import { Toast, ToastTitle, ToastBody, ToastFooter, Toaster } from '@fluentui/react-toast'; export { Default } from './Default.stories'; export { DefaultToastOptions } from './DefaultToastOptions.stories'; export { CustomTimeout } from './CustomTimeout.stories'; @@ -17,6 +18,13 @@ import descriptionMd from './ToastDescription.md'; export default { title: 'Preview Components/Toast', + component: Toast, + subcomponents: { + ToastTitle, + ToastBody, + ToastFooter, + Toaster, + }, parameters: { docs: { description: { From 1d7cd9a4e4797e6b7ddcb67318b95bd24c635ec0 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 7 Jun 2023 17:46:13 +0200 Subject: [PATCH 5/8] hack to add toast stories to storybook --- apps/public-docsite-v9/.storybook/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/public-docsite-v9/.storybook/main.js b/apps/public-docsite-v9/.storybook/main.js index a57e9a915e54d5..25ddbbf511ead9 100644 --- a/apps/public-docsite-v9/.storybook/main.js +++ b/apps/public-docsite-v9/.storybook/main.js @@ -14,6 +14,8 @@ module.exports = /** @type {Omit Date: Wed, 7 Jun 2023 17:57:07 +0200 Subject: [PATCH 6/8] remove console.log --- .../react-toast/stories/Toast/CustomTimeout.stories.tsx | 1 - .../react-toast/stories/Toast/DismissAll.stories.tsx | 2 +- .../react-toast/stories/Toast/Offset.stories.tsx | 2 -- .../react-toast/stories/Toast/ToasterLimit.stories.tsx | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx b/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx index 6e9e0241b97ea2..fa289fe118ee6c 100644 --- a/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/CustomTimeout.stories.tsx @@ -29,7 +29,6 @@ export const CustomTimeout = () => { { - console.log(data.value); if (data.value) { setDismissTimeout(data.value); } else if (data.displayValue !== undefined) { diff --git a/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx index 89da66b3319e04..a11e8f9fdf3dfc 100644 --- a/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx @@ -17,7 +17,7 @@ export const DismissAll = () => { <> - + ); }; diff --git a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx index 9c85e85392f384..9d151fe74ac8bf 100644 --- a/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/Offset.stories.tsx @@ -51,7 +51,6 @@ export const Offset = () => { { - console.log(data.value); if (data.value) { setHorizontal(data.value); } else if (data.displayValue !== undefined) { @@ -67,7 +66,6 @@ export const Offset = () => { { - console.log(data.value); if (data.value) { setVertical(data.value); } else if (data.displayValue !== undefined) { diff --git a/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx b/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx index fded7aa2984906..c8938752966a1c 100644 --- a/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/ToasterLimit.stories.tsx @@ -19,7 +19,6 @@ export const ToasterLimit = () => { { - console.log(data.value); if (data.value) { setLimit(data.value); } else if (data.displayValue !== undefined) { From 21354faa3ae5dc0ae77c839aa983d2d5cbd6fca6 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 7 Jun 2023 19:40:12 +0200 Subject: [PATCH 7/8] remove hack --- apps/public-docsite-v9/.storybook/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/public-docsite-v9/.storybook/main.js b/apps/public-docsite-v9/.storybook/main.js index 25ddbbf511ead9..a57e9a915e54d5 100644 --- a/apps/public-docsite-v9/.storybook/main.js +++ b/apps/public-docsite-v9/.storybook/main.js @@ -14,8 +14,6 @@ module.exports = /** @type {Omit Date: Wed, 7 Jun 2023 19:50:14 +0200 Subject: [PATCH 8/8] fix toaster limit --- .../react-components/react-toast/src/state/vanilla/toaster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-toast/src/state/vanilla/toaster.ts b/packages/react-components/react-toast/src/state/vanilla/toaster.ts index 8b672247188d63..184ef5989c8519 100644 --- a/packages/react-components/react-toast/src/state/vanilla/toaster.ts +++ b/packages/react-components/react-toast/src/state/vanilla/toaster.ts @@ -160,7 +160,7 @@ export class Toaster { const remove = () => { this.toasts.delete(toastId); - if (this.queue.peek()) { + if (this.visibleToasts.size < this.limit && this.queue.peek()) { const nextToast = this.queue.dequeue(); this.toasts.set(nextToast.toastId, nextToast); this.visibleToasts.add(nextToast.toastId);