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(Toaster)!: use theme prop instead of type #1198

Merged
merged 1 commit into from
Dec 18, 2023
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
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
Loading