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