Skip to content

Commit

Permalink
feat: wiggle toasts on update + toast.wiggle()
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnartorfis committed Sep 11, 2024
1 parent c01f5bd commit de60ddc
Show file tree
Hide file tree
Showing 12 changed files with 690 additions and 366 deletions.
5 changes: 1 addition & 4 deletions docs/docs/Toaster.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,4 @@ import { ZView } from 'react-native-z-view';
| `swipToDismissDirection` | Swipe direction to dismiss (`left`, `up`). | `up` |
| cn | Custom function for constructing/merging classes. | `filter(Boolean).join(' ')` |
|  ToasterOverlayWrapper |  Custom component to wrap the Toaster. | `div` |

```
```
|  autoWiggleOnUpdate | Adds a wiggle animation on toast update. `never`, `toast-change`, `always` | `never` |
20 changes: 18 additions & 2 deletions docs/docs/toast.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import { toast } from 'sonner-native';

toast.success('Operation successful!', {
className: 'bg-green-500',
style: { backgroundColor: 'blue' },
style: { backgroundColor: 'blue' },
description: 'Everything worked as expected.',
duration: 6000,
icon: <SomeIcon />
icon: <SomeIcon />,
});
```

Expand Down Expand Up @@ -188,6 +188,18 @@ toast('World');
toast.dismiss();
```

### Wiggling toasts

To make a toast wiggle, call toast.wiggle with the toast ID:

```jsx
const id = toast('Hello');

toast.wiggle(id);
```

Toasts can also be automatically wiggled by passing the `autoWiggleOnUpdate` prop to [Toaster](Toaster#api-reference):

## API Reference

| Property | Description | Default |
Expand All @@ -209,3 +221,7 @@ toast.dismiss();
| actionButtonTextStyles | Styles for the action button text | `{}` |
| cancelButtonStyles | Styles for the cancel button | `{}` |
| cancelButtonTextStyles | Styles for the cancel button text | `{}` |

```
```
1 change: 1 addition & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const App: React.FC = () => {
swipToDismissDirection="up"
visibleToasts={4}
closeButton
autoWiggleOnUpdate="toast-change"
theme="system"
icons={{
error: <Text>💥</Text>,
Expand Down
16 changes: 16 additions & 0 deletions example/src/ToastDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ export const ToastDemo: React.FC = () => {
});
}}
/>
<Button
title="Wiggle on update"
onPress={() =>
toast('Wiggle on update', {
id: '123',
description: new Date().toISOString(),
duration: 10000,
})
}
/>
<Button
title="Wiggle toast"
onPress={() => {
toast.wiggle('123');
}}
/>
<Button
title="Invert toast"
onPress={() => toast('Inverted toast', { invert: true })}
Expand Down
1 change: 0 additions & 1 deletion src/__tests__/index.test.tsx

This file was deleted.

168 changes: 168 additions & 0 deletions src/__tests__/toast-comparator.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { View } from 'react-native';
import type { ToastProps } from '../types'; // Adjust the import path Adjust the import path
import { areToastsEqual } from '../toast-comparator';

// Mock helper function to simulate a click handler
const mockClickHandler = () => {};

describe('areToastsEqual', () => {
it('should return true when all important properties are equal', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
closeButton: true,
invert: false,
position: 'top-center',
dismissible: true,
action: { label: 'Retry', onClick: mockClickHandler },
cancel: { label: 'Cancel', onClick: mockClickHandler },
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
closeButton: true,
invert: false,
position: 'top-center',
dismissible: true,
action: { label: 'Retry', onClick: mockClickHandler },
cancel: { label: 'Cancel', onClick: mockClickHandler },
};

expect(areToastsEqual(toast1, toast2)).toBe(true);
});

it('should return false when IDs are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
};

const toast2: ToastProps = {
id: 2,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});

it('should return false when titles are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 2',
variant: 'success',
description: 'This is a toast',
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});

it('should return false when variants are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'error',
description: 'This is a toast',
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});

it('should return false when descriptions are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is a toast',
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
description: 'This is another toast',
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});

it('should return false when action labels are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
action: { label: 'Retry', onClick: mockClickHandler },
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
action: { label: 'Ignore', onClick: mockClickHandler },
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});

it('should return true when action and cancel are both React nodes and equal', () => {
const mockReactNode = <View />;

const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
action: mockReactNode,
cancel: mockReactNode,
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
action: mockReactNode,
cancel: mockReactNode,
};

expect(areToastsEqual(toast1, toast2)).toBe(true);
});

it('should return false when cancel labels are different', () => {
const toast1: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
cancel: { label: 'Cancel', onClick: mockClickHandler },
};

const toast2: ToastProps = {
id: 1,
title: 'Toast 1',
variant: 'success',
cancel: { label: 'Dismiss', onClick: mockClickHandler },
};

expect(areToastsEqual(toast1, toast2)).toBe(false);
});
});
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
AutoWiggle,
ToastPosition,
ToastSwipeDirection,
ToastTheme,
Expand All @@ -20,6 +21,7 @@ export const toastDefaultValues: {
cn: (...classes: Array<string | undefined>) => string;
gap: number;
theme: ToastTheme;
autoWiggleOnUpdate: AutoWiggle;
} = {
duration: 4000,
position: 'top-center',
Expand All @@ -35,4 +37,5 @@ export const toastDefaultValues: {
cn: (...classes) => classes.filter(Boolean).join(' '),
gap: 14,
theme: 'system',
autoWiggleOnUpdate: 'never',
};
25 changes: 25 additions & 0 deletions src/toast-comparator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { isToastAction, type ToastProps } from './types';

const areActionsEqual = (a: ToastProps['action'], b: ToastProps['action']) => {
if (isToastAction(a) && isToastAction(b)) {
if (a.label !== b.label) return false;
return true;
}

return true;
};

export const areToastsEqual = (a: ToastProps, b: ToastProps) => {
return (
a.id === b.id &&
a.title === b.title &&
a.variant === b.variant &&
a.description === b.description &&
a.closeButton === b.closeButton &&
a.invert === b.invert &&
a.position === b.position &&
a.dismissible === b.dismissible &&
areActionsEqual(a.action, b.action) &&
areActionsEqual(a.cancel, b.cancel)
);
};
4 changes: 4 additions & 0 deletions src/toast-fns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ toast.success = (title, options = {}) => {
});
};

toast.wiggle = (id) => {
return getToastContext().wiggleToast(id);
};

toast.error = (title: string, options = {}) => {
return getToastContext().addToast({
...options,
Expand Down
Loading

0 comments on commit de60ddc

Please sign in to comment.