tabs.go

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