tabs.go RawHistoryFolder
1package styles
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/charmbracelet/lipgloss"
8)
9
10// TabsModel represents a horizontal tab bar.
11type TabsModel struct {
12 Tabs []string
13 Active int
14 theme *Theme
15}
16
17// NewTabs creates a new tab bar with the given labels.
18func NewTabs(theme *Theme, tabs ...string) TabsModel {
19 return TabsModel{
20 Tabs: tabs,
21 Active: 0,
22 theme: theme,
23 }
24}
25
26// SetActive sets the active tab index.
27func (m *TabsModel) SetActive(index int) {
28 if index >= 0 && index < len(m.Tabs) {
29 m.Active = index
30 }
31}
32
33// Next moves to the next tab.
34func (m *TabsModel) Next() {
35 m.Active = (m.Active + 1) % len(m.Tabs)
36}
37
38// Prev moves to the previous tab.
39func (m *TabsModel) Prev() {
40 m.Active = (m.Active - 1 + len(m.Tabs)) % len(m.Tabs)
41}
42
43// View renders the tab bar.
44func (m TabsModel) View() string {
45 var tabs []string
46
47 for i, tab := range m.Tabs {
48 style := m.theme.InactiveTab
49 if i == m.Active {
50 style = m.theme.ActiveTab
51 }
52 tabs = append(tabs, style.Render(tab))
53 }
54
55 // Join tabs with separator
56 gap := lipgloss.NewStyle().
57 Foreground(m.theme.Border).
58 Render(" │ ")
59
60 var row strings.Builder
61 for i, tab := range tabs {
62 if i > 0 {
63 row.WriteString(gap)
64 }
65 row.WriteString(tab)
66 }
67
68 const tabBarWidth = 80
69 return m.theme.TabBar.Width(tabBarWidth).Render(row.String())
70}
71
72// ViewCompact renders a compact tab bar without separators.
73func (m TabsModel) ViewCompact() string {
74 var tabs []string
75
76 for i, tab := range m.Tabs {
77 style := m.theme.InactiveTab
78 if i == m.Active {
79 style = m.theme.ActiveTab
80 }
81 tabs = append(tabs, style.Render(tab))
82 }
83
84 return lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
85}
86
87// TimelineTabs creates tabs for history timeline view.
88func TimelineTabs(theme *Theme) TabsModel {
89 return NewTabs(theme, "Today", "Yesterday", "This Week", "Older")
90}
91
92// ViewWithCounts renders tabs with item counts.
93func (m TabsModel) ViewWithCounts(counts []int) string {
94 var tabs []string
95
96 for i, tab := range m.Tabs {
97 count := 0
98 if i < len(counts) {
99 count = counts[i]
100 }
101
102 label := tab
103 if count > 0 {
104 label = lipgloss.JoinHorizontal(
105 lipgloss.Center,
106 tab,
107 " ",
108 m.theme.BadgeMuted.Render(formatCount(count)),
109 )
110 }
111
112 style := m.theme.InactiveTab
113 if i == m.Active {
114 style = m.theme.ActiveTab
115 }
116 tabs = append(tabs, style.Render(label))
117 }
118
119 gap := lipgloss.NewStyle().
120 Foreground(m.theme.Border).
121 Render(" ")
122
123 return lipgloss.JoinHorizontal(lipgloss.Top, join(tabs, gap)...)
124}
125
126// join inserts a separator between items.
127func join(items []string, sep string) []string {
128 if len(items) == 0 {
129 return items
130 }
131 result := make([]string, 0, len(items)*2-1)
132 for i, item := range items {
133 if i > 0 {
134 result = append(result, sep)
135 }
136 result = append(result, item)
137 }
138 return result
139}
140
141// formatCount formats a count for display.
142func formatCount(n int) string {
143 if n >= 1000 {
144 return "999+"
145 }
146 return fmt.Sprintf("%d", n)
147}