Skip to content

Commit

Permalink
refactor: TET-225 toast
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusz-kleszcz committed Sep 11, 2023
1 parent f41b486 commit f0c65f0
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 138 deletions.
14 changes: 6 additions & 8 deletions src/components/Toast/Toast.props.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { MouseEvent } from 'react';
import type { MouseEvent } from 'react';

import { ToastConfig } from './Toast.styles';
import { ToastIntent } from './types';
import type { ToastConfig } from './Toast.styles';
import type { ToastEmphasis, ToastIntent } from './types';

import { ActionProp } from '@/types';
import { Emphasis } from '@/types/Emphasis';
import { DeepPartial } from '@/utility-types/DeepPartial';
import type { ActionProp } from '@/types';

export type ToastProps = {
text: string;
emphasis?: Emphasis;
emphasis?: ToastEmphasis;
intent?: ToastIntent;
action?: ActionProp;
onCloseClick?: (e: MouseEvent<HTMLButtonElement>) => void;
custom?: DeepPartial<ToastConfig>;
custom?: ToastConfig;
};
47 changes: 33 additions & 14 deletions src/components/Toast/Toast.styles.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import type { ToastIntent } from './types';
import { ButtonProps } from '../Button';
import type { ToastEmphasis, ToastIntent } from './types';
import type { ButtonProps } from '../Button';

import { BaseProps } from '@/types/BaseProps';
import { Emphasis } from '@/types/Emphasis';
import type { BaseProps } from '@/types/BaseProps';
import type { IconName } from '@/utility-types/IconName';

