style(FN-1847): unify dialog overlay styling and add accessibility attributes
- Add role=dialog and aria-modal attributes to all modal dialogs for accessibility - Standardize close button aria-labels across all modals - Unify overlay backdrop-filter, z-index, and background styles in styles.css - Add light theme overrides for non-standard overlay backgrounds - Update AgentListModal tests to reflect new aria attributes - Add changeset for @gsxdsm/fusion package
This commit is contained in:
@@ -180,6 +180,8 @@ export function ActivityLogModal({
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-testid="activity-log-modal-overlay"
|
||||
>
|
||||
<div className="modal modal-lg activity-log-modal" data-testid="activity-log-modal">
|
||||
|
||||
@@ -286,7 +286,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="agent-detail-overlay" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||||
<div className="agent-detail-overlay" onClick={(e) => e.target === e.currentTarget && onClose()} role="dialog" aria-modal="true">
|
||||
<div className="agent-detail-modal">
|
||||
<div className="agent-detail-loading">
|
||||
<Loader2 className="animate-spin" size={24} />
|
||||
@@ -305,7 +305,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
||||
const health = getHealthStatus();
|
||||
|
||||
return (
|
||||
<div className="agent-detail-overlay" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||||
<div className="agent-detail-overlay" onClick={(e) => e.target === e.currentTarget && onClose()} role="dialog" aria-modal="true">
|
||||
<div className="agent-detail-modal">
|
||||
{/* Header */}
|
||||
<div className="agent-detail-header">
|
||||
@@ -413,7 +413,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
||||
<button className="btn-icon" onClick={() => void loadAgent()} title="Refresh">
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
<button className="btn-icon" onClick={onClose} title="Close">
|
||||
<button className="btn-icon" onClick={onClose} aria-label="Close" title="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,7 @@ export function AgentGenerationModal({
|
||||
Generate Agent
|
||||
</span>
|
||||
<button
|
||||
className="btn-icon"
|
||||
className="modal-close"
|
||||
onClick={handleCancel}
|
||||
aria-label="Close"
|
||||
style={{
|
||||
@@ -185,7 +185,7 @@ export function AgentGenerationModal({
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
×
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -346,8 +346,8 @@ export function AgentImportModal({ isOpen, onClose, onImported, projectId }: Age
|
||||
{/* Header */}
|
||||
<div className="agent-dialog-header">
|
||||
<span className="agent-dialog-header-title">Import Agents</span>
|
||||
<button className="btn-icon" onClick={handleClose} aria-label="Close">
|
||||
×
|
||||
<button className="modal-close" onClick={handleClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()} role="dialog" aria-modal="true">
|
||||
<div className="modal modal--wide">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">
|
||||
@@ -212,8 +212,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
|
||||
>
|
||||
<RefreshCw size={16} className={isLoading ? "spin" : ""} />
|
||||
</button>
|
||||
<button className="btn-icon" onClick={onClose} title="Close">
|
||||
<X size={20} />
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,7 +112,7 @@ export function ChangesDiffModal({
|
||||
const isDone = column === "done";
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={onClose}>
|
||||
<div className="modal-overlay open" onClick={onClose} role="dialog" aria-modal="true">
|
||||
<div
|
||||
className="modal changes-diff-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
@@ -171,7 +171,7 @@ export function ChangesDiffModal({
|
||||
Refresh
|
||||
</button>
|
||||
)}
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@ function NewChatDialog({ onClose, onCreate }: NewChatDialogProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-new-dialog-backdrop" onClick={onClose}>
|
||||
<div className="chat-new-dialog-backdrop" onClick={onClose} role="dialog" aria-modal="true">
|
||||
<div className="chat-new-dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<h3>New Chat</h3>
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
||||
@@ -167,7 +167,7 @@ export function FileBrowserModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={onClose}>
|
||||
<div className="modal-overlay open" onClick={onClose} role="dialog" aria-modal="true">
|
||||
<div className="modal file-browser-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header file-browser-modal-header">
|
||||
<div className="file-browser-header-title">
|
||||
@@ -186,7 +186,7 @@ export function FileBrowserModal({
|
||||
workspaces={workspaces}
|
||||
onSelect={handleWorkspaceSelect}
|
||||
/>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -346,7 +346,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
const showInlineErrorBanner = activeTab === "issues" ? showIssuesError : showPullsError;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg github-import-modal">
|
||||
<div className="modal-header github-import-modal__header">
|
||||
<div>
|
||||
|
||||
@@ -675,7 +675,7 @@ export function GitManagerModal({ isOpen, onClose, tasks, addToast, projectId }:
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && onClose()} role="dialog" aria-modal="true">
|
||||
<div className="modal gm-modal" ref={modalRef}>
|
||||
<div className="modal-header">
|
||||
<h3>
|
||||
@@ -691,7 +691,7 @@ export function GitManagerModal({ isOpen, onClose, tasks, addToast, projectId }:
|
||||
>
|
||||
<RefreshCw size={14} className={loading ? "spin" : ""} />
|
||||
</button>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -271,6 +271,8 @@ export function MailboxModal({
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-testid="mailbox-modal-overlay"
|
||||
>
|
||||
<div className="modal modal-lg mailbox-modal" data-testid="mailbox-modal">
|
||||
|
||||
@@ -448,6 +448,8 @@ export function MilestoneSliceInterviewModal({
|
||||
<div
|
||||
className="modal-overlay open"
|
||||
onClick={(e) => e.target === e.currentTarget && handleCancel()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-testid="milestone-slice-interview-modal"
|
||||
>
|
||||
<div className="modal modal-lg planning-modal">
|
||||
|
||||
@@ -684,7 +684,7 @@ export function MissionInterviewModal({
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && handleCancel()}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && handleCancel()} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
|
||||
@@ -3481,6 +3481,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
||||
className="mission-manager-overlay open"
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
data-testid="mission-manager-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
{manager}
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@ export function ModelSelectionModal({
|
||||
const hasValidatorOverride = Boolean(validatorValue);
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick} data-testid="model-selection-modal">
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick} role="dialog" aria-modal="true" data-testid="model-selection-modal">
|
||||
<div className="modal modal-lg">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
|
||||
@@ -208,14 +208,14 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleClose} onKeyDown={handleKeyDown}>
|
||||
<div
|
||||
className="modal modal-lg new-task-modal"
|
||||
<div className="modal-overlay open" onClick={handleClose} onKeyDown={handleKeyDown} role="dialog" aria-modal="true">
|
||||
<div
|
||||
className="modal modal-lg new-task-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="modal-header">
|
||||
<h3>New Task</h3>
|
||||
<button className="modal-close" onClick={handleClose} disabled={isSubmitting}>
|
||||
<button className="modal-close" onClick={handleClose} disabled={isSubmitting} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -717,7 +717,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && handleCancel()}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && handleCancel()} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
|
||||
@@ -465,7 +465,7 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick}>
|
||||
<div className="modal modal-lg" role="dialog" aria-labelledby="schedules-modal-title">
|
||||
<div className="modal modal-lg" role="dialog" aria-modal="true" aria-labelledby="schedules-modal-title">
|
||||
<div className="modal-header">
|
||||
<h3 id="schedules-modal-title">Scheduled Tasks</h3>
|
||||
<div className="modal-header-actions">
|
||||
|
||||
@@ -172,6 +172,7 @@ export function ScriptsModal({ isOpen, onClose, addToast, projectId, onRunScript
|
||||
className="modal scripts-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Scripts"
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -180,8 +181,8 @@ export function ScriptsModal({ isOpen, onClose, addToast, projectId, onRunScript
|
||||
<Terminal size={18} style={{ marginRight: "8px", verticalAlign: "middle" }} />
|
||||
Scripts
|
||||
</h2>
|
||||
<button className="btn-icon" onClick={onClose} aria-label="Close">
|
||||
<X size={16} />
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2636,11 +2636,11 @@ export function SettingsModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick}>
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg">
|
||||
<div className="modal-header">
|
||||
<h3>Settings</h3>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -2726,11 +2726,11 @@ export function SettingsModal({
|
||||
|
||||
{/* Import Confirmation Dialog */}
|
||||
{importDialogOpen && importPreview && (
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && setImportDialogOpen(false)}>
|
||||
<div className="modal-overlay open" onClick={(e) => e.target === e.currentTarget && setImportDialogOpen(false)} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-md">
|
||||
<div className="modal-header">
|
||||
<h3>Import Settings</h3>
|
||||
<button className="modal-close" onClick={() => setImportDialogOpen(false)}>
|
||||
<button className="modal-close" onClick={() => setImportDialogOpen(false)} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -515,7 +515,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={(event) => event.target === event.currentTarget && void handleClose()}>
|
||||
<div className="modal-overlay open" onClick={(event) => event.target === event.currentTarget && void handleClose()} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export function TaskDetailModal({
|
||||
const prAutomationLabel = task.status ? prAutomationStatusLabels[task.status] : undefined;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick}>
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick} role="dialog" aria-modal="true">
|
||||
<div className="modal modal-lg" onDragOver={handleDragOver} onDrop={handleDrop}>
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
@@ -1057,7 +1057,7 @@ export function TaskDetailModal({
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
)}
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -1798,6 +1798,8 @@ export function TaskDetailModal({
|
||||
<div
|
||||
className="modal-overlay open detail-refine-overlay"
|
||||
onClick={handleCloseRefineModal}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div
|
||||
className="modal detail-refine-modal"
|
||||
@@ -1805,7 +1807,7 @@ export function TaskDetailModal({
|
||||
>
|
||||
<div className="modal-header">
|
||||
<h3 className="detail-refine-title">Refine</h3>
|
||||
<button className="modal-close" onClick={handleCloseRefineModal}>
|
||||
<button className="modal-close" onClick={handleCloseRefineModal} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -779,6 +779,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
<div
|
||||
className="modal-overlay open"
|
||||
onClick={handleOverlayClick}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-testid="terminal-modal-overlay"
|
||||
style={
|
||||
keyboardOverlap > 0
|
||||
|
||||
@@ -373,13 +373,14 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
className="modal workflow-step-manager-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Workflow Steps"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="modal-header">
|
||||
<h2>Workflow Steps</h2>
|
||||
<button className="btn-icon" onClick={onClose} aria-label="Close">
|
||||
<X size={16} />
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -130,11 +130,11 @@ describe("AgentListModal", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const closeButton = screen.getByTitle("Close");
|
||||
const closeButton = screen.getByRole("button", { name: "Close" });
|
||||
expect(closeButton).toBeTruthy();
|
||||
});
|
||||
|
||||
const closeButton = screen.getByTitle("Close");
|
||||
const closeButton = screen.getByRole("button", { name: "Close" });
|
||||
fireEvent.click(closeButton);
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
@@ -1412,7 +1412,7 @@ describe("AgentListModal", () => {
|
||||
});
|
||||
|
||||
// Close via close button
|
||||
fireEvent.click(screen.getByTitle("Close"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Unmount and verify closed state works
|
||||
|
||||
Reference in New Issue
Block a user