diff --git a/app/soapbox/components/ui/index.ts b/app/soapbox/components/ui/index.ts
index c3f148833..f0ea8f07c 100644
--- a/app/soapbox/components/ui/index.ts
+++ b/app/soapbox/components/ui/index.ts
@@ -49,6 +49,7 @@ export { default as Tabs } from './tabs/tabs';
export { default as TagInput } from './tag-input/tag-input';
export { default as Text } from './text/text';
export { default as Textarea } from './textarea/textarea';
+export { default as Toast } from './toast/toast';
export { default as Toggle } from './toggle/toggle';
export { default as Tooltip } from './tooltip/tooltip';
export { default as Widget } from './widget/widget';
diff --git a/app/soapbox/components/ui/toast/toast.tsx b/app/soapbox/components/ui/toast/toast.tsx
new file mode 100644
index 000000000..a7e26c98c
--- /dev/null
+++ b/app/soapbox/components/ui/toast/toast.tsx
@@ -0,0 +1,146 @@
+import classNames from 'clsx';
+import React from 'react';
+import toast, { Toast as RHToast } from 'react-hot-toast';
+import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router-dom';
+
+import { ToastText, ToastType } from 'soapbox/toast';
+
+import Icon from '../icon/icon';
+
+const renderText = (text: ToastText) => {
+ if (typeof text === 'string') {
+ return text;
+ } else {
+ return ;
+ }
+};
+
+interface IToast {
+ t: RHToast
+ message: ToastText
+ type: ToastType
+ action?(): void
+ actionLink?: string
+ actionLabel?: ToastText
+}
+
+/**
+ * Customizable Toasts for in-app notifications.
+ */
+const Toast = (props: IToast) => {
+ const { t, message, type, action, actionLink, actionLabel } = props;
+
+ const dismissToast = () => toast.dismiss(t.id);
+
+ const renderIcon = () => {
+ switch (type) {
+ case 'success':
+ return (
+
+ );
+ case 'info':
+ return (
+
+ );
+ case 'error':
+ return (
+
+ );
+ }
+ };
+
+ const renderAction = () => {
+ const classNames = 'ml-3 mt-0.5 flex-shrink-0 rounded-full text-sm font-medium text-primary-600 dark:text-accent-blue hover:underline focus:outline-none';
+
+ if (action && actionLabel) {
+ return (
+
+ );
+ }
+
+ if (actionLink && actionLabel) {
+ return (
+
+ {renderText(actionLabel)}
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+
+
+
+
+
+ {renderIcon()}
+
+
+
+ {renderText(message)}
+
+
+
+ {/* Action */}
+ {renderAction()}
+
+
+ {/* Dismiss Button */}
+
+
+
+
+
+
+ );
+};
+
+export default Toast;
\ No newline at end of file
diff --git a/app/soapbox/toast.tsx b/app/soapbox/toast.tsx
index 68cf83e82..6095737f4 100644
--- a/app/soapbox/toast.tsx
+++ b/app/soapbox/toast.tsx
@@ -1,15 +1,13 @@
import { AxiosError } from 'axios';
-import classNames from 'clsx';
import React from 'react';
-import toast, { Toast } from 'react-hot-toast';
-import { defineMessages, FormattedMessage, MessageDescriptor } from 'react-intl';
-import { Link } from 'react-router-dom';
+import toast from 'react-hot-toast';
+import { defineMessages, MessageDescriptor } from 'react-intl';
-import { Icon } from './components/ui';
+import { Toast } from './components/ui';
import { httpErrorMessages } from './utils/errors';
-type ToastText = string | MessageDescriptor
-type ToastType = 'success' | 'error' | 'info'
+export type ToastText = string | MessageDescriptor
+export type ToastType = 'success' | 'error' | 'info'
interface IToastOptions {
action?(): void
@@ -20,133 +18,10 @@ interface IToastOptions {
const DEFAULT_DURATION = 4000;
-const renderText = (text: ToastText) => {
- if (typeof text === 'string') {
- return text;
- } else {
- return ;
- }
-};
-
-const buildToast = (t: Toast, message: ToastText, type: ToastType, opts: Omit = {}) => {
- const { action, actionLabel, actionLink } = opts;
-
- const dismissToast = () => toast.dismiss(t.id);
-
- const renderIcon = () => {
- switch (type) {
- case 'success':
- return (
-
- );
- case 'info':
- return (
-
- );
- case 'error':
- return (
-
- );
- }
- };
-
- const renderAction = () => {
- const classNames = 'ml-3 mt-0.5 flex-shrink-0 rounded-full text-sm font-medium text-primary-600 dark:text-accent-blue hover:underline focus:outline-none';
-
- if (action && actionLabel) {
- return (
-
- );
- }
-
- if (actionLink && actionLabel) {
- return (
-
- {renderText(actionLabel)}
-
- );
- }
-
- return null;
- };
-
- return (
-
-
-
-
-
-
- {renderIcon()}
-
-
-
- {renderText(message)}
-
-
-
- {/* Action */}
- {renderAction()}
-
-
- {/* Dismiss Button */}
-
-
-
-
-
-
- );
-};
-
const createToast = (type: ToastType, message: ToastText, opts?: IToastOptions) => {
const duration = opts?.duration || DEFAULT_DURATION;
- toast.custom((t) => buildToast(t, message, type, opts), {
+ toast.custom((t) => , {
duration,
});
};