Refactor StoryProgressSegment to have better controlled animations
This commit is contained in:
parent
a973c27fd4
commit
74b90a5cdd
16 changed files with 171 additions and 147 deletions
65
ts/components/StoryProgressSegment.tsx
Normal file
65
ts/components/StoryProgressSegment.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
|
||||
export type StoryProgressSegmentProps = Readonly<{
|
||||
currentIndex: number;
|
||||
duration: number | null;
|
||||
index: number;
|
||||
playing: boolean;
|
||||
onFinish: () => void;
|
||||
}>;
|
||||
|
||||
function isValidDuration(duration: number | null): boolean {
|
||||
return duration != null && Number.isFinite(duration) && duration > 0;
|
||||
}
|
||||
|
||||
export const StoryProgressSegment = memo(function StoryProgressSegment({
|
||||
currentIndex,
|
||||
duration,
|
||||
index,
|
||||
playing,
|
||||
onFinish,
|
||||
}: StoryProgressSegmentProps): JSX.Element {
|
||||
const onFinishRef = useRef(onFinish);
|
||||
useEffect(() => {
|
||||
onFinishRef.current = onFinish;
|
||||
}, [onFinish]);
|
||||
|
||||
const [progressBarStyle] = useSpring(() => {
|
||||
return {
|
||||
// Override default value from `Globals` to ignore "Reduce Motion" setting.
|
||||
// This animation is important for progressing through stories and is minor
|
||||
// enough that it shouldn't cause issues for users with sensitivity to motion.
|
||||
skipAnimation: false,
|
||||
immediate: index !== currentIndex,
|
||||
// Pause while we are waiting for a valid duration
|
||||
pause: !playing || !isValidDuration(duration),
|
||||
from: { x: index < currentIndex ? 1 : 0 },
|
||||
to: { x: index <= currentIndex ? 1 : 0 },
|
||||
config: { duration: duration ?? Infinity },
|
||||
onRest: result => {
|
||||
if (index === currentIndex && result.finished) {
|
||||
onFinishRef.current();
|
||||
}
|
||||
},
|
||||
};
|
||||
}, [index, playing, currentIndex, duration]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="StoryProgressSegment"
|
||||
aria-current={index === currentIndex ? 'step' : false}
|
||||
>
|
||||
<animated.div
|
||||
className="StoryProgressSegment__bar"
|
||||
style={{
|
||||
transform: progressBarStyle.x.to(
|
||||
value => `translateX(${value * 100 - 100}%)`
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue