Skip to content

Commit

Permalink
BREAKING CHANGE(web-react): Rename inverted variant of Toast to `…
Browse files Browse the repository at this point in the history
…neutral` #DS-1446

Refactor ToastBarLink and ToastCloseButton.
  • Loading branch information
crishpeen committed Sep 26, 2024
1 parent 81581d2 commit ba4dfc2
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 113 deletions.
64 changes: 29 additions & 35 deletions packages/web-react/src/components/Toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Alternatively, a custom icon can be used:
| ------------- | ------------- |
| `danger` | `danger` |
| `informative` | `info` |
| `inverted` | `info` |
| `neutral` | `info` |
| `success` | `check-plain` |
| `warning` | `warning` |

Expand Down Expand Up @@ -227,15 +227,12 @@ Usage example:

#### 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 |
| Name | Type | Default | Required | Description |
| ------------- | --------------------------------- | ------- | -------- | ----------------------------- |
| `children` | `ReactNode` ||| Content of the ToastBarLink |
| `elementType` | `ElementType` | `a` || Type of element used as |
| `href` | `string` ||| ToastBarLink's href attribute |
| `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]
Expand All @@ -247,7 +244,7 @@ said action), as it is very hard (if not impossible) to reach for users with ass

### Colors

The ToastBar component is available in all [emotion colors][dictionary-color], plus the `inverted` variant (default).
The ToastBar component is available in all [emotion colors][dictionary-color], plus the `neutral` variant (default).
Use the `color` option to change the color of the ToastBar component.

For example:
Expand Down Expand Up @@ -284,16 +281,16 @@ To make the ToastBar dismissible, add the `isDismissible` prop along with a `onC

### API

