Skip to content

Commit

Permalink
✨(frontend) teacher dashboard course loading
Browse files Browse the repository at this point in the history
add minimum loading time and transition when course apear
  • Loading branch information
rlecellier committed Apr 18, 2023
1 parent 5799052 commit a9e52c8
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 15 deletions.
25 changes: 25 additions & 0 deletions src/frontend/js/components/DashboardCourseList/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,34 @@ $r-course-glimpse-dashboard-gutter: rem-calc(30px) !default;
margin-bottom: 10px;
}

//
// Loading transition
//
.fade-in-enter {
opacity: 0;
}
.fade-in-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-in-exit {
display: none;
opacity: 0;
}
.fade-in-exit-active {
display: none;
opacity: 0;
}

//
// Course Glimpse in dashboards
//
&__placeholder {
display: flex;
justify-content: center;
align-items: center;
height: rem-calc(360px);
}

.dashboard {
&__course-glimpse-list {
Expand Down
44 changes: 31 additions & 13 deletions src/frontend/js/components/DashboardCourseList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useRef } from 'react';
import { CSSTransition } from 'react-transition-group';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import queryString from 'query-string';
Expand All @@ -7,6 +9,7 @@ import { useCourses, TeacherCourseSearchFilters } from 'hooks/useCourses';
import { getDashboardRoutePath } from 'widgets/Dashboard/utils/dashboardRoutes';
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherRouteMessages';
import context from 'utils/context';
import useIsLoading from 'hooks/useIsLoading';

const messages = defineMessages({
loading: {
Expand All @@ -30,6 +33,10 @@ const DashboardCourseList = ({ titleTranslated, filters }: DashboardCourseListPr
states: { fetching },
} = coursesResults;

const isLoading = useIsLoading([fetching], 300);

const fadeInNodeRef = useRef(null);

return (
<div className="dashboard-course-list">
{titleTranslated && (
Expand All @@ -41,20 +48,31 @@ const DashboardCourseList = ({ titleTranslated, filters }: DashboardCourseListPr
<h2 className="dashboard-course-list__title">{titleTranslated}</h2>
</Link>
)}
{fetching && (
<Spinner aria-labelledby="loading-courses-data">
<span id="loading-courses-data">
<FormattedMessage {...messages.loading} />
</span>
</Spinner>
)}
{!fetching && courses.length && (
<CourseGlimpseList
courses={getCourseGlimpsListProps(courses)}
context={context}
className="dashboard__course-glimpse-list"
/>
{isLoading && (
<div className="dashboard-course-list__placeholder">
<Spinner aria-labelledby="loading-courses-data">
<span id="loading-courses-data">
<FormattedMessage {...messages.loading} />
</span>
</Spinner>
</div>
)}

<CSSTransition
in={!isLoading && !!courses.length}
nodeRef={fadeInNodeRef}
timeout={300}
classNames="fade-in"
unmountOnExit
>
<div ref={fadeInNodeRef}>
<CourseGlimpseList
courses={getCourseGlimpsListProps(courses)}
context={context}
className="dashboard__course-glimpse-list"
/>
</div>
</CSSTransition>
</div>
);
};
Expand Down
43 changes: 43 additions & 0 deletions src/frontend/js/hooks/useIsLoading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from 'react';

// await new Promise(resolve => setTimeout(resolve, 1000))

const useIsLoading = (loadingStates: boolean[], delayInMillisecond = 500) => {
const [isLoading, setIsLoading] = useState<boolean>(!loadingStates.every((v) => v === false));
const [timerId, setTimerId] = useState<ReturnType<typeof setTimeout>>();
const [canStopLoading, setCanStopLoading] = useState(true);

const startLoading = () => {
setCanStopLoading(false);
setIsLoading(true);
setTimerId(
setTimeout(() => {
setCanStopLoading(true);
}, delayInMillisecond),
);
};

const stopLoading = () => {
setIsLoading(false);
clearTimeout(timerId);
setTimerId(undefined);
};

useEffect(() => {
const newIsLoading = !loadingStates.every((v) => v === false);
if (newIsLoading && !timerId) {
startLoading();
}
}, [loadingStates, timerId]);

useEffect(() => {
const newIsLoading = !loadingStates.every((v) => v === false);
if (isLoading && !newIsLoading && canStopLoading) {
stopLoading();
}
}, [loadingStates, isLoading, canStopLoading]);

return isLoading;
};

export default useIsLoading;
2 changes: 2 additions & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@types/react-autosuggest": "10.1.6",
"@types/react-dom": "18.0.11",
"@types/react-modal": "3.13.1",
"@types/react-transition-group": "4.4.5",
"@types/uuid": "9.0.1",
"@typescript-eslint/eslint-plugin": "5.58.0",
"@typescript-eslint/parser": "5.58.0",
Expand Down Expand Up @@ -122,6 +123,7 @@
"react-intl": "6.3.2",
"react-modal": "3.16.1",
"react-router-dom": "6.10.0",
"react-transition-group": "4.4.5",
"sass": "1.62.0",
"source-map-loader": "4.0.1",
"storybook": "7.0.3",
Expand Down
29 changes: 27 additions & 2 deletions src/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==

"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.7", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
Expand Down Expand Up @@ -3268,6 +3268,13 @@
dependencies:
"@types/react" "*"

"@types/[email protected]":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@16 || 17 || 18", "@types/[email protected]", "@types/react@>=16":
version "18.0.34"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.34.tgz#e553444a578f023e6e1ac499514688fb80b0a984"
Expand Down Expand Up @@ -5008,6 +5015,14 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"

dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"

dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
Expand Down Expand Up @@ -8676,7 +8691,7 @@ prompts@^2.0.1, prompts@^2.4.0:
kleur "^3.0.3"
sisteransi "^1.0.5"

prop-types@^15.7.2, prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
Expand Down Expand Up @@ -8981,6 +8996,16 @@ react-themeable@^1.1.0:
dependencies:
object-assign "^3.0.0"

[email protected]:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"

[email protected]:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Expand Down

0 comments on commit a9e52c8

Please sign in to comment.