Skip to content

Commit

Permalink
feat: allow to pass custom elements to title and description (#502)
Browse files Browse the repository at this point in the history
* allow to pass custom elements

* cleanup hero
  • Loading branch information
emilkowalski authored Nov 2, 2024
1 parent ff91e0a commit 8789d52
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 36 deletions.
13 changes: 10 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,15 @@ const Toast = (props: ToastProps) => {
{icons?.close ?? CloseIcon}
</button>
) : null}
{/* TODO: This can be cleaner */}
{toast.jsx || React.isValidElement(toast.title) ? (
toast.jsx || toast.title
toast.jsx ? (
toast.jsx
) : typeof toast.title === 'function' ? (
toast.title()
) : (
toast.title
)
) : (
<>
{toastType || toast.icon || toast.promise ? (
Expand All @@ -366,7 +373,7 @@ const Toast = (props: ToastProps) => {

<div data-content="" className={cn(classNames?.content, toast?.classNames?.content)}>
<div data-title="" className={cn(classNames?.title, toast?.classNames?.title)}>
{toast.title}
{typeof toast.title === 'function' ? toast.title() : toast.title}
</div>
{toast.description ? (
<div
Expand All @@ -378,7 +385,7 @@ const Toast = (props: ToastProps) => {
toast?.classNames?.description,
)}
>
{toast.description}
{typeof toast.description === 'function' ? toast.description() : toast.description}
</div>
) : null}
</div>
Expand Down
28 changes: 15 additions & 13 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import React from 'react';

let toastsCounter = 1;

type titleT = (() => React.ReactNode) | React.ReactNode;

class Observer {
subscribers: Array<(toast: ExternalToast | ToastToDismiss) => void>;
toasts: Array<ToastT | ToastToDismiss>;
Expand Down Expand Up @@ -34,7 +36,7 @@ class Observer {

create = (
data: ExternalToast & {
message?: string | React.ReactNode;
message?: titleT;
type?: ToastTypes;
promise?: PromiseT;
jsx?: React.ReactElement;
Expand Down Expand Up @@ -80,27 +82,27 @@ class Observer {
return id;
};

message = (message: string | React.ReactNode, data?: ExternalToast) => {
message = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, message });
};

error = (message: string | React.ReactNode, data?: ExternalToast) => {
error = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, message, type: 'error' });
};

success = (message: string | React.ReactNode, data?: ExternalToast) => {
success = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, type: 'success', message });
};

info = (message: string | React.ReactNode, data?: ExternalToast) => {
info = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, type: 'info', message });
};

warning = (message: string | React.ReactNode, data?: ExternalToast) => {
warning = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, type: 'warning', message });
};

loading = (message: string | React.ReactNode, data?: ExternalToast) => {
loading = (message: titleT | React.ReactNode, data?: ExternalToast) => {
return this.create({ ...data, type: 'loading', message });
};

Expand Down Expand Up @@ -129,11 +131,11 @@ class Observer {
const originalPromise = p
.then(async (response) => {
result = ['resolve', response];
const isReactElementResponse = React.isValidElement(response);
if (isReactElementResponse) {
shouldDismiss = false;
this.create({ id, type: 'default', message: response });
} else if (isHttpResponse(response) && !response.ok) {
const isReactElementResponse = React.isValidElement(response);
if (isReactElementResponse) {
shouldDismiss = false;
this.create({ id, type: 'default', message: response });
} else if (isHttpResponse(response) && !response.ok) {
shouldDismiss = false;
const message =
typeof data.error === 'function' ? await data.error(`HTTP error! status: ${response.status}`) : data.error;
Expand Down Expand Up @@ -192,7 +194,7 @@ class Observer {
export const ToastState = new Observer();

// bind this to the toast function
const toastFunction = (message: string | React.ReactNode, data?: ExternalToast) => {
const toastFunction = (message: titleT, data?: ExternalToast) => {
const id = data?.id || toastsCounter++;

ToastState.addToast({
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ export interface Action {

export interface ToastT {
id: number | string;
title?: string | React.ReactNode;
title?: (() => React.ReactNode) | React.ReactNode;
type?: ToastTypes;
icon?: React.ReactNode;
jsx?: React.ReactNode;
richColors?: boolean;
invert?: boolean;
closeButton?: boolean;
dismissible?: boolean;
description?: React.ReactNode;
description?: (() => React.ReactNode) | React.ReactNode;
duration?: number;
delete?: boolean;
important?: boolean;
Expand Down
60 changes: 42 additions & 18 deletions website/src/pages/toast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -204,23 +204,47 @@ You can also dismiss all toasts at once by calling `toast.dismiss()` without an
toast.dismiss();
```

### Rendering custom elements

You can render custom elements inside the toast like `<a />` or custom components by passing a function instead of a string. This work for both the title and description.

```jsx
toast(
() => (
<>
View{' '}
<a href="https://google.com" target="_blank">
Animation on the Web
</a>
</>
),
{
description: () => <button>This is a button element!</button>,
},
);
```

## API Reference

| Property | Description | Default |
| :----------------- | :----------------------------------------------------------------------------------------------------: | -------------: |
| description | Toast's description, renders underneath the title. | `-` |
| closeButton | Adds a close button. | `false` |
| invert | Dark toast in light mode and vice versa. | `false` |
| important | Control the sensitivity of the toast for screen readers | `false` |
| duration | Time in milliseconds that should elapse before automatically closing the toast. | `4000` |
| position | Position of the toast. | `bottom-right` |
| dismissible | If `false`, it'll prevent the user from dismissing the toast. | `true` |
| icon | Icon displayed in front of toast's text, aligned vertically. | `-` |
| action | Renders a primary button, clicking it will close the toast. | `-` |
| cancel | Renders a secondary button, clicking it will close the toast. | `-` |
| id | Custom id for the toast. | `-` |
| onDismiss | The function gets called when either the close button is clicked, or the toast is swiped. | `-` |
| onAutoClose | Function that gets called when the toast disappears automatically after it's timeout (duration` prop). | `-` |
| unstyled | Removes the default styling, which allows for easier customization. | `false` |
| actionButtonStyle | Styles for the action button | `{}` |
| cancelButtonStyle | Styles for the cancel button | `{}` |
| Property | Description | Default |
| :---------------- | :----------------------------------------------------------------------------------------------------: | -------------: |
| description | Toast's description, renders underneath the title. | `-` |
| closeButton | Adds a close button. | `false` |
| invert | Dark toast in light mode and vice versa. | `false` |
| important | Control the sensitivity of the toast for screen readers | `false` |
| duration | Time in milliseconds that should elapse before automatically closing the toast. | `4000` |
| position | Position of the toast. | `bottom-right` |
| dismissible | If `false`, it'll prevent the user from dismissing the toast. | `true` |
| icon | Icon displayed in front of toast's text, aligned vertically. | `-` |
| action | Renders a primary button, clicking it will close the toast. | `-` |
| cancel | Renders a secondary button, clicking it will close the toast. | `-` |
| id | Custom id for the toast. | `-` |
| onDismiss | The function gets called when either the close button is clicked, or the toast is swiped. | `-` |
| onAutoClose | Function that gets called when the toast disappears automatically after it's timeout (duration` prop). | `-` |
| unstyled | Removes the default styling, which allows for easier customization. | `false` |
| actionButtonStyle | Styles for the action button | `{}` |
| cancelButtonStyle | Styles for the cancel button | `{}` |

```
```

0 comments on commit 8789d52

Please sign in to comment.