Skip to content
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

Feat: Add Message and Link for ToastBar #DS-1213 #1426

Merged
merged 3 commits into from
May 28, 2024
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: 7 additions & 1 deletion apps/web-twig-demo/assets/scripts/toast-dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ const addDynamicToast = (event, containerId) => {
autoCloseInterval: formElement.querySelector('#toast-auto-close-interval').value,
color: formElement.querySelector('#toast-color').value,
containerId,
content: formElement.querySelector('#toast-content').value,
enableAutoClose: formElement.querySelector('#toast-enable-auto-close').checked,
hasIcon: formElement.querySelector('#toast-has-icon').checked,
id: `my-dynamic-toast-${Date.now()}`,
isDismissible: formElement.querySelector('#toast-is-dismissible').checked,
message: formElement.querySelector('#toast-message').value,
linkContent: formElement.querySelector('#toast-enable-link').checked
? formElement.querySelector('#toast-link').value
: null,
linkProps: {
href: '#',
},
};

const toast = new Toast(null, config);
Expand Down
114 changes: 82 additions & 32 deletions packages/web-react/src/components/Toast/README.md
curdaj marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Toast is a composition of a few subcomponents:

- [Toast](#toast)
- [ToastBar](#toastbar)
- [ToastBarMessage](#toastbarmessage)
- [ToastBarLink](#toastbarlink)
- [UncontrolledToast](#uncontrolledToast)

## Toast
Expand Down Expand Up @@ -150,11 +152,11 @@ elements.
Minimum example:

```jsx
import { ToastBar } from '@lmc-eu/spirit-web-react/components';
```
import { ToastBar, ToastBarMessage } from '@lmc-eu/spirit-web-react/components';

```jsx
<ToastBar id="my-toast">Message only</ToastBar>
<ToastBar id="my-toast">
<ToastBarMessage>Message only</ToastBarMessage>
</ToastBar>;
```

### Optional Icon
Expand All @@ -163,15 +165,15 @@ An icon can be displayed in the ToastBar component, depending on the color of th

```jsx
<ToastBar id="my-toast" color="success" hasIcon>
Message with icon
<ToastBarMessage>Message with icon</ToastBarMessage>
</ToastBar>
```

Alternatively, a custom icon can be used:

```jsx
<ToastBar id="my-toast" iconName="download">
Message with custom icon
<ToastBarMessage>Message with custom icon</ToastBarMessage>
</ToastBar>
```

Expand All @@ -185,21 +187,59 @@ Alternatively, a custom icon can be used:
| `success` | `check-plain` |
| `warning` | `warning` |

### Action Link
### ToastBar Components

The content of `ToastBar` can be assembled from the following subcomponents:

An action link can be added to the ToastBar component:
#### ToastBarMessage
crishpeen marked this conversation as resolved.
Show resolved Hide resolved

`ToastBarMessage` is a subcomponent designated for the main message in `ToastBar`.

Usage example:

```jsx
<ToastBar id="my-toast">
Message with action
<Link href="#" color="inverted" isUnderlined>
Action
</Link>
<ToastBar id="my-toast" isOpen={isOpen} onClose={() => setIsOpen(false)} isDismissible>
<ToastBarMessage>This is the main toast message.</ToastBarMessage>
</ToastBar>
```

👉 For the sake of flexibility, developers can pass the link as part of the message. However, it is strongly recommended
to use the **inverted underlined** variant of the link (for all ToastBar colors) to make it stand out from the message.
#### API

| Name | Type | Default | Required | Description |
| ---------- | ----------- | ------- | -------- | ------------------------------ |
| `children` | `ReactNode` | — | ✓ | Content of the ToastBarMessage |
curdaj marked this conversation as resolved.
Show resolved Hide resolved

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].

#### ToastBarLink

`ToastBarLink` is a subcomponent designated to create an action link within `ToastBar`.

Usage example:

```jsx
<ToastBar id="my-toast" isOpen={isOpen} onClose={() => setIsOpen(false)} isDismissible>
<ToastBarLink href="#">This is the action link.</ToastBarLink>
</ToastBar>
```

#### API

| Name | Type | Default | Required | Description |
| -------------- | ------------------------------------------------ | ---------- | -------- | ------------------------------ |
| `children` | `ReactNode` | — | ✓ | Content of the ToastBarLink |
| `color` | [Action Link Color dictionary][dictionary-color] | `inverted` | ✕ | Color of the link |
| `elementType` | `ElementType` | `a` | ✕ | Type of element used as |
| `href` | `string` | — | ✕ | ToastBarLink's href attribute |
| `isDisabled` | `bool` | `false` | ✕ | Whether is the link disabled |
| `isUnderlined` | `bool` | `true` | ✕ | Whether is the link underlined |
| `ref` | `ForwardedRef<HTMLAnchorElement>` | — | ✕ | Link element reference |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].

👉 **Do not put any important actions** like "Undo" in the ToastBar component (unless there are other means to perform
said action), as it is very hard (if not impossible) to reach for users with assistive technologies. Read more about
Expand All @@ -213,9 +253,11 @@ Use the `color` option to change the color of the ToastBar component.
For example:

```jsx
import { ToastBarMessage } from '@lmc-eu/spirit-web-react/components';

<ToastBar id="my-toast" color="success">
Success message
</ToastBar>
<ToastBarMessage>Success message</ToastBarMessage>
</ToastBar>;
```

### Opening the ToastBar
Expand All @@ -224,7 +266,7 @@ Set `isOpen` prop to `true` to open a Toast **that is present in the DOM,** e.g.

```jsx
<ToastBar id="my-toast" isOpen>
Opened ToastBar
<ToastBarMessage>Opened ToastBar</ToastBarMessage>
</ToastBar>
```

Expand All @@ -236,7 +278,7 @@ To make the ToastBar dismissible, add the `isDismissible` prop along with a `onC

```jsx
<ToastBar id="my-toast" onClose={() => {}} isDismissible>
Dismissible message
<ToastBarMessage>Dismissible message</ToastBarMessage>
</ToastBar>
```

Expand All @@ -248,7 +290,7 @@ To make the ToastBar dismissible, add the `isDismissible` prop along with a `onC
| `color` | [[Emotion Color dictionary][dictionary-color] \| `inverted`] | `inverted` | ✕ | Color variant |
| `hasIcon` | `bool` | `false` \* | ✕ | If true, an icon is shown along the message |
| `iconName` | `string` | `info` \* | ✕ | Name of a custom icon to be shown along the message |
| `id` | `string` | — | | ToastBar ID |
| `id` | `string` | — | | ToastBar ID |
| `isDismissible` | `bool` | `false` | ✕ | If true, ToastBar can be dismissed by user |
| `isOpen` | `bool` | `true` | ✕ | If true, ToastBar is visible |
| `onClose` | `function` | — | ✕ | Close button callback |
Expand All @@ -264,18 +306,18 @@ and [escape hatches][readme-escape-hatches].
## Full Example

```jsx
import { Button, Toast, ToastBar } from '@lmc-eu/spirit-web-react/components';
import { Button, Toast, ToastBar, ToastBarMessage, ToastBarLink } from '@lmc-eu/spirit-web-react/components';

const [isOpen, setIsOpen] = React.useState(false)
const [isOpen, setIsOpen] = useState(false);

<Button onClick={() => setIsOpen(true)} aria-expanded={isOpen} aria-controls="my-toast">
{buttonLabel}
</Button>

<Toast>
<ToastBar id="my-toast" isOpen={isOpen} onClose={() => setIsOpen(false)} isDismissible>
Toast message
<Link href="#" color="inverted" isUnderlined>Action</Link>
<ToastBarMessage>Toast message</ToastBarMessage>
<ToastBarLink href="#">Action</ToastBarLink>
</ToastBar>
</Toast>
```
Expand Down Expand Up @@ -348,17 +390,25 @@ All other options are not required and can be omitted entirely.
```jsx
const { show } = useToast();

┌─⫸ Message inside UncontrolledToast (required)
┌─⫸ ToastBar ID (required)
show('Toast message', 'toast-id', {
autoCloseInterval: 3000 // Set interval in ms after ToastBar will be closed, default: 3000
┌─⫸ Message inside UncontrolledToast (required)
│ ┌─⫸ Link text inside UncontrolledToast (required)
│ │ ┌─⫸ ToastBar ID (required)
│ │
show({content: { message: 'Toast message', link: 'Link action' }}, 'toast-id', {
autoCloseInterval: 3000, // Set interval in ms after ToastBar will be closed, default: 3000
color: 'danger', // Color variant, default: 'inverted'
enableAutoClose: true // If true, ToastBar will close after `autoCloseInterval`, default: true
hasIcon: true // If true, an icon is shown along the message, default: false \*
enableAutoClose: true, // If true, ToastBar will close after `autoCloseInterval`, default: true
hasIcon: true, // If true, an icon is shown along the message, default: false \*
iconName: 'download', // Name of a custom icon to be shown along the message, default: undefined
isDismissible: true // If true, ToastBar can be dismissed by user, default: false
linkProps: { // Props for the link
href: 'https://example.com', // Link URL
target: '_blank', // Optional link target attribute
isUnderlined: false, // Optional link underlining, default: true
isDisabled: false, // Optional link disabling, default: false
elementType: 'a', // Optional link element type, default: 'a'
color: 'inverted', // Optional link color variant, default: 'inverted'
},
});
```

Expand Down
7 changes: 3 additions & 4 deletions packages/web-react/src/components/Toast/ToastBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const ToastBar = (props: SpiritToastBarProps) => {
...restProps,
color,
isDismissible,
id,
});
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

Expand All @@ -44,15 +43,15 @@ const ToastBar = (props: SpiritToastBarProps) => {
<div
{...styleProps}
{...otherProps}
id={id}
className={classNames(classProps.root, TRANSITIONING_STYLES[transitionState], styleProps.className)}
ref={rootElementRef}
>
<div className={classProps.box}>
<div className={classProps.content}>
<div className={classProps.container}>
{(hasIcon || iconName) && <Icon name={toastIconName} boxSize={ICON_BOX_SIZE} />}
<div className={classProps.message}>{children}</div>
<div className={classProps.content}>{children}</div>
</div>

<ToastCloseButton
id={id}
color={color}
Expand Down
40 changes: 40 additions & 0 deletions packages/web-react/src/components/Toast/ToastBarLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { ElementType, ForwardedRef, forwardRef } from 'react';
import classNames from 'classnames';
import { ActionLinkColors } from '../../constants';
import { useStyleProps } from '../../hooks';
import { SpiritLinkProps } from '../../types';
import { Link } from '../Link';
import { useToastBarStyleProps } from './useToastBarStyleProps';

const defaultProps: Partial<SpiritLinkProps> = {
color: ActionLinkColors.INVERTED,
isUnderlined: true,
};

/* We need an exception for components exported with forwardRef */
/* eslint no-underscore-dangle: ['error', { allow: ['_ToastBarLink'] }] */
const _ToastBarLink = <E extends ElementType = typeof Link, C = void>(
props: SpiritLinkProps<E, C>,
ref: ForwardedRef<HTMLAnchorElement>,
) => {
const propsWithDefaults = { ...defaultProps, ...props };
const { children, ...restProps } = propsWithDefaults;
const { classProps, props: modifiedProps } = useToastBarStyleProps({ ...restProps });
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<Link
{...propsWithDefaults}
{...otherProps}
ref={ref}
UNSAFE_className={classNames(classProps.link, styleProps.className)}
UNSAFE_style={styleProps.style}
>
{children}
</Link>
);
};

export const ToastBarLink = forwardRef<HTMLAnchorElement, SpiritLinkProps<ElementType>>(_ToastBarLink);

export default ToastBarLink;
21 changes: 21 additions & 0 deletions packages/web-react/src/components/Toast/ToastBarMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import classNames from 'classnames';
import { useClassNamePrefix, useStyleProps } from '../../hooks';
import { ChildrenProps } from '../../types';

const ToastBarMessage = (props: ChildrenProps) => {
const { children, ...restProps } = props;
const { styleProps, props: otherProps } = useStyleProps(restProps);

return (
<div
{...styleProps}
{...otherProps}
className={classNames(useClassNamePrefix('text-truncate-multiline'), styleProps.className)}
>
{children}
</div>
);
};

export default ToastBarMessage;
Loading
Loading