Skip to content

Commit badd31d

Browse files
arpandhakalclaude
andcommitted
fix(OUT-3261): use dark text variants for colored multiselect chips
Named colors (amber, cyan, teal, etc.) were used directly as chip text color, making them unreadable against the light chip background. Added a color map with dark text-safe variants and a getChipColors utility that returns proper background, border, text, and icon colors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f6bf0ab commit badd31d

2 files changed

Lines changed: 88 additions & 38 deletions

File tree

src/components/multiSelect/MultiSelect.tsx

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Autocomplete, Chip } from '@mui/material';
22
import { StyledTextInput } from '../styled/StyledTextInput';
33
import { ClearOutlined, FiberManualRecord } from '@mui/icons-material';
4-
import { updateColor } from '@/utils/updateColor';
4+
import { getChipColors } from '@/utils/updateColor';
55

66
interface IMultiSelect<T extends object> {
77
data: T[];
@@ -27,31 +27,34 @@ export const MultiSelect = <T extends object>({ data, nameField, value, getSelec
2727
disabled={disabled}
2828
renderInput={(params) => <StyledTextInput {...params} />}
2929
renderTags={(value: T[], getTagProps) =>
30-
value.map((option: any, index: number) => (
31-
<Chip
32-
variant="outlined"
33-
label={nameField(option)}
34-
{...getTagProps({ index })}
35-
key={index}
36-
deleteIcon={<ClearOutlined />}
37-
avatar={<FiberManualRecord fontSize="small" />}
38-
sx={{
39-
'&.MuiChip-root': {
40-
borderColor: updateColor(option.color, 0.3),
41-
border: '2px solid',
42-
background: updateColor(option.color, 0.1),
43-
color: option.color,
44-
fontWeight: 500,
45-
},
46-
'& .MuiChip-deleteIcon': {
47-
color: option.color,
48-
},
49-
'& .MuiChip-avatar': {
50-
color: option.color,
51-
},
52-
}}
53-
/>
54-
))
30+
value.map((option: any, index: number) => {
31+
const chipColors = getChipColors(option.color);
32+
return (
33+
<Chip
34+
variant="outlined"
35+
label={nameField(option)}
36+
{...getTagProps({ index })}
37+
key={index}
38+
deleteIcon={<ClearOutlined />}
39+
avatar={<FiberManualRecord fontSize="small" />}
40+
sx={{
41+
'&.MuiChip-root': {
42+
borderColor: chipColors.border,
43+
border: '2px solid',
44+
background: chipColors.background,
45+
color: chipColors.text,
46+
fontWeight: 500,
47+
},
48+
'& .MuiChip-deleteIcon': {
49+
color: chipColors.icon,
50+
},
51+
'& .MuiChip-avatar': {
52+
color: chipColors.icon,
53+
},
54+
}}
55+
/>
56+
);
57+
})
5558
}
5659
sx={{
5760
'& .MuiOutlinedInput-root': {

src/utils/updateColor.ts

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,65 @@
1-
export function updateColor(rgbaColor: any, newOpacity: number) {
2-
// Parse the input RGBA color string
3-
const colorRegex = /^rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)$/;
4-
const match = rgbaColor?.match(colorRegex);
1+
// Map of named colors to their RGB values and a darker text-safe variant
2+
const COLOR_MAP: Record<string, { r: number; g: number; b: number; dark: string }> = {
3+
red: { r: 239, g: 68, b: 68, dark: '#991b1b' },
4+
orange: { r: 249, g: 115, b: 22, dark: '#9a3412' },
5+
amber: { r: 245, g: 158, b: 11, dark: '#92400e' },
6+
yellow: { r: 234, g: 179, b: 8, dark: '#854d0e' },
7+
lime: { r: 132, g: 204, b: 22, dark: '#3f6212' },
8+
green: { r: 34, g: 197, b: 94, dark: '#166534' },
9+
emerald: { r: 16, g: 185, b: 129, dark: '#065f46' },
10+
teal: { r: 20, g: 184, b: 166, dark: '#115e59' },
11+
cyan: { r: 6, g: 182, b: 212, dark: '#155e75' },
12+
sky: { r: 14, g: 165, b: 233, dark: '#075985' },
13+
blue: { r: 59, g: 130, b: 246, dark: '#1e40af' },
14+
indigo: { r: 99, g: 102, b: 241, dark: '#3730a3' },
15+
violet: { r: 139, g: 92, b: 246, dark: '#5b21b6' },
16+
purple: { r: 168, g: 85, b: 247, dark: '#6b21a8' },
17+
fuchsia: { r: 217, g: 70, b: 239, dark: '#86198f' },
18+
pink: { r: 236, g: 72, b: 153, dark: '#9d174d' },
19+
rose: { r: 244, g: 63, b: 94, dark: '#9f1239' },
20+
gray: { r: 107, g: 114, b: 128, dark: '#374151' },
21+
slate: { r: 100, g: 116, b: 139, dark: '#334155' },
22+
zinc: { r: 113, g: 113, b: 122, dark: '#3f3f46' },
23+
neutral: { r: 115, g: 115, b: 115, dark: '#404040' },
24+
stone: { r: 120, g: 113, b: 108, dark: '#44403c' },
25+
};
526

6-
if (!rgbaColor || !match) {
7-
// Invalid input format, return the original color
8-
return rgbaColor;
27+
function getColorEntry(color: any): { r: number; g: number; b: number; dark: string } | null {
28+
if (!color || typeof color !== 'string') return null;
29+
30+
const named = COLOR_MAP[color.toLowerCase()];
31+
if (named) return named;
32+
33+
// Fallback: try rgba format
34+
const rgbaMatch = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)$/);
35+
if (rgbaMatch) {
36+
const r = Number(rgbaMatch[1]);
37+
const g = Number(rgbaMatch[2]);
38+
const b = Number(rgbaMatch[3]);
39+
// Darken by 40% for text
40+
const dark = `rgb(${Math.round(r * 0.5)}, ${Math.round(g * 0.5)}, ${Math.round(b * 0.5)})`;
41+
return { r, g, b, dark };
942
}
1043

11-
// Extract color components
12-
const [, red, green, blue] = match;
44+
return null;
45+
}
46+
47+
export function updateColor(color: any, newOpacity: number) {
48+
const entry = getColorEntry(color);
49+
if (!entry) return color;
50+
return `rgba(${entry.r}, ${entry.g}, ${entry.b}, ${newOpacity})`;
51+
}
1352

14-
// Build the updated RGBA color string with the new opacity
15-
const updatedColor = `rgba(${red}, ${green}, ${blue}, ${newOpacity})`;
53+
export function getChipColors(color: any): { background: string; border: string; text: string; icon: string } {
54+
const entry = getColorEntry(color);
55+
if (!entry) {
56+
return { background: 'transparent', border: color, text: color, icon: color };
57+
}
1658

17-
return updatedColor;
59+
return {
60+
background: `rgba(${entry.r}, ${entry.g}, ${entry.b}, 0.1)`,
61+
border: `rgba(${entry.r}, ${entry.g}, ${entry.b}, 0.3)`,
62+
text: entry.dark,
63+
icon: entry.dark,
64+
};
1865
}

0 commit comments

Comments
 (0)