feat: hashColor utility with FNV-1a for deterministic badge/avatar colors
This commit is contained in:
50
src/design-system/utils/hashColor.test.ts
Normal file
50
src/design-system/utils/hashColor.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { hashColor, fnv1a } from './hashColor'
|
||||
|
||||
describe('fnv1a', () => {
|
||||
it('returns a number for any string', () => {
|
||||
expect(typeof fnv1a('test')).toBe('number')
|
||||
})
|
||||
|
||||
it('returns consistent hash for same input', () => {
|
||||
expect(fnv1a('order-service')).toBe(fnv1a('order-service'))
|
||||
})
|
||||
|
||||
it('returns different hashes for different inputs', () => {
|
||||
expect(fnv1a('order-service')).not.toBe(fnv1a('payment-service'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('hashColor', () => {
|
||||
it('returns bg, text, and border properties', () => {
|
||||
const result = hashColor('test')
|
||||
expect(result).toHaveProperty('bg')
|
||||
expect(result).toHaveProperty('text')
|
||||
expect(result).toHaveProperty('border')
|
||||
})
|
||||
|
||||
it('returns HSL color strings', () => {
|
||||
const result = hashColor('order-service')
|
||||
expect(result.bg).toMatch(/^hsl\(\d+,\s*\d+%,\s*\d+%\)$/)
|
||||
expect(result.text).toMatch(/^hsl\(\d+,\s*\d+%,\s*\d+%\)$/)
|
||||
expect(result.border).toMatch(/^hsl\(\d+,\s*\d+%,\s*\d+%\)$/)
|
||||
})
|
||||
|
||||
it('returns consistent colors for same name', () => {
|
||||
const a = hashColor('VIEWER')
|
||||
const b = hashColor('VIEWER')
|
||||
expect(a).toEqual(b)
|
||||
})
|
||||
|
||||
it('returns different hues for different names', () => {
|
||||
const a = hashColor('VIEWER')
|
||||
const b = hashColor('OPERATOR')
|
||||
expect(a.bg).not.toBe(b.bg)
|
||||
})
|
||||
|
||||
it('accepts dark mode parameter', () => {
|
||||
const light = hashColor('test', 'light')
|
||||
const dark = hashColor('test', 'dark')
|
||||
expect(light.bg).not.toBe(dark.bg)
|
||||
})
|
||||
})
|
||||
32
src/design-system/utils/hashColor.ts
Normal file
32
src/design-system/utils/hashColor.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
const FNV_OFFSET = 2166136261
|
||||
const FNV_PRIME = 16777619
|
||||
|
||||
export function fnv1a(str: string): number {
|
||||
let hash = FNV_OFFSET
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash ^= str.charCodeAt(i)
|
||||
hash = Math.imul(hash, FNV_PRIME)
|
||||
}
|
||||
return hash >>> 0
|
||||
}
|
||||
|
||||
export function hashColor(
|
||||
name: string,
|
||||
theme: 'light' | 'dark' = 'light',
|
||||
): { bg: string; text: string; border: string } {
|
||||
const hue = fnv1a(name) % 360
|
||||
|
||||
if (theme === 'dark') {
|
||||
return {
|
||||
bg: `hsl(${hue}, 35%, 20%)`,
|
||||
text: `hsl(${hue}, 45%, 75%)`,
|
||||
border: `hsl(${hue}, 30%, 30%)`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bg: `hsl(${hue}, 45%, 92%)`,
|
||||
text: `hsl(${hue}, 55%, 35%)`,
|
||||
border: `hsl(${hue}, 35%, 82%)`,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user