({ toast }: { toast: ToastModel })
| 42 | } |
| 43 | |
| 44 | function ToastItem({ toast }: { toast: ToastModel }) { |
| 45 | const dismiss = useCodesignStore((s) => s.dismissToast); |
| 46 | const t = useT(); |
| 47 | const Icon = iconFor[toast.variant]; |
| 48 | const isError = toast.variant === 'error'; |
| 49 | const autoMs = AUTO_DISMISS_MS[toast.variant]; |
| 50 | |
| 51 | useEffect(() => { |
| 52 | const cleanup = scheduleAutoDismiss(toast.variant, () => { |
| 53 | dismiss(toast.id); |
| 54 | }); |
| 55 | return cleanup ?? undefined; |
| 56 | }, [toast.id, toast.variant, dismiss]); |
| 57 | |
| 58 | // Synchronous — the ReportableError was already registered in the store |
| 59 | // when the toast was pushed, so Report opens instantly without an IPC |
| 60 | // round-trip or DB lookup. |
| 61 | function openReport(): void { |
| 62 | const localId = toast.localId; |
| 63 | if (!localId) return; |
| 64 | useCodesignStore.getState().openReportDialog(localId); |
| 65 | } |
| 66 | |
| 67 | return ( |
| 68 | <div |
| 69 | role={isError ? 'alert' : 'status'} |
| 70 | aria-live={isError ? 'assertive' : 'polite'} |
| 71 | className="relative overflow-hidden flex items-start gap-3 min-w-72 max-w-96 px-4 py-3 rounded-[var(--radius-lg)] bg-[var(--color-surface)] border border-[var(--color-border)] shadow-[var(--shadow-elevated)] animate-[toast-in_180ms_ease-out] motion-reduce:animate-none" |
| 72 | > |
| 73 | <Icon className="w-5 h-5 mt-0.5 shrink-0" style={{ color: accentFor[toast.variant] }} /> |
| 74 | <div className="flex-1 min-w-0"> |
| 75 | <div className="text-[var(--text-sm)] font-medium text-[var(--color-text-primary)] break-words"> |
| 76 | {toast.title} |
| 77 | </div> |
| 78 | {toast.description ? ( |
| 79 | <div className="text-[var(--text-xs)] text-[var(--color-text-secondary)] mt-0.5 break-words"> |
| 80 | {toast.description} |
| 81 | </div> |
| 82 | ) : null} |
| 83 | {toast.action ? ( |
| 84 | <button |
| 85 | type="button" |
| 86 | onClick={() => { |
| 87 | toast.action?.onClick(); |
| 88 | dismiss(toast.id); |
| 89 | }} |
| 90 | className="mt-2 inline-flex items-center h-6 px-2 rounded-[var(--radius-sm)] text-[var(--text-xs)] font-medium text-[var(--color-on-accent)] bg-[var(--color-accent)] hover:opacity-90 transition-opacity" |
| 91 | > |
| 92 | {toast.action.label} |
| 93 | </button> |
| 94 | ) : null} |
| 95 | </div> |
| 96 | <div className="flex items-center gap-1 shrink-0"> |
| 97 | {isError && toast.localId ? ( |
| 98 | <button |
| 99 | type="button" |
| 100 | onClick={openReport} |
| 101 | className="h-6 px-2 inline-flex items-center gap-1 rounded-[var(--radius-sm)] text-[var(--text-xs)] font-medium text-[var(--color-text-secondary)] border border-[var(--color-border)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors" |
nothing calls this directly
no test coverage detected