helpers.go

  1package core
  2
  3import (
  4	"image/color"
  5	"strings"
  6
  7	"github.com/charmbracelet/crush/internal/tui/styles"
  8	"github.com/charmbracelet/lipgloss/v2"
  9	"github.com/charmbracelet/x/ansi"
 10)
 11
 12func Section(text string, width int) string {
 13	t := styles.CurrentTheme()
 14	char := "─"
 15	length := lipgloss.Width(text) + 1
 16	remainingWidth := width - length
 17	lineStyle := t.S().Base.Foreground(t.Border)
 18	if remainingWidth > 0 {
 19		text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
 20	}
 21	return text
 22}
 23
 24func Title(title string, width int) string {
 25	t := styles.CurrentTheme()
 26	char := "╱"
 27	length := lipgloss.Width(title) + 1
 28	remainingWidth := width - length
 29	titleStyle := t.S().Base.Foreground(t.Primary)
 30	if remainingWidth > 0 {
 31		lines := strings.Repeat(char, remainingWidth)
 32		lines = styles.ApplyForegroundGrad(lines, t.Primary, t.Secondary)
 33		title = titleStyle.Render(title) + " " + lines
 34	}
 35	return title
 36}
 37
 38type StatusOpts struct {
 39	Icon             string
 40	IconColor        color.Color
 41	Title            string
 42	TitleColor       color.Color
 43	Description      string
 44	DescriptionColor color.Color
 45	ExtraContent     string // Additional content to append after the description
 46}
 47
 48func Status(ops StatusOpts, width int) string {
 49	t := styles.CurrentTheme()
 50	icon := "●"
 51	iconColor := t.Success
 52	if ops.Icon != "" {
 53		icon = ops.Icon
 54	}
 55	if ops.IconColor != nil {
 56		iconColor = ops.IconColor
 57	}
 58	title := ops.Title
 59	titleColor := t.FgMuted
 60	if ops.TitleColor != nil {
 61		titleColor = ops.TitleColor
 62	}
 63	description := ops.Description
 64	descriptionColor := t.FgSubtle
 65	if ops.DescriptionColor != nil {
 66		descriptionColor = ops.DescriptionColor
 67	}
 68	icon = t.S().Base.Foreground(iconColor).Render(icon)
 69	title = t.S().Base.Foreground(titleColor).Render(title)
 70	if description != "" {
 71		extraContent := len(ops.ExtraContent)
 72		if extraContent > 0 {
 73			extraContent += 1
 74		}
 75		description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContent, "…")
 76	}
 77	description = t.S().Base.Foreground(descriptionColor).Render(description)
 78	content := []string{
 79		icon,
 80		title,
 81		description,
 82	}
 83	if ops.ExtraContent != "" {
 84		content = append(content, ops.ExtraContent)
 85	}
 86
 87	return strings.Join(content, " ")
 88}
 89
 90type ButtonOpts struct {
 91	Text           string
 92	UnderlineIndex int  // Index of character to underline (0-based)
 93	Selected       bool // Whether this button is selected
 94}
 95
 96// SelectableButton creates a button with an underlined character and selection state
 97func SelectableButton(opts ButtonOpts) string {
 98	t := styles.CurrentTheme()
 99
100	// Base style for the button
101	buttonStyle := t.S().Text
102
103	// Apply selection styling
104	if opts.Selected {
105		buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
106	} else {
107		buttonStyle = buttonStyle.Background(t.BgSubtle)
108	}
109
110	// Create the button text with underlined character
111	text := opts.Text
112	if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
113		before := text[:opts.UnderlineIndex]
114		underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
115		after := text[opts.UnderlineIndex+1:]
116
117		message := buttonStyle.Render(before) +
118			buttonStyle.Underline(true).Render(underlined) +
119			buttonStyle.Render(after)
120
121		return buttonStyle.Padding(0, 2).Render(message)
122	}
123
124	// Fallback if no underline index specified
125	return buttonStyle.Padding(0, 2).Render(text)
126}
127
128// SelectableButtons creates a horizontal row of selectable buttons
129func SelectableButtons(buttons []ButtonOpts, spacing string) string {
130	if spacing == "" {
131		spacing = "  "
132	}
133
134	var parts []string
135	for i, button := range buttons {
136		parts = append(parts, SelectableButton(button))
137		if i < len(buttons)-1 {
138			parts = append(parts, spacing)
139		}
140	}
141
142	return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
143}