feat: update sources bubble UI#377
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the source document display by introducing a more robust SourceBubble component that supports favicons, dynamic color mixing for backgrounds, and improved layout styling. It also adds new FileIcon and GlobeIcon components and includes safety checks for source document processing. Feedback was provided regarding the mixHex utility, which lacks validation for hex strings and could produce invalid CSS if named colors or variables are passed.
| const mixHex = (base: string, target: string, ratio: number): string => { | ||
| const parse = (hex: string) => { | ||
| const h = hex.replace('#', ''); | ||
| const full = | ||
| h.length === 3 | ||
| ? h | ||
| .split('') | ||
| .map((c) => c + c) | ||
| .join('') | ||
| : h; | ||
| return [parseInt(full.slice(0, 2), 16), parseInt(full.slice(2, 4), 16), parseInt(full.slice(4, 6), 16)]; | ||
| }; | ||
| try { | ||
| const [r1, g1, b1] = parse(base); | ||
| const [r2, g2, b2] = parse(target); | ||
| const mix = (a: number, b: number) => Math.round(a + (b - a) * ratio); | ||
| const toHex = (n: number) => n.toString(16).padStart(2, '0'); | ||
| return `#${toHex(mix(r1, r2))}${toHex(mix(g1, g2))}${toHex(mix(b1, b2))}`; | ||
| } catch { | ||
| return base; | ||
| } | ||
| }; |
There was a problem hiding this comment.
The mixHex function does not validate whether the input strings are valid hex colors. If props.backgroundColor is provided as a named color (e.g., "white") or a CSS variable, parseInt will return NaN, resulting in an invalid color string like "#NaNNaNNaN". Adding a regex check within the parse helper allows the try-catch block to correctly fall back to the base color for non-hex inputs.
const mixHex = (base: string, target: string, ratio: number): string => {
const parse = (hex: string) => {
const h = hex.replace('#', '');
if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(h)) throw new Error('Invalid hex');
const full =
h.length === 3
? h
.split('')
.map((c) => c + c)
.join('')
: h;
return [parseInt(full.slice(0, 2), 16), parseInt(full.slice(2, 4), 16), parseInt(full.slice(4, 6), 16)];
};
try {
const [r1, g1, b1] = parse(base);
const [r2, g2, b2] = parse(target);
const mix = (a: number, b: number) => Math.round(a + (b - a) * ratio);
const toHex = (n: number) => n.toString(16).padStart(2, '0');
return `#${toHex(mix(r1, r2))}${toHex(mix(g1, g2))}${toHex(mix(b1, b2))}`;
} catch {
return base;
}
};
Updated sources UI bubbles:
Prev:

After:

