[Update] Change the notification UI.
(Change the base notification library from MUI to React-Toastify.)
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -28,6 +28,7 @@
|
|||||||
"react-error-boundary": "5.0.0",
|
"react-error-boundary": "5.0.0",
|
||||||
"react-i18next": "15.5.1",
|
"react-i18next": "15.5.1",
|
||||||
"react-resizable-layout": "0.7.2",
|
"react-resizable-layout": "0.7.2",
|
||||||
|
"react-toastify": "11.0.5",
|
||||||
"sass": "1.79.4",
|
"sass": "1.79.4",
|
||||||
"semver": "7.7.1"
|
"semver": "7.7.1"
|
||||||
},
|
},
|
||||||
@@ -5534,6 +5535,19 @@
|
|||||||
"react-dom": ">=17.0.0"
|
"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": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"react-error-boundary": "5.0.0",
|
"react-error-boundary": "5.0.0",
|
||||||
"react-i18next": "15.5.1",
|
"react-i18next": "15.5.1",
|
||||||
"react-resizable-layout": "0.7.2",
|
"react-resizable-layout": "0.7.2",
|
||||||
|
"react-toastify": "11.0.5",
|
||||||
"sass": "1.79.4",
|
"sass": "1.79.4",
|
||||||
"semver": "7.7.1"
|
"semver": "7.7.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const ErrorContainer = ({error}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Duplicated
|
||||||
const CloseButtonContainer = () => {
|
const CloseButtonContainer = () => {
|
||||||
const { asyncCloseApp } = useWindow();
|
const { asyncCloseApp } = useWindow();
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -72,6 +72,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Duplicated
|
||||||
.close_button_wrapper {
|
.close_button_wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
109
src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss
Normal file
109
src-ui/app/snackbar_controller/ReactToastifyOverrideClass.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,46 +1,114 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { ToastContainer, toast, Bounce } from "react-toastify";
|
||||||
import clsx from "clsx";
|
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 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";
|
import { useNotificationStatus } from "@logics_common";
|
||||||
|
|
||||||
export const SnackbarController = () => {
|
export const SnackbarController = () => {
|
||||||
const { currentNotificationStatus, closeNotification } = useNotificationStatus();
|
const { currentNotificationStatus, closeNotification } = useNotificationStatus();
|
||||||
|
const [containerKey, setContainerKey] = useState(0);
|
||||||
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 settings = currentNotificationStatus.data;
|
const settings = currentNotificationStatus.data;
|
||||||
|
|
||||||
let hide_duration = 5000;
|
const snackbar_classname = clsx(
|
||||||
if (settings.options?.hide_duration === null) hide_duration = null;
|
styles.snackbar_content,
|
||||||
if (Number(settings.options?.hide_duration)) hide_duration = settings.options.hide_duration;
|
{
|
||||||
|
[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: <CloseButtonContainer />,
|
||||||
|
onClose: () => {
|
||||||
|
closeNotification();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
toast(message_text, {
|
||||||
|
toastId: message_text,
|
||||||
|
type: settings.status,
|
||||||
|
autoClose: hideDuration,
|
||||||
|
transition: Bounce,
|
||||||
|
toastClassName: snackbar_classname,
|
||||||
|
progressClassName: styles.toast_progress,
|
||||||
|
closeButton: <CloseButtonContainer />,
|
||||||
|
onClose: () => {
|
||||||
|
closeNotification();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [settings, hideDuration, closeNotification, snackbar_classname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<ToastContainer
|
||||||
<Snackbar
|
key={containerKey}
|
||||||
open={settings.is_open}
|
position="bottom-left"
|
||||||
onClose={handleClose}
|
transition={Bounce}
|
||||||
TransitionComponent={SlideTransition}
|
hideProgressBar={false}
|
||||||
key={settings.key}
|
newestOnTop={false}
|
||||||
autoHideDuration={hide_duration}
|
closeOnClick={false}
|
||||||
>
|
pauseOnFocusLoss={false}
|
||||||
<div className={snackbar_classname}>
|
draggable={false}
|
||||||
<p className={styles.snackbar_message}>{settings.message}</p>
|
pauseOnHover={true}
|
||||||
</div>
|
theme="dark"
|
||||||
</Snackbar>
|
icon={({ type }) => {
|
||||||
</div>
|
switch (type) {
|
||||||
|
case "info":
|
||||||
|
return <MegaphoneSvg className={styles.megaphone_svg} />;
|
||||||
|
case "error":
|
||||||
|
return <ErrorSvg className={styles.error_svg} />;
|
||||||
|
case "success":
|
||||||
|
return <CheckMarkSvg className={styles.check_mark_svg} />;
|
||||||
|
case "warning":
|
||||||
|
return <WarningSvg className={styles.warning_svg} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SlideTransition = (props) => {
|
const CloseButtonContainer = ({ closeToast }) => {
|
||||||
return <Slide {...props} direction="up" />;
|
return (
|
||||||
|
<button className={styles.close_button_wrapper} onClick={closeToast}>
|
||||||
|
<div className={styles.close_button}>
|
||||||
|
<XMarkSvg className={styles.x_mark_svg} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
@@ -1,19 +1,83 @@
|
|||||||
|
/* SnackbarController.module.scss */
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
1) トースト共通のスタイル
|
||||||
|
--------------------------------------------------*/
|
||||||
.snackbar_content {
|
.snackbar_content {
|
||||||
width: 100%;
|
position: relative;
|
||||||
height: 100%;
|
padding: 1.2rem 1.6rem;
|
||||||
padding: 2rem;
|
border-radius: 0.8rem;
|
||||||
color: #fff;
|
font-size: 1.4rem;
|
||||||
&.is_success {
|
box-shadow: 0 0.2rem 0.8rem rgba(0, 0, 0, 0.5);
|
||||||
background-color: var(--success_bc_color);
|
color: #fff; /* ダークテーマ前提で文字を白に */
|
||||||
}
|
|
||||||
&.is_warning {
|
|
||||||
background-color: var(--waring_bc_color);
|
|
||||||
}
|
|
||||||
&.is_error {
|
|
||||||
background-color: var(--error_bc_color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.snackbar_message {
|
/* ステータス別に背景色を指定 */
|
||||||
font-size: 1.4rem;
|
.is_success {
|
||||||
|
background-color: var(--success_bc_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is_warning {
|
||||||
|
background-color: var(--warning_bc_color);
|
||||||
|
color: #212529; /* 黄系の背景に合わせて文字を濃く */
|
||||||
|
}
|
||||||
|
|
||||||
|
.is_error {
|
||||||
|
background-color: var(--error_bc_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.megaphone_svg {
|
||||||
|
color: var(--dark_200_color);
|
||||||
|
width: 2.4rem;
|
||||||
|
}
|
||||||
|
.error_svg {
|
||||||
|
color: var(--error_bc_color);
|
||||||
|
width: 2.4rem;
|
||||||
|
}
|
||||||
|
.check_mark_svg {
|
||||||
|
color: var(--primary_400_color);
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
.warning_svg {
|
||||||
|
color: var(--waring_color);
|
||||||
|
width: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Duplicated (Customized)
|
||||||
|
.close_button_wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 100%;
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: end;
|
||||||
|
width: 5.6rem;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--error_bc_color);
|
||||||
|
& .x_mark_svg {
|
||||||
|
color: var(--dark_200_color);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--error_bc_active_color);
|
||||||
|
}
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close_button {
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x_mark_svg {
|
||||||
|
width: 2rem;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
color: var(--dark_700_color);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ const AnnouncementsContainer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Duplicated
|
||||||
const CloseButtonContainer = () => {
|
const CloseButtonContainer = () => {
|
||||||
const { asyncCloseApp } = useWindow();
|
const { asyncCloseApp } = useWindow();
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Duplicated
|
||||||
.close_button_wrapper {
|
.close_button_wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
1
src-ui/assets/error.svg
Normal file
1
src-ui/assets/error.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.31 7.526c-.099-.807.528-1.526 1.348-1.526.771 0 1.377.676 1.28 1.451l-.757 6.053c-.035.283-.276.496-.561.496s-.526-.213-.562-.496l-.748-5.978zm1.31 10.724c-.69 0-1.25-.56-1.25-1.25s.56-1.25 1.25-1.25 1.25.56 1.25 1.25-.56 1.25-1.25 1.25z"/></svg>
|
||||||
|
After Width: | Height: | Size: 468 B |
@@ -35,8 +35,7 @@ export const useNotificationStatus = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNotification = (event, reason) => {
|
const closeNotification = () => {
|
||||||
if (reason === "clickaway") return;
|
|
||||||
updateNotificationStatus((prev) => ({
|
updateNotificationStatus((prev) => ({
|
||||||
...prev.data,
|
...prev.data,
|
||||||
is_open: false,
|
is_open: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user