feat(icons): add brand icon picker from dashboard-icons
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.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
BRAND_ICON_PREFIX,
|
||||
isBrandIconKey,
|
||||
brandIconSlug,
|
||||
brandIconUrl,
|
||||
resolveCustomIcon,
|
||||
ICON_MAP,
|
||||
} from '../nodeIcons'
|
||||
|
||||
describe('brand icon helpers', () => {
|
||||
it('isBrandIconKey returns true only for prefixed keys', () => {
|
||||
expect(isBrandIconKey('brand:plex')).toBe(true)
|
||||
expect(isBrandIconKey('plex')).toBe(false)
|
||||
expect(isBrandIconKey('plug')).toBe(false)
|
||||
expect(isBrandIconKey(undefined)).toBe(false)
|
||||
expect(isBrandIconKey(null)).toBe(false)
|
||||
expect(isBrandIconKey('')).toBe(false)
|
||||
})
|
||||
|
||||
it('brandIconSlug strips the prefix', () => {
|
||||
expect(brandIconSlug(`${BRAND_ICON_PREFIX}home-assistant`)).toBe('home-assistant')
|
||||
})
|
||||
|
||||
it('brandIconUrl points at jsDelivr CDN', () => {
|
||||
expect(brandIconUrl('plex')).toBe(
|
||||
'https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveCustomIcon', () => {
|
||||
it('returns null when no key', () => {
|
||||
expect(resolveCustomIcon(undefined)).toBeNull()
|
||||
expect(resolveCustomIcon('')).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves legacy lucide keys', () => {
|
||||
const r = resolveCustomIcon('plug')
|
||||
expect(r?.kind).toBe('lucide')
|
||||
if (r?.kind === 'lucide') expect(r.icon).toBe(ICON_MAP['plug'])
|
||||
})
|
||||
|
||||
it('resolves brand-prefixed keys to a CDN url', () => {
|
||||
const r = resolveCustomIcon('brand:plex')
|
||||
expect(r?.kind).toBe('brand')
|
||||
if (r?.kind === 'brand') {
|
||||
expect(r.slug).toBe('plex')
|
||||
expect(r.url).toContain('cdn.jsdelivr.net')
|
||||
expect(r.url).toContain('/plex.svg')
|
||||
}
|
||||
})
|
||||
|
||||
it('returns null for unknown legacy key', () => {
|
||||
expect(resolveCustomIcon('definitely-not-a-known-icon-key')).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user