quit_dialog.go

  1package ui
  2
  3import (
  4	"github.com/charmbracelet/bubbles/v2/key"
  5	tea "github.com/charmbracelet/bubbletea/v2"
  6	"github.com/charmbracelet/lipgloss/v2"
  7)
  8
  9// QuitDialogKeyMap represents key bindings for the quit dialog.
 10type QuitDialogKeyMap struct {
 11	LeftRight,
 12	EnterSpace,
 13	Yes,
 14	No,
 15	Tab,
 16	Close key.Binding
 17}
 18
 19// DefaultQuitKeyMap returns the default key bindings for the quit dialog.
 20func DefaultQuitKeyMap() QuitDialogKeyMap {
 21	return QuitDialogKeyMap{
 22		LeftRight: key.NewBinding(
 23			key.WithKeys("left", "right"),
 24			key.WithHelp("←/→", "switch options"),
 25		),
 26		EnterSpace: key.NewBinding(
 27			key.WithKeys("enter", " "),
 28			key.WithHelp("enter/space", "confirm"),
 29		),
 30		Yes: key.NewBinding(
 31			key.WithKeys("y", "Y", "ctrl+c"),
 32			key.WithHelp("y/Y/ctrl+c", "yes"),
 33		),
 34		No: key.NewBinding(
 35			key.WithKeys("n", "N"),
 36			key.WithHelp("n/N", "no"),
 37		),
 38		Tab: key.NewBinding(
 39			key.WithKeys("tab"),
 40			key.WithHelp("tab", "switch options"),
 41		),
 42		Close: key.NewBinding(
 43			key.WithKeys("esc", "alt+esc"),
 44			key.WithHelp("esc", "cancel"),
 45		),
 46	}
 47}
 48
 49// QuitDialog represents a confirmation dialog for quitting the application.
 50type QuitDialog struct {
 51	com        *Common
 52	keyMap     QuitDialogKeyMap
 53	selectedNo bool // true if "No" button is selected
 54}
 55
 56// NewQuitDialog creates a new quit confirmation dialog.
 57func NewQuitDialog(com *Common) *QuitDialog {
 58	q := &QuitDialog{
 59		com:    com,
 60		keyMap: DefaultQuitKeyMap(),
 61	}
 62	return q
 63}
 64
 65// ID implements [Model].
 66func (*QuitDialog) ID() string {
 67	return "quit"
 68}
 69
 70// Update implements [Model].
 71func (q *QuitDialog) Update(msg tea.Msg) (Dialog, tea.Cmd) {
 72	switch msg := msg.(type) {
 73	case tea.KeyPressMsg:
 74		switch {
 75		case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
 76			q.selectedNo = !q.selectedNo
 77			return q, nil
 78		case key.Matches(msg, q.keyMap.EnterSpace):
 79			if !q.selectedNo {
 80				return q, tea.Quit
 81			}
 82			return nil, nil
 83		case key.Matches(msg, q.keyMap.Yes):
 84			return q, tea.Quit
 85		case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
 86			return nil, nil
 87		}
 88	}
 89
 90	return q, nil
 91}
 92
 93// View implements [Model].
 94func (q *QuitDialog) View() string {
 95	const question = "Are you sure you want to quit?"
 96	baseStyle := q.com.Styles.Base
 97	yesStyle := q.com.Styles.ButtonSelected
 98	noStyle := q.com.Styles.ButtonUnselected
 99
100	if q.selectedNo {
101		noStyle = q.com.Styles.ButtonSelected
102		yesStyle = q.com.Styles.ButtonUnselected
103	}
104
105	const horizontalPadding = 3
106	yesButton := yesStyle.PaddingLeft(horizontalPadding).Underline(true).Render("Y") +
107		yesStyle.PaddingRight(horizontalPadding).Render("ep!")
108	noButton := noStyle.PaddingLeft(horizontalPadding).Underline(true).Render("N") +
109		noStyle.PaddingRight(horizontalPadding).Render("ope")
110
111	buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
112		lipgloss.JoinHorizontal(lipgloss.Center, yesButton, "  ", noButton),
113	)
114
115	content := baseStyle.Render(
116		lipgloss.JoinVertical(
117			lipgloss.Center,
118			question,
119			"",
120			buttons,
121		),
122	)
123
124	return q.com.Styles.BorderFocus.Render(content)
125}
126
127// ShortHelp implements [help.KeyMap].
128func (q *QuitDialog) ShortHelp() []key.Binding {
129	return []key.Binding{
130		q.keyMap.LeftRight,
131		q.keyMap.EnterSpace,
132	}
133}
134
135// FullHelp implements [help.KeyMap].
136func (q *QuitDialog) FullHelp() [][]key.Binding {
137	return [][]key.Binding{
138		{q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
139		{q.keyMap.Tab, q.keyMap.Close},
140	}
141}