Skip to content

Commit c18582d

Browse files
committed
feat(tui): add goto modal for slide navigation
- add `GoTo` struct with text input for slide number entry - implement modal dialog with centered overlay positioning - add keyboard controls for enter to confirm and esc to cancel - include input validation for slide number bounds checking - style modal with rounded borders and help text display
1 parent 92c6019 commit c18582d

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

internal/tui/goto.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package tui
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/charmbracelet/bubbles/textinput"
9+
tea "github.com/charmbracelet/bubbletea"
10+
"github.com/charmbracelet/lipgloss"
11+
"github.com/museslabs/kyma/internal/config"
12+
)
13+
14+
var (
15+
gotoModalTitleStyle = lipgloss.NewStyle().
16+
MarginBottom(1)
17+
gotoModalInputStyle = lipgloss.NewStyle().
18+
Border(lipgloss.RoundedBorder()).
19+
Padding(0, 1)
20+
veryMutedStyle = lipgloss.NewStyle().
21+
Foreground(lipgloss.Color("#3C3C3C"))
22+
mutedStyle = lipgloss.NewStyle().
23+
Foreground(lipgloss.Color("#5C5C5C"))
24+
)
25+
26+
type GoTo struct {
27+
input textinput.Model
28+
choice int
29+
quitting bool
30+
maxSlide int
31+
showing bool
32+
}
33+
34+
func NewGoTo(maxSlides int) GoTo {
35+
ti := textinput.New()
36+
ti.Placeholder = "Enter slide number..."
37+
ti.Focus()
38+
ti.CharLimit = 10
39+
ti.Width = 20
40+
41+
return GoTo{
42+
input: ti,
43+
choice: -1,
44+
maxSlide: maxSlides,
45+
}
46+
}
47+
48+
func (m GoTo) Init() tea.Cmd {
49+
return textinput.Blink
50+
}
51+
52+
func (m GoTo) Update(msg tea.Msg) (GoTo, tea.Cmd) {
53+
var cmd tea.Cmd
54+
55+
switch msg := msg.(type) {
56+
case tea.KeyMsg:
57+
switch msg.String() {
58+
case "enter":
59+
if slideNum, err := strconv.Atoi(m.input.Value()); err == nil && slideNum > 0 {
60+
m.choice = slideNum
61+
}
62+
m.quitting = true
63+
m.showing = false
64+
return m, tea.Quit
65+
case "esc", "ctrl+c":
66+
m.quitting = true
67+
m.showing = false
68+
return m, tea.Quit
69+
}
70+
}
71+
72+
m.input, cmd = m.input.Update(msg)
73+
return m, cmd
74+
}
75+
76+
func (m GoTo) View() string {
77+
title := gotoModalTitleStyle.Render("Go to Slide")
78+
79+
input := gotoModalInputStyle.Render(m.input.View())
80+
81+
enterHelp := lipgloss.JoinHorizontal(
82+
lipgloss.Center,
83+
mutedStyle.Render("enter"),
84+
" ",
85+
veryMutedStyle.MarginTop(1).Render("• go"),
86+
)
87+
88+
escHelp := lipgloss.JoinHorizontal(
89+
lipgloss.Center,
90+
mutedStyle.Render("esc"),
91+
" ",
92+
veryMutedStyle.MarginTop(1).Render("• cancel"),
93+
)
94+
95+
help := veryMutedStyle.MarginTop(1).Render(
96+
lipgloss.JoinHorizontal(lipgloss.Center, enterHelp, " ", escHelp),
97+
)
98+
99+
maxInfo := mutedStyle.Render(fmt.Sprintf("(1-%d slides available)", m.maxSlide))
100+
101+
return lipgloss.JoinVertical(
102+
lipgloss.Center,
103+
title,
104+
input,
105+
help,
106+
maxInfo,
107+
)
108+
}
109+
110+
func (m GoTo) Show(slideView string, width, height int) string {
111+
view := m.View()
112+
modalContent := lipgloss.NewStyle().
113+
Border(lipgloss.RoundedBorder()).
114+
BorderForeground(lipgloss.Color(config.DefaultBorderColor)).
115+
Padding(3).
116+
Render(view)
117+
118+
_, modalWidth := getLines(modalContent)
119+
modalHeight := strings.Count(modalContent, "\n") + 1
120+
121+
centerX := (width - modalWidth) / 2
122+
centerY := (height - modalHeight) / 2
123+
124+
return placeOverlay(centerX, centerY, modalContent, slideView)
125+
}
126+
127+
func (m GoTo) IsShowing() bool {
128+
return m.showing
129+
}
130+
131+
func (m GoTo) SetShowing(showing bool) GoTo {
132+
m.showing = showing
133+
return m
134+
}
135+
136+
func (m GoTo) Choice() int {
137+
return m.choice
138+
}
139+
140+
func (m GoTo) Quitting() bool {
141+
return m.quitting
142+
}

0 commit comments

Comments
 (0)