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