Rewrite AnimatedNumber component with React hooks (#24559)
This commit is contained in:
		
							parent
							
								
									85b1b45820
								
							
						
					
					
						commit
						ab740f464a
					
				
					 2 changed files with 58 additions and 76 deletions
				
			
		|  | @ -1,76 +0,0 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ShortNumber from 'mastodon/components/short_number'; | ||||
| import TransitionMotion from 'react-motion/lib/TransitionMotion'; | ||||
| import spring from 'react-motion/lib/spring'; | ||||
| import { reduceMotion } from 'mastodon/initial_state'; | ||||
| 
 | ||||
| const obfuscatedCount = count => { | ||||
|   if (count < 0) { | ||||
|     return 0; | ||||
|   } else if (count <= 1) { | ||||
|     return count; | ||||
|   } else { | ||||
|     return '1+'; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export default class AnimatedNumber extends React.PureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     value: PropTypes.number.isRequired, | ||||
|     obfuscate: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     direction: 1, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.value > this.props.value) { | ||||
|       this.setState({ direction: 1 }); | ||||
|     } else if (nextProps.value < this.props.value) { | ||||
|       this.setState({ direction: -1 }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   willEnter = () => { | ||||
|     const { direction } = this.state; | ||||
| 
 | ||||
|     return { y: -1 * direction }; | ||||
|   }; | ||||
| 
 | ||||
|   willLeave = () => { | ||||
|     const { direction } = this.state; | ||||
| 
 | ||||
|     return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) }; | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { value, obfuscate } = this.props; | ||||
|     const { direction } = this.state; | ||||
| 
 | ||||
|     if (reduceMotion) { | ||||
|       return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />; | ||||
|     } | ||||
| 
 | ||||
|     const styles = [{ | ||||
|       key: `${value}`, | ||||
|       data: value, | ||||
|       style: { y: spring(0, { damping: 35, stiffness: 400 }) }, | ||||
|     }]; | ||||
| 
 | ||||
|     return ( | ||||
|       <TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}> | ||||
|         {items => ( | ||||
|           <span className='animated-number'> | ||||
|             {items.map(({ key, data, style }) => ( | ||||
|               <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span> | ||||
|             ))} | ||||
|           </span> | ||||
|         )} | ||||
|       </TransitionMotion> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										58
									
								
								app/javascript/mastodon/components/animated_number.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/javascript/mastodon/components/animated_number.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| import React, { useCallback, useState } from 'react'; | ||||
| import ShortNumber from './short_number'; | ||||
| import { TransitionMotion, spring } from 'react-motion'; | ||||
| import { reduceMotion } from '../initial_state'; | ||||
| 
 | ||||
| const obfuscatedCount = (count: number) => { | ||||
|   if (count < 0) { | ||||
|     return 0; | ||||
|   } else if (count <= 1) { | ||||
|     return count; | ||||
|   } else { | ||||
|     return '1+'; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| type Props = { | ||||
|   value: number; | ||||
|   obfuscate?: boolean; | ||||
| } | ||||
| export const AnimatedNumber: React.FC<Props> = ({ | ||||
|   value, | ||||
|   obfuscate, | ||||
| })=> { | ||||
|   const [previousValue, setPreviousValue] = useState(value); | ||||
|   const [direction, setDirection] = useState<1|-1>(1); | ||||
| 
 | ||||
|   if (previousValue !== value) { | ||||
|     setPreviousValue(value); | ||||
|     setDirection(value > previousValue ? 1 : -1); | ||||
|   } | ||||
| 
 | ||||
|   const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]); | ||||
|   const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]); | ||||
| 
 | ||||
|   if (reduceMotion) { | ||||
|     return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />; | ||||
|   } | ||||
| 
 | ||||
|   const styles = [{ | ||||
|     key: `${value}`, | ||||
|     data: value, | ||||
|     style: { y: spring(0, { damping: 35, stiffness: 400 }) }, | ||||
|   }]; | ||||
| 
 | ||||
|   return ( | ||||
|     <TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}> | ||||
|       {items => ( | ||||
|         <span className='animated-number'> | ||||
|           {items.map(({ key, data, style }) => ( | ||||
|             <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span> | ||||
|           ))} | ||||
|         </span> | ||||
|       )} | ||||
|     </TransitionMotion> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default AnimatedNumber; | ||||
		Reference in a new issue