tabs.go

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