Files
swanadiva 847989df75 refactor: move V1 code into v1/ subdirectory
- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/
- Create v1/README.md with V1 documentation
- Update root README for V1 + V2 structure
- V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass
- Root is now clean for V2 development
2026-07-07 11:56:27 +07:00

138 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}