From 5bcec1c9c574b2db0a9a4beaa054fad8e544047f Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Mon, 18 Dec 2023 16:48:19 +0300 Subject: [PATCH] feat(Toaster)!: use theme prop instead of type (#1198) --- src/components/Toaster/README.md | 22 +++--- src/components/Toaster/Toast/Toast.scss | 68 +++++++------------ src/components/Toaster/Toast/Toast.tsx | 21 +++--- .../Toaster/__stories__/ToasterShowcase.scss | 2 +- .../Toaster/__stories__/ToasterShowcase.tsx | 46 ++++++------- src/components/Toaster/types.ts | 4 +- 6 files changed, 71 insertions(+), 92 deletions(-) diff --git a/src/components/Toaster/README.md b/src/components/Toaster/README.md index 901f7533e8..c56f348e37 100644 --- a/src/components/Toaster/README.md +++ b/src/components/Toaster/README.md @@ -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: diff --git a/src/components/Toaster/Toast/Toast.scss b/src/components/Toaster/Toast/Toast.scss index 24a0997812..2a470a87fb 100644 --- a/src/components/Toaster/Toast/Toast.scss +++ b/src/components/Toaster/Toast/Toast.scss @@ -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; @@ -22,7 +21,7 @@ $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; @@ -30,61 +29,38 @@ $block: '.#{variables.$ns}toast'; 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; @@ -103,6 +79,7 @@ $block: '.#{variables.$ns}toast'; height: 100%; pointer-events: none; z-index: -1; + background-color: var(--_--container-background-color); } } @@ -110,6 +87,7 @@ $block: '.#{variables.$ns}toast'; flex: 0 0 auto; padding-inline-end: 8px; padding-block-start: 2px; + color: var(--_--icon-color); } &__title { diff --git a/src/components/Toaster/Toast/Toast.tsx b/src/components/Toaster/Toast/Toast.tsx index f5a82ee6ec..eda63618bd 100644 --- a/src/components/Toaster/Toast/Toast.tsx +++ b/src/components/Toaster/Toast/Toast.tsx @@ -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 = { +const TITLE_ICONS: Record = { + normal: null, info: CircleInfo, success: CircleCheck, warning: TriangleExclamation, - error: TriangleExclamation, + danger: TriangleExclamation, utility: Thunderbolt, }; @@ -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 ; + return ; } export const Toast = React.forwardRef(function Toast(props, ref) { @@ -86,7 +87,7 @@ export const Toast = React.forwardRef(function actions, title, className, - type, + theme = 'normal', renderIcon, autoHiding: timeoutProp = DEFAULT_TIMEOUT, isClosable = true, @@ -100,13 +101,13 @@ export const Toast = React.forwardRef(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 (
{icon &&
{icon}
} diff --git a/src/components/Toaster/__stories__/ToasterShowcase.scss b/src/components/Toaster/__stories__/ToasterShowcase.scss index 5681cc5441..28f471c37f 100644 --- a/src/components/Toaster/__stories__/ToasterShowcase.scss +++ b/src/components/Toaster/__stories__/ToasterShowcase.scss @@ -18,7 +18,7 @@ color: var(--g-color-text-warning-heavy); } - &_error { + &_danger { color: var(--g-color-text-danger-heavy); } diff --git a/src/components/Toaster/__stories__/ToasterShowcase.tsx b/src/components/Toaster/__stories__/ToasterShowcase.tsx index b490b32ef9..a47a1872f9 100644 --- a/src/components/Toaster/__stories__/ToasterShowcase.tsx +++ b/src/components/Toaster/__stories__/ToasterShowcase.tsx @@ -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[]; @@ -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 @@ -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); @@ -127,7 +127,7 @@ export const ToasterDemo = ({ const createInfoToast = () => { const toastProps = getToastProps({ name: 'info', - type: 'info', + theme: 'info', title: 'Info toast', }); @@ -139,7 +139,7 @@ export const ToasterDemo = ({ const createSuccessToast = () => { const toastProps = getToastProps({ name: 'success', - type: 'success', + theme: 'success', title: 'Success toast', }); @@ -151,7 +151,7 @@ export const ToasterDemo = ({ const createWarningToast = () => { const toastProps = getToastProps({ name: 'warning', - type: 'warning', + theme: 'warning', title: 'Warning toast', }); @@ -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); @@ -175,7 +175,7 @@ export const ToasterDemo = ({ const createUtilityToast = () => { const toastProps = getToastProps({ name: 'utility', - type: 'utility', + theme: 'utility', title: 'Utility toast', }); @@ -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?', @@ -300,9 +300,9 @@ export const ToasterDemo = ({ const btnStyle = {marginInlineStart: 20}; - const defaultToastBtn = ( - ); @@ -327,10 +327,10 @@ export const ToasterDemo = ({ ); - const errorToastBtn = ( - ); @@ -375,11 +375,11 @@ export const ToasterDemo = ({ return ( -

{defaultToastBtn}

+

{normalToastBtn}

{infoToastBtn}

{successToastBtn}

{warningToastBtn}

-

{errorToastBtn}

+

{dangerToastBtn}

{utilityToastBtn}

{customToastBtn}

{toastWithLongContent}

diff --git a/src/components/Toaster/types.ts b/src/components/Toaster/types.ts index fe74158bbb..72dce8616a 100644 --- a/src/components/Toaster/types.ts +++ b/src/components/Toaster/types.ts @@ -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; @@ -20,7 +20,7 @@ export type ToastProps = { className?: string; autoHiding?: number | false; content?: React.ReactNode; - type?: ToastType; + theme?: ToastTheme; isClosable?: boolean; actions?: ToastAction[];