Move toasts to sonner (#6053)
This commit is contained in:
@@ -5,6 +5,7 @@ import Initials from "./Initials";
|
||||
|
||||
export enum AvatarSize {
|
||||
Small = 16,
|
||||
Toast = 18,
|
||||
Medium = 24,
|
||||
Large = 32,
|
||||
XLarge = 48,
|
||||
|
||||
@@ -2,12 +2,12 @@ import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import Collection from "~/models/Collection";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import Text from "~/components/Text";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { homePath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@@ -18,7 +18,6 @@ type Props = {
|
||||
function CollectionDeleteDialog({ collection, onSubmit }: Props) {
|
||||
const team = useCurrentTeam();
|
||||
const { ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -31,7 +30,7 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
|
||||
|
||||
await collection.delete();
|
||||
onSubmit();
|
||||
showToast(t("Collection deleted"), { type: "success" });
|
||||
toast.success(t("Collection deleted"));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import Collection from "~/models/Collection";
|
||||
@@ -13,7 +14,6 @@ import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
@@ -21,7 +21,6 @@ type Props = {
|
||||
|
||||
function CollectionDescription({ collection }: Props) {
|
||||
const { collections } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setExpanded] = React.useState(false);
|
||||
const [isEditing, setEditing] = React.useState(false);
|
||||
@@ -59,15 +58,11 @@ function CollectionDescription({ collection }: Props) {
|
||||
});
|
||||
setDirty(false);
|
||||
} catch (err) {
|
||||
showToast(
|
||||
t("Sorry, an error occurred saving the collection", {
|
||||
type: "error",
|
||||
})
|
||||
);
|
||||
toast.error(t("Sorry, an error occurred saving the collection"));
|
||||
throw err;
|
||||
}
|
||||
}, 1000),
|
||||
[collection, showToast, t]
|
||||
[collection, t]
|
||||
);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import Comment from "~/models/Comment";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
comment: Comment;
|
||||
@@ -14,7 +14,6 @@ type Props = {
|
||||
|
||||
function CommentDeleteDialog({ comment, onSubmit }: Props) {
|
||||
const { comments } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const hasChildComments = comments.inThread(comment.id).length > 1;
|
||||
|
||||
@@ -23,7 +22,7 @@ function CommentDeleteDialog({ comment, onSubmit }: Props) {
|
||||
await comment.delete();
|
||||
onSubmit?.();
|
||||
} catch (err) {
|
||||
showToast(err.message, { type: "error" });
|
||||
toast.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
/** Callback when the dialog is submitted */
|
||||
@@ -30,7 +30,6 @@ const ConfirmationDialog: React.FC<Props> = ({
|
||||
}: Props) => {
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const { dialogs } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async (ev: React.SyntheticEvent) => {
|
||||
@@ -40,14 +39,12 @@ const ConfirmationDialog: React.FC<Props> = ({
|
||||
await onSubmit();
|
||||
dialogs.closeAllModals();
|
||||
} catch (err) {
|
||||
showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
toast.error(err.message);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[onSubmit, dialogs, showToast]
|
||||
[onSubmit, dialogs]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { HomeIcon } from "outline-icons";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { Optional } from "utility-types";
|
||||
import Flex from "~/components/Flex";
|
||||
import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import { IconWrapper } from "~/components/Sidebar/components/SidebarLink";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type DefaultCollectionInputSelectProps = Optional<
|
||||
React.ComponentProps<typeof InputSelect>
|
||||
@@ -25,7 +25,6 @@ const DefaultCollectionInputSelect = ({
|
||||
const { collections } = useStores();
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [fetchError, setFetchError] = useState();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
React.useEffect(() => {
|
||||
async function fetchData() {
|
||||
@@ -36,11 +35,8 @@ const DefaultCollectionInputSelect = ({
|
||||
limit: 100,
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(
|
||||
t("Collections could not be loaded, please reload the app"),
|
||||
{
|
||||
type: "error",
|
||||
}
|
||||
toast.error(
|
||||
t("Collections could not be loaded, please reload the app")
|
||||
);
|
||||
setFetchError(error);
|
||||
} finally {
|
||||
@@ -49,7 +45,7 @@ const DefaultCollectionInputSelect = ({
|
||||
}
|
||||
}
|
||||
void fetchData();
|
||||
}, [showToast, fetchError, t, fetching, collections]);
|
||||
}, [fetchError, t, fetching, collections]);
|
||||
|
||||
const options = React.useMemo(
|
||||
() =>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
|
||||
import { useDesktopTitlebar } from "~/hooks/useDesktopTitlebar";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
|
||||
export default function DesktopEventHandler() {
|
||||
@@ -12,7 +12,6 @@ export default function DesktopEventHandler() {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const { dialogs } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
React.useEffect(() => {
|
||||
Desktop.bridge?.redirect((path: string, replace = false) => {
|
||||
@@ -24,11 +23,11 @@ export default function DesktopEventHandler() {
|
||||
});
|
||||
|
||||
Desktop.bridge?.updateDownloaded(() => {
|
||||
showToast("An update is ready to install.", {
|
||||
type: "info",
|
||||
timeout: Infinity,
|
||||
toast.message("An update is ready to install.", {
|
||||
duration: Infinity,
|
||||
dismissible: true,
|
||||
action: {
|
||||
text: "Install now",
|
||||
label: t("Install now"),
|
||||
onClick: () => {
|
||||
void Desktop.bridge?.restartAndInstall();
|
||||
},
|
||||
@@ -50,7 +49,7 @@ export default function DesktopEventHandler() {
|
||||
content: <KeyboardShortcuts />,
|
||||
});
|
||||
});
|
||||
}, [t, history, dialogs, showToast]);
|
||||
}, [t, history, dialogs]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@@ -14,7 +14,6 @@ type Props = {
|
||||
|
||||
function DocumentTemplatizeDialog({ documentId }: Props) {
|
||||
const history = useHistory();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const { documents } = useStores();
|
||||
const document = documents.get(documentId);
|
||||
@@ -24,11 +23,9 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
|
||||
const template = await document?.templatize();
|
||||
if (template) {
|
||||
history.push(documentPath(template));
|
||||
showToast(t("Template created, go ahead and customize it"), {
|
||||
type: "info",
|
||||
});
|
||||
toast.message(t("Template created, go ahead and customize it"));
|
||||
}
|
||||
}, [document, showToast, history, t]);
|
||||
}, [document, history, t]);
|
||||
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
|
||||
@@ -24,7 +24,6 @@ import type { Props as EditorProps, Editor as SharedEditor } from "~/editor";
|
||||
import useDictionary from "~/hooks/useDictionary";
|
||||
import useEmbeds from "~/hooks/useEmbeds";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import useUserLocale from "~/hooks/useUserLocale";
|
||||
import { NotFoundError } from "~/utils/errors";
|
||||
import { uploadFile } from "~/utils/files";
|
||||
@@ -43,7 +42,6 @@ export type Props = Optional<
|
||||
| "onClickLink"
|
||||
| "embeds"
|
||||
| "dictionary"
|
||||
| "onShowToast"
|
||||
| "extensions"
|
||||
> & {
|
||||
shareId?: string | undefined;
|
||||
@@ -68,7 +66,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
||||
const userLocale = useUserLocale();
|
||||
const locale = dateLocale(userLocale);
|
||||
const { auth, comments, documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const dictionary = useDictionary();
|
||||
const embeds = useEmbeds(!shareId);
|
||||
const history = useHistory();
|
||||
@@ -241,7 +238,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
||||
uploadFile: handleUploadFile,
|
||||
onFileUploadStart: props.onFileUploadStart,
|
||||
onFileUploadStop: props.onFileUploadStop,
|
||||
onShowToast: showToast,
|
||||
dictionary,
|
||||
isAttachment,
|
||||
});
|
||||
@@ -252,7 +248,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
||||
props.onFileUploadStop,
|
||||
dictionary,
|
||||
handleUploadFile,
|
||||
showToast,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -336,7 +331,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
||||
<LazyLoadedEditor
|
||||
ref={mergeRefs([ref, localRef, handleRefChanged])}
|
||||
uploadFile={handleUploadFile}
|
||||
onShowToast={showToast}
|
||||
embeds={embeds}
|
||||
userPreferences={preferences}
|
||||
dictionary={dictionary}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { FileOperationFormat, NotificationEventType } from "@shared/types";
|
||||
import Collection from "~/models/Collection";
|
||||
@@ -10,7 +11,6 @@ import Text from "~/components/Text";
|
||||
import env from "~/env";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import history from "~/utils/history";
|
||||
import { settingsPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -26,7 +26,6 @@ function ExportDialog({ collection, onSubmit }: Props) {
|
||||
const [includeAttachments, setIncludeAttachments] =
|
||||
React.useState<boolean>(true);
|
||||
const user = useCurrentUser();
|
||||
const { showToast } = useToasts();
|
||||
const { collections } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const appName = env.APP_NAME;
|
||||
@@ -48,23 +47,20 @@ function ExportDialog({ collection, onSubmit }: Props) {
|
||||
const handleSubmit = async () => {
|
||||
if (collection) {
|
||||
await collection.export(format, includeAttachments);
|
||||
showToast(
|
||||
t(`Your file will be available in {{ location }} soon`, {
|
||||
toast.success(t("Export started"), {
|
||||
description: t(`Your file will be available in {{ location }} soon`, {
|
||||
location: `"${t("Settings")} > ${t("Export")}"`,
|
||||
}),
|
||||
{
|
||||
type: "success",
|
||||
action: {
|
||||
text: t("Go to exports"),
|
||||
onClick: () => {
|
||||
history.push(settingsPath("export"));
|
||||
},
|
||||
action: {
|
||||
label: t("View"),
|
||||
onClick: () => {
|
||||
history.push(settingsPath("export"));
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await collections.export(format, includeAttachments);
|
||||
showToast(t("Export started"), { type: "success" });
|
||||
toast.success(t("Export started"));
|
||||
}
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
@@ -3,24 +3,21 @@ import { ArchiveIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { archivePath } from "~/utils/routeHelpers";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
function ArchiveLink() {
|
||||
const { policies, documents } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const [{ isDocumentDropping }, dropToArchiveDocument] = useDrop({
|
||||
accept: "document",
|
||||
drop: async (item: DragObject) => {
|
||||
const document = documents.get(item.id);
|
||||
await document?.archive();
|
||||
showToast(t("Document archived"), {
|
||||
type: "success",
|
||||
});
|
||||
toast.success(t("Document archived"));
|
||||
},
|
||||
canDrop: (item) => policies.abilities(item.id).archive,
|
||||
collect: (monitor) => ({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
@@ -9,7 +10,6 @@ import DocumentsLoader from "~/components/DocumentsLoader";
|
||||
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import DocumentLink from "./DocumentLink";
|
||||
import DropCursor from "./DropCursor";
|
||||
import EmptyCollectionPlaceholder from "./EmptyCollectionPlaceholder";
|
||||
@@ -30,7 +30,6 @@ function CollectionLinkChildren({
|
||||
prefetchDocument,
|
||||
}: Props) {
|
||||
const can = usePolicy(collection);
|
||||
const { showToast } = useToasts();
|
||||
const manualSort = collection.sort.field === "index";
|
||||
const { documents } = useStores();
|
||||
const { t } = useTranslation();
|
||||
@@ -42,14 +41,10 @@ function CollectionLinkChildren({
|
||||
accept: "document",
|
||||
drop: (item: DragObject) => {
|
||||
if (!manualSort && item.collectionId === collection?.id) {
|
||||
showToast(
|
||||
toast.message(
|
||||
t(
|
||||
"You can't reorder documents in an alphabetically sorted collection"
|
||||
),
|
||||
{
|
||||
type: "info",
|
||||
timeout: 5000,
|
||||
}
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useDrag, useDrop } from "react-dnd";
|
||||
import { getEmptyImage } from "react-dnd-html5-backend";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||
@@ -18,7 +19,6 @@ import Tooltip from "~/components/Tooltip";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { newDocumentPath } from "~/utils/routeHelpers";
|
||||
import DropCursor from "./DropCursor";
|
||||
@@ -53,7 +53,6 @@ function InnerDocumentLink(
|
||||
}: Props,
|
||||
ref: React.RefObject<HTMLAnchorElement>
|
||||
) {
|
||||
const { showToast } = useToasts();
|
||||
const { documents, policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const canUpdate = usePolicy(node.id).update;
|
||||
@@ -222,14 +221,10 @@ function InnerDocumentLink(
|
||||
accept: "document",
|
||||
drop: (item: DragObject) => {
|
||||
if (!manualSort) {
|
||||
showToast(
|
||||
toast.message(
|
||||
t(
|
||||
"You can't reorder documents in an alphabetically sorted collection"
|
||||
),
|
||||
{
|
||||
type: "info",
|
||||
timeout: 5000,
|
||||
}
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import Dropzone from "react-dropzone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled, { css } from "styled-components";
|
||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import useImportDocument from "~/hooks/useImportDocument";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element;
|
||||
@@ -21,7 +21,6 @@ type Props = {
|
||||
function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { handleFiles, isImporting } = useImportDocument(
|
||||
collectionId,
|
||||
documentId
|
||||
@@ -35,13 +34,10 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
const canDocument = usePolicy(documentId);
|
||||
|
||||
const handleRejection = React.useCallback(() => {
|
||||
showToast(
|
||||
t("Document not supported – try Markdown, Plain text, HTML, or Word"),
|
||||
{
|
||||
type: "error",
|
||||
}
|
||||
toast.error(
|
||||
t("Document not supported – try Markdown, Plain text, HTML, or Word")
|
||||
);
|
||||
}, [t, showToast]);
|
||||
}, [t]);
|
||||
|
||||
if (
|
||||
disabled ||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
onSubmit: (title: string) => Promise<void>;
|
||||
@@ -22,7 +22,6 @@ function EditableTitle(
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
const [originalValue, setOriginalValue] = React.useState(title);
|
||||
const [value, setValue] = React.useState(title);
|
||||
const { showToast } = useToasts();
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setIsEditing,
|
||||
@@ -78,14 +77,12 @@ function EditableTitle(
|
||||
setOriginalValue(trimmedValue);
|
||||
} catch (error) {
|
||||
setValue(originalValue);
|
||||
showToast(error.message, {
|
||||
type: "error",
|
||||
});
|
||||
toast.error(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
[originalValue, showToast, value, onSubmit]
|
||||
[originalValue, value, onSubmit]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { CheckboxIcon, InfoIcon, WarningIcon } from "outline-icons";
|
||||
import { darken } from "polished";
|
||||
import * as React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import { fadeAndScaleIn, pulse } from "~/styles/animations";
|
||||
import { Toast as TToast } from "~/types";
|
||||
import Spinner from "./Spinner";
|
||||
|
||||
type Props = {
|
||||
onRequestClose: () => void;
|
||||
closeAfterMs?: number;
|
||||
toast: TToast;
|
||||
};
|
||||
|
||||
function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
|
||||
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const [pulse, setPulse] = React.useState(false);
|
||||
const { action, type = "info", reoccurring } = toast;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (toast.timeout !== 0) {
|
||||
timeout.current = setTimeout(
|
||||
onRequestClose,
|
||||
toast.timeout || closeAfterMs
|
||||
);
|
||||
}
|
||||
return () => timeout.current && clearTimeout(timeout.current);
|
||||
}, [onRequestClose, toast, closeAfterMs]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (reoccurring) {
|
||||
setPulse(!!reoccurring);
|
||||
// must match animation time in css below vvv
|
||||
setTimeout(() => setPulse(false), 250);
|
||||
}
|
||||
}, [reoccurring]);
|
||||
|
||||
const handlePause = React.useCallback(() => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleResume = React.useCallback(() => {
|
||||
if (timeout.current && toast.timeout !== 0) {
|
||||
timeout.current = setTimeout(
|
||||
onRequestClose,
|
||||
toast.timeout || closeAfterMs
|
||||
);
|
||||
}
|
||||
}, [onRequestClose, toast, closeAfterMs]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
$pulse={pulse}
|
||||
onMouseEnter={handlePause}
|
||||
onMouseLeave={handleResume}
|
||||
>
|
||||
<Container onClick={action ? undefined : onRequestClose}>
|
||||
{type === "loading" && <Spinner />}
|
||||
{type === "info" && <InfoIcon />}
|
||||
{type === "success" && <CheckboxIcon checked />}
|
||||
{(type === "warning" || type === "error") && <WarningIcon />}
|
||||
<Message>{toast.message}</Message>
|
||||
{action && <Action onClick={action.onClick}>{action.text}</Action>}
|
||||
</Container>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
const Action = styled.span`
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
color: ${s("toastText")};
|
||||
background: ${(props) => darken(0.05, props.theme.toastBackground)};
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
margin-right: -4px;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => darken(0.1, props.theme.toastBackground)};
|
||||
}
|
||||
`;
|
||||
|
||||
const ListItem = styled.li<{ $pulse?: boolean }>`
|
||||
${(props) =>
|
||||
props.$pulse &&
|
||||
css`
|
||||
animation: ${pulse} 250ms;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
animation: ${fadeAndScaleIn} 100ms ease;
|
||||
margin: 8px 0;
|
||||
padding: 0 12px;
|
||||
color: ${s("toastText")};
|
||||
background: ${s("toastBackground")};
|
||||
font-size: 15px;
|
||||
border-radius: 5px;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => darken(0.05, props.theme.toastBackground)};
|
||||
}
|
||||
`;
|
||||
|
||||
const Message = styled.div`
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
padding: 10px 4px;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export default Toast;
|
||||
@@ -1,34 +1,28 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { depths } from "@shared/styles";
|
||||
import Toast from "~/components/Toast";
|
||||
import { Toaster } from "sonner";
|
||||
import { useTheme } from "styled-components";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { Toast as TToast } from "~/types";
|
||||
|
||||
function Toasts() {
|
||||
const { toasts } = useStores();
|
||||
const { ui } = useStores();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<List>
|
||||
{toasts.orderedData.map((toast: TToast) => (
|
||||
<Toast
|
||||
key={toast.id}
|
||||
toast={toast}
|
||||
onRequestClose={() => toasts.hideToast(toast.id)}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
<Toaster
|
||||
theme={ui.resolvedTheme}
|
||||
toastOptions={{
|
||||
duration: 5000,
|
||||
style: {
|
||||
color: theme.toastText,
|
||||
background: theme.toastBackground,
|
||||
border: `1px solid ${theme.divider}`,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: "14px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const List = styled.ol`
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
bottom: 16px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: ${depths.toasts};
|
||||
`;
|
||||
|
||||
export default observer(Toasts);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
import { toast } from "sonner";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Collection from "~/models/Collection";
|
||||
import Comment from "~/models/Comment";
|
||||
@@ -77,7 +78,6 @@ class WebsocketProvider extends React.Component<Props> {
|
||||
this.socket.authenticated = false;
|
||||
const {
|
||||
auth,
|
||||
toasts,
|
||||
documents,
|
||||
collections,
|
||||
groups,
|
||||
@@ -111,9 +111,7 @@ class WebsocketProvider extends React.Component<Props> {
|
||||
if (this.socket) {
|
||||
this.socket.authenticated = false;
|
||||
}
|
||||
toasts.showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
toast.error(err.message);
|
||||
throw err;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user