import { useEffect, useMemo, useRef } from 'react'
import { StyleSheet, ViewStyle, View } from 'react-native'
import { MotiTransitionProp, useDynamicAnimation, motify } from '../../core'
const MotiView = motify(View)()
export type MotiProgressBarProps = {
/**
* Number between 0-1
*
* @requires
*/
progress: number
/**
* Height of the bar in pixels.
*
* @default `12`
*/
height?: number
color?: string
containerColor?: string
/**
* Container border radius
*/
borderRadius?: number
containerStyle?: ViewStyle
style?: ViewStyle
/**
* Transition for the animation. See the `transition` docs from Moti's `` to see how to use it.
*
* @default
* ```jsx
* {
* type: 'timing',
* duration: 300,
* }
* ```
*/
transition?: MotiTransitionProp
/**
* @default `dark`
*/
colorMode?: 'dark' | 'light'
/**
* @default false
*
* When `false`, Moti will warn you if you're re-rendering this component too often.
*/
silenceRenderWarnings?: boolean
}
export function MotiProgressBar({
height = 12,
progress = 0,
borderRadius = height / 2,
style,
colorMode = 'dark',
containerColor = colorMode === 'dark' ? '#333' : '#eee',
containerStyle,
color = '#00C806',
transition = {
type: 'timing',
duration: 200,
},
silenceRenderWarnings = false,
}: MotiProgressBarProps) {
const barState = useDynamicAnimation(() => ({
translateX: '-100%',
}))
// TODO this won't be necessary once Moti memoizes props for you.
if (!transition) {
console.error(
`[moti] "transition" prop must be undefined or a Moti transition object, but it got this type instead: ${typeof transition}.`,
transition
)
}
const transitionString = JSON.stringify(transition)
const _transition = useMemo(
() => JSON.parse(transitionString),
[transitionString]
)
const outerStyle = useMemo(
() => [
styles.container,
containerStyle,
{ height, borderRadius, backgroundColor: containerColor },
],
[borderRadius, containerColor, containerStyle, height]
)
const progressStyle = useMemo(
() => [style, styles.bar, { borderRadius, backgroundColor: color }],
[borderRadius, color, style]
)
useEffect(
function animateOnProgressChange() {
const percent = Math.round(progress * 100)
const translateX = `${percent - 100}%`
if (barState.current?.translateX !== translateX) {
barState.animateTo((current) => ({ ...current, translateX }))
}
},
[barState, progress]
)
const unnecessaryRerenders = useRef({
containerStyle: {
previousValue: containerStyle,
changes: 0,
},
style: {
previousValue: style,
changes: 0,
},
})
useEffect(
function checkUnnecessaryRerenders() {
const isDev = typeof __DEV__ === 'undefined' || __DEV__
if (silenceRenderWarnings || !isDev) {
return
}
if (
containerStyle !==
unnecessaryRerenders.current.containerStyle.previousValue
) {
unnecessaryRerenders.current.containerStyle.changes += 1
}
if (style !== unnecessaryRerenders.current.style.previousValue) {
unnecessaryRerenders.current.style.changes += 1
}
const warningProps: { changes: number; prop: string }[] = []
Object.entries(unnecessaryRerenders.current).forEach(
([prop, { changes }]) => {
if (changes > 5) {
warningProps.push({ prop, changes })
}
}
)
if (warningProps.length) {
console.warn(
`[moti] is re-rendering often due to these props: ${warningProps
.map(
(warning) => `"${warning.prop}: ${warning.changes} re-renders"`
)
.join(
', '
)}. This can reduce animation performance. Please memoize these props with useMemo, or create them outside of render code.`,
`If you are intentionally re-rendering this often, for some reason, pass silenceRenderWarnings={true} on this component.`
)
}
},
[containerStyle, silenceRenderWarnings, style]
)
return useMemo(
() => (
),
[_transition, barState, outerStyle, progressStyle]
)
}
const styles = StyleSheet.create({
container: {
width: '100%',
overflow: 'hidden',
},
bar: {
width: '100%',
height: '100%',
},
})