1package tabs
  2
  3import (
  4	"strings"
  5
  6	tea "github.com/charmbracelet/bubbletea/v2"
  7	"github.com/charmbracelet/lipgloss/v2"
  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.MouseClickMsg:
 67		switch msg.Button {
 68		case tea.MouseLeft:
 69			for i, tab := range t.tabs {
 70				if t.common.Zone.Get(tab).InBounds(msg) {
 71					t.activeTab = i
 72					cmds = append(cmds, t.activeTabCmd)
 73				}
 74			}
 75		}
 76	case SelectTabMsg:
 77		tab := int(msg)
 78		if tab >= 0 && tab < len(t.tabs) {
 79			t.activeTab = int(msg)
 80		}
 81	}
 82	return t, tea.Batch(cmds...)
 83}
 84
 85// View implements tea.Model.
 86func (t *Tabs) View() string {
 87	s := strings.Builder{}
 88	sep := t.TabSeparator
 89	for i, tab := range t.tabs {
 90		style := t.TabInactive
 91		prefix := "  "
 92		if i == t.activeTab {
 93			style = t.TabActive
 94			prefix = t.TabDot.Render("• ")
 95		}
 96		if t.UseDot {
 97			s.WriteString(prefix)
 98		}
 99		s.WriteString(
100			t.common.Zone.Mark(
101				tab,
102				style.Render(tab),
103			),
104		)
105		if i != len(t.tabs)-1 {
106			s.WriteString(sep.String())
107		}
108	}
109	return lipgloss.NewStyle().
110		MaxWidth(t.common.Width).
111		Render(s.String())
112}
113
114func (t *Tabs) activeTabCmd() tea.Msg {
115	return ActiveTabMsg(t.activeTab)
116}
117
118// SelectTabCmd is a bubbletea command that selects the tab at the given index.
119func SelectTabCmd(tab int) tea.Cmd {
120	return func() tea.Msg {
121		return SelectTabMsg(tab)
122	}
123}