feat: colour device-inventory role badge by node-type accent

The role/type badge on each inventory tile was always flat grey
(bg-[#21262d] text-muted-foreground), which read as disabled and gave no
visual cue about the device kind. Colour it with the same per-type accent
the node uses on the canvas — resolved through the active theme / style
section via resolveNodeColors — so a zigbee_coordinator, zwave_router, etc.
gets its style-section colour (translucent background + solid text).

Applies to every source (IP, zigbee, zwave) since all node types carry an
accent in the theme.

Test: role badge renders with the node-type accent colour, not the grey
muted class.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-07-03 11:07:48 +02:00
parent 3f6e9b00f7
commit 1de6e91ba3
2 changed files with 24 additions and 2 deletions
@@ -7,6 +7,8 @@ import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle } from '@
import { scanApi } from '@/api/client'
import { useCanvasStore } from '@/stores/canvasStore'
import { useDesignStore } from '@/stores/designStore'
import { useThemeStore } from '@/stores/themeStore'
import { resolveNodeColors } from '@/utils/nodeColors'
import { toast } from 'sonner'
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
import type { NodeType, ServiceInfo } from '@/types'
@@ -657,7 +659,12 @@ interface DeviceCardProps {
function DeviceCard({ device, selected, selectMode, highlighted, onClick, cardRef }: DeviceCardProps) {
const source = inferSource(device)
const Icon = TYPE_ICONS[device.suggested_type ?? 'generic'] ?? Circle
const roleType = (device.suggested_type ?? 'generic') as NodeType
const Icon = TYPE_ICONS[roleType] ?? Circle
const activeTheme = useThemeStore((s) => s.activeTheme)
// Colour the role badge with the same accent the node uses on the canvas
// (from the active theme / style section), instead of a flat grey.
const roleColor = resolveNodeColors({ type: roleType, custom_colors: undefined }, activeTheme).border
const label = deviceLabel(device)
const sourceColor = source === 'zigbee' ? '#00d4ff' : source === 'zwave' ? '#ff6e00' : '#a855f7'
const sourceLabel =
@@ -734,7 +741,10 @@ function DeviceCard({ device, selected, selectMode, highlighted, onClick, cardRe
{sourceLabel}
</span>
{device.suggested_type && (
<span className="text-[9px] font-mono px-1.5 py-0.5 rounded uppercase tracking-wider bg-[#21262d] text-muted-foreground">
<span
className="text-[9px] font-mono px-1.5 py-0.5 rounded uppercase tracking-wider"
style={{ background: `${roleColor}22`, color: roleColor }}
>
{device.suggested_type}
</span>
)}
@@ -153,6 +153,18 @@ describe('PendingDevicesModal', () => {
expect(screen.getByText('Z-WAVE')).toBeInTheDocument()
})
it('colours the role badge with the node-type accent, not flat grey', async () => {
mockPending.mockResolvedValue({ data: [DEVICE_ZWAVE] })
render(<PendingDevicesModal {...baseProps} />)
const card = await waitFor(() => screen.getByTestId('pending-card-dev-c'))
// zwave_router accent from the default theme = #e3b341 (amber), applied to
// both the text colour and a translucent background.
const badge = within(card).getByText('zwave_router')
// #e3b341 → rgb(227, 179, 65) once jsdom normalises the inline colour.
expect(badge).toHaveStyle({ color: 'rgb(227, 179, 65)' })
expect(badge.className).not.toContain('text-muted-foreground')
})
it('filters by source (zwave only)', async () => {
mockPending.mockResolvedValue({ data: [DEVICE_IP, DEVICE_ZWAVE] })
render(<PendingDevicesModal {...baseProps} />)