Service state colours, activity badge colours, readable named colours

This commit is contained in:
2026-08-29 17:00:34 -04:00
parent b26bbd0456
commit 6beb2afc09
5 changed files with 141 additions and 3 deletions

View File

@@ -19,10 +19,33 @@ export const PALETTE = [
'#6c757d', // gray
]
// Plugin panels emit colour NAMES, not hex - a services row says 'yellow', an
// activity row says 'gray'. Without this the brightness test cannot parse them
// and falls through to white, which is why an amber badge rendered white text
// on yellow and could not be read. Green, red and grey are dark enough that
// white worked by luck, so the fault only showed when amber was introduced.
const NAMED_COLORS = {
red: '#dc3545',
green: '#28a745',
yellow: '#ffc107',
amber: '#ffc107',
orange: '#fd7e14',
blue: '#4181ff',
grey: '#6c757d',
gray: '#6c757d',
black: '#000000',
white: '#ffffff',
}
export function resolveColor(color) {
if (!color || typeof color !== 'string') return color
return NAMED_COLORS[color.trim().toLowerCase()] || color
}
// Black or white text for a given background, by perceived brightness.
export function readableText(bg) {
if (!bg || typeof bg !== 'string') return '#ffffff'
let hex = bg.trim().replace('#', '')
let hex = resolveColor(bg).trim().replace('#', '')
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('')
if (hex.length !== 6) return '#ffffff'
const r = parseInt(hex.slice(0, 2), 16)
@@ -34,6 +57,8 @@ export function readableText(bg) {
// Style object for a badge/pill from a stored color (with a neutral fallback).
export function colorStyle(color, fallback = '#6c757d') {
const backgroundColor = color || fallback
// Resolve the name for the BACKGROUND too, so the swatch and the text
// contrast are computed from the same value.
const backgroundColor = resolveColor(color) || fallback
return { backgroundColor, color: readableText(backgroundColor) }
}

View File

@@ -0,0 +1,27 @@
import { describe, it, expect } from 'vitest'
import { readableText, colorStyle } from './colorStyle'
describe('named colours', () => {
// Panels emit names, not hex. Before these resolved, the brightness test
// could not parse them and returned white for everything - which is how an
// amber badge shipped as white-on-yellow.
it('gives yellow dark text, not white', () => {
expect(readableText('yellow')).toBe('#1a1a1a')
expect(colorStyle('yellow').color).toBe('#1a1a1a')
})
it('still gives dark colours white text', () => {
expect(readableText('red')).toBe('#ffffff')
expect(readableText('green')).toBe('#ffffff')
expect(readableText('gray')).toBe('#ffffff')
})
it('resolves the name for the background as well as the text', () => {
expect(colorStyle('yellow').backgroundColor).toBe('#ffc107')
})
it('leaves hex and unknown values alone', () => {
expect(colorStyle('#ffffff').color).toBe('#1a1a1a')
expect(colorStyle('rebeccapurple').backgroundColor).toBe('rebeccapurple')
})
})