export type ToastConfig = {
emphasis: Record<Emphasis, BaseProps>;
intent: Record<ToastIntent, BaseProps>;
closeButton: BaseProps;
innerElements: {
iconContainer: {
intent: Record<ToastIntent, { emphasis: Record<Emphasis, BaseProps> }>;
emphasis?: Partial<Record<ToastEmphasis, BaseProps>>;
intent?: Partial<Record<ToastIntent, BaseProps>>;
closeButton?: BaseProps;
innerElements?: {
iconContainer?: {
intent: Partial<
Record<
ToastIntent,
{ emphasis: Partial<Record<ToastEmphasis, BaseProps>> }
>
>;
} & BaseProps;
actionContainer: BaseProps;
middleDot: {
emphasis: Record<Emphasis, BaseProps | Partial<ButtonProps<'bare'>>>;
actionContainer?: BaseProps;
middleDot?: {
emphasis: Partial<
Record<ToastEmphasis, BaseProps | Partial<ButtonProps<'bare'>>>
>;
} & BaseProps;
closeButton: BaseProps;
closeButton?: BaseProps;
};
} & BaseProps;

Expand Down Expand Up @@ -135,3 +142,15 @@ export const defaultConfig = {
},
},
} satisfies ToastConfig;

export const resolveIconName = (intent: ToastIntent): IconName<20> | null => {
const iconConfig: Record<ToastIntent, IconName<20> | null> = {
neutral: null,
informative: '20-info-fill',
success: '20-check-circle-fill',
warning: '20-warning-fill',
negative: '20-alert-fill',
};

return iconConfig[intent];
};
53 changes: 38 additions & 15 deletions src/components/Toast/Toast.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { vi } from 'vitest';

import { Toast } from './Toast';
import { render } from '../../tests/render';
import { fireEvent, render } from '../../tests/render';

import { customPropTester } from '@/tests/customPropTester';

const getToast = (jsx: JSX.Element) => {
const { getByTestId } = render(jsx);
Expand All @@ -11,6 +13,32 @@ const getToast = (jsx: JSX.Element) => {
const handleEventMock = vi.fn();

describe('Toast', () => {
customPropTester(
<Toast
text="text"
intent="informative"
action={[{ label: 'Label' }, { label: 'Label' }]}
onCloseClick={handleEventMock}
/>,
{
containerId: 'toast',
props: {
emphasis: ['low', 'high'],
intent: ['neutral', 'informative', 'success', 'warning', 'negative'],
},
innerElements: {
iconContainer: [],
actionContainer: [],
middleDot: ['emphasis'],
closeButton: [],
},
},
);

beforeEach(() => {
handleEventMock.mockReset();
});

it('should render the toast', () => {
const toast = getToast(<Toast text="Toast text" />);
expect(toast).toBeInTheDocument();
Expand Down Expand Up @@ -84,7 +112,9 @@ describe('Toast', () => {
/>,
);
const button = toast.querySelector('button');
button?.click();
if (button) {
fireEvent.click(button);
}
expect(handleEventMock).toHaveBeenCalled();
});

Expand All @@ -96,7 +126,9 @@ describe('Toast', () => {
/>,
);
const button = toast.querySelector('button');
button?.focus();
if (button) {
fireEvent.focus(button);
}
expect(handleEventMock).toHaveBeenCalled();
});

Expand All @@ -108,7 +140,9 @@ describe('Toast', () => {
/>,
);
const button = toast.querySelector('button');
button?.blur();
if (button) {
fireEvent.blur(button);
}
expect(handleEventMock).toHaveBeenCalled();
});

Expand All @@ -128,15 +162,4 @@ describe('Toast', () => {
button?.click();
expect(handleEventMock).toHaveBeenCalled();
});

it('should propagate a custom prop', () => {
const toast = getToast(
<Toast
text="Toast text"
custom={{ color: 'background-negative-subtle' }}
/>,
);

expect(toast).toHaveStyle('color: rgb(254, 245, 245)');
});
});
60 changes: 34 additions & 26 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import { Icon } from '@virtuslab/tetrisly-icons';
import { FC, useMemo } from 'react';

import { stylesBuilder } from './stylesBuilder';
import { ToastProps } from './Toast.props';
import type { ToastProps } from './Toast.props';
import { resolveIconName } from './Toast.styles';
import { Button } from '../Button';
import { IconButton } from '../IconButton';

import { tet } from '@/tetrisly';
import { MarginProps } from '@/types';
import type { MarginProps } from '@/types';

type Props = ToastProps & MarginProps;

export const Toast: FC<Props> = ({
export const Toast: FC<ToastProps & MarginProps> = ({
text,
emphasis = 'low',
intent = 'neutral',
Expand All @@ -20,22 +19,13 @@ export const Toast: FC<Props> = ({
custom,
...restProps
}) => {
const {
actionProps,
actionContainerStyles,
closeButtonProps,
closeButtonStyles,
containerStyles,
iconProps,
iconContainerStyles,
middleDotStyles,
} = useMemo(
const styles = useMemo(
() =>
stylesBuilder({
custom,
emphasis,
intent,
closeButton: !!onCloseClick,
onCloseClick,
}),
[custom, emphasis, intent, onCloseClick],
);
Expand All @@ -44,21 +34,38 @@ export const Toast: FC<Props> = ({
? action
: [action, undefined];

const iconName = useMemo(() => resolveIconName(intent), [intent]);

const appearance = useMemo(() => {
const buttonIntentAppearance =
intent === 'warning' ? 'reverseInverted' : 'inverted';
return emphasis === 'high' ? buttonIntentAppearance : 'primary';
}, [intent, emphasis]);

return (
<tet.div {...containerStyles} {...restProps} data-testid="toast">
{!!iconProps.name && (
<tet.span {...iconContainerStyles}>
<Icon {...iconProps} name={iconProps.name} />
<tet.div {...styles.container} data-testid="toast" {...restProps}>
{!!iconName && (
<tet.span {...styles.iconContainer} data-testid="toast-iconContainer">
<Icon name={iconName} />
</tet.span>
)}
{text}
{firstAction && (
<tet.div {...actionContainerStyles}>
<Button variant="bare" {...actionProps} {...firstAction} />
<tet.div
{...styles.actionContainer}
data-testid="toast-actionContainer"
>
<Button variant="bare" appearance={appearance} {...firstAction} />
{secondAction && (
<>
<tet.div {...middleDotStyles}>&middot;</tet.div>
<Button variant="bare" {...actionProps} {...secondAction} />
<tet.div {...styles.middleDot} data-testid="toast-middleDot">
&middot;
</tet.div>
<Button
variant="bare"
appearance={appearance}
{...secondAction}
/>
</>
)}
</tet.div>
Expand All @@ -70,8 +77,9 @@ export const Toast: FC<Props> = ({
intent="none"
icon="20-close"
onClick={onCloseClick}
{...closeButtonProps}
{...closeButtonStyles}
appearance={appearance}
{...styles.closeButton}
data-testid="toast-closeButton"
/>
)}
</tet.div>
Expand Down
Loading

0 comments on commit f0c65f0

Please sign in to comment.