fix(issues): use muted label colors across issue list and detail panel

Replace full-saturation label backgrounds with low-opacity tinted
style (12% bg, 25% border, full color text) for better readability
on dark themes. Also ensure issue labels are always available for
color lookup in LabelManager.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Sondre Engebråten
2026-02-14 00:14:13 +01:00
co-authored by Claude Opus 4.6
parent be6465d2bc
commit 044bd0eec0
3 changed files with 13 additions and 38 deletions
@@ -192,27 +192,19 @@ export function IssueDetail({
{onAddLabels && onRemoveLabels && repoLabels ? (
<LabelManager
currentLabels={issue.labels.map(l => l.name)}
repoLabels={repoLabels}
repoLabels={[...repoLabels, ...issue.labels.filter(il => !repoLabels.some(rl => rl.name === il.name))]}
onAddLabel={(label) => onAddLabels([label])}
onRemoveLabel={(label) => onRemoveLabels([label])}
/>
) : issue.labels.length > 0 ? (
<div className="flex flex-wrap gap-2">
{issue.labels.map((label) => {
const bg = `#${label.color}`;
const lum = (() => {
const r = Number.parseInt(label.color.substring(0, 2), 16);
const g = Number.parseInt(label.color.substring(2, 4), 16);
const b = Number.parseInt(label.color.substring(4, 6), 16);
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
})();
const textColor = lum > 0.5 ? '#24292f' : '#ffffff';
const color = `#${label.color}`;
return (
<Badge
key={label.id}
variant="outline"
className="border-transparent"
style={{ backgroundColor: bg, borderColor: bg, color: textColor }}
style={{ backgroundColor: `${color}20`, borderColor: `${color}40`, color }}
>
{label.name}
</Badge>
@@ -98,16 +98,12 @@ export const IssueListItem = memo(function IssueListItem({
{issue.labels.length > 0 && (
<div className="flex items-center gap-1 overflow-hidden">
{issue.labels.slice(0, 3).map((label) => {
const bg = `#${label.color}`;
const r = Number.parseInt(label.color.substring(0, 2), 16);
const g = Number.parseInt(label.color.substring(2, 4), 16);
const b = Number.parseInt(label.color.substring(4, 6), 16);
const textColor = (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5 ? '#24292f' : '#ffffff';
const color = `#${label.color}`;
return (
<span
key={label.id}
className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium leading-none"
style={{ backgroundColor: bg, color: textColor }}
className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium leading-none border"
style={{ backgroundColor: `${color}20`, borderColor: `${color}40`, color }}
>
{label.name}
</span>
@@ -4,18 +4,6 @@ import { Plus, X, Check } from 'lucide-react';
import { Button } from '../../ui/button';
import { Badge } from '../../ui/badge';
/**
* Returns white or dark text color for readable contrast against a hex background.
* Uses perceived luminance formula (ITU-R BT.601).
*/
function getContrastTextColor(hexColor: string): string {
const hex = hexColor.replace('#', '');
const r = Number.parseInt(hex.substring(0, 2), 16);
const g = Number.parseInt(hex.substring(2, 4), 16);
const b = Number.parseInt(hex.substring(4, 6), 16);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? '#24292f' : '#ffffff';
}
interface LabelManagerProps {
currentLabels: string[];
@@ -70,17 +58,16 @@ export function LabelManager({
<div className="flex flex-wrap gap-1.5">
{currentLabels.map((label) => {
const repoLabel = repoLabels.find((rl) => rl.name === label);
const bgColor = repoLabel ? `#${repoLabel.color}` : undefined;
const textColor = repoLabel ? getContrastTextColor(repoLabel.color) : undefined;
const labelColor = repoLabel ? `#${repoLabel.color}` : undefined;
return (
<Badge
key={label}
variant="outline"
className="gap-1 text-xs border-transparent"
style={bgColor ? {
backgroundColor: bgColor,
borderColor: bgColor,
color: textColor,
className="gap-1 text-xs"
style={labelColor ? {
backgroundColor: `${labelColor}20`,
borderColor: `${labelColor}40`,
color: labelColor,
} : undefined}
>
{label}
@@ -90,7 +77,7 @@ export function LabelManager({
className="ml-0.5 opacity-70 hover:opacity-100"
onClick={() => onRemoveLabel(label)}
aria-label={`Remove label ${label}`}
style={textColor ? { color: textColor } : undefined}
style={labelColor ? { color: labelColor } : undefined}
>
<X className="h-3 w-3" />
</button>