Skip to content

Commit

Permalink
feat(Toaster)!: use theme prop instead of type (#1198)
Browse files Browse the repository at this point in the history
  • Loading branch information
amje authored Dec 18, 2023
1 parent c86782f commit 5bcec1c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 92 deletions.
22 changes: 11 additions & 11 deletions src/components/Toaster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,17 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';

Accepts the argument `toastOptions` with ongoing notification details:

| Parameter | Type | Required | Default | Description |
| :--------- | :-------------------------------------- | :------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | `string` | yes | | A unique notification name. Notifications with identical names are collapsed into one |
| title | `string` | | | Notification title |
| className | `string` | | | CSS-class |
| autoHiding | `number` or `false` | | 5000 | Number of ms to delay before hiding the notification. Use `false` to disable toast hiding after timeout. |
| content | `node` | | `undefined` | Notification content. [Anything that can be rendered: numbers, strings, elements or an array](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes) |
| type | `string` | | `undefined` | A notification type. Possible values: `error`, `success`. If `type` is set, the icon (success/error) will be added into notification title. _By default, there is no icon_ |
| isClosable | `boolean` | | `true` | A configuration that manages the visibility of the X icon, which allows the user to close the notification |
| actions | `ToastAction[]` | | `undefined` | An array of [actions](./types.ts#L9) that display after `content` |
| renderIcon | `(toastProps: ToastProps) => ReactNode` | | `undefined` | Used to customize the toast icon. Type-based behavior is used by default |
| Parameter | Type | Required | Default | Description |
| :--------- | :-------------------------------------- | :------- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | `string` | yes | | A unique notification name. Notifications with identical names are collapsed into one |
| title | `string` | | | Notification title |
| className | `string` | | | CSS-class |
| autoHiding | `number` or `false` | | 5000 | Number of ms to delay before hiding the notification. Use `false` to disable toast hiding after timeout. |
| content | `node` | | `undefined` | Notification content. [Anything that can be rendered: numbers, strings, elements or an array](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes) |
| theme | `string` | | `"normal"` | A notification theme. Possible values: `"normal"`, `"info"`, `"success"`, `"warning"`, `danger`, `"utility"`. If `theme` is set to other than `"normal"`, the icon will be added into notification title. _By default, there is no icon_ |
| isClosable | `boolean` | | `true` | A configuration that manages the visibility of the X icon, which allows the user to close the notification |
| actions | `ToastAction[]` | | `undefined` | An array of [actions](./types.ts#L9) that display after `content` |
| renderIcon | `(toastProps: ToastProps) => ReactNode` | | `undefined` | Used to customize the toast icon. Type-based behavior is used by default |

Every `action` is an object with following parameters:

Expand Down
68 changes: 23 additions & 45 deletions src/components/Toaster/Toast/Toast.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ $block: '.#{variables.$ns}toast';
$closeButtonSize: 24px;
$closeButtonEdgesSpacing: 16px;
$closeButtonTitleSpacing: 8px;
$containerClass: #{&}__container;
$iconClass: #{&}__icon;

--_--item-gap: 10px;
--_--item-padding: 16px;
--_--background-color: var(--g-color-base-background);

display: flex;
box-sizing: border-box;
Expand All @@ -22,69 +21,46 @@ $block: '.#{variables.$ns}toast';
font-size: var(--g-text-body-2-font-size);
border-radius: 8px;
box-shadow: 0px 0px 15px var(--g-color-sfx-shadow);
background-color: var(--g-color-base-background);
background-color: var(--_--background-color);
overflow: hidden;
z-index: 0;

&_mobile {
width: 100%;
}

&_default {
background-color: var(--g-color-base-float);
}

&_info {
& #{$containerClass}:before {
background-color: var(--g-color-base-info-light);
}

& #{$iconClass} {
color: var(--g-color-text-info-heavy);
}
}

&_success {
& #{$containerClass}:before {
background-color: var(--g-color-base-positive-light);
&_theme {
&_normal {
--_--background-color: var(--g-color-base-float);
}

& #{$iconClass} {
color: var(--g-color-text-positive-heavy);
}
}

&_warning {
& #{$containerClass}:before {
background-color: var(--g-color-base-warning-light);
&_info {
--_--container-background-color: var(--g-color-base-info-light);
--_--icon-color: var(--g-color-text-info-heavy);
}

& #{$iconClass} {
color: var(--g-color-text-warning-heavy);
&_success {
--_--container-background-color: var(--g-color-base-positive-light);
--_--icon-color: var(--g-color-text-positive-heavy);
}
}

&_error {
& #{$containerClass}:before {
background-color: var(--g-color-base-danger-light);
&_warning {
--_--container-background-color: var(--g-color-base-warning-light);
--_--icon-color: var(--g-color-text-warning-heavy);
}

& #{$iconClass} {
color: var(--g-color-text-danger-heavy);
}
}

&_utility {
& #{$containerClass}:before {
background-color: var(--g-color-base-utility-light);
&_danger {
--_--container-background-color: var(--g-color-base-danger-light);
--_--icon-color: var(--g-color-text-danger-heavy);
}

& #{$iconClass} {
color: var(--g-color-text-utility-heavy);
&_utility {
--_--container-background-color: var(--g-color-base-utility-light);
--_--icon-color: var(--g-color-text-utility-heavy);
}
}

#{$containerClass} {
&__container {
flex: 1 1 auto;
display: grid;
row-gap: 8px;
Expand All @@ -103,13 +79,15 @@ $block: '.#{variables.$ns}toast';
height: 100%;
pointer-events: none;
z-index: -1;
background-color: var(--_--container-background-color);
}
}

&__icon-container {
flex: 0 0 auto;
padding-inline-end: 8px;
padding-block-start: 2px;
color: var(--_--icon-color);
}

&__title {
Expand Down
21 changes: 11 additions & 10 deletions src/components/Toaster/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import {Icon} from '../../Icon';
import type {IconProps} from '../../Icon';
import {block} from '../../utils/cn';
import i18n from '../i18n';
import type {InternalToastProps, ToastAction, ToastType} from '../types';
import type {InternalToastProps, ToastAction, ToastTheme} from '../types';

import './Toast.scss';

const b = block('toast');
const DEFAULT_TIMEOUT = 5000;
const TITLE_ICONS: Record<ToastType, IconProps['data']> = {
const TITLE_ICONS: Record<ToastTheme, IconProps['data'] | null> = {
normal: null,
info: CircleInfo,
success: CircleCheck,
warning: TriangleExclamation,
error: TriangleExclamation,
danger: TriangleExclamation,
utility: Thunderbolt,
};

Expand Down Expand Up @@ -68,15 +69,15 @@ function renderActions({actions, onClose}: RenderActionsProps) {
}

interface RenderIconProps {
type?: ToastType;
theme?: ToastTheme;
}

function renderIconByType({type}: RenderIconProps) {
if (!type) {
function renderIconByType({theme}: RenderIconProps) {
if (!theme || !TITLE_ICONS[theme]) {
return null;
}

return <Icon data={TITLE_ICONS[type]} size={20} className={b('icon', {[type]: true})} />;
return <Icon data={TITLE_ICONS[theme]!} size={20} className={b('icon', {[theme]: true})} />;
}

export const Toast = React.forwardRef<HTMLDivElement, ToastUnitedProps>(function Toast(props, ref) {
Expand All @@ -86,7 +87,7 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastUnitedProps>(function
actions,
title,
className,
type,
theme = 'normal',
renderIcon,
autoHiding: timeoutProp = DEFAULT_TIMEOUT,
isClosable = true,
Expand All @@ -100,13 +101,13 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastUnitedProps>(function

const mods = {
mobile,
[type || 'default']: true,
theme,
};

const hasTitle = Boolean(title);
const hasContent = Boolean(content);

const icon = renderIcon ? renderIcon(props) : renderIconByType({type});
const icon = renderIcon ? renderIcon(props) : renderIconByType({theme});
return (
<div ref={ref} className={b(mods, className)} {...closeOnTimeoutProps} data-toast>
{icon && <div className={b('icon-container')}>{icon}</div>}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Toaster/__stories__/ToasterShowcase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
color: var(--g-color-text-warning-heavy);
}

&_error {
&_danger {
color: var(--g-color-text-danger-heavy);
}

Expand Down
46 changes: 23 additions & 23 deletions src/components/Toaster/__stories__/ToasterShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const ToasterDemo = ({
function getToastProps(extra: {
name: string;
title?: string;
type?: ToastProps['type'];
theme?: ToastProps['theme'];
className?: string;
content?: React.ReactNode;
actions?: ToastAction[];
Expand Down Expand Up @@ -101,7 +101,7 @@ export const ToasterDemo = ({
name: getToastName(extra.name),
className: extra.className,
title,
type: extra.type,
theme: extra.theme,
isClosable: showCloseIcon,
autoHiding: timeout,
actions: setActions
Expand All @@ -113,10 +113,10 @@ export const ToasterDemo = ({
};
}

const createDefaultToast = () => {
const createNormalToast = () => {
const toastProps = getToastProps({
name: 'default',
title: 'Default toast',
name: 'normal',
title: 'Normal toast',
});

toaster.add(toastProps);
Expand All @@ -127,7 +127,7 @@ export const ToasterDemo = ({
const createInfoToast = () => {
const toastProps = getToastProps({
name: 'info',
type: 'info',
theme: 'info',
title: 'Info toast',
});

Expand All @@ -139,7 +139,7 @@ export const ToasterDemo = ({
const createSuccessToast = () => {
const toastProps = getToastProps({
name: 'success',
type: 'success',
theme: 'success',
title: 'Success toast',
});

Expand All @@ -151,7 +151,7 @@ export const ToasterDemo = ({
const createWarningToast = () => {
const toastProps = getToastProps({
name: 'warning',
type: 'warning',
theme: 'warning',
title: 'Warning toast',
});

Expand All @@ -160,11 +160,11 @@ export const ToasterDemo = ({
setState((state) => ({...state, lastToastName: toastProps.name}));
};

const createErrorToast = () => {
const createDangerToast = () => {
const toastProps = getToastProps({
name: 'error',
type: 'error',
title: 'Error toast',
name: 'danger',
theme: 'danger',
title: 'Danger toast',
});

toaster.add(toastProps);
Expand All @@ -175,7 +175,7 @@ export const ToasterDemo = ({
const createUtilityToast = () => {
const toastProps = getToastProps({
name: 'utility',
type: 'utility',
theme: 'utility',
title: 'Utility toast',
});

Expand Down Expand Up @@ -236,7 +236,7 @@ export const ToasterDemo = ({
const createToastLongContent = () => {
const toastProps = getToastProps({
name: 'overflow',
type: 'error',
theme: 'danger',
title: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
content:
'Excepturi cumque dicta, et a repellat culpa totam minus vero, error ducimus nesciunt? Dicta soluta earum sapiente explicabo commodi pariatur nulla eius?',
Expand Down Expand Up @@ -300,9 +300,9 @@ export const ToasterDemo = ({

const btnStyle = {marginInlineStart: 20};

const defaultToastBtn = (
<Button view="outlined" size="l" onClick={createDefaultToast} style={btnStyle}>
Create default toast
const normalToastBtn = (
<Button view="outlined" size="l" onClick={createNormalToast} style={btnStyle}>
Create normal toast
</Button>
);

Expand All @@ -327,10 +327,10 @@ export const ToasterDemo = ({
</Button>
);

const errorToastBtn = (
<Button view="outlined" size="l" onClick={createErrorToast} style={btnStyle}>
<Icon className={b('icon', {error: true})} data={TriangleExclamation} />
Create error toast
const dangerToastBtn = (
<Button view="outlined" size="l" onClick={createDangerToast} style={btnStyle}>
<Icon className={b('icon', {danger: true})} data={TriangleExclamation} />
Create danger toast
</Button>
);

Expand Down Expand Up @@ -375,11 +375,11 @@ export const ToasterDemo = ({

return (
<React.Fragment>
<p>{defaultToastBtn}</p>
<p>{normalToastBtn}</p>
<p>{infoToastBtn}</p>
<p>{successToastBtn}</p>
<p>{warningToastBtn}</p>
<p>{errorToastBtn}</p>
<p>{dangerToastBtn}</p>
<p>{utilityToastBtn}</p>
<p>{customToastBtn}</p>
<p>{toastWithLongContent}</p>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Toaster/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type ToasterArgs = {
mobile?: boolean;
};

export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'utility';
export type ToastTheme = 'normal' | 'info' | 'success' | 'warning' | 'danger' | 'utility';

export type ToastAction = {
onClick: VoidFunction;
Expand All @@ -20,7 +20,7 @@ export type ToastProps = {
className?: string;
autoHiding?: number | false;
content?: React.ReactNode;
type?: ToastType;
theme?: ToastTheme;
isClosable?: boolean;
actions?: ToastAction[];

Expand Down

0 comments on commit 5bcec1c

Please sign in to comment.