| Name | Type | Default | Required | Description |
| --------------- | ------------------------------------------------------------ | ---------- | -------- | --------------------------------------------------- |
| `closeLabel` | `string` | `Close` || Close label |
| `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 |
| `isDismissible` | `bool` | `false` || If true, ToastBar can be dismissed by user |
| `isOpen` | `bool` | `true` || If true, ToastBar is visible |
| `onClose` | `function` ||| Close button callback |
| Name | Type | Default | Required | Description |
| --------------- | ----------------------------------------------------------- | ---------- | -------- | --------------------------------------------------- |
| `closeLabel` | `string` | `Close` || Close label |
| `color` | [[Emotion Color dictionary][dictionary-color] \| `neutral`] | `neutral` || 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 |
| `isDismissible` | `bool` | `false` || If true, ToastBar can be dismissed by user |
| `isOpen` | `bool` | `true` || If true, ToastBar is visible |
| `onClose` | `function` ||| Close button callback |

(\*) For each emotion color, a default icon is defined.
The icons come from the [Icon package][icon-package], or from your custom source of icons.
Expand Down Expand Up @@ -371,16 +368,16 @@ What is uncontrolled component you can find [here][react-uncontrolled]

This hook returns:

| Name | Type | Default | Description |
| ---------- | ------------------------------------------------------------ | ---------- | --------------------------------------------------- |
| `clear` | `() => void` | () => {} | Function that will clear toast queue |
| `color` | [[Emotion Color dictionary][dictionary-color] \| `inverted`] | `inverted` | Color variant |
| `hide` | `(toastId) => void` | () => {} | Function that will hide UncontrolledToast |
| `iconName` | `string` | | Name of a custom icon to be shown along the message |
| `id` | `string` | `''` | ToastBar ID |
| `isOpen` | `bool` | `false` | Open state of UncontrolledToast |
| `message` | [`string` \| `ReactNode`] | null | Message inside UncontrolledToast |
| `show` | `(message, toastId, options?) => void` | () => {} | Function that will show UncontrolledToast |
| Name | Type | Default | Description |
| ---------- | ----------------------------------------------------------- | --------- | --------------------------------------------------- |
| `clear` | `() => void` | () => {} | Function that will clear toast queue |
| `color` | [[Emotion Color dictionary][dictionary-color] \| `neutral`] | `neutral` | Color variant |
| `hide` | `(toastId) => void` | () => {} | Function that will hide UncontrolledToast |
| `iconName` | `string` || Name of a custom icon to be shown along the message |
| `id` | `string` | `''` | ToastBar ID |
| `isOpen` | `bool` | `false` | Open state of UncontrolledToast |
| `message` | [`string` \| `ReactNode`] | null | Message inside UncontrolledToast |
| `show` | `(message, toastId, options?) => void` | () => {} | Function that will show UncontrolledToast |

#### How to use `show` function:

Expand All @@ -396,18 +393,15 @@ const { show } = useToast();
│ │ │
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'
color: 'danger', // Color variant, default: 'neutral'
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
1 change: 0 additions & 1 deletion packages/web-react/src/components/Toast/ToastBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const ToastBar = (props: SpiritToastBarProps) => {
</div>
<ToastCloseButton
id={id}
color={color}
isOpen={isOpen}
closeLabel={closeLabel}
onClose={onClose}
Expand Down
34 changes: 13 additions & 21 deletions packages/web-react/src/components/Toast/ToastBarLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,33 @@

import classNames from 'classnames';
import React, { ElementType, ForwardedRef, forwardRef } from 'react';
import { ActionLinkColors } from '../../constants';
import { useStyleProps } from '../../hooks';
import { SpiritLinkProps } from '../../types';
import { Link } from '../Link';
import { ToastLinkProps } from '../../types';
import { useToastBarStyleProps } from './useToastBarStyleProps';

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

/* 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 _ToastBarLink = (props: ToastLinkProps, ref: ForwardedRef<HTMLAnchorElement>) => {
const propsWithDefaults = { ...defaultProps, ...props };
const { children, ...restProps } = propsWithDefaults;
const {
elementType: ElementTag = defaultProps.elementType as ElementType,
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>
<ElementTag {...otherProps} {...styleProps} ref={ref} className={classNames(classProps.link, styleProps.className)}>
{children as React.ReactNode}
</ElementTag>
);
};

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

export default ToastBarLink;
20 changes: 13 additions & 7 deletions packages/web-react/src/components/Toast/ToastCloseButton.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
'use client';

import classNames from 'classnames';
import React from 'react';
import { useStyleProps } from '../../hooks';
import { ToastCloseButtonProps } from '../../types';
import { Button } from '../Button';
import { Icon } from '../Icon';
import { VisuallyHidden } from '../VisuallyHidden';
import { useToastBarStyleProps } from './useToastBarStyleProps';

const ToastCloseButton = (props: ToastCloseButtonProps) => {
const { onClose, isOpen, id, closeLabel, isDismissible, ...restProps } = props;
const { classProps, props: modifiedProps } = useToastBarStyleProps({ ...restProps });
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

const ToastCloseButton = ({ color, onClose, isOpen, id, closeLabel, isDismissible }: ToastCloseButtonProps) => {
if (isDismissible && onClose) {
return (
<Button
<button
{...otherProps}
{...styleProps}
type="button"
color={color}
className={classNames(classProps.close, styleProps.className)}
onClick={onClose}
size="small"
isSquare
aria-expanded={isOpen}
aria-controls={id}
>
<Icon name="close" />
<VisuallyHidden>{closeLabel}</VisuallyHidden>
</Button>
</button>
);
}

Expand Down
10 changes: 5 additions & 5 deletions packages/web-react/src/components/Toast/ToastContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import React, { FC, ReactNode, createContext, useCallback, useMemo, useReducer } from 'react';
import { LinkProps, ToastColorType } from '../../types';
import { ToastColorType, ToastLinkProps } from '../../types';
import { delayedCallback } from '../../utils';
import { DEFAULT_TOAST_AUTO_CLOSE_INTERVAL } from './constants';

Expand All @@ -18,7 +18,7 @@ export interface ToastItem {
id: string;
isDismissible: boolean;
isOpen: boolean;
linkProps: LinkProps;
linkProps: ToastLinkProps;
content: {
message: JSX.Element | string;
link?: JSX.Element | string;
Expand All @@ -42,7 +42,7 @@ export interface ToastContextType extends ToastState {
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
linkProps: LinkProps;
linkProps: ToastLinkProps;
},
) => void;
}
Expand Down Expand Up @@ -73,7 +73,7 @@ type ActionType =
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
linkProps: LinkProps;
linkProps: ToastLinkProps;
};
};
}
Expand Down Expand Up @@ -155,7 +155,7 @@ export const ToastProvider: FC<ToastProviderProps> = ({ children }) => {
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
linkProps: LinkProps;
linkProps: ToastLinkProps;
},
) => {
dispatch({ type: 'show', payload: { content, toastId, options } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('ToastBar', () => {

expect(element).toBeInTheDocument();
expect(element).toHaveClass('ToastBar');
expect(element).toHaveClass('ToastBar--inverted');
expect(element).toHaveClass('ToastBar--neutral');
});

it('should render text children', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('ToastBarLink', () => {
it('should render with correct classnames', () => {
const element = screen.getByRole('link');

expect(element).toHaveClass('link-inverted');
expect(element).toHaveClass('link-underlined');
expect(element).toHaveClass('ToastBar__link');
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import React from 'react';
import { LinkProps } from '../../../types';
import { ToastLinkProps } from '../../../types';
import { ToastContext } from '../ToastContext';
import UncontrolledToast from '../UncontrolledToast';

Expand All @@ -18,9 +18,7 @@ const defaultToast = {
color: undefined,
linkProps: {
href: '#',
color: 'inverted',
isUnderlined: true,
} as LinkProps,
} as ToastLinkProps,
};

const defaultContextValue = {
Expand Down Expand Up @@ -61,7 +59,7 @@ describe('UncontrolledToast', () => {

expect(elementToast).toBeInTheDocument();
expect(elementToastBar).toBeInTheDocument();
expect(elementToastBar).toHaveClass('is-open ToastBar--inverted');
expect(elementToastBar).toHaveClass('is-open ToastBar--neutral');
expect(elementToastBar.querySelector('.ToastBar .ToastBar__container svg')).not.toBeInTheDocument();
});

Expand All @@ -83,7 +81,7 @@ describe('UncontrolledToast', () => {
expect(elementToast).toBeInTheDocument();
expect(elementToastBar).toBeInTheDocument();
expect(elementToast).toHaveClass('Toast--right Toast--top');
expect(elementToastBar).toHaveClass('ToastBar ToastBar--inverted ToastBar--dismissible is-open');
expect(elementToastBar).toHaveClass('ToastBar ToastBar--neutral ToastBar--dismissible is-open');
expect(elementToastBar.querySelector('.ToastBar__container svg')).toBeInTheDocument();
expect(elementToastBar.querySelector('button')).toHaveTextContent('Close test');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ describe('useToastBarStyleProps', () => {
const props = { isOpen: true } as SpiritToastBarProps;
const { result } = renderHook(() => useToastBarStyleProps(props));

expect(result.current.classProps.root).toBe('ToastBar ToastBar--inverted');
expect(result.current.classProps.root).toBe('ToastBar ToastBar--neutral');
expect(result.current.classProps.close).toBe('ToastBar__close');
expect(result.current.classProps.link).toBe('ToastBar__link link-underlined');
expect(result.current.classProps.content).toBe('ToastBar__content');
expect(result.current.classProps.container).toBe('ToastBar__container');
});
Expand All @@ -19,7 +21,7 @@ describe('useToastBarStyleProps', () => {
expect(result.current.classProps.root).toContain('ToastBar--dismissible');
});

it.each([['inverted'], ['informative'], ['success'], ['warning'], ['danger']])(
it.each([['neutral'], ['informative'], ['success'], ['warning'], ['danger']])(
'should return color class %s',
(color) => {
const props = { color } as SpiritToastBarProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('useToastIcon', () => {
// color, expected icon name
['danger', 'danger'],
['informative', 'info'],
['inverted', 'info'],
['neutral', 'info'],
['success', 'check-plain'],
['warning', 'warning'],
])('danger alert should return warning icon', (color, iconName) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/web-react/src/components/Toast/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export const ICON_BOX_SIZE = 20;

export const TOAST_BAR_CLOSE_BUTTON_LABEL_DEFAULT = 'Close';

export const DEFAULT_TOAST_COLOR = 'inverted';
export const DEFAULT_TOAST_COLOR = 'neutral';

export const DEFAULT_TOAST_AUTO_CLOSE_INTERVAL = 3000; // milliseconds
4 changes: 2 additions & 2 deletions packages/web-react/src/components/Toast/demo/ToastColors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import ToastBarMessage from '../ToastBarMessage';
const ToastColors = () => {
return (
<>
<ToastBar id="inverted" onClose={() => {}} color="inverted" hasIcon isDismissible>
<ToastBarMessage>Inverted</ToastBarMessage>
<ToastBar id="neutral" onClose={() => {}} color="neutral" hasIcon isDismissible>
<ToastBarMessage>Neutral</ToastBarMessage>
<ToastBarLink href="#">Action</ToastBarLink>
</ToastBar>
<ToastBar id="informative" onClose={() => {}} color="informative" hasIcon isDismissible>
Expand Down
Loading

0 comments on commit ba4dfc2

Please sign in to comment.