54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
import { useState } from 'react'
|
|
|
|
interface RepoAvatarProps {
|
|
ownerName: string
|
|
name: string
|
|
avatarUrl?: string
|
|
size?: number
|
|
className?: string
|
|
}
|
|
|
|
// Consistent color per repo name (not random on each render)
|
|
function hashColor(name: string): string {
|
|
const palette = [
|
|
'var(--c-brand)', 'var(--c-success)', '#FF5630', 'var(--c-warning)',
|
|
'#6554C0', '#00B8D9', '#36B37E', 'var(--c-text)',
|
|
]
|
|
let hash = 0
|
|
for (let i = 0; i < name.length; i++) hash = (hash * 31 + name.charCodeAt(i)) | 0
|
|
return palette[Math.abs(hash) % palette.length]
|
|
}
|
|
|
|
export function RepoAvatar({ ownerName, name, avatarUrl, size = 32, className = '' }: RepoAvatarProps) {
|
|
const [imgError, setImgError] = useState(false)
|
|
|
|
// Use provided avatarUrl; fall back to the API endpoint (which 404s if not set → onError fires)
|
|
const src = avatarUrl || `/api/v1/repos/${ownerName}/${name}/avatar`
|
|
const letter = (name[0] ?? '?').toUpperCase()
|
|
const bg = hashColor(name)
|
|
const fontSize = Math.round(size * 0.42)
|
|
|
|
const style = { width: size, height: size, minWidth: size, minHeight: size, fontSize }
|
|
|
|
if (!imgError) {
|
|
return (
|
|
<img
|
|
src={src}
|
|
alt={name}
|
|
style={style}
|
|
className={`rounded object-cover shrink-0 ${className}`}
|
|
onError={() => setImgError(true)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={{ ...style, backgroundColor: bg }}
|
|
className={`rounded flex items-center justify-center text-white font-bold select-none shrink-0 ${className}`}
|
|
>
|
|
{letter}
|
|
</div>
|
|
)
|
|
}
|