Skip to content

Commit

Permalink
improve toaster
Browse files Browse the repository at this point in the history
  • Loading branch information
Thykof committed Feb 29, 2024
1 parent 12ff8a5 commit a799ef5
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 89 deletions.
27 changes: 24 additions & 3 deletions src/components/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Toast, toast } from './Toast';
import { Toast, ToastContent, toast } from './Toast';
import { Button } from './../Button/Button';

export default { title: 'Components/Toast' };
Expand All @@ -14,11 +14,32 @@ export const _Toast = {
Create ERROR Toast
</Button>
<br />
<br />
<Button onClick={() => toast.success('Uuurraa! This is a success CSS')}>
Create SUCCESS Toast
</Button>
<Toast storageKey="stories-theme" theme="theme-dark" />
<br />
<Button
onClick={() =>
toast((t) => <ToastContent t={t}>Toast content</ToastContent>, {
duration: Infinity,
})
}
>
Create info infinite Toast
</Button>
<br />
<Button
onClick={() =>
toast.loading(
(t) => <ToastContent t={t}>Work in Progress</ToastContent>,
{ duration: Infinity },
)
}
>
Create loading Toast
</Button>

<Toast />
</>
),
};
8 changes: 1 addition & 7 deletions src/components/Toast/Toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { Toast } from './Toast';

describe('Components | Toast', () => {
test('it should render', () => {
render(
<Toast
storageKey="test-theme"
content="this is the content"
error="Oupps!"
/>,
);
render(<Toast />);

let input = screen.getByTestId('toast');

Expand Down
165 changes: 86 additions & 79 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,114 @@
// @ts-ignore
import React from 'react';

import { useEffect, useState } from 'react';
import { toast as _toast, Toaster } from 'react-hot-toast';
import { toast as _toast, Toaster, Toast as ToastType } from 'react-hot-toast';
import { ComponentPropsWithoutRef } from 'react';
import { FiX, FiCheckCircle, FiAlertCircle } from 'react-icons/fi';
import { FiX, FiCheckCircle, FiAlertCircle, FiInfo } from 'react-icons/fi';
import { Button } from '../Button';
import { useLocalStorage } from '../../util/useLocalStorage';
import { Transition } from '@headlessui/react';
import { Spinner } from '../Spinner';

export interface ToastProps extends ComponentPropsWithoutRef<'div'> {
storageKey: string;
error?: string;
success?: string;
theme?: string;
export interface ToastProps {
durationMs?: number;
}

const defaultDurationMs = 10000;

function Error(props: ToastProps) {
const { error } = props;
export const toast = _toast;

export function Toast(props: ToastProps) {
const { durationMs } = props;

return (
<>
<div
className="inline-flex items-center justify-center flex-shrink-0
w-10 h-10 text-s-error bg-s-error bg-opacity-25 rounded-lg"
<div data-testid="toast">
<Toaster
position="top-center"
toastOptions={{
duration: durationMs ?? defaultDurationMs,
}}
>
<FiAlertCircle size={24} />
</div>
<div className="ml-3 text-sm font-normal pr-2 text-s-error">{error}</div>
</>
{(t) => <ToastContent t={t} />}
</Toaster>
</div>
);
}

function Success(props: ToastProps) {
const { success } = props;

return (
<>
<div
className="inline-flex items-center justify-center flex-shrink-0
w-10 h-10 text-s-success bg-s-success bg-opacity-25 rounded-lg"
>
<FiCheckCircle size={24} />
</div>
<div className="ml-3 text-sm font-normal pr-2 text-s-success">
{success}
</div>
</>
);
interface ToastContentProps extends ComponentPropsWithoutRef<'div'> {
t: ToastType;
}

export const toast = _toast;
export function ToastContent(props: ToastContentProps) {
const { t, ...rest } = props;

export function Toast(props: ToastProps) {
const { storageKey, error, theme: _theme, ...rest } = props;
const toastIcons = {
success: <FiCheckCircle size={24} />,
error: <FiAlertCircle size={24} />,
loading: <Spinner />,
blank: <FiInfo size={24} />,
custom: t.icon ?? <FiInfo size={24} />,
};

const toastTypeToThemeState = {
success: 's-success',
error: 's-error',
loading: 'f-secondary',
blank: 'f-secondary',
custom: 'f-secondary',
};

const [storedTheme] = useLocalStorage<string>(storageKey, 'theme-dark');
const [theme, setTheme] = useState<string>(storedTheme);
const isError = Boolean(error);
const toastTypeToBGThemeState = {
success: 's-success',
error: 's-error',
loading: 'info',
blank: 'info',
custom: 'info',
};

useEffect(() => {
setTheme(_theme ?? storedTheme);
}, [_theme]);
const state = toastTypeToThemeState[t.type];

let content;
if (typeof t.message === 'string') {
content = t.message;
} else {
if (!t.message) return;
content = (t.message as CallableFunction)().props.children;
}

return (
<div data-testid="toast">
<Toaster
position="top-center"
toastOptions={{
duration: props.durationMs ?? defaultDurationMs,
}}
<Transition
appear
show={t.visible}
className="transform transition-all duration-200"
enter="transition ease-in-out transform"
enterFrom="-translate-y-0"
enterTo="translate-y-full"
leave="transition ease-in-out transform"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className="theme-dark flex items-center w-fit p-4 text-f-primary bg-c-default rounded-lg shadow"
{...rest}
>
{(t) => (
<Transition
appear
show={t.visible}
className="transform transition-all duration-200"
enter="transition ease-in-out transform"
enterFrom="-translate-y-0"
enterTo="translate-y-full"
leave="transition ease-in-out transform"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className={`${theme} flex items-center w-fit p-4 text-primary bg-secondary rounded-lg shadow`}
{...rest}
>
{t.type === 'error' || isError ? (
<Error error={t.message?.toString()} {...props} />
) : (
<Success success={t.message?.toString()} {...props} />
)}
<Button variant="icon" onClick={() => toast.dismiss(t.id)}>
<FiX />
</Button>
</div>
</Transition>
)}
</Toaster>
</div>
<div
className={`inline-flex items-center justify-center flex-shrink-0
w-10 h-10 text-${state} bg-${
toastTypeToBGThemeState[t.type]
} bg-opacity-25 rounded-lg`}
>
{toastIcons[t.type]}
</div>
<div className={`ml-3 text-sm font-normal pr-2 text-${state}`}>
{content}
</div>
<Button
customClass="theme-light"
variant="icon"
onClick={() => toast.dismiss(t.id)}
>
<FiX />
</Button>
</div>
</Transition>
);
}

0 comments on commit a799ef5

Please sign in to comment.