1// Package tabs provides tab UI components.
2package tabs
3
4import (
5 "strings"
6
7 tea "github.com/charmbracelet/bubbletea/v2"
8 "github.com/charmbracelet/lipgloss/v2"
9 "github.com/charmbracelet/soft-serve/pkg/ui/common"
10)
11
12// SelectTabMsg is a message that contains the index of the tab to select.
13type SelectTabMsg int
14
15// ActiveTabMsg is a message that contains the index of the current active tab.
16type ActiveTabMsg int
17
18// Tabs is bubbletea component that displays a list of tabs.
19type Tabs struct {
20 common common.Common
21 tabs []string
22 activeTab int
23 TabSeparator lipgloss.Style
24 TabInactive lipgloss.Style
25 TabActive lipgloss.Style
26 TabDot lipgloss.Style
27 UseDot bool
28}
29
30// New creates a new Tabs component.
31func New(c common.Common, tabs []string) *Tabs {
32 r := &Tabs{
33 common: c,
34 tabs: tabs,
35 activeTab: 0,
36 TabSeparator: c.Styles.TabSeparator,
37 TabInactive: c.Styles.TabInactive,
38 TabActive: c.Styles.TabActive,
39 }
40 return r
41}
42
43// SetSize implements common.Component.
44func (t *Tabs) SetSize(width, height int) {
45 t.common.SetSize(width, height)
46}
47
48// Init implements tea.Model.
49func (t *Tabs) Init() tea.Cmd {
50 t.activeTab = 0
51 return nil
52}
53
54// Update implements tea.Model.
55func (t *Tabs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
56 cmds := make([]tea.Cmd, 0)
57 switch msg := msg.(type) {
58 case tea.KeyPressMsg:
59 switch msg.String() {
60 case "tab":
61 t.activeTab = (t.activeTab + 1) % len(t.tabs)
62 cmds = append(cmds, t.activeTabCmd)
63 case "shift+tab":
64 t.activeTab = (t.activeTab - 1 + len(t.tabs)) % len(t.tabs)
65 cmds = append(cmds, t.activeTabCmd)
66 }
67 case tea.MouseClickMsg:
68 switch msg.Button {
69 case tea.MouseLeft:
70 for i, tab := range t.tabs {
71 if t.common.Zone.Get(tab).InBounds(msg) {
72 t.activeTab = i
73 cmds = append(cmds, t.activeTabCmd)
74 }
75 }
76 }
77 case SelectTabMsg:
78 tab := int(msg)
79 if tab >= 0 && tab < len(t.tabs) {
80 t.activeTab = int(msg)
81 }
82 }
83 return t, tea.Batch(cmds...)
84}
85
86// View implements tea.Model.
87func (t *Tabs) View() string {
88 s := strings.Builder{}
89 sep := t.TabSeparator
90 for i, tab := range t.tabs {
91 style := t.TabInactive
92 prefix := " "
93 if i == t.activeTab {
94 style = t.TabActive
95 prefix = t.TabDot.Render("• ")
96 }
97 if t.UseDot {
98 s.WriteString(prefix)
99 }
100 s.WriteString(
101 t.common.Zone.Mark(
102 tab,
103 style.Render(tab),
104 ),
105 )
106 if i != len(t.tabs)-1 {
107 s.WriteString(sep.String())
108 }
109 }
110 return lipgloss.NewStyle().
111 MaxWidth(t.common.Width).
112 Render(s.String())
113}
114
115func (t *Tabs) activeTabCmd() tea.Msg {
116 return ActiveTabMsg(t.activeTab)
117}
118
119// SelectTabCmd is a bubbletea command that selects the tab at the given index.
120func SelectTabCmd(tab int) tea.Cmd {
121 return func() tea.Msg {
122 return SelectTabMsg(tab)
123 }
124}