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, }); };