tabs.go

  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}