🗣 feat: MCP Status Accessibility Improvements (#10738)

* feat: make MultiSelect highlight same opacity as other focus highlights in app

* feat: add better screenreader announcements for mcp server and variable states

* feat: memoize fullTitle calculation
This commit is contained in:
Dustin Healy 2025-12-01 07:08:28 -08:00 committed by Danny Avila
parent ad6ba4b6d1
commit 1a38e2a081
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
4 changed files with 49 additions and 15 deletions

View file

@ -27,6 +27,7 @@ interface AuthFieldProps {
function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) {
const localize = useLocalize();
const statusText = hasValue ? localize('com_ui_set') : localize('com_ui_unset');
const sanitizer = useMemo(() => {
const instance = DOMPurify();
@ -60,19 +61,21 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor={name} className="text-sm font-medium">
{config.title}
{config.title} <span className="sr-only">({statusText})</span>
</Label>
{hasValue ? (
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span>{localize('com_ui_set')}</span>
</div>
) : (
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
<div className="h-1.5 w-1.5 rounded-full border border-border-medium" />
<span>{localize('com_ui_unset')}</span>
</div>
)}
<div aria-hidden="true">
{hasValue ? (
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span>{localize('com_ui_set')}</span>
</div>
) : (
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
<div className="h-1.5 w-1.5 rounded-full border border-border-medium" />
<span>{localize('com_ui_unset')}</span>
</div>
)}
</div>
</div>
<Controller
name={name}

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { KeyRound, PlugZap, AlertTriangle } from 'lucide-react';
import {
Spinner,
@ -44,6 +44,33 @@ export default function MCPConfigDialog({
? localize('com_ui_configure_mcp_variables_for', { 0: serverName })
: `${serverName} MCP Server`;
const fullTitle = useMemo(() => {
if (!serverStatus) {
return localize('com_ui_mcp_dialog_title', {
serverName,
status: '',
});
}
const { connectionState, requiresOAuth } = serverStatus;
let statusText = '';
if (connectionState === 'connecting') {
statusText = localize('com_ui_connecting');
} else if (connectionState === 'error') {
statusText = localize('com_ui_error');
} else if (connectionState === 'connected') {
statusText = localize('com_ui_active');
} else if (connectionState === 'disconnected') {
statusText = requiresOAuth ? localize('com_ui_oauth') : localize('com_ui_offline');
}
return localize('com_ui_mcp_dialog_title', {
serverName,
status: statusText,
});
}, [serverStatus, serverName, localize]);
// Helper function to render status badge based on connection state
const renderStatusBadge = () => {
if (!serverStatus) {
@ -102,7 +129,10 @@ export default function MCPConfigDialog({
return (
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
<OGDialogContent className="flex max-h-screen w-11/12 max-w-lg flex-col space-y-2">
<OGDialogContent
className="flex max-h-screen w-11/12 max-w-lg flex-col space-y-2"
title={fullTitle}
>
<OGDialogHeader>
<div className="flex items-center gap-3">
<OGDialogTitle className="text-xl">

View file

@ -789,6 +789,7 @@
"com_ui_complete_setup": "Complete Setup",
"com_ui_concise": "Concise",
"com_ui_configure_mcp_variables_for": "Configure Variables for {{0}}",
"com_ui_mcp_dialog_title": "Configure Variables for {{serverName}}. Server Status: {{status}}",
"com_ui_confirm": "Confirm",
"com_ui_confirm_action": "Confirm Action",
"com_ui_confirm_admin_use_change": "Changing this setting will block access for admins, including yourself. Are you sure you want to proceed?",

View file

@ -82,7 +82,7 @@ export default function MultiSelect<T extends string>({
className={cn(
'flex items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',
'bg-surface-tertiary text-text-primary shadow-sm hover:cursor-pointer hover:bg-surface-hover',
'outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75',
'outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white',
selectClassName,
selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName,
)}