Skip to content

Commit 5611bb5

Browse files
committed
Add delay to trigger loading spinner
1 parent 6589db1 commit 5611bb5

File tree

1 file changed

+34
-3
lines changed

1 file changed

+34
-3
lines changed

packages/ui/src/elements/Avatar.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2225
export const Avatar = (props: AvatarProps) => {
2326
const {
2427
size = () => 26,
@@ -34,14 +37,42 @@ 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);
3742

38-
// Reset loaded state when imageUrl changes
3943
React.useEffect(() => {
4044
setLoaded(false);
4145
setError(false);
46+
setSpinnerVisible(false);
47+
spinnerShownAtRef.current = null;
4248
}, [imageUrl]);
4349

44-
const isLoading = showLoadingSpinner && imageUrl && !loaded && !error;
50+
React.useEffect(() => {
51+
if (!showLoadingSpinner || !imageUrl || loaded || error) {
52+
return;
53+
}
54+
55+
const timer = setTimeout(() => {
56+
setSpinnerVisible(true);
57+
spinnerShownAtRef.current = Date.now();
58+
}, SPINNER_DELAY_MS);
59+
60+
return () => clearTimeout(timer);
61+
}, [showLoadingSpinner, imageUrl, loaded, error]);
62+
63+
const handleImageLoad = React.useCallback(() => {
64+
if (spinnerShownAtRef.current) {
65+
const elapsed = Date.now() - spinnerShownAtRef.current;
66+
const remaining = SPINNER_MIN_DURATION_MS - elapsed;
67+
if (remaining > 0) {
68+
const timer = setTimeout(() => setLoaded(true), remaining);
69+
return () => clearTimeout(timer);
70+
}
71+
}
72+
setLoaded(true);
73+
}, []);
74+
75+
const isLoading = showLoadingSpinner && spinnerVisible && imageUrl && !loaded && !error;
4576

4677
const ImgOrFallback =
4778
initials && (!imageUrl || error) ? (
@@ -60,7 +91,7 @@ export const Avatar = (props: AvatarProps) => {
6091
transition: 'opacity 0.2s ease-in-out',
6192
}}
6293
onError={() => setError(true)}
63-
onLoad={() => setLoaded(true)}
94+
onLoad={handleImageLoad}
6495
size={imageFetchSize}
6596
/>
6697
);

0 commit comments

Comments
 (0)