3b0dbd7a8b
Add a second tab in the node Icon picker to choose from the ~2250 brand icons hosted by homarr-labs/dashboard-icons (Plex, Sonarr, Home Assistant, etc.) served via jsDelivr CDN. The Generic tab keeps the existing lucide picker unchanged. Storage uses a 'brand:<slug>' prefix on custom_icon, so existing nodes referencing lucide keys keep working with zero migration. A new resolveCustomIcon helper returns a discriminated union (lucide | brand) and a NodeIcon component centralizes rendering for both kinds. Includes a manifest fetch script (scripts/fetch-dashboard-icons.mjs) and a checked-in dashboardIcons.json snapshot.
36 lines
1.2 KiB
TypeScript
36 lines
1.2 KiB
TypeScript
import { createElement } from 'react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
import { resolveCustomIcon, brandIconUrl, isBrandIconKey } from '@/utils/nodeIcons'
|
|
|
|
interface NodeIconProps {
|
|
/** Default icon for the node type (lucide). Used when no customIconKey or unknown key. */
|
|
typeIcon: LucideIcon
|
|
/** Optional override key. Legacy lucide keys or `brand:<slug>` for dashboard-icons. */
|
|
customIconKey?: string
|
|
size?: number
|
|
className?: string
|
|
/** Optional inline color (lucide only — ignored for brand icons). */
|
|
color?: string
|
|
}
|
|
|
|
export function NodeIcon({ typeIcon, customIconKey, size = 16, className, color }: NodeIconProps) {
|
|
const resolved = resolveCustomIcon(customIconKey)
|
|
if (resolved?.kind === 'brand') {
|
|
return (
|
|
<img
|
|
src={resolved.url}
|
|
alt={resolved.slug}
|
|
width={size}
|
|
height={size}
|
|
loading="lazy"
|
|
className={className}
|
|
style={{ width: size, height: size, objectFit: 'contain' }}
|
|
/>
|
|
)
|
|
}
|
|
const Icon = resolved?.kind === 'lucide' ? resolved.icon : typeIcon
|
|
return createElement(Icon, { size, className, color })
|
|
}
|
|
|
|
export { brandIconUrl, isBrandIconKey }
|