@wix/design-system
Version:
@wix/design-system
541 lines (487 loc) • 15.1 kB
Markdown
## Feature Examples
### Status
- description: <p>Control the toast type with the <code>status</code> prop: </p><li><code>success</code> is for a toast displayed after a successful action.</li><li><code>error</code> is used for error messages and information about critical issues that prevent users from continuing.</li><li><code>warning</code> is meant to draw the user's attention to unresolved issues that might affect the application but doesn't block the process.</li><li><code>info</code> is for informational updates that aren't urgent.</li>
- example:
```jsx
<StorybookComponents.Stack flexDirection="column" gap="24px">
<StatusToast
status="success"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
Success
</StatusToast>
<StatusToast
status="error"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
Error
</StatusToast>
<StatusToast
status="warning"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
Warning
</StatusToast>
<StatusToast
status="info"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
Info
</StatusToast>
</StorybookComponents.Stack>;
```
### Close button
- description: <p>Control whether the user can dismiss a status toast with the<code>dismissible</code> prop. </p><p></p><p>NOTE: If this is not enabled, <code>error</code>, <code>warning</code> and <code>info</code> toasts will remain visible on the screen until an action is taken or the issue is resolved. </p>
- example:
```jsx
<StorybookComponents.Stack flexDirection="column" gap="24px">
<StatusToast
status="error"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}
>
Dismissable
</StatusToast>
<StatusToast
status="error"
action={<StatusToast.Action>Action</StatusToast.Action>}
>
Non-dismissable
</StatusToast>
</StorybookComponents.Stack>;
```
### Action
- description: <p>Add a button to a notification with the <code>action</code> prop. Offer one action only to avoid complexity. </p>
- example:
```jsx
<StorybookComponents.Stack flexDirection="column" gap="24px">
<StatusToast
status="success"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
With action
</StatusToast>
<StatusToast
status="success"
dismissible>
Without action
</StatusToast>
</StorybookComponents.Stack>;
```
### Width
- description: <p>Component width is fixed to 600px. If the context exceeds the length, it will wrap to the next line. </p>
- example:
```jsx
<StatusToast
status="warning"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}
>
Width is 680px
</StatusToast>
```
### Height
- description: <p>Minimum component height is 42px. The component height will grow together with the content. Height is not limited, so be cautious about using very long texts, as a long message will cover any content behind it. </p>
- example:
```jsx
<StorybookComponents.Stack flexDirection="column" gap="24px">
<StatusToast
status="warning"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
This is a one-line content example </StatusToast>
<StatusToast
status="warning"
dismissible
action={<StatusToast.Action>Action</StatusToast.Action>}>
This is a much longer content example that wraps automatically to a second line while the status toast height grows with it </StatusToast>
</StorybookComponents.Stack>;
```
### Positioning
- description: <p>The position of a status toast is not customizable. <code><ToastContainer/></code> position is attached to the viewport with 26px gap from the bottom and not to the parent container or content within the page. </p>
- example:
```jsx
(() => {
const makeId = () => Math.random().toString(36).substring(2, 9);
const Blank = () => (
<div
style={{
background:
'repeating-linear-gradient(45deg, rgba(0,0,0,0.2), rgba(0,0,0,0.2) 10px, rgba(255,255,255,0.2) 10px, rgba(255,255,255,0.2) 20px)',
flex: '1 1 auto',
}}
/>
);
const Example = () => {
const [toasts, setToasts] = React.useState([]);
const clearToasts = () => {
setToasts([]);
};
const removeToast = (id) => {
setToasts((toasts) => toasts.filter((toast) => toast.id !== id));
};
const addToast = (id, toast) => {
setToasts((toasts) => [...toasts, { id, toast }]);
};
const createStatusToast = (status, content, action) => {
const id = makeId();
addToast(
id,
<StatusToast
key={id}
dataHook={id}
status={status}
dismissible
dismissLabel="Dismiss"
onDismiss={() => {
removeToast(id);
}}
action={action}
>
{content}
</StatusToast>,
);
};
return (
<>
<Box gap={1}>
<Button
size="small"
onClick={() =>
createStatusToast(
'success',
'I am placed 26px from the bottom of the screen',
<StatusToast.Action>Got it</StatusToast.Action>,
)
}
>
Click here to see positioning
</Button>
</Box>
<ToastContainer>{toasts.map(({ toast }) => toast)}</ToastContainer>
</>
);
};
return Example;
})();
```
### Timeout
- description: <p>Whether notification disappears automatically or not can be controlled with <code>duration</code> prop.</p><p></p><p>Recommended delay until toast disappears is at least <strong>7 seconds</strong>.</p><p></p><p>When possible, it is recommended to make <code>error</code> toasts persistent, giving the user a chance to resolve the error or dismiss it at their own pace.</p>
- example:
```jsx
(() => {
const makeId = () => Math.random().toString(36).substring(2, 9);
const Blank = () => (
<div
style={{
background:
'repeating-linear-gradient(45deg, rgba(0,0,0,0.2), rgba(0,0,0,0.2) 10px, rgba(255,255,255,0.2) 10px, rgba(255,255,255,0.2) 20px)',
flex: '1 1 auto',
}}
/>
);
const Example = () => {
const [toasts, setToasts] = React.useState([]);
const clearToasts = () => {
setToasts([]);
};
const removeToast = (id) => {
setToasts((toasts) => toasts.filter((toast) => toast.id !== id));
};
const addToast = (id, toast) => {
setToasts((toasts) => [...toasts, { id, toast }]);
};
const createStatusToast = (status, content, action) => {
const id = makeId();
addToast(
id,
<StatusToast
key={id}
dataHook={id}
status={status}
dismissible
dismissLabel="Dismiss"
onDismiss={() => {
removeToast(id);
}}
action={action}
>
{content}
</StatusToast>,
);
};
return (
<>
<Box gap={1}>
<Button
size="small"
onClick={() =>
createStatusToast(
'info',
'Info toast - no timeout',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
Info
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'warning',
'Warning toast - no timeout',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
Warning
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'error',
'Error toast - no timeout',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
Error
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'success',
'Success toast - timeout after 7 seconds',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
Success
</Button>
{toasts.length ? (
<Tooltip content="Clear">
<IconButton
size="small"
onClick={clearToasts}
priority="secondary"
>
<Icons.DeleteSmall />
</IconButton>
</Tooltip>
) : null}
</Box>
<ToastContainer>{toasts.map(({ toast }) => toast)}</ToastContainer>
</>
);
};
return Example;
})();
```
### Stacking
- description: <p>By default, only one toast is visible at a time and the rest will be hidden under the first one. </p><p></p><p>However, you can control how many toast are visible at the same time. <strong>Up to thee toast</strong> can be visible at the same time. If more than given number is shown, they will be stacked with 6px gap between each other. </p><p></p><p>If three toasts are already visible, the newest one will be shown instead of the oldest one, until a slot is freed up by closing a toast, solving an issue, or it times out. </p>
- example:
```jsx
() => {
const makeId = () => Math.random().toString(36).substring(2, 9);
const [toasts, setToasts] = React.useState([]);
const clearToasts = () => {
setToasts([]);
};
const removeToast = (id) => {
setToasts((toasts) => toasts.filter((toast) => toast.id !== id));
};
const addToast = (id, toast) => {
setToasts((toasts) => [...toasts, { id, toast }]);
};
const createStatusToast = (status, content, action) => {
const id = makeId();
addToast(
id,
<StatusToast
key={id}
dataHook={id}
status={status}
dismissible
dismissLabel="Dismiss"
onDismiss={() => {
removeToast(id);
}}
action={action}
>
{content}
</StatusToast>,
);
};
return (
<>
<Box gap={1}>
<Button
size="small"
onClick={() =>
createStatusToast(
'error',
'I am 1st toast',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
1st toast
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'error',
'I am 2nd toast',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
2nd toast
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'error',
'I am 3rd toast',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
3rd toast
</Button>
<Button
size="small"
onClick={() =>
createStatusToast(
'error',
'I am 4th toast',
<StatusToast.Action>Action</StatusToast.Action>,
)
}
>
4th toast
</Button>
{toasts.length ? (
<Tooltip content="Clear">
<IconButton size="small" onClick={clearToasts} priority="secondary">
<Icons.DeleteSmall />
</IconButton>
</Tooltip>
) : null}
</Box>
<ToastContainer maxToasts={3}>
{toasts.map(({ toast }) => toast)}
</ToastContainer>
</>
);
};
```
## Common Use Case Examples
### In modals
- description: <p>Status toasts should be used inside of modals as well. Toasts in modals have the same logic as in pages. They appear at the same location, with an offset of 26px from the bottom of the page and have the same stacking behavior.</p>
- example:
```jsx
() => {
const [shown, setShown] = React.useState(false);
const [copy, setCopy] = React.useState(false);
const [toast, setToast] = React.useState(null);
const createStatusToast = (status, content, action) => {
setToast(
<StatusToast
key={'test-id'}
status={status}
dismissible
dismissLabel="Dismiss"
onDismiss={clearToast}
action={action}
>
{content}
</StatusToast>,
);
};
const clearToast = () => {
setToast(null);
};
const onShowModal = () => {
setShown(true);
createStatusToast(
'error',
'Your internet connection has been lost. Refresh and try again',
<StatusToast.Action>Refresh</StatusToast.Action>,
);
};
const onCloseModal = () => {
setShown(false);
clearToast();
};
const data = [
{ name: 'User 1', plan: 'Gold', email: 'user1@mail.com' },
{ name: 'User 2', plan: 'Silver', email: 'user2@mail.com' },
{ name: 'User 3', plan: 'Gold', email: 'user3@mail.com' },
{ name: 'User 4', plan: 'Gold', email: 'user4@mail.com' },
{ name: 'User 5', plan: 'Bronze', email: 'user5@mail.com' },
{ name: 'User 6', plan: 'Bronze', email: 'user6@mail.com' },
{ name: 'User 7', plan: 'Gold', email: 'user7@mail.com' },
{ name: 'User 8', plan: 'Bronze', email: 'user8@mail.com' },
];
const columns = [
{ title: 'Name', render: (row) => row.name },
{ title: 'Plan', render: (row) => row.plan },
{ title: 'Email', render: (row) => row.email },
];
return (
<>
<Card>
<Card.Header
title="Send coupons to your members"
subtitle="Select members who will receive a new coupon via email."
suffix={
<Button size="small" priority="secondary" onClick={onShowModal}>
Send Coupons
</Button>
}
/>
<Modal
isOpen={shown}
onRequestClose={onCloseModal}
shouldCloseOnOverlayClick
screen="desktop"
>
<CustomModalLayout
title="Send coupons to members"
subtitle="Select members who will receive coupons via email."
onCloseButtonClick={() => setShown(false)}
primaryButtonOnClick={() => setShown(false)}
secondaryButtonOnClick={() => setShown(false)}
width="720px"
maxHeight="350px"
removeContentPadding
primaryButtonText="Send"
secondaryButtonText="Cancel"
sideActions={
<Checkbox
size="small"
checked={copy}
onChange={() => setCopy(!copy)}
>
Send copy Email to myself
</Checkbox>
}
content={
<Table data={data} columns={columns} showSelection>
<Table.Content />
</Table>
}
/>
</Modal>
</Card>
<ToastContainer>{toast}</ToastContainer>
</>
);
};
```