fix: improve minimized group label contrast and typography

Applied luminosity-based contrast color calculation to minimized
group labels, matching the same accessibility approach used for
actor nodes.

Changes:
- Added rgbToHex() helper to convert rgb/rgba colors to hex format
- Import getContrastColor() from colorUtils
- Calculate text color based on background luminosity
- Apply calculated color to both label and subtitle

Typography updates to match actor nodes:
- Main label: text-base font-bold (was text-sm font-semibold)
- Subtitle: text-xs font-medium with 0.7 opacity (was text-gray-600)
- Both use dynamic textColor instead of fixed gray values

Benefits:
-  Readable labels on both light and dark group backgrounds
-  Consistent typography between actor nodes and group nodes
-  Follows WCAG 2.0 luminance contrast standards
-  Professional appearance across all color choices

The minimized group labels now automatically switch between white
and black text based on the background color's luminosity, ensuring
optimal readability in all cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-10-20 15:12:42 +02:00
parent 60748a2235
commit f29c55a1b8

View file

@ -2,6 +2,21 @@ import { memo, useState, useMemo } from 'react';
import { NodeProps, NodeResizer, useStore, Handle, Position } from '@xyflow/react';
import type { Group } from '../../types';
import type { Actor } from '../../types';
import { getContrastColor } from '../../utils/colorUtils';
/**
* Helper function to convert rgb/rgba color string to hex
*/
const rgbToHex = (rgb: string): string => {
const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
if (!match) return '#000000';
const r = parseInt(match[1]).toString(16).padStart(2, '0');
const g = parseInt(match[2]).toString(16).padStart(2, '0');
const b = parseInt(match[3]).toString(16).padStart(2, '0');
return `#${r}${g}${b}`;
};
/**
* GroupNode - Simple label overlay for React Flow's native group nodes
@ -91,6 +106,10 @@ const GroupNode = ({ id, data, selected }: NodeProps<Group>) => {
? data.color.replace(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/, 'rgb($1, $2, $3)')
: '#f0f2f5';
// Calculate contrast color for text based on background
const hexColor = rgbToHex(solidColor);
const textColor = getContrastColor(hexColor);
return (
<div
className="group-minimized"
@ -175,10 +194,16 @@ const GroupNode = ({ id, data, selected }: NodeProps<Group>) => {
textAlign: 'center',
}}
>
<div className="text-sm font-semibold text-gray-800 leading-tight">
<div
className="text-base font-bold leading-tight"
style={{ color: textColor }}
>
{data.label}
</div>
<div className="text-xs text-gray-600 mt-1.5">
<div
className="text-xs font-medium leading-tight mt-1.5"
style={{ color: textColor, opacity: 0.7 }}
>
{data.actorIds.length} actor{data.actorIds.length !== 1 ? 's' : ''}
</div>
</div>