Skip to content

Commit

Permalink
allow to pass jsx to actions (#379)
Browse files Browse the repository at this point in the history
* allow to pass jsx to actions

* Allow to pass jsx as actions

* update docs
  • Loading branch information
emilkowalski authored Mar 21, 2024
1 parent 0d395c4 commit 5d160e6
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 28 deletions.
6 changes: 3 additions & 3 deletions .github/pull-request-template.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
### Issue 😱:
### Issue:

Closes https://github.com/emilkowalski/sonner/issues/

### What has been done:
### What has been done:

- [ ]

### Screenshots/Videos 🎥:
### Screenshots/Videos:

N/A
45 changes: 29 additions & 16 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
import React from 'react';
import ReactDOM from 'react-dom';

import DOMPurify from "dompurify";
import DOMPurify from 'dompurify';
import { getAsset, Loader } from './assets';
import { useIsDocumentHidden } from './hooks';
import { toast, ToastState } from './state';
import './styles.css';
import type { ExternalToast, HeightT, ToasterProps, ToastProps, ToastT, ToastToDismiss } from './types';
import {
isAction,
type ExternalToast,
type HeightT,
type ToasterProps,
type ToastProps,
type ToastT,
type ToastToDismiss,
} from './types';

// Visible toasts amount
const VISIBLE_TOASTS_AMOUNT = 3;
Expand Down Expand Up @@ -56,7 +64,7 @@ const Toast = (props: ToastProps) => {
descriptionClassName = '',
duration: durationFromToaster,
position,
gap = GAP,
gap,
loadingIcon: loadingIconProp,
expandByDefault,
classNames,
Expand Down Expand Up @@ -375,16 +383,16 @@ const Toast = (props: ToastProps) => {
<>
{toastType || toast.icon || toast.promise ? (
<div data-icon="" className={cn(classNames?.icon)}>
{toast.promise || (toast.type === 'loading' && !toast.icon)
? toast.icon || getLoadingIcon()
: null}
{toast.promise || (toast.type === 'loading' && !toast.icon) ? toast.icon || getLoadingIcon() : null}
{toast.type !== 'loading' ? toast.icon || icons?.[toastType] || getAsset(toastType) : null}
</div>
) : null}

<div data-content="" className={cn(classNames?.content)}>
<div data-title="" className={cn(classNames?.title, toast?.classNames?.title)}
dangerouslySetInnerHTML={sanitizeHTML(toast.title as string)}
<div
data-title=""
className={cn(classNames?.title, toast?.classNames?.title)}
dangerouslySetInnerHTML={sanitizeHTML(toast.title as string)}
></div>
{toast.description ? (
<div
Expand All @@ -399,29 +407,35 @@ const Toast = (props: ToastProps) => {
></div>
) : null}
</div>
{toast.cancel ? (
{React.isValidElement(toast.cancel) ? (
toast.cancel
) : toast.cancel && isAction(toast.cancel) ? (
<button
data-button
data-cancel
style={toast.cancelButtonStyle || cancelButtonStyle}
onClick={(event) => {
// We need to check twice because typescript
if (!isAction(toast.cancel)) return;
if (!dismissible) return;
deleteToast();
if (toast.cancel?.onClick) {
toast.cancel.onClick(event);
}
toast.cancel.onClick(event);
}}
className={cn(classNames?.cancelButton, toast?.classNames?.cancelButton)}
>
{toast.cancel.label}
</button>
) : null}
{toast.action ? (
{React.isValidElement(toast.action) ? (
toast.action
) : toast.action && isAction(toast.action) ? (
<button
data-button=""
style={toast.actionButtonStyle || actionButtonStyle}
onClick={(event) => {
toast.action?.onClick(event);
// We need to check twice because typescript
if (!isAction(toast.action)) return;
toast.action.onClick(event);
if (event.defaultPrevented) return;
deleteToast();
}}
Expand Down Expand Up @@ -465,7 +479,7 @@ const Toaster = (props: ToasterProps) => {
visibleToasts = VISIBLE_TOASTS_AMOUNT,
toastOptions,
dir = getDocumentDirection(),
gap,
gap = GAP,
loadingIcon,
icons,
containerAriaLabel = 'Notifications',
Expand Down Expand Up @@ -703,4 +717,3 @@ const Toaster = (props: ToasterProps) => {
);
};
export { toast, Toaster, type ExternalToast, type ToastT };

20 changes: 12 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export interface ToastIcons {
loading?: React.ReactNode;
}

interface Action {
label: string;
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
actionButtonStyle?: React.CSSProperties;
}

export interface ToastT {
id: number | string;
title?: string | React.ReactNode;
Expand All @@ -53,14 +59,8 @@ export interface ToastT {
duration?: number;
delete?: boolean;
important?: boolean;
action?: {
label: React.ReactNode;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
};
cancel?: {
label: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
};
action?: Action | React.ReactNode;
cancel?: Action | React.ReactNode;
onDismiss?: (toast: ToastT) => void;
onAutoClose?: (toast: ToastT) => void;
promise?: PromiseT;
Expand All @@ -74,6 +74,10 @@ export interface ToastT {
position?: Position;
}

export function isAction(action: Action | React.ReactNode): action is Action {
return (action as Action).label !== undefined && typeof (action as Action).onClick === 'function';
}

export type Position = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
export interface HeightT {
height: number;
Expand Down
18 changes: 17 additions & 1 deletion website/src/pages/toast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ To render a toast on initial page load it is required that the function `toast()
```jsx
setTimeout(() => {
toast('My toast on a page load');
})
});
```

## Creating toasts
Expand Down Expand Up @@ -68,6 +68,14 @@ toast('My action toast', {
});
```

You can also render jsx as your action.

```jsx
toast('My action toast', {
action: <Button onClick={() => console.log('Action!')}>Action</Button>,
});
```

### Cancel

Renders a secondary button, clicking it will close the toast and run the callback passed via `onClick`.
Expand All @@ -81,6 +89,14 @@ toast('My cancel toast', {
});
```

You can also render jsx as your action.

```jsx
toast('My cancel toast', {
action: <Button onClick={() => console.log('Cancel!')}>Cancel</Button>,
});
```

### Promise

Starts in a loading state and will update automatically after the promise resolves or fails.
Expand Down

0 comments on commit 5d160e6

Please sign in to comment.