81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
import React from 'react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface AppIconProps {
|
|
name: string
|
|
iconUrl?: string | null
|
|
className?: string
|
|
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
}
|
|
|
|
export function AppIcon({ name, iconUrl, className, size = 'md' }: AppIconProps) {
|
|
const sizeClasses = {
|
|
sm: 'w-7 h-7 text-xs rounded-lg',
|
|
md: 'w-10 h-10 text-base rounded-xl',
|
|
lg: 'w-12 h-12 text-lg rounded-2xl',
|
|
xl: 'w-16 h-16 text-2xl rounded-3xl',
|
|
}[size]
|
|
|
|
// If iconUrl is an image (starts with http, / or data:image)
|
|
const isImage = iconUrl && (
|
|
iconUrl.startsWith('http://') ||
|
|
iconUrl.startsWith('https://') ||
|
|
iconUrl.startsWith('/') ||
|
|
iconUrl.startsWith('data:image/')
|
|
)
|
|
|
|
// Gradient generator from app name
|
|
const initial = name ? name.charAt(0).toUpperCase() : 'A'
|
|
const gradients = [
|
|
'from-emerald-500 to-teal-700',
|
|
'from-cyan-500 to-blue-700',
|
|
'from-indigo-500 to-purple-700',
|
|
'from-amber-500 to-orange-700',
|
|
'from-rose-500 to-pink-700',
|
|
'from-violet-500 to-fuchsia-700',
|
|
]
|
|
const gradientIndex = (name.charCodeAt(0) || 0) % gradients.length
|
|
const bgGradient = gradients[gradientIndex]
|
|
|
|
if (isImage) {
|
|
return (
|
|
<div className={cn('relative overflow-hidden bg-slate-900 border border-slate-800 shadow-md flex items-center justify-center flex-shrink-0', sizeClasses, className)}>
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={iconUrl}
|
|
alt={name}
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
// fallback if image fails to load
|
|
e.currentTarget.style.display = 'none'
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// If iconUrl is an emoji or custom text
|
|
if (iconUrl && iconUrl.trim()) {
|
|
return (
|
|
<div className={cn(
|
|
'bg-slate-900 border border-slate-800/80 shadow-md flex items-center justify-center flex-shrink-0 select-none',
|
|
sizeClasses,
|
|
className
|
|
)}>
|
|
<span className="leading-none">{iconUrl}</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Fallback initial with background gradient
|
|
return (
|
|
<div className={cn(
|
|
`bg-gradient-to-br ${bgGradient} p-[1px] shadow-lg flex items-center justify-center text-white font-extrabold flex-shrink-0 select-none`,
|
|
sizeClasses,
|
|
className
|
|
)}>
|
|
<span className="drop-shadow-sm">{initial}</span>
|
|
</div>
|
|
)
|
|
}
|