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