Skip to content

Commit

Permalink
Feat: toast animations (#1620)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendan-defi authored Nov 15, 2024
1 parent 26d4d67 commit 31afa2e
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 30 deletions.
2 changes: 1 addition & 1 deletion playground/nextjs-app-router/onchainkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/onchainkit",
"version": "0.35.4",
"version": "0.35.5",
"type": "module",
"repository": "https://github.com/coinbase/onchainkit.git",
"license": "MIT",
Expand Down
138 changes: 127 additions & 11 deletions src/internal/components/Toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,182 @@ import { describe, expect, it, vi } from 'vitest';
import { Toast } from './Toast';

describe('Toast component', () => {
it('should render bottom-right correctly', () => {
it('should render bottom-right with default animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast isVisible={true} position="bottom-right" onClose={handleClose}>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToast');
const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('bottom-5 left-3/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterRight');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render bottom-right with custom animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast
isVisible={true}
position="bottom-right"
animation="animate-enterUp"
onClose={handleClose}
>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('bottom-5 left-3/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterUp');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render top-right correctly', () => {
it('should render top-right with default animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast isVisible={true} position="top-right" onClose={handleClose}>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToast');
const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('top-[100px] left-3/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterRight');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render top-right with custom animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast
isVisible={true}
position="top-right"
animation="animate-enterUp"
onClose={handleClose}
>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('top-[100px] left-3/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterUp');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render top-center correctly', () => {
it('should render top-center with default animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast isVisible={true} position="top-center" onClose={handleClose}>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToast');
const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('top-[100px] left-2/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterDown');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render top-center with custom animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast
isVisible={true}
position="top-center"
animation="animate-enterRight"
onClose={handleClose}
>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('top-[100px] left-2/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterRight');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render bottom-center correctly', () => {
it('should render bottom-center with default animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast isVisible={true} position="bottom-center" onClose={handleClose}>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToast');
const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('bottom-5 left-2/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterUp');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});

it('should render bottom-center with custom animation correctly', () => {
const handleClose = vi.fn();
const { getByTestId } = render(
<Toast
isVisible={true}
position="bottom-center"
animation="animate-enterRight"
onClose={handleClose}
>
<div>Test</div>
</Toast>,
);

const toastContainer = getByTestId('ockToastContainer');
expect(toastContainer).toBeInTheDocument();
expect(toastContainer).toHaveClass('bottom-5 left-2/4');

const toast = getByTestId('ockToast');
expect(toast).toBeInTheDocument();
expect(toast).toHaveClass('animate-enterRight');

const closeButton = getByTestId('ockCloseButton');
expect(closeButton).toBeInTheDocument();
});
Expand All @@ -81,8 +197,8 @@ describe('Toast component', () => {
</Toast>,
);

const toastContainer = getByTestId('ockToast');
expect(toastContainer).toHaveClass('custom-class');
const toast = getByTestId('ockToast');
expect(toast).toHaveClass('custom-class');
});

it('should not be visible when isVisible is false', () => {
Expand All @@ -92,7 +208,7 @@ describe('Toast component', () => {
<div>Test</div>
</Toast>,
);
const toastContainer = queryByTestId('ockToast');
const toastContainer = queryByTestId('ockToastContainer');
expect(toastContainer).not.toBeInTheDocument();
});

Expand Down
48 changes: 31 additions & 17 deletions src/internal/components/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@ type ToastProps = {
className?: string;
durationMs?: number;
position: 'top-center' | 'top-right' | 'bottom-center' | 'bottom-right';
animation?: 'animate-enterRight' | 'animate-enterUp' | 'animate-enterDown';
isVisible: boolean;
onClose: () => void;
children: React.ReactNode;
};

const defaultAnimationByPosition = {
'top-center': 'animate-enterDown',
'top-right': 'animate-enterRight',
'bottom-center': 'animate-enterUp',
'bottom-right': 'animate-enterRight',
};

export function Toast({
className,
durationMs = 3000,
position = 'bottom-center',
animation,
isVisible,
onClose,
children,
}: ToastProps) {
const positionClass = getToastPosition(position);
const animationClass = animation ?? defaultAnimationByPosition[position];

useEffect(() => {
const timer = setTimeout(() => {
Expand All @@ -42,25 +52,29 @@ export function Toast({

return (
<div
className={cn(
background.default,
'flex animate-enter items-center justify-between rounded-lg',
'p-2 shadow-[0px_8px_24px_0px_rgba(0,0,0,0.12)]',
'-translate-x-2/4 fixed z-20',
positionClass,
className,
)}
data-testid="ockToast"
className={cn('-translate-x-2/4 fixed z-20', positionClass)}
data-testid="ockToastContainer"
>
<div className="flex items-center gap-4 p-2">{children}</div>
<button
className="p-2"
onClick={onClose}
type="button"
data-testid="ockCloseButton"
<div
className={cn(
background.default,
'flex items-center justify-between rounded-lg',
'p-2 shadow-[0px_8px_24px_0px_rgba(0,0,0,0.12)]',
animationClass,
className,
)}
data-testid="ockToast"
>
{closeSvg}
</button>
<div className="flex items-center gap-4 p-2">{children}</div>
<button
className="p-2"
onClick={onClose}
type="button"
data-testid="ockCloseButton"
>
{closeSvg}
</button>
</div>
</div>
);
}
24 changes: 23 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,31 @@ export default {
transform: 'translate(0)',
},
},
fadeInUp: {
'0%': {
opacity: '0',
transform: 'translateY(2rem)',
},
'100%': {
opacity: '1',
transform: 'translateY(0)',
},
},
fadeInDown: {
'0%': {
opacity: '0',
transform: 'translateY(-2rem)',
},
'100%': {
opacity: '1',
transform: 'translateY(0)',
},
},
},
animation: {
enter: 'fadeInRight 500ms ease-out',
enterRight: 'fadeInRight 500ms ease-out',
enterUp: 'fadeInUp 500ms ease-out',
enterDown: 'fadeInDown 500ms ease-out',
},
},
},
Expand Down

0 comments on commit 31afa2e

Please sign in to comment.