All files / src/components/channels ChannelLogo.tsx

92.3% Statements 12/13
90% Branches 9/10
100% Functions 3/3
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69                    8x             8x   668x 668x 668x 4x 4x 4x   668x                         8x             567x 567x                                          
// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
 
import { useState, type ReactNode } from "react";
import { cn } from "@/lib/utils";
 
/**
 * Logo URLs that failed to load. It lives at module level because the virtual
 * lists remount cards while scrolling, and each remount would retry a dead URL.
 */
const brokenImageUrls = new Set<string>();
 
/**
 * Whether the image at url has failed before, and a callback for its onError.
 * The answer follows url, so a card that is given a new logo tries it rather
 * than keeping the old one's failure.
 */
export const useImageFailed = (url?: string): [boolean, () => void] => {
	// Only re-renders after a failure. The set is the record of what failed.
	const [, setFailedUrl] = useState<string>();
	const failed = !!url && brokenImageUrls.has(url);
	const markFailed = () => {
		Iif (!url) return;
		brokenImageUrls.add(url);
		setFailedUrl(url);
	};
	return [failed, markFailed];
};
 
interface ChannelLogoProps {
	url?: string;
	/** Size, shape and background of the logo box. */
	className?: string;
	/** Shown when there is no URL or the image failed to load. */
	fallback: ReactNode;
	imgClassName?: string;
	alt?: string;
}
 
export const ChannelLogo = ({
	url,
	className,
	fallback,
	imgClassName = "object-contain",
	alt = "",
}: ChannelLogoProps) => {
	const [failed, markFailed] = useImageFailed(url);
	return (
		<div
			className={cn(
				"relative bg-secondary flex items-center justify-center overflow-hidden shrink-0",
				className
			)}
		>
			{url && !failed ? (
				<img
					src={url}
					alt={alt}
					className={cn("h-full w-full", imgClassName)}
					loading="lazy"
					onError={markFailed}
				/>
			) : (
				fallback
			)}
		</div>
	);
};