1package dialog
2
3import (
4 "charm.land/bubbles/v2/key"
5 tea "charm.land/bubbletea/v2"
6 "charm.land/lipgloss/v2"
7 "github.com/charmbracelet/crush/internal/ui/common"
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 selectedNo: true,
66 }
67 return q
68}
69
70// ID implements [Model].
71func (*Quit) ID() string {
72 return QuitDialogID
73}
74
75// Update implements [Model].
76func (q *Quit) Update(msg tea.Msg) (Action, tea.Cmd) {
77 switch msg := msg.(type) {
78 case tea.KeyPressMsg:
79 switch {
80 case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
81 q.selectedNo = !q.selectedNo
82 return Action{}, nil
83 case key.Matches(msg, q.keyMap.EnterSpace):
84 if !q.selectedNo {
85 return Action{}, tea.Quit
86 }
87 return Action{}, nil
88 case key.Matches(msg, q.keyMap.Yes):
89 return Action{}, tea.Quit
90 case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
91 return Action{}, nil
92 }
93 }
94
95 return Action{}, nil
96}
97
98// Layer implements [Model].
99func (q *Quit) Layer() *lipgloss.Layer {
100 const question = "Are you sure you want to quit?"
101 baseStyle := q.com.Styles.Base
102 buttonOpts := []common.ButtonOpts{
103 {Text: "Yep!", Selected: !q.selectedNo, Padding: 3},
104 {Text: "Nope", Selected: q.selectedNo, Padding: 3},
105 }
106 buttons := common.ButtonGroup(q.com.Styles, buttonOpts, " ")
107 content := baseStyle.Render(
108 lipgloss.JoinVertical(
109 lipgloss.Center,
110 question,
111 "",
112 buttons,
113 ),
114 )
115
116 return lipgloss.NewLayer(q.com.Styles.BorderFocus.Render(content))
117}
118
119// ShortHelp implements [help.KeyMap].
120func (q *Quit) ShortHelp() []key.Binding {
121 return []key.Binding{
122 q.keyMap.LeftRight,
123 q.keyMap.EnterSpace,
124 }
125}
126
127// FullHelp implements [help.KeyMap].
128func (q *Quit) FullHelp() [][]key.Binding {
129 return [][]key.Binding{
130 {q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
131 {q.keyMap.Tab, q.keyMap.Close},
132 }
133}