Skip to content

Commit ae13c2f

Browse files
committed
fix animation
1 parent 0b0bc97 commit ae13c2f

File tree

6 files changed

+133
-32
lines changed

6 files changed

+133
-32
lines changed

front/src/applications/stdcm/components/StdcmForm/StdcmConfig.tsx

+35-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect, useRef, useState } from 'react';
22

33
import { Button } from '@osrd-project/ui-core';
44
import { ArrowDown, ArrowUp } from '@osrd-project/ui-icons';
@@ -7,6 +7,7 @@ import { compact } from 'lodash';
77
import { useTranslation } from 'react-i18next';
88
import { useSelector } from 'react-redux';
99

10+
import { LOADER_HEIGHT } from 'applications/stdcm/consts';
1011
import { useOsrdConfActions, useOsrdConfSelectors } from 'common/osrdContext';
1112
import useInfraStatus from 'modules/pathfinding/hooks/useInfraStatus';
1213
import { Map } from 'modules/trainschedule/components/ManageTrainSchedule';
@@ -19,7 +20,7 @@ import StdcmDestination from './StdcmDestination';
1920
import StdcmLinkedPathSearch from './StdcmLinkedPathSearch';
2021
import StdcmOrigin from './StdcmOrigin';
2122
import useStaticPathfinding from '../../hooks/useStaticPathfinding';
22-
import type { StdcmConfigErrors } from '../../types';
23+
import type { LoaderStatus, StdcmConfigErrors } from '../../types';
2324
import StdcmSimulationParams from '../StdcmSimulationParams';
2425
import StdcmVias from './StdcmVias';
2526
import { ArrivalTimeTypes, StdcmConfigErrorTypes } from '../../types';
@@ -49,6 +50,7 @@ const StdcmConfig = ({
4950
cancelStdcmRequest,
5051
}: StdcmConfigProps) => {
5152
const { t } = useTranslation('stdcm');
53+
const launchButtonRef = useRef<HTMLDivElement>(null);
5254

5355
const { infra } = useInfraStatus();
5456
const dispatch = useAppDispatch();
@@ -77,9 +79,23 @@ const StdcmConfig = ({
7779
const pathfinding = useStaticPathfinding(infra);
7880

7981
const [formErrors, setFormErrors] = useState<StdcmConfigErrors>();
82+
const [loaderStatus, setLoaderStatus] = useState<LoaderStatus>();
8083

8184
const disabled = isPending || retainedSimulationIndex > -1;
8285

86+
useEffect(() => {
87+
if (!isPending || !launchButtonRef.current) {
88+
setLoaderStatus(undefined);
89+
} else {
90+
const { top } = launchButtonRef.current.getBoundingClientRect();
91+
const windowHeight = window.innerHeight;
92+
setLoaderStatus({
93+
status: windowHeight - top - 32 > LOADER_HEIGHT ? 'absolute' : 'sticky',
94+
firstLaunch: true,
95+
});
96+
}
97+
}, [isPending]);
98+
8399
const startSimulation = () => {
84100
const isPathfindingFailed = !!pathfinding && pathfinding.status !== 'success';
85101
const formErrorsStatus = checkStdcmConfigErrors(
@@ -176,15 +192,16 @@ const StdcmConfig = ({
176192
className={cx('stdcm-launch-request', {
177193
'wizz-effect': pathfinding?.status !== 'success' || formErrors,
178194
})}
195+
ref={launchButtonRef}
179196
>
180-
{showBtnToLaunchSimulation && (
181-
<Button
182-
data-testid="launch-simulation-button"
183-
className={cx({ 'fade-out': isPending })}
184-
label={t('simulation.getSimulation')}
185-
onClick={startSimulation}
186-
/>
187-
)}
197+
<Button
198+
data-testid="launch-simulation-button"
199+
className={cx({
200+
'fade-out': !showBtnToLaunchSimulation,
201+
})}
202+
label={t('simulation.getSimulation')}
203+
onClick={startSimulation}
204+
/>
188205
{formErrors && (
189206
<StdcmWarningBox
190207
errorInfos={formErrors}
@@ -194,7 +211,14 @@ const StdcmConfig = ({
194211
)}
195212
</div>
196213

197-
{isPending && <StdcmLoader cancelStdcmRequest={cancelStdcmRequest} />}
214+
{loaderStatus && (
215+
<StdcmLoader
216+
cancelStdcmRequest={cancelStdcmRequest}
217+
loaderStatus={loaderStatus}
218+
setLoaderStatus={setLoaderStatus}
219+
launchButtonRef={launchButtonRef}
220+
/>
221+
)}
198222
</div>
199223
</div>
200224
</div>

front/src/applications/stdcm/components/StdcmLoader.tsx

+52-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,69 @@
1-
import { useEffect, useRef, useState } from 'react';
1+
import { useEffect, useRef, type RefObject } from 'react';
22

33
import { Button } from '@osrd-project/ui-core';
44
import cx from 'classnames';
55
import { useTranslation } from 'react-i18next';
66

7-
const LOADER_OFFSET = 32;
7+
import { LOADER_HEIGHT } from '../consts';
8+
import type { LoaderStatus } from '../types';
9+
10+
const LOADER_BOTTOM_OFFSET = 32;
11+
const FORM_TOP_OFFSET = 120 + 68; // navbar + prev path heights
12+
const LAUNCH_BUTTON_HEIGHT = 40;
813

914
type StdcmLoaderProps = {
1015
cancelStdcmRequest: () => void;
16+
loaderStatus: LoaderStatus;
17+
setLoaderStatus: (loaderStatus?: LoaderStatus) => void;
18+
launchButtonRef: RefObject<HTMLDivElement>;
1119
};
1220

13-
const StdcmLoader = ({ cancelStdcmRequest }: StdcmLoaderProps) => {
21+
const StdcmLoader = ({
22+
cancelStdcmRequest,
23+
loaderStatus,
24+
setLoaderStatus,
25+
launchButtonRef,
26+
}: StdcmLoaderProps) => {
1427
const { t } = useTranslation('stdcm');
1528
const loaderRef = useRef<HTMLDivElement>(null);
16-
const [isPageScrolledBottom, setIsPageScrolledBottom] = useState(false);
1729

1830
useEffect(() => {
19-
// Depending on the scroll change the position of the loader between fixed and sticky
31+
// Depending on the scroll, change the position of the loader between fixed, sticky or absolute
2032
const handleScroll = () => {
21-
if (!loaderRef.current) return;
33+
if (!loaderRef.current || !launchButtonRef.current) return;
2234

2335
const { scrollY, innerHeight } = window;
2436

25-
if (scrollY > innerHeight - LOADER_OFFSET * 2) {
26-
setIsPageScrolledBottom(true);
37+
const isLoaderFitting =
38+
innerHeight - launchButtonRef.current.getBoundingClientRect().top >
39+
LOADER_HEIGHT + LOADER_BOTTOM_OFFSET;
40+
41+
if (launchButtonRef.current && isLoaderFitting) {
42+
setLoaderStatus({
43+
firstLaunch: false,
44+
status: 'absolute',
45+
});
2746
} else {
28-
setIsPageScrolledBottom(false);
47+
setLoaderStatus({
48+
firstLaunch: false,
49+
status: 'sticky',
50+
});
51+
}
52+
53+
const currentFormHeight = loaderRef.current.parentElement!.clientHeight;
54+
const shouldLoaderStickTop =
55+
scrollY > currentFormHeight + FORM_TOP_OFFSET - LAUNCH_BUTTON_HEIGHT;
56+
57+
if (shouldLoaderStickTop) {
58+
setLoaderStatus({
59+
firstLaunch: false,
60+
status: 'fixed',
61+
});
62+
} else if (loaderStatus.status === 'fixed') {
63+
setLoaderStatus({
64+
firstLaunch: false,
65+
status: 'absolute',
66+
});
2967
}
3068
};
3169

@@ -39,7 +77,11 @@ const StdcmLoader = ({ cancelStdcmRequest }: StdcmLoaderProps) => {
3977
<div
4078
ref={loaderRef}
4179
className={cx(`stdcm-loader`, {
42-
'scrolled-bottom': isPageScrolledBottom,
80+
fixed: loaderStatus.status === 'fixed',
81+
absolute: loaderStatus.status === 'absolute',
82+
'with-fade-in-animation': loaderStatus.status === 'absolute' && loaderStatus.firstLaunch,
83+
sticky: loaderStatus.status === 'sticky',
84+
'with-slide-animation': loaderStatus.status === 'sticky' && loaderStatus.firstLaunch,
4385
})}
4486
>
4587
<div className="stdcm-loader__wrapper">

front/src/applications/stdcm/consts.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ export const COMPOSITION_CODES = [
2222
];
2323

2424
export const DEFAULT_TOLERANCE = 1800; // 30min
25+
26+
export const LOADER_HEIGHT = 176;

front/src/applications/stdcm/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,8 @@ export type StdcmLinkedPathResult = {
200200
};
201201

202202
export type ExtremityPathStepType = 'origin' | 'destination';
203+
204+
export type LoaderStatus = {
205+
status: 'fixed' | 'sticky' | 'absolute';
206+
firstLaunch: boolean;
207+
};

front/src/styles/scss/applications/stdcm/_home.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
cursor: default !important;
1111

1212
.stdcm__body {
13-
padding: 32px 32px 48px;
13+
padding: 32px;
1414
background-color: rgb(239, 243, 245);
1515
display: flex;
1616
flex-direction: column;
@@ -123,7 +123,7 @@
123123

124124
&.fade-out {
125125
opacity: 0;
126-
transition: opacity 0.5s ease;
126+
transition: opacity 0.3s ease-out;
127127
}
128128
}
129129

front/src/styles/scss/applications/stdcm/_loader.scss

+37-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
.stdcm-loader {
2-
position: sticky;
3-
bottom: 32px;
4-
top: unset;
52
z-index: 2;
63
padding: 16px 16px 18px;
74
border-radius: 24px;
@@ -11,16 +8,45 @@
118
0 6px 21px -5px rgba(24, 68, 239, 0.26),
129
0 16px 30px -5px rgba(0, 0, 0, 0.16),
1310
0 3px 5px -2px rgba(0, 0, 0, 0.1);
14-
animation: slideUp 0.8s ease-in-out;
1511

16-
&.scrolled-bottom {
12+
&.sticky {
13+
position: fixed;
14+
bottom: 32px;
15+
top: unset;
16+
17+
&.with-slide-animation {
18+
animation: slideUp 0.8s ease-in-out;
19+
}
20+
}
21+
22+
&.absolute {
23+
position: absolute;
24+
bottom: calc(-176px + 40px); // height of the loader minus the height of the launch button
25+
26+
&.with-fade-in-animation {
27+
animation: fade-in 0.5s ease-in-out;
28+
}
29+
}
30+
31+
&.fixed {
1732
position: fixed;
1833
top: 32px;
1934
bottom: unset;
2035
}
2136

37+
@keyframes fade-in {
38+
0% {
39+
opacity: 0.1;
40+
}
41+
100% {
42+
opacity: 1;
43+
}
44+
}
45+
2246
@keyframes slideUp {
2347
0% {
48+
// Le translate ne doit se passer que si la popup ne rentre pas dans l'espace restant
49+
// La map ne doit jamais bouger quand le formulaire grandit ou retrécit ?
2450
transform: translateY(100%);
2551
opacity: 0;
2652
}
@@ -39,7 +65,7 @@
3965

4066
h2 {
4167
color: var(--info60);
42-
font-size: 24px;
68+
font-size: 1.5rem;
4369
font-weight: 400;
4470
line-height: 32px;
4571
margin-block: 15px 0;
@@ -58,16 +84,18 @@
5884
}
5985

6086
.stdcm-loader__cancel-btn {
61-
margin-block: 16px 23px;
87+
margin-block: 18px 23px;
6288

6389
button {
64-
font-size: 14px;
90+
font-size: 0.875rem;
91+
font-weight: 500;
6592
}
6693
}
6794

6895
.stdcm-loader__info-message {
6996
color: var(--grey80);
70-
font-size: 12px;
97+
font-size: 0.75rem;
98+
font-weight: 400;
7199
margin-block: 14px 0;
72100
text-align: center;
73101
}

0 commit comments

Comments
 (0)