package tui import ( "fmt" "strings" "time" "github.com/charmbracelet/lipgloss" ) // ErrorSeverity indicates the level of an error message type ErrorSeverity int const ( SevError ErrorSeverity = iota SevWarning SevInfo ) // ErrorBanner displays a structured error message with title, details, and hints type ErrorBanner struct { Title string Detail string Hints []string Severity ErrorSeverity AutoDismiss bool DismissAfter time.Duration createdAt time.Time visible bool } // NewErrorBanner creates a new error banner with the given severity func NewErrorBanner(severity ErrorSeverity) *ErrorBanner { return &ErrorBanner{ Severity: severity, AutoDismiss: true, DismissAfter: 5 * time.Second, visible: false, } } // Show displays the error banner with the given message func (b *ErrorBanner) Show(title, detail string, hints ...string) { b.Title = title b.Detail = detail b.Hints = hints b.visible = true b.createdAt = time.Now() } // Hide hides the error banner func (b *ErrorBanner) Hide() { b.visible = false } // IsVisible returns whether the banner is currently visible func (b *ErrorBanner) IsVisible() bool { return b.visible } // Update checks if auto-dismiss time has elapsed func (b *ErrorBanner) Update() { if b.visible && b.AutoDismiss && time.Since(b.createdAt) > b.DismissAfter { b.visible = false } } // View renders the error banner func (b *ErrorBanner) View(width int) string { if !b.visible { return "" } var ( titleStyle lipgloss.Style borderColor lipgloss.Color prefix string ) switch b.Severity { case SevError: titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ea6962")).Bold(true) borderColor = lipgloss.Color("#ea6962") prefix = "✖" case SevWarning: titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#d8a657")).Bold(true) borderColor = lipgloss.Color("#d8a657") prefix = "⚠" case SevInfo: titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#7daea3")).Bold(true) borderColor = lipgloss.Color("#7daea3") prefix = "ℹ" } var sb strings.Builder // Title line with prefix sb.WriteString(titleStyle.Render(fmt.Sprintf("%s %s", prefix, b.Title))) // Detail line if b.Detail != "" { sb.WriteString("\n") sb.WriteString(strings.Repeat(" ", len(prefix)+1)) detailStyle := lipgloss.NewStyle().Foreground(activeTheme.Fg) sb.WriteString(detailStyle.Render(b.Detail)) } // Hints if len(b.Hints) > 0 { sb.WriteString("\n") sb.WriteString(strings.Repeat(" ", len(prefix)+1)) hintStyle := lipgloss.NewStyle().Foreground(activeTheme.FgMute) sb.WriteString(hintStyle.Render("Hints:")) for i, hint := range b.Hints { sb.WriteString("\n") sb.WriteString(strings.Repeat(" ", len(prefix)+2)) sb.WriteString(hintStyle.Render(fmt.Sprintf("%d. %s", i+1, hint))) } } // Wrap in a styled box borderStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(borderColor). Padding(0, 1). Width(min(width-2, 80)) return borderStyle.Render(sb.String()) } // min returns the smaller of two integers func min(a, b int) int { if a < b { return a } return b }