From bcef981955db69f9465f92e0c9bd177a391ee311 Mon Sep 17 00:00:00 2001
From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com>
Date: Thu, 5 Jun 2025 18:11:53 +0900
Subject: [PATCH] [Update] Change the notification UI. (Change the base
notification library from MUI to React-Toastify.)
---
package-lock.json | 14 ++
package.json | 1 +
.../app/error_boundary/AppErrorBoundary.jsx | 1 +
.../AppErrorBoundary.module.scss | 1 +
.../ReactToastifyOverrideClass.scss | 109 +++++++++++++++
.../SnackbarController.jsx | 130 +++++++++++++-----
.../SnackbarController.module.scss | 94 +++++++++++--
.../app/splash_component/SplashComponent.jsx | 2 +-
.../SplashComponent.module.scss | 2 +-
src-ui/assets/error.svg | 1 +
src-ui/logics/common/useNotificationStatus.js | 3 +-
11 files changed, 308 insertions(+), 50 deletions(-)
create mode 100644 src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss
create mode 100644 src-ui/assets/error.svg
diff --git a/package-lock.json b/package-lock.json
index f131d4a8..cbe6e142 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"react-error-boundary": "5.0.0",
"react-i18next": "15.5.1",
"react-resizable-layout": "0.7.2",
+ "react-toastify": "11.0.5",
"sass": "1.79.4",
"semver": "7.7.1"
},
@@ -5534,6 +5535,19 @@
"react-dom": ">=17.0.0"
}
},
+ "node_modules/react-toastify": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
+ "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
diff --git a/package.json b/package.json
index a2d9030f..c15471e5 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"react-error-boundary": "5.0.0",
"react-i18next": "15.5.1",
"react-resizable-layout": "0.7.2",
+ "react-toastify": "11.0.5",
"sass": "1.79.4",
"semver": "7.7.1"
},
diff --git a/src-ui/app/error_boundary/AppErrorBoundary.jsx b/src-ui/app/error_boundary/AppErrorBoundary.jsx
index 4aeb34a2..37a1cc23 100644
--- a/src-ui/app/error_boundary/AppErrorBoundary.jsx
+++ b/src-ui/app/error_boundary/AppErrorBoundary.jsx
@@ -65,6 +65,7 @@ const ErrorContainer = ({error}) => {
);
};
+// Duplicated
const CloseButtonContainer = () => {
const { asyncCloseApp } = useWindow();
return (
diff --git a/src-ui/app/error_boundary/AppErrorBoundary.module.scss b/src-ui/app/error_boundary/AppErrorBoundary.module.scss
index 2abb2710..2ff85390 100644
--- a/src-ui/app/error_boundary/AppErrorBoundary.module.scss
+++ b/src-ui/app/error_boundary/AppErrorBoundary.module.scss
@@ -72,6 +72,7 @@
+// Duplicated
.close_button_wrapper {
position: absolute;
top: 0;
diff --git a/src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss b/src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss
new file mode 100644
index 00000000..9325d747
--- /dev/null
+++ b/src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss
@@ -0,0 +1,109 @@
+:root {
+ --toastify-color-light: #fff;
+ --toastify-color-dark: var(--dark_950_color);
+ --toastify-color-info: var(--sent_400_color);
+ --toastify-color-success: var(--primary_400_color);
+ --toastify-color-warning: var(--waring_bc_color);
+ --toastify-color-error: var(--error_bc_color);
+ --toastify-color-transparent: rgba(255, 255, 255, 0.7);
+
+ --toastify-icon-color-info: var(--toastify-color-info);
+ --toastify-icon-color-success: var(--toastify-color-success);
+ --toastify-icon-color-warning: var(--toastify-color-warning);
+ --toastify-icon-color-error: var(--toastify-color-error);
+
+ --toastify-container-width: fit-content;
+ --toastify-toast-width: 32rem;
+ --toastify-toast-offset: 1.6rem;
+ --toastify-toast-top: max(var(--toastify-toast-offset), env(safe-area-inset-top));
+ --toastify-toast-right: max(var(--toastify-toast-offset), env(safe-area-inset-right));
+ --toastify-toast-left: max(var(--toastify-toast-offset), env(safe-area-inset-left));
+ --toastify-toast-bottom: max(var(--toastify-toast-offset), env(safe-area-inset-bottom));
+ --toastify-toast-background: #fff;
+ --toastify-toast-padding: 1.4rem;
+ --toastify-toast-min-height: 6.4rem;
+ --toastify-toast-max-height: 80rem;
+ --toastify-toast-bd-radius: 0.6rem;
+ --toastify-toast-shadow: .0 0.4rem 1.2rem rgba(0, 0, 0, 0.1);
+ --toastify-font-family: var(--font_family);
+ --toastify-z-index: 9999;
+ --toastify-text-color-light: #757575;
+ --toastify-text-color-dark: var(--dark_basic_text_color);
+
+ /* Used only for colored theme */
+ --toastify-text-color-info: var(--dark_basic_text_color);
+ --toastify-text-color-success: var(--dark_basic_text_color);
+ --toastify-text-color-warning: var(--dark_basic_text_color);
+ --toastify-text-color-error: var(--dark_basic_text_color);
+
+ --toastify-spinner-color: #616161;
+ --toastify-spinner-color-empty-area: #e0e0e0;
+ --toastify-color-progress-light: linear-gradient(to right, #4cd964, #5ac8fa, #007aff, #34aadc, #5856d6, #ff2d55);
+ --toastify-color-progress-dark: #bb86fc;
+ --toastify-color-progress-info: var(--toastify-color-info);
+ --toastify-color-progress-success: var(--toastify-color-success);
+ --toastify-color-progress-warning: var(--toastify-color-warning);
+ --toastify-color-progress-error: var(--toastify-color-error);
+ /* used to control the opacity of the progress trail */
+ --toastify-color-progress-bgo: 0.2;
+}
+
+
+.Toastify__toast {
+ // --------------------------------------------------------
+ // Default Settings
+ // --------------------------------------------------------
+ position: relative;
+ touch-action: none;
+ // width: var(--toastify-toast-width);
+ min-height: var(--toastify-toast-min-height);
+ box-sizing: border-box;
+ margin-bottom: 1rem;
+ // padding: var(--toastify-toast-padding);
+ border-radius: 0.6rem;
+ box-shadow: none;
+ max-height: var(--toastify-toast-max-height);
+ // font-family: "Yu Gothic UI";
+ // font-family: var(--toastify-font-family);
+ // z-index: 0;
+ // display: flex;
+ // flex: 1 auto;
+ // align-items: center;
+ word-break: break-word;
+ // --------------------------------------------------------
+ // --------------------------------------------------------
+
+
+ // Comment out above and override. Commented out is just for memorization.
+ overflow: hidden;
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ font-size: 1.4rem;
+ width: fit-content;
+ max-width: 50vw;
+ padding-right: 4rem;
+ background-color: var(--dark_950_color);
+ gap: 0.6rem;
+}
+
+.Toastify__progress-bar--success {
+ background: var(--success_bc_color);
+}
+
+.Toastify__progress-bar--warning {
+ background: var(--warning_bc_color);
+}
+
+.Toastify__progress-bar--error {
+ background: var(--error_bc_color);
+}
+
+
+.Toastify__toast-icon {
+ width: fit-content;
+ max-width: 2.8rem;
+ min-width: 2.8rem;
+ justify-content: center;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/src-ui/app/snackbar_controller/SnackbarController.jsx b/src-ui/app/snackbar_controller/SnackbarController.jsx
index e503e695..28bd6325 100644
--- a/src-ui/app/snackbar_controller/SnackbarController.jsx
+++ b/src-ui/app/snackbar_controller/SnackbarController.jsx
@@ -1,46 +1,114 @@
+import React, { useEffect, useState } from "react";
+import { ToastContainer, toast, Bounce } from "react-toastify";
import clsx from "clsx";
-import Snackbar from "@mui/material/Snackbar";
-import Slide from "@mui/material/Slide";
+import "./ReactToastifyOverrideClass.scss";
import styles from "./SnackbarController.module.scss";
+
+import XMarkSvg from "@images/cancel.svg?react";
+import WarningSvg from "@images/warning.svg?react";
+import MegaphoneSvg from "@images/megaphone.svg?react";
+import CheckMarkSvg from "@images/check_mark.svg?react";
+import ErrorSvg from "@images/error.svg?react";
+
import { useNotificationStatus } from "@logics_common";
export const SnackbarController = () => {
const { currentNotificationStatus, closeNotification } = useNotificationStatus();
-
- const handleClose = (event, reason) => {
- closeNotification(event, reason);
- };
-
- const snackbar_classname = clsx(styles.snackbar_content, {
- [styles.is_success]: currentNotificationStatus.data.status === "success",
- [styles.is_warning]: currentNotificationStatus.data.status === "warning",
- [styles.is_error]: currentNotificationStatus.data.status === "error",
- });
+ const [containerKey, setContainerKey] = useState(0);
const settings = currentNotificationStatus.data;
- let hide_duration = 5000;
- if (settings.options?.hide_duration === null) hide_duration = null;
- if (Number(settings.options?.hide_duration)) hide_duration = settings.options.hide_duration;
+ const snackbar_classname = clsx(
+ styles.snackbar_content,
+ {
+ [styles.is_success]: settings.status === "success",
+ [styles.is_warning]: settings.status === "warning",
+ [styles.is_error]: settings.status === "error",
+ }
+ );
+
+ let hideDuration = 5000;
+ if (settings.options?.hide_duration === null) {
+ hideDuration = false;
+ } else if (Number(settings.options?.hide_duration)) {
+ hideDuration = Number(settings.options?.hide_duration);
+ }
+
+ useEffect(() => {
+ if (!settings.is_open) return;
+
+ const message_text = settings.message;
+
+ if (toast.isActive(message_text)) {
+ setContainerKey(prevKey => prevKey + 1);
+
+ setTimeout(() => {
+ toast(message_text, {
+ toastId: message_text,
+ type: settings.status,
+ autoClose: hideDuration,
+ transition: Bounce,
+ toastClassName: snackbar_classname,
+ progressClassName: styles.toast_progress,
+ closeButton:
{settings.message}
-