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