1package quit
2
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
5 tea "github.com/charmbracelet/bubbletea/v2"
6 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
7 "github.com/charmbracelet/crush/internal/tui/styles"
8 "github.com/charmbracelet/crush/internal/tui/util"
9 "github.com/charmbracelet/lipgloss/v2"
10)
11
12const (
13 question = "Are you sure you want to quit?"
14 QuitDialogID dialogs.DialogID = "quit"
15)
16
17// QuitDialog represents a confirmation dialog for quitting the application.
18type QuitDialog interface {
19 dialogs.DialogModel
20}
21
22type quitDialogCmp struct {
23 wWidth int
24 wHeight int
25
26 selectedNo bool // true if "No" button is selected
27 keymap KeyMap
28}
29
30// NewQuitDialog creates a new quit confirmation dialog.
31func NewQuitDialog() QuitDialog {
32 return &quitDialogCmp{
33 selectedNo: true, // Default to "No" for safety
34 keymap: DefaultKeymap(),
35 }
36}
37
38func (q *quitDialogCmp) Init() tea.Cmd {
39 return nil
40}
41
42// Update handles keyboard input for the quit dialog.
43func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
44 switch msg := msg.(type) {
45 case tea.WindowSizeMsg:
46 q.wWidth = msg.Width
47 q.wHeight = msg.Height
48 case tea.KeyPressMsg:
49 switch {
50 case key.Matches(msg, q.keymap.LeftRight, q.keymap.Tab):
51 q.selectedNo = !q.selectedNo
52 return q, nil
53 case key.Matches(msg, q.keymap.EnterSpace):
54 if !q.selectedNo {
55 return q, tea.Quit
56 }
57 return q, util.CmdHandler(dialogs.CloseDialogMsg{})
58 case key.Matches(msg, q.keymap.Yes):
59 return q, tea.Quit
60 case key.Matches(msg, q.keymap.No, q.keymap.Close):
61 return q, util.CmdHandler(dialogs.CloseDialogMsg{})
62 }
63 }
64 return q, nil
65}
66
67// View renders the quit dialog with Yes/No buttons.
68func (q *quitDialogCmp) View() string {
69 t := styles.CurrentTheme()
70 baseStyle := t.S().Base
71 yesStyle := t.S().Text
72 noStyle := yesStyle
73
74 if q.selectedNo {
75 noStyle = noStyle.Foreground(t.White).Background(t.Secondary)
76 yesStyle = yesStyle.Background(t.BgSubtle)
77 } else {
78 yesStyle = yesStyle.Foreground(t.White).Background(t.Secondary)
79 noStyle = noStyle.Background(t.BgSubtle)
80 }
81
82 yesButton := yesStyle.Padding(0, 1).Render("Yep!")
83 noButton := noStyle.Padding(0, 1).Render("Nope")
84
85 buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
86 lipgloss.JoinHorizontal(lipgloss.Center, yesButton, " ", noButton),
87 )
88
89 content := baseStyle.Render(
90 lipgloss.JoinVertical(
91 lipgloss.Center,
92 question,
93 "",
94 buttons,
95 ),
96 )
97
98 quitDialogStyle := baseStyle.
99 Padding(1, 2).
100 Border(lipgloss.RoundedBorder()).
101 BorderForeground(t.BorderFocus)
102
103 return quitDialogStyle.Render(content)
104}
105
106func (q *quitDialogCmp) Position() (int, int) {
107 row := q.wHeight / 2
108 row -= 7 / 2
109 col := q.wWidth / 2
110 col -= (lipgloss.Width(question) + 4) / 2
111
112 return row, col
113}
114
115func (q *quitDialogCmp) ID() dialogs.DialogID {
116 return QuitDialogID
117}