From afa86023c5ad41cdd29bb4237ec120d6cde851ee Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 19 May 2023 11:12:02 +0200 Subject: [PATCH 1/6] feat(routeprogressbar): implement progressbar when loading chunks --- package.json | 1 + src/app/layout/Layout.tsx | 8 ++- .../progressbar/RouteProgressBar.module.css | 23 +++++++ .../layout/progressbar/RouteProgressBar.tsx | 63 +++++++++++++++++++ yarn.lock | 24 ++++++- 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/app/layout/progressbar/RouteProgressBar.module.css create mode 100644 src/app/layout/progressbar/RouteProgressBar.tsx diff --git a/package.json b/package.json index ed91bc0c..2e173677 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@dhis2/app-runtime": "^3.8.0", + "@tanem/react-nprogress": "^5.0.38", "react-router-dom": "^6.10.0" } } diff --git a/src/app/layout/Layout.tsx b/src/app/layout/Layout.tsx index 8e174f7c..09f2b8e4 100644 --- a/src/app/layout/Layout.tsx +++ b/src/app/layout/Layout.tsx @@ -4,6 +4,7 @@ import { Outlet, useMatches } from "react-router-dom"; import { MatchRouteHandle } from "../routes/types"; import { Sidebar } from "../sidebar"; import css from "./Layout.module.css"; +import { RouteProgress } from "./progressbar/RouteProgressBar"; interface BaseLayoutProps { children: React.ReactNode; @@ -28,7 +29,11 @@ export const SidebarLayout = ({ }) => { return ( } + sidebar={ + + } > {children} @@ -43,6 +48,7 @@ export const Layout = () => { return ( + ); diff --git a/src/app/layout/progressbar/RouteProgressBar.module.css b/src/app/layout/progressbar/RouteProgressBar.module.css new file mode 100644 index 00000000..7fa99494 --- /dev/null +++ b/src/app/layout/progressbar/RouteProgressBar.module.css @@ -0,0 +1,23 @@ +.progressBarWrapper { + --animationDuration: 200ms; + transition: opacity var(--animationDuration) linear; + pointer-events: none; + opacity: 1; +} + +.progressBarWrapper.finished { + opacity: 0; +} + +.progressBar { + --progress: 0%; + margin-left: var(--progress); + transition: margin-left var(--animationDuration) linear; + background: #29d; + height: 2px; + left: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 1000; +} diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx new file mode 100644 index 00000000..8dd26939 --- /dev/null +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -0,0 +1,63 @@ +import { useNProgress } from "@tanem/react-nprogress"; +import cx from "classnames"; +import React from "react"; +import { useNavigation, useLocation } from "react-router-dom"; +import css from "./RouteProgressBar.module.css"; + +const LOADING_STATE = "loading"; +const ANIMATION_DURATION = 200; + +export const RouteProgress = () => { + const navigation = useNavigation(); + const location = useLocation(); + const isLoading = navigation.state === LOADING_STATE; + // key is used to reset the state of the progress when location changes + const [keyIncrement, setKeyIncrement] = React.useState(0); + + React.useEffect(() => { + // use timeout so animation has a chance to complete before resetting + const timeout = setTimeout( + () => setKeyIncrement((inc) => inc + 1), + ANIMATION_DURATION + ); + () => clearTimeout(timeout); + }, [location.key]); + + return ; +}; + +export const RouteProgressBar = ({ isLoading }) => { + const { isFinished, progress } = useNProgress({ + animationDuration: ANIMATION_DURATION, + isAnimating: isLoading, + }); + + return ( +
+ +
+ ); +}; + +const ProgressBar = ({ progress }: { progress: number }) => { + console.log({ progress }); + return ( +
+ ); +}; diff --git a/yarn.lock b/yarn.lock index f889b7af..f51ebd1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1068,6 +1068,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -2842,6 +2849,14 @@ dependencies: defer-to-connect "^1.0.1" +"@tanem/react-nprogress@^5.0.38": + version "5.0.38" + resolved "https://registry.yarnpkg.com/@tanem/react-nprogress/-/react-nprogress-5.0.38.tgz#348ced46a86a7d5bdc3f42afce394a2a51d74923" + integrity sha512-iyUzLfqiXa/EZ558mV8Wp/iqy7lKT9K+6sy5SF76sYD7mgZQ67VA8nUnsQXKFdEXoMdmfIUj7dbyDHN2OOI6BQ== + dependencies: + "@babel/runtime" "^7.21.5" + hoist-non-react-statics "^3.3.2" + "@testing-library/dom@^8.0.0": version "8.20.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6" @@ -6926,6 +6941,13 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hoopy@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" @@ -10281,7 +10303,7 @@ react-final-form@^6.5.3: dependencies: "@babel/runtime" "^7.15.4" -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== From 3e80c2a6ebe8716915f20840e3618a0fde06d70c Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 19 May 2023 11:17:16 +0200 Subject: [PATCH 2/6] docs(routeprogressbar): clarify key usage --- src/app/layout/progressbar/RouteProgressBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx index 8dd26939..fbad8332 100644 --- a/src/app/layout/progressbar/RouteProgressBar.tsx +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -12,6 +12,7 @@ export const RouteProgress = () => { const location = useLocation(); const isLoading = navigation.state === LOADING_STATE; // key is used to reset the state of the progress when location changes + // cannot use location.key directly since that will cancel the animation early (location.key and isLoading are updated at the same time) const [keyIncrement, setKeyIncrement] = React.useState(0); React.useEffect(() => { @@ -49,7 +50,6 @@ export const RouteProgressBar = ({ isLoading }) => { }; const ProgressBar = ({ progress }: { progress: number }) => { - console.log({ progress }); return (
Date: Sat, 20 May 2023 00:01:12 +0200 Subject: [PATCH 3/6] refactor: simplify key reset --- .../layout/progressbar/RouteProgressBar.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx index fbad8332..9c298c95 100644 --- a/src/app/layout/progressbar/RouteProgressBar.tsx +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -1,7 +1,7 @@ import { useNProgress } from "@tanem/react-nprogress"; import cx from "classnames"; -import React from "react"; -import { useNavigation, useLocation } from "react-router-dom"; +import React, { useEffect } from "react"; +import { useNavigation } from "react-router-dom"; import css from "./RouteProgressBar.module.css"; const LOADING_STATE = "loading"; @@ -9,22 +9,23 @@ const ANIMATION_DURATION = 200; export const RouteProgress = () => { const navigation = useNavigation(); - const location = useLocation(); const isLoading = navigation.state === LOADING_STATE; - // key is used to reset the state of the progress when location changes - // cannot use location.key directly since that will cancel the animation early (location.key and isLoading are updated at the same time) - const [keyIncrement, setKeyIncrement] = React.useState(0); + // key is used to reset the state of the progress when location changes. + // Cannot use navigation.location.key directly because navigation.location + // will be undefined when navigation is not in a loading state, causing the animation + // to cancel early because the key will change at the same time as isLoading. + // ref holds the previous version of this value, so we can use it to create a "sticky" key + const prevNavigationKeyRef = React.useRef(undefined); + const stickyKey = navigation.location?.key || prevNavigationKeyRef.current; - React.useEffect(() => { - // use timeout so animation has a chance to complete before resetting - const timeout = setTimeout( - () => setKeyIncrement((inc) => inc + 1), - ANIMATION_DURATION - ); - () => clearTimeout(timeout); - }, [location.key]); + useEffect(() => { + const currentNavigationKey = navigation.location?.key; + if (currentNavigationKey) { + prevNavigationKeyRef.current = currentNavigationKey; + } + }, [navigation.location?.key]); - return ; + return ; }; export const RouteProgressBar = ({ isLoading }) => { From d03e70fd982d952f19e6f81ffafaadddbe2a2196 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sat, 20 May 2023 15:46:36 +0200 Subject: [PATCH 4/6] refactor(routeprogress): some cleanup --- src/app/layout/progressbar/RouteProgressBar.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx index 9c298c95..c9293c8f 100644 --- a/src/app/layout/progressbar/RouteProgressBar.tsx +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -1,6 +1,6 @@ import { useNProgress } from "@tanem/react-nprogress"; import cx from "classnames"; -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { useNavigation } from "react-router-dom"; import css from "./RouteProgressBar.module.css"; @@ -15,15 +15,15 @@ export const RouteProgress = () => { // will be undefined when navigation is not in a loading state, causing the animation // to cancel early because the key will change at the same time as isLoading. // ref holds the previous version of this value, so we can use it to create a "sticky" key - const prevNavigationKeyRef = React.useRef(undefined); - const stickyKey = navigation.location?.key || prevNavigationKeyRef.current; + const prevNavigationKeyRef = useRef(undefined); + const currentNavigationKey = navigation.location?.key; + const stickyKey = currentNavigationKey || prevNavigationKeyRef.current; useEffect(() => { - const currentNavigationKey = navigation.location?.key; if (currentNavigationKey) { prevNavigationKeyRef.current = currentNavigationKey; } - }, [navigation.location?.key]); + }, [currentNavigationKey]); return ; }; From b2a809d0953328ddd4fb5b7eae8a9a73b7d7dd00 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sat, 20 May 2023 16:00:23 +0200 Subject: [PATCH 5/6] refactor(routeprogress): further simplify resetkey --- .../layout/progressbar/RouteProgressBar.tsx | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx index c9293c8f..aa9335fa 100644 --- a/src/app/layout/progressbar/RouteProgressBar.tsx +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -1,7 +1,7 @@ import { useNProgress } from "@tanem/react-nprogress"; import cx from "classnames"; -import React, { useEffect, useRef } from "react"; -import { useNavigation } from "react-router-dom"; +import React from "react"; +import { useNavigation, useLocation } from "react-router-dom"; import css from "./RouteProgressBar.module.css"; const LOADING_STATE = "loading"; @@ -9,23 +9,18 @@ const ANIMATION_DURATION = 200; export const RouteProgress = () => { const navigation = useNavigation(); + const { key: locationKey } = useLocation(); const isLoading = navigation.state === LOADING_STATE; // key is used to reset the state of the progress when location changes. // Cannot use navigation.location.key directly because navigation.location // will be undefined when navigation is not in a loading state, causing the animation // to cancel early because the key will change at the same time as isLoading. - // ref holds the previous version of this value, so we can use it to create a "sticky" key - const prevNavigationKeyRef = useRef(undefined); - const currentNavigationKey = navigation.location?.key; - const stickyKey = currentNavigationKey || prevNavigationKeyRef.current; + // Once navigation has finished, locationKey will be the same as previous (but now undefined) + // navigation.location.key + // so fallback to that once navigation has finished. + const resetKey = navigation.location?.key || locationKey; - useEffect(() => { - if (currentNavigationKey) { - prevNavigationKeyRef.current = currentNavigationKey; - } - }, [currentNavigationKey]); - - return ; + return ; }; export const RouteProgressBar = ({ isLoading }) => { From c1a035e205a3dc9d05e7e9cbb5673d87189abddd Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sun, 21 May 2023 20:42:41 +0200 Subject: [PATCH 6/6] docs: cleanup comment --- src/app/layout/progressbar/RouteProgressBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/layout/progressbar/RouteProgressBar.tsx b/src/app/layout/progressbar/RouteProgressBar.tsx index aa9335fa..4b109e67 100644 --- a/src/app/layout/progressbar/RouteProgressBar.tsx +++ b/src/app/layout/progressbar/RouteProgressBar.tsx @@ -16,7 +16,6 @@ export const RouteProgress = () => { // will be undefined when navigation is not in a loading state, causing the animation // to cancel early because the key will change at the same time as isLoading. // Once navigation has finished, locationKey will be the same as previous (but now undefined) - // navigation.location.key // so fallback to that once navigation has finished. const resetKey = navigation.location?.key || locationKey;