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 const horizontalPadding = 3
83 yesButton := yesStyle.Padding(0, horizontalPadding).Render("Yep!")
84 noButton := noStyle.Padding(0, horizontalPadding).Render("Nope")
85
86 buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
87 lipgloss.JoinHorizontal(lipgloss.Center, yesButton, " ", noButton),
88 )
89
90 content := baseStyle.Render(
91 lipgloss.JoinVertical(
92 lipgloss.Center,
93 question,
94 "",
95 buttons,
96 ),
97 )
98
99 quitDialogStyle := baseStyle.
100 Padding(1, 2).
101 Border(lipgloss.RoundedBorder()).
102 BorderForeground(t.BorderFocus)
103
104 return quitDialogStyle.Render(content)
105}
106
107func (q *quitDialogCmp) Position() (int, int) {
108 row := q.wHeight / 2
109 row -= 7 / 2
110 col := q.wWidth / 2
111 col -= (lipgloss.Width(question) + 4) / 2
112
113 return row, col
114}
115
116func (q *quitDialogCmp) ID() dialogs.DialogID {
117 return QuitDialogID
118}