diff --git a/docs/UI_UX_DECISION_LEDGER.md b/docs/UI_UX_DECISION_LEDGER.md index 02a56b8..60f67bc 100644 --- a/docs/UI_UX_DECISION_LEDGER.md +++ b/docs/UI_UX_DECISION_LEDGER.md @@ -223,6 +223,10 @@ instead of reproducing their behavior. - `help` content is contextual guidance, not the accessible name. The persisted `show_inline_help_hints` user preference hides only the `InlineHelp` marker by applying `ui-hide-help-hints` at the document root. +- Shared action-bearing components accept an optional disabled reason. In + particular, `MailServerSettingsPanel` forwards protocol-specific test + blockers into the shared focusable disabled-action tooltip; modules provide + the domain-specific required field, permission, or in-progress reason. - A dirty editor registers once with `useUnsavedDraftGuard`. An explicit Discard button calls `useUnsavedChanges().requestDiscard(afterResolve)`; SPA navigation uses `useGuardedNavigate` or `requestNavigation`. Both paths show diff --git a/webui/src/components/mail/MailServerSettingsPanel.tsx b/webui/src/components/mail/MailServerSettingsPanel.tsx index 593e244..53f5601 100644 --- a/webui/src/components/mail/MailServerSettingsPanel.tsx +++ b/webui/src/components/mail/MailServerSettingsPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, type ReactNode } from "react"; import Button from "../Button"; import { CredentialFields } from "../CredentialPanel"; import DismissibleAlert from "../DismissibleAlert"; @@ -64,11 +64,13 @@ export type MailServerSettingsPanelProps = { smtpPasswordSaved?: boolean; smtpSavedPasswordPlaceholder?: string; smtpActionDisabled?: boolean; + smtpActionDisabledReason?: ReactNode; imapServerDisabled?: boolean; imapCredentialDisabled?: boolean; imapPasswordSaved?: boolean; imapSavedPasswordPlaceholder?: string; imapActionDisabled?: boolean; + imapActionDisabledReason?: ReactNode; smtpTestLabel?: string; imapTestLabel?: string; busyAction?: "smtp" | "imap" | "folders" | string | null; @@ -209,11 +211,13 @@ export default function MailServerSettingsPanel({ smtpPasswordSaved = false, smtpSavedPasswordPlaceholder = "••••••••", smtpActionDisabled = smtpDisabled, + smtpActionDisabledReason, imapServerDisabled = false, imapCredentialDisabled = imapServerDisabled, imapPasswordSaved = false, imapSavedPasswordPlaceholder = "••••••••", imapActionDisabled = imapServerDisabled, + imapActionDisabledReason, smtpTestLabel = "i18n:govoplan-core.test_smtp.e5697981", imapTestLabel = "i18n:govoplan-core.test_imap.ef1bd79c", busyAction = null, @@ -323,7 +327,7 @@ export default function MailServerSettingsPanel({ {onTestSmtp &&
- +
} @@ -352,7 +356,7 @@ export default function MailServerSettingsPanel({ {onTestImap &&
- +
} diff --git a/webui/tests/mail-components.test.tsx b/webui/tests/mail-components.test.tsx index cfe2690..59181da 100644 --- a/webui/tests/mail-components.test.tsx +++ b/webui/tests/mail-components.test.tsx @@ -108,6 +108,20 @@ assert(!settingsPanel.includes("i18n:govoplan-core.advanced.4d064726"), "advance assert(!settingsPanel.includes("Enable IMAP"), "legacy IMAP enable toggle is not rendered"); assert(settingsPanel.includes('placeholder="Saved SMTP password"'), "SMTP saved credential placeholder is rendered"); +const disabledSettingsPanel = renderToStaticMarkup( + +); +assert(disabledSettingsPanel.includes("disabled-action-tooltip"), "disabled SMTP test reason uses the shared focusable tooltip"); +assert(disabledSettingsPanel.includes('tabindex="0"'), "disabled SMTP test reason is keyboard focusable"); + const imapSettingsPanel = renderToStaticMarkup(