aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/components/alert
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/components/alert')
-rw-r--r--FrontEnd/src/components/alert/AlertHost.tsx113
-rw-r--r--FrontEnd/src/components/alert/alert.css33
2 files changed, 146 insertions, 0 deletions
diff --git a/FrontEnd/src/components/alert/AlertHost.tsx b/FrontEnd/src/components/alert/AlertHost.tsx
new file mode 100644
index 00000000..b234ac03
--- /dev/null
+++ b/FrontEnd/src/components/alert/AlertHost.tsx
@@ -0,0 +1,113 @@
+import * as React from "react";
+import without from "lodash/without";
+import { useTranslation } from "react-i18next";
+import classNames from "classnames";
+
+import { alertService, AlertInfoEx, AlertInfo } from "~src/services/alert";
+import { convertI18nText } from "~src/common";
+
+import IconButton from "../button/IconButton";
+
+import "./alert.css";
+
+interface AutoCloseAlertProps {
+ alert: AlertInfo;
+ close: () => void;
+}
+
+export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
+ const { alert, close } = props;
+ const { dismissTime } = alert;
+
+ const { t } = useTranslation();
+
+ const timerTag = React.useRef<number | null>(null);
+ const closeHandler = React.useRef<(() => void) | null>(null);
+
+ React.useEffect(() => {
+ closeHandler.current = close;
+ }, [close]);
+
+ React.useEffect(() => {
+ const tag =
+ dismissTime === "never"
+ ? null
+ : typeof dismissTime === "number"
+ ? window.setTimeout(() => closeHandler.current?.(), dismissTime)
+ : window.setTimeout(() => closeHandler.current?.(), 5000);
+ timerTag.current = tag;
+ return () => {
+ if (tag != null) {
+ window.clearTimeout(tag);
+ }
+ };
+ }, [dismissTime]);
+
+ const cancelTimer = (): void => {
+ const { current: tag } = timerTag;
+ if (tag != null) {
+ window.clearTimeout(tag);
+ }
+ };
+
+ return (
+ <div
+ className={classNames(
+ "m-3 cru-alert",
+ "cru-" + (alert.type ?? "primary")
+ )}
+ onClick={cancelTimer}
+ >
+ <div className="cru-alert-content">
+ {(() => {
+ const { message, customMessage } = alert;
+ if (customMessage != null) {
+ return customMessage;
+ } else {
+ return convertI18nText(message, t);
+ }
+ })()}
+ </div>
+ <div className="cru-alert-close-button-container">
+ <IconButton
+ icon="x"
+ className="cru-alert-close-button"
+ onClick={close}
+ />
+ </div>
+ </div>
+ );
+};
+
+const AlertHost: React.FC = () => {
+ const [alerts, setAlerts] = React.useState<AlertInfoEx[]>([]);
+
+ React.useEffect(() => {
+ const consume = (alert: AlertInfoEx): void => {
+ setAlerts((old) => [...old, alert]);
+ };
+
+ alertService.registerConsumer(consume);
+ return () => {
+ alertService.unregisterConsumer(consume);
+ };
+ }, []);
+
+ return (
+ <div className="alert-container">
+ {alerts.map((alert) => {
+ return (
+ <AutoCloseAlert
+ key={alert.id}
+ alert={alert}
+ close={() => {
+ setAlerts((old) => without(old, alert));
+ }}
+ />
+ );
+ })}
+ </div>
+ );
+};
+
+export default AlertHost;
diff --git a/FrontEnd/src/components/alert/alert.css b/FrontEnd/src/components/alert/alert.css
new file mode 100644
index 00000000..54c2b87f
--- /dev/null
+++ b/FrontEnd/src/components/alert/alert.css
@@ -0,0 +1,33 @@
+.alert-container {
+ position: fixed;
+ z-index: 1040;
+}
+
+.cru-alert {
+ border-radius: 5px;
+ border: var(--cru-key-color) 1px solid;
+ color: var(--cru-key-t-color);
+ background-color: var(--cru-key-b1-color);
+
+ display: flex;
+ overflow: hidden;
+}
+
+.cru-alert-content {
+ padding: 0.5em 2em;
+}
+
+.cru-alert-close-button-container {
+ flex-shrink: 0;
+ margin-left: auto;
+ width: 2em;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--cru-key-t-color);
+}
+
+.cru-alert-close-button {
+ color: var(--cru-key-color);
+}