badge.go RawHistoryFolder
1package styles
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/charmbracelet/lipgloss"
8)
9
10// Badge renders styled metadata badges.
11
12// VisitBadge renders a visit count badge.
13func (t *Theme) VisitBadge(count int) string {
14 text := fmt.Sprintf("%d visits", count)
15 if count == 1 {
16 text = "1 visit"
17 }
18 return t.BadgeMuted.Render(text)
19}
20
21// TimeBadge renders a relative time badge.
22func (t *Theme) TimeBadge(tm time.Time) string {
23 text := RelativeTime(tm)
24 return t.BadgeMuted.Render(text)
25}
26
27// DomainBadge renders a domain badge.
28func (t *Theme) DomainBadge(domain string) string {
29 return t.Badge.Render(domain)
30}
31
32// AccentBadge renders a badge with accent color.
33func (t *Theme) AccentBadge(text string) string {
34 return t.Badge.Render(text)
35}
36
37// MutedBadge renders a badge with muted colors.
38func (t *Theme) MutedBadge(text string) string {
39 return t.BadgeMuted.Render(text)
40}
41
42// StatusBadge renders a status badge with custom colors.
43func (t *Theme) StatusBadge(text string, fg, bg lipgloss.Color) string {
44 style := lipgloss.NewStyle()
45 if t != nil {
46 style = t.Badge
47 }
48 style = style.
49 Foreground(fg).
50 Background(bg).
51 Padding(0, 1)
52 return style.Render(text)
53}
54
55// RelativeTime formats a time as a human-readable relative string.
56func RelativeTime(tm time.Time) string {
57 now := time.Now()
58 diff := now.Sub(tm)
59 const (
60 hoursPerDay = 24
61 daysPerWeek = 7
62 daysPerMonth = 30
63 daysPerYear = 365
64 )
65
66 switch {
67 case diff < time.Minute:
68 return "just now"
69 case diff < time.Hour:
70 mins := int(diff.Minutes())
71 if mins == 1 {
72 return "1m ago"
73 }
74 return fmt.Sprintf("%dm ago", mins)
75 case diff < hoursPerDay*time.Hour:
76 hours := int(diff.Hours())
77 if hours == 1 {
78 return "1h ago"
79 }
80 return fmt.Sprintf("%dh ago", hours)
81 case diff < time.Duration(daysPerWeek*hoursPerDay)*time.Hour:
82 days := int(diff.Hours() / hoursPerDay)
83 if days == 1 {
84 return "1d ago"
85 }
86 return fmt.Sprintf("%dd ago", days)
87 case diff < time.Duration(daysPerMonth*hoursPerDay)*time.Hour:
88 weeks := int(diff.Hours() / (hoursPerDay * daysPerWeek))
89 if weeks == 1 {
90 return "1w ago"
91 }
92 return fmt.Sprintf("%dw ago", weeks)
93 case diff < time.Duration(daysPerYear*hoursPerDay)*time.Hour:
94 months := int(diff.Hours() / (hoursPerDay * daysPerMonth))
95 if months == 1 {
96 return "1mo ago"
97 }
98 return fmt.Sprintf("%dmo ago", months)
99 default:
100 years := int(diff.Hours() / (24 * 365))
101 if years == 1 {
102 return "1y ago"
103 }
104 return fmt.Sprintf("%dy ago", years)
105 }
106}
107
108// TimeCategory returns which timeline category a time belongs to.
109type TimeCategory int
110
111const (
112 TimeCategoryToday TimeCategory = iota
113 TimeCategoryYesterday
114 TimeCategoryThisWeek
115 TimeCategoryOlder
116)
117
118// GetTimeCategory returns the category for a given time.
119func GetTimeCategory(tm time.Time) TimeCategory {
120 now := time.Now()
121 today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
122 yesterday := today.AddDate(0, 0, -1)
123 weekAgo := today.AddDate(0, 0, -7)
124
125 switch {
126 case tm.After(today) || tm.Equal(today):
127 return TimeCategoryToday
128 case tm.After(yesterday) || tm.Equal(yesterday):
129 return TimeCategoryYesterday
130 case tm.After(weekAgo):
131 return TimeCategoryThisWeek
132 default:
133 return TimeCategoryOlder
134 }
135}
136
137// TimeCategoryLabel returns the display label for a time category.
138func TimeCategoryLabel(cat TimeCategory) string {
139 switch cat {
140 case TimeCategoryToday:
141 return "Today"
142 case TimeCategoryYesterday:
143 return "Yesterday"
144 case TimeCategoryThisWeek:
145 return "This Week"
146 case TimeCategoryOlder:
147 return "Older"
148 default:
149 return "Unknown"
150 }
151}