61 lines
		
	
	
	
		
			1.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			61 lines
		
	
	
	
		
			1.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// 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 {
 | 
						|
      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>
 | 
						|
  );
 | 
						|
});
 |