internal/tui/view/detail/tabs.go

package detail

import (
	"strings"

	"github.com/charmbracelet/lipgloss"

	"mercemay.top/httptap/internal/tui/theme"
)

// Tabs renders a horizontal row of tab labels with one active.
type Tabs struct {
	pal    theme.Palette
	labels []string
	active lipgloss.Style
	idle   lipgloss.Style
	gap    lipgloss.Style
}

// NewTabs constructs a Tabs model with the supplied labels.
func NewTabs(pal theme.Palette, labels []string) Tabs {
	return Tabs{
		pal:    pal,
		labels: labels,
		active: lipgloss.NewStyle().
			Foreground(pal.HighlightFG).
			Background(pal.HighlightBG).
			Bold(true).
			Padding(0, 2),
		idle: lipgloss.NewStyle().
			Foreground(pal.Dim).
			Padding(0, 2),
		gap: lipgloss.NewStyle().
			Foreground(pal.Dim).
			Render("│"),
	}
}

// Labels returns the label strings.
func (t Tabs) Labels() []string { return t.labels }

// Render draws the tab strip padded or truncated to width.
func (t Tabs) Render(active, width int) string {
	cells := make([]string, 0, len(t.labels))
	for i, label := range t.labels {
		if i == active {
			cells = append(cells, t.active.Render(label))
		} else {
			cells = append(cells, t.idle.Render(label))
		}
	}
	row := strings.Join(cells, t.gap)
	if width <= 0 {
		return row
	}
	if lipgloss.Width(row) > width {
		return row[:width]
	}
	return row + strings.Repeat(" ", width-lipgloss.Width(row))
}

// Next returns the next active index; used by detail.Update.
func (t Tabs) Next(active int) int {
	return (active + 1) % len(t.labels)
}

// Prev is the mirror of Next.
func (t Tabs) Prev(active int) int {
	return (active - 1 + len(t.labels)) % len(t.labels)
}