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:
co-authored by
Claude Opus 4.6
parent
be6465d2bc
commit
044bd0eec0
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user