feat: add semantic status-color tokens; migrate MCP status badge

Add a status-color layer (status-{success|info|warning|error|neutral} plus
-subtle variants) to style.css and the unified createTailwindColors map, with a
blue palette for the info hue. Migrate MCPStatusBadge (badges + dots) and
MCPCardActions to the new tokens, removing all hardcoded status colors and
dark: twins. Status colors are now themeable like the rest of the system.
This commit is contained in:
Marco Beretta 2026-06-19 17:30:32 +02:00
parent daeb899859
commit b126d2ed0b
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
4 changed files with 51 additions and 15 deletions

View file

@ -92,7 +92,7 @@ export default function MCPCardActions({
>
<div className="relative size-4">
<Spinner className="size-4 group-hover:opacity-0" />
<X className="absolute inset-0 size-4 text-red-500 opacity-0 group-hover:opacity-100" />
<X className="absolute inset-0 size-4 text-text-destructive opacity-0 group-hover:opacity-100" />
</div>
</TooltipAnchor>
) : (
@ -171,7 +171,7 @@ export default function MCPCardActions({
<TooltipAnchor
description={localize('com_ui_revoke')}
side="top"
className={cn(buttonBaseClass, 'text-red-500 hover:text-red-600')}
className={cn(buttonBaseClass, 'text-text-destructive')}
aria-label={localize('com_ui_revoke')}
role="button"
onClick={onRevoke}

View file

@ -40,7 +40,7 @@ export default function MCPStatusBadge({
aria-live="polite"
className={cn(
badgeBaseClass,
'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400',
'bg-status-info-subtle text-status-info',
)}
>
<Spinner className="size-3" />
@ -63,7 +63,7 @@ export default function MCPStatusBadge({
aria-live="polite"
className={cn(
badgeBaseClass,
'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400',
'bg-status-info-subtle text-status-info',
)}
>
<Spinner className="size-3" />
@ -81,7 +81,7 @@ export default function MCPStatusBadge({
role="status"
className={cn(
badgeBaseClass,
'bg-amber-50 text-amber-600 dark:bg-amber-950 dark:text-amber-400',
'bg-status-warning-subtle text-status-warning',
)}
>
<PlugZap className="size-3" aria-hidden="true" />
@ -95,7 +95,7 @@ export default function MCPStatusBadge({
role="status"
className={cn(
badgeBaseClass,
'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400',
'bg-status-neutral-subtle text-status-neutral',
)}
>
<span>{localize('com_nav_mcp_status_disconnected')}</span>
@ -108,7 +108,7 @@ export default function MCPStatusBadge({
return (
<div
role="status"
className={cn(badgeBaseClass, 'bg-red-50 text-red-600 dark:bg-red-950 dark:text-red-400')}
className={cn(badgeBaseClass, 'bg-status-error-subtle text-status-error')}
>
<span>{localize('com_nav_mcp_status_error')}</span>
</div>
@ -122,7 +122,7 @@ export default function MCPStatusBadge({
role="status"
className={cn(
badgeBaseClass,
'bg-green-50 text-green-600 dark:bg-green-950 dark:text-green-400',
'bg-status-success-subtle text-status-success',
)}
>
<Check className="size-3" aria-hidden="true" />
@ -149,31 +149,31 @@ export function getStatusDotColor(
isInitializing?: boolean,
): string {
if (isInitializing) {
return 'bg-blue-500';
return 'bg-status-info';
}
if (!serverStatus) {
return 'bg-gray-400';
return 'bg-status-neutral';
}
const { connectionState, requiresOAuth } = serverStatus;
if (connectionState === 'connecting') {
return 'bg-blue-500';
return 'bg-status-info';
}
if (connectionState === 'connected') {
return 'bg-green-500';
return 'bg-status-success';
}
if (connectionState === 'error') {
return 'bg-red-500';
return 'bg-status-error';
}
if (connectionState === 'disconnected') {
// Needs OAuth = amber, otherwise gray
return requiresOAuth ? 'bg-amber-500' : 'bg-gray-400';
return requiresOAuth ? 'bg-status-warning' : 'bg-status-neutral';
}
return 'bg-gray-400';
return 'bg-status-neutral';
}

View file

@ -55,6 +55,11 @@
--amber-800: #92400e;
--amber-900: #78350f;
--amber-950: #451a03;
--blue-50: #eff6ff;
--blue-400: #60a5fa;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-950: #172554;
--brand-purple: #ab68ff;
--gizmo-gray-500: #999;
--gizmo-gray-600: #666;
@ -102,6 +107,16 @@ html {
--border-heavy: var(--gray-400);
--border-xheavy: var(--gray-500);
--border-destructive: var(--red-600);
--status-success: var(--green-600);
--status-success-subtle: var(--green-50);
--status-info: var(--blue-600);
--status-info-subtle: var(--blue-50);
--status-warning: var(--amber-600);
--status-warning-subtle: var(--amber-50);
--status-error: var(--red-600);
--status-error-subtle: var(--red-50);
--status-neutral: var(--gray-600);
--status-neutral-subtle: var(--gray-100);
/* These are test styles */
--background: 0 0% 100%;
@ -164,6 +179,16 @@ html {
--border-heavy: var(--gray-500);
--border-xheavy: var(--gray-400);
--border-destructive: var(--red-500);
--status-success: var(--green-400);
--status-success-subtle: var(--green-950);
--status-info: var(--blue-400);
--status-info-subtle: var(--blue-950);
--status-warning: var(--amber-400);
--status-warning-subtle: var(--amber-950);
--status-error: var(--red-400);
--status-error-subtle: var(--red-950);
--status-neutral: var(--gray-400);
--status-neutral-subtle: var(--gray-800);
/* These are test styles */
--background: 0 0% 7%;

View file

@ -100,6 +100,17 @@ function createTailwindColors() {
'border-xheavy': cssVar('--border-xheavy'),
'border-destructive': cssVar('--border-destructive'),
'status-success': cssVar('--status-success'),
'status-success-subtle': cssVar('--status-success-subtle'),
'status-info': cssVar('--status-info'),
'status-info-subtle': cssVar('--status-info-subtle'),
'status-warning': cssVar('--status-warning'),
'status-warning-subtle': cssVar('--status-warning-subtle'),
'status-error': cssVar('--status-error'),
'status-error-subtle': cssVar('--status-error-subtle'),
'status-neutral': cssVar('--status-neutral'),
'status-neutral-subtle': cssVar('--status-neutral-subtle'),
border: hslVar('--border'),
input: hslVar('--input'),
'switch-unchecked': hslVar('--switch-unchecked'),