@@ -19,6 +19,9 @@ type AvatarProps = PropsOfComponent<typeof Flex> & {
1919 showLoadingSpinner ?: boolean ;
2020} ;
2121
22+ const SPINNER_DELAY_MS = 150 ;
23+ const SPINNER_MIN_DURATION_MS = 400 ;
24+
2225export const Avatar = ( props : AvatarProps ) => {
2326 const {
2427 size = ( ) => 26 ,
@@ -34,14 +37,56 @@ export const Avatar = (props: AvatarProps) => {
3437 } = props ;
3538 const [ error , setError ] = React . useState ( false ) ;
3639 const [ loaded , setLoaded ] = React . useState ( false ) ;
40+ const [ spinnerVisible , setSpinnerVisible ] = React . useState ( false ) ;
41+ const spinnerShownAtRef = React . useRef < number | null > ( null ) ;
42+ const loadTimerRef = React . useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
3743
38- // Reset loaded state when imageUrl changes
3944 React . useEffect ( ( ) => {
4045 setLoaded ( false ) ;
4146 setError ( false ) ;
47+ setSpinnerVisible ( false ) ;
48+ spinnerShownAtRef . current = null ;
49+
50+ return ( ) => {
51+ if ( loadTimerRef . current ) {
52+ clearTimeout ( loadTimerRef . current ) ;
53+ loadTimerRef . current = null ;
54+ }
55+ } ;
4256 } , [ imageUrl ] ) ;
4357
44- const isLoading = showLoadingSpinner && imageUrl && ! loaded && ! error ;
58+ React . useEffect ( ( ) => {
59+ if ( ! showLoadingSpinner || ! imageUrl || loaded || error ) {
60+ return ;
61+ }
62+
63+ const timer = setTimeout ( ( ) => {
64+ setSpinnerVisible ( true ) ;
65+ spinnerShownAtRef . current = Date . now ( ) ;
66+ } , SPINNER_DELAY_MS ) ;
67+
68+ return ( ) => clearTimeout ( timer ) ;
69+ } , [ showLoadingSpinner , imageUrl , loaded , error ] ) ;
70+
71+ /**
72+ * Prevents the loading spinner from appearing and disappearing too quickly
73+ */
74+ const handleImageLoad = React . useCallback ( ( ) => {
75+ if ( spinnerShownAtRef . current ) {
76+ const elapsed = Date . now ( ) - spinnerShownAtRef . current ;
77+ const remaining = SPINNER_MIN_DURATION_MS - elapsed ;
78+ if ( remaining > 0 ) {
79+ loadTimerRef . current = setTimeout ( ( ) => {
80+ loadTimerRef . current = null ;
81+ setLoaded ( true ) ;
82+ } , remaining ) ;
83+ return ;
84+ }
85+ }
86+ setLoaded ( true ) ;
87+ } , [ ] ) ;
88+
89+ const isLoading = showLoadingSpinner && spinnerVisible && imageUrl && ! loaded && ! error ;
4590
4691 const ImgOrFallback =
4792 initials && ( ! imageUrl || error ) ? (
@@ -60,7 +105,7 @@ export const Avatar = (props: AvatarProps) => {
60105 transition : 'opacity 0.2s ease-in-out' ,
61106 } }
62107 onError = { ( ) => setError ( true ) }
63- onLoad = { ( ) => setLoaded ( true ) }
108+ onLoad = { handleImageLoad }
64109 size = { imageFetchSize }
65110 />
66111 ) ;
0 commit comments