From 5bc9976566850e6e62ea038f17fdc7da94f70404 Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Thu, 29 May 2025 22:32:19 +0900 Subject: [PATCH] introduced modal loading spinner. --- src/common/DynamicPortal.tsx | 27 +++++ src/common/Loading.css | 196 ---------------------------------- src/common/Loading.module.css | 117 ++++++++++++++++++++ src/common/Loading.tsx | 14 ++- 4 files changed, 156 insertions(+), 198 deletions(-) create mode 100644 src/common/DynamicPortal.tsx delete mode 100644 src/common/Loading.css create mode 100644 src/common/Loading.module.css diff --git a/src/common/DynamicPortal.tsx b/src/common/DynamicPortal.tsx new file mode 100644 index 0000000..ab1cffa --- /dev/null +++ b/src/common/DynamicPortal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import ReactDOM from 'react-dom'; +import invariant from 'tiny-invariant'; + +interface DynamicPortalProps { + children: React.ReactNode; +} + +const DynamicPortal: React.FC = (props) => { + const { children } = props; + const [el] = React.useState(() => document.createElement('div')); + React.useEffect(() => { + const body = document.querySelector('body'); + invariant(body, 'The body element is not available. Please ensure this code runs in a browser context.'); + if (el.parentNode == null) { + body.appendChild(el); + } + return () => { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + }; + }, []); + return ReactDOM.createPortal(children, el); +}; +export default DynamicPortal; diff --git a/src/common/Loading.css b/src/common/Loading.css deleted file mode 100644 index 2571fee..0000000 --- a/src/common/Loading.css +++ /dev/null @@ -1,196 +0,0 @@ -.loading { - margin: 100px auto; - font-size: 25px; - width: 1em; - height: 1em; - border-radius: 50%; - position: relative; - text-indent: -9999em; - -webkit-animation: loadingKeyframes 1.1s infinite ease; - animation: loadingKeyframes 1.1s infinite ease; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); -} -@-webkit-keyframes loadingKeyframes { - 0%, - 100% { - box-shadow: - 0em -2.6em 0em 0em #000000, - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.5), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7); - } - 12.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.7), - 1.8em -1.8em 0 0em #000000, - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5); - } - 25% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.5), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7), - 2.5em 0em 0 0em #000000, - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 37.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.7), - 1.75em 1.75em 0 0em #000000, - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 50% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.5), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.7), - 0em 2.5em 0 0em #000000, - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 62.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.5), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.7), - -1.8em 1.8em 0 0em #000000, - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 75% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.5), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.7), - -2.6em 0em 0 0em #000000, - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 87.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.5), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.7), - -1.8em -1.8em 0 0em #000000; - } -} -@keyframes loadingKeyframes { - 0%, - 100% { - box-shadow: - 0em -2.6em 0em 0em #000000, - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.5), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7); - } - 12.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.7), - 1.8em -1.8em 0 0em #000000, - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5); - } - 25% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.5), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7), - 2.5em 0em 0 0em #000000, - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 37.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.7), - 1.75em 1.75em 0 0em #000000, - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 50% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.5), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.7), - 0em 2.5em 0 0em #000000, - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 62.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.5), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.7), - -1.8em 1.8em 0 0em #000000, - -2.6em 0em 0 0em rgba(0, 0, 0, 0.2), - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 75% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.5), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.7), - -2.6em 0em 0 0em #000000, - -1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2); - } - 87.5% { - box-shadow: - 0em -2.6em 0em 0em rgba(0, 0, 0, 0.2), - 1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2), - 2.5em 0em 0 0em rgba(0, 0, 0, 0.2), - 1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2), - 0em 2.5em 0 0em rgba(0, 0, 0, 0.2), - -1.8em 1.8em 0 0em rgba(0, 0, 0, 0.5), - -2.6em 0em 0 0em rgba(0, 0, 0, 0.7), - -1.8em -1.8em 0 0em #000000; - } -} diff --git a/src/common/Loading.module.css b/src/common/Loading.module.css new file mode 100644 index 0000000..23e96dd --- /dev/null +++ b/src/common/Loading.module.css @@ -0,0 +1,117 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 9999; +} +.spinner-container { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10000; +} +.spinner-container .spinner { + font-size: 10px; + width: 1em; + height: 1em; + border-radius: 50%; + position: relative; + text-indent: -9999em; + animation: mulShdSpin 1.1s infinite ease; + transform: translateZ(0); +} +@keyframes mulShdSpin { + 0%, + 100% { + box-shadow: + 0em -2.6em 0em 0em #ffffff, + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.5), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7); + } + 12.5% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7), + 1.8em -1.8em 0 0em #ffffff, + 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5); + } + 25% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), + 2.5em 0em 0 0em #ffffff, + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2); + } + 37.5% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.7), + 1.75em 1.75em 0 0em #ffffff, + 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2); + } + 50% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.5), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), + 0em 2.5em 0 0em #ffffff, + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2); + } + 62.5% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.7), + -1.8em 1.8em 0 0em #ffffff, + -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2); + } + 75% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.5), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), + -2.6em 0em 0 0em #ffffff, + -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2); + } + 87.5% { + box-shadow: + 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), + 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), + 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), + 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), + 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), + -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5), + -2.6em 0em 0 0em rgba(255, 255, 255, 0.7), + -1.8em -1.8em 0 0em #ffffff; + } +} diff --git a/src/common/Loading.tsx b/src/common/Loading.tsx index 4371b39..3e7142b 100644 --- a/src/common/Loading.tsx +++ b/src/common/Loading.tsx @@ -1,8 +1,18 @@ import React from 'react'; -import './Loading.css'; +import DynamicPortal from './DynamicPortal'; + +import styles from './Loading.module.css'; const Loading: React.FC = () => { - return
Loading...
; + return ( + +
+
+
Loading..
+
+
+
+ ); }; export default Loading;