| 1 | import { useId, useState } from "react"; |
| 2 | import { useI18n } from "../lib/i18n"; |
| 3 | import { presentError } from "../lib/errorPresentation"; |
| 4 | import type { WireEvent } from "../lib/types"; |
| 5 | |
| 6 | /** Inline-safe error surface, including inside paragraphs and existing banners. */ |
| 7 | export function ErrorMessage({ error, summary, diagnostic, onInspect }: { error: unknown; summary?: string; diagnostic?: WireEvent["diagnostic"]; onInspect?: () => void }) { |
| 8 | const { t, locale } = useI18n(); |
| 9 | const id = useId(); |
| 10 | const [expandedError, setExpandedError] = useState<{ error: unknown } | null>(null); |
| 11 | const presentation = presentError(error, t, locale, diagnostic); |
| 12 | const expanded = expandedError !== null && expandedError.error === error; |
| 13 | const title = summary || presentation.summary; |
| 14 | const detail = error == null || error === "" ? "" : presentation.detail || (summary ? presentation.summary : ""); |
| 15 | if ((error == null || error === "") && !summary) return null; |
| 16 | return <span className="user-error"> |
| 17 | <span className="user-error__summary">{title}</span> |
| 18 | {detail && detail !== title && <> |
| 19 | <button type="button" className="user-error__toggle" aria-expanded={expanded} aria-controls={id} |
| 20 | onClick={(event) => { event.stopPropagation(); onInspect?.(); setExpandedError(expanded ? null : { error }); }}> |
| 21 | {t(expanded ? "error.hideDetails" : "error.details")} |
| 22 | </button> |
| 23 | {expanded && <span id={id} className="user-error__detail" onClick={event => event.stopPropagation()}>{detail}</span>} |
| 24 | </>} |
| 25 | </span>; |
| 26 | } |
| 27 |