Skip to content

Commit

Permalink
Toast docs + children support (#2048)
Browse files Browse the repository at this point in the history
* Toaster renders children after state.toasts

* Toast docs: clarify usage

* Overlay supports falsy children

* docs

* three ways to use a toaster
  • Loading branch information
giladgray authored Jan 29, 2018
1 parent 0dacc6c commit 5b6961f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 54 deletions.
7 changes: 5 additions & 2 deletions packages/core/src/components/overlay/overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,11 @@ export class Overlay extends React.PureComponent<IOverlayProps, IOverlayState> {

const { children, className, inline, isOpen, transitionDuration, transitionName } = this.props;

const childrenWithTransitions = React.Children.map(children, (child: React.ReactElement<any>) => {
// add a special class to each child that will automatically set the appropriate
const childrenWithTransitions = React.Children.map(children, (child?: React.ReactChild) => {
if (child == null || typeof child !== "object") {
return child;
}
// add a special class to each child element that will automatically set the appropriate
// CSS position mode under the hood. also, make the container focusable so we can
// trap focus inside it (via `enforceFocus`).
const decoratedChild = React.cloneElement(child, {
Expand Down
127 changes: 76 additions & 51 deletions packages/core/src/components/toast/toast.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

A toast is a lightweight, ephemeral notice from an application in direct response to a user's action.

`Toast`s have a built-in timeout of five seconds. Users can also dismiss them manually by clicking the &times; button.
Hovering the cursor over a toast prevents it from disappearing. When the cursor leaves the toast, the toast's timeout restarts.
Similarly, focusing the toast (for example, by hitting the `tab` key) halts the timeout, and blurring restarts the timeout.

You can add one additional action button to a toast. You might use this to undo the user's action, for example.

You can also apply the same visual intent styles to `Toast`s that you can to [`Button`s](#core/components/button.css-api).

Toasts can be configured to appear at either the top or the bottom of an application window, and it is possible to
have more than one toast onscreen at a time.
Expand All @@ -20,29 +13,30 @@ have more than one toast onscreen at a time.
The `Toast` and `Toaster` components are available in the __@blueprintjs/core__ package.
Make sure to review the [general usage docs for JS components](#blueprint.usage).

The `Toaster` component provides the static `create` method that returns a new `Toaster` instance, rendered into an
element attached to `<body>`. (You can also specify the element to render into if desired.) A `Toaster` instance
has a collection of methods to show and hide toasts in its given container.
@### Toast

Your application can contain several `Toaster` instances and easily share them across the codebase as modules.
`Toast`s have a built-in timeout of five seconds. Users can also dismiss them manually by clicking the &times; button.
Hovering the cursor over a toast prevents it from disappearing. When the cursor leaves the toast, the toast's timeout restarts.
Similarly, focusing the toast (for example, by hitting the `tab` key) halts the timeout, and blurring restarts the timeout.

```tsx
// toaster.ts
import { Position, Toaster } from "@blueprintjs/core";
You can add one additional action button to a toast. You might use this to undo the user's action, for example.

export const OurToaster = Toaster.create({
className: "my-toaster",
position: Position.BOTTOM_RIGHT,
});
```
You can also apply the same visual intent styles to `Toast`s that you can to [`Button`s](#core/components/button.css-api).

```tsx
// application.ts
import { OurToaster } from "./toaster";
@interface IToastProps

const key = OurToaster.show({ message: "Toasted!" });
OurToaster.update(key, { message: "Still toasted!" });
```
@### Toaster

The `Toaster` React component is a stateful container for a single list of toasts. Internally, it
uses [`Overlay`](#core/components/overlay) to manage children and transitions. It can be vertically
aligned along the top or bottom edge of its container (new toasts will slide in from that edge) and
horizontally aligned along the left edge, center, or right edge of its container.

There are three ways to use the `Toaster` component:

1. `Toaster.create(props)` static method returns a new `IToaster` instance. Use the instance method `toaster.show()` to manipulate this instance. __(recommended)__
1. `<Toaster><Toast />...</Toaster>`: Render a `<Toaster>` element with React `children`.
1. `<Toaster ref={ref => ref.show({ ...toast })} />`: Render a `<Toaster>` element and use the `ref` prop to access its instance methods.

<div class="pt-callout pt-intent-primary pt-icon-info-sign">
<h5>Working with multiple toasters</h5>
Expand All @@ -58,51 +52,79 @@ OurToaster.update(key, { message: "Still toasted!" });
enable `autoFocus` for a `Toaster` via a prop, if desired.
</div>

@### Static method

@interface IToasterProps

@## Static usage

The `Toaster` component provides the static `create` method that returns a new `Toaster` instance, rendered into an
element attached to `<body>`. A `Toaster` instance
has a collection of methods to show and hide toasts in its given container.

```ts
Toaster.create(props?: IToasterProps, container = document.body): IToaster
```

Create a new `Toaster` instance. The `Toaster` will be rendered into a new element appended to the
given `container`. The `container` determines which element toasts are positioned relative to; the
default value of `<body>` allows them to use the entire viewport.

The `Toaster` will be rendered into a new element appended to the given `container`. The `container` determines which element toasts are positioned relative to; the default value of `<body>` allows them to use the entire viewport.

Note that the return type is `IToaster`, which is a minimal interface that exposes only the instance
methods detailed below. It can be thought of as `Toaster` minus the `React.Component` methods,
because the `Toaster` should not be treated as a normal React component.

@interface IToasterProps
@interface IToaster

@### Instance methods
@### Example

<div class="docs-interface-name">IToaster</div>
Your application can contain several `Toaster` instances and easily share them across the codebase as modules.

- `show(props: IToastProps): string` — Show a new toast to the user.
Returns the unique key of the new toast.
- `update(key: string, props: IToastProps): void`
Updates the toast with the given key to use the new props.
Updating a key that does not exist is effectively a no-op.
- `dismiss(key: string): void` — Dismiss the given toast instantly.
- `clear(): void` — Dismiss all toasts instantly.
- `getToasts(): IToastProps[]` — Returns the options for all current toasts.
The following code samples demonstrate our preferred pattern for intergrating a toaster into a React application:

@interface IToastProps
#### `toaster.ts`
```tsx
import { Position, Toaster } from "@blueprintjs/core";

/** Singleton toaster instance. Create separate instances for different options. */
export const AppToaster = Toaster.create({
className: "recipe-toaster",
position: Position.TOP,
});
```

@### React component
#### `application.ts`
```tsx
import { Button } from "@blueprintjs/core";
import * as React from "react";
import { AppToaster } from "./toaster";

The `Toaster` React component is a stateful container for a single list of toasts. Internally, it
uses [`Overlay`](#core/components/overlay) to manage children and transitions. It can be vertically
aligned along the top or bottom edge of its container (new toasts will slide in from that edge) and
horizontally aligned along the left edge, center, or right edge of its container.
export class App extends React.PureComponent {
render() {
return <Button onClick={this.showToast} text="Toast please" />;
}

You should use [`Toaster.create`](#core/components/toast.static-method), rather than using the
`Toaster` component API directly in React, to avoid having to use `ref` to access the instance.
showToast = () => {
// create toasts in response to interactions.
// in most cases, it's enough to simply create and forget (thanks to timeout).
AppToaster.show({ message: "Toasted." });
}
}
```

@## React component usage

Render the `<Toaster>` component like any other element and supply `<Toast>` elements as `children`. You can
optionally attach a `ref` handler to access the instance methods, but we strongly recommend using the
[`Toaster.create` static method](#core/components/toast.static-usage) documented above instead. Note that
`children` and `ref` can be used together, but `children` will always appear _after_ toasts created with
`ref.show()`.

```tsx
import { Button, Position, Toaster } from "@blueprintjs/core";
import { Button, Position, Toast, Toaster } from "@blueprintjs/core";
import * as React from "react";

class MyComponent extends React.PureComponent {
public state = { toasts: [ /* IToastProps[] */ ] }

class MyComponent extends React.Component<{}, {}> {
private toaster: Toaster;
private refHandlers = {
toaster: (ref: Toaster) => this.toaster = ref,
Expand All @@ -112,7 +134,10 @@ class MyComponent extends React.Component<{}, {}> {
return (
<div>
<Button onClick={this.addToast} text="Procure toast" />
<Toaster position={Position.TOP_RIGHT} ref={this.refHandlers.toaster} />
<Toaster position={Position.TOP_RIGHT} ref={this.refHandlers.toaster}>
{/* "Toasted!" will appear here after clicking button. */}
{this.state.toasts.map(toast => <Toast {...toast} />)}
</Toaster>
</div>
)
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/components/toast/toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type ToasterPosition =
| Position.BOTTOM_LEFT
| Position.BOTTOM_RIGHT;

/** Instance methods available on a `<Toaster>` component instance. */
export interface IToaster {
/**
* Shows a new toast to the user, or updates an existing toast corresponding to the provided key (optional).
Expand All @@ -45,6 +46,10 @@ export interface IToaster {
getToasts(): IToastOptions[];
}

/**
* Props supported by the `<Toaster>` component.
* These props can be passed as an argument to the static `Toaster.create(props?, container?)` method.
*/
export interface IToasterProps extends IProps {
/**
* Whether a toast should acquire application focus when it first opens.
Expand Down Expand Up @@ -159,12 +164,13 @@ export class Toaster extends AbstractPureComponent<IToasterProps, IToasterState>
enforceFocus={false}
hasBackdrop={false}
inline={this.props.inline}
isOpen={this.state.toasts.length > 0}
isOpen={this.state.toasts.length > 0 || this.props.children != null}
onClose={this.handleClose}
transitionDuration={350}
transitionName="pt-toast"
>
{this.state.toasts.map(this.renderToast, this)}
{this.props.children}
</Overlay>
);
}
Expand Down
10 changes: 10 additions & 0 deletions packages/core/test/overlay/overlayTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ describe("<Overlay>", () => {
assert.lengthOf(overlay.find(BACKDROP_SELECTOR), 1);
});

it("supports non-element children", () => {
assert.doesNotThrow(() =>
shallow(
<Overlay inline={true} isOpen={true}>
{null} {undefined}
</Overlay>,
),
);
});

it("hasBackdrop=false does not render backdrop", () => {
const overlay = shallow(
<Overlay hasBackdrop={false} inline={true} isOpen={true}>
Expand Down

1 comment on commit 5b6961f

@blueprint-bot
Copy link

Choose a reason for hiding this comment

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

Toast docs + children support (#2048)

Preview: documentation | landing | table

Please sign in to comment.