button.go

 1package common
 2
 3import (
 4	"strings"
 5
 6	"charm.land/lipgloss/v2"
 7	"github.com/charmbracelet/crush/internal/ui/styles"
 8)
 9
10// ButtonOpts defines the configuration for a single button
11type ButtonOpts struct {
12	// Text is the button label
13	Text string
14	// UnderlineIndex is the 0-based index of the character to underline (-1 for none)
15	UnderlineIndex int
16	// Selected indicates whether this button is currently selected
17	Selected bool
18	// Padding inner horizontal padding defaults to 2 if this is 0
19	Padding int
20}
21
22// Button creates a button with an underlined character and selection state
23func Button(t *styles.Styles, opts ButtonOpts) string {
24	// Select style based on selection state
25	style := t.ButtonBlur
26	if opts.Selected {
27		style = t.ButtonFocus
28	}
29
30	text := opts.Text
31	if opts.Padding == 0 {
32		opts.Padding = 2
33	}
34
35	// the index is out of bound
36	if opts.UnderlineIndex > -1 && opts.UnderlineIndex > len(text)-1 {
37		opts.UnderlineIndex = -1
38	}
39
40	text = style.Padding(0, opts.Padding).Render(text)
41
42	if opts.UnderlineIndex != -1 {
43		text = lipgloss.StyleRanges(text, lipgloss.NewRange(opts.Padding+opts.UnderlineIndex, opts.Padding+opts.UnderlineIndex+1, style.Underline(true)))
44	}
45
46	return text
47}
48
49// ButtonGroup creates a row of selectable buttons
50// Spacing is the separator between buttons
51// Use "  " or similar for horizontal layout
52// Use "\n"  for vertical layout
53// Defaults to "  " (horizontal)
54func ButtonGroup(t *styles.Styles, buttons []ButtonOpts, spacing string) string {
55	if len(buttons) == 0 {
56		return ""
57	}
58
59	if spacing == "" {
60		spacing = "  "
61	}
62
63	parts := make([]string, len(buttons))
64	for i, button := range buttons {
65		parts[i] = Button(t, button)
66	}
67
68	return strings.Join(parts, spacing)
69}