Files
ilovetui/examples/layout/fullapp/sidebar/sidebar.go
T

102 lines
2.9 KiB
Go

package sidebar
import (
"charm.land/bubbles/v2/key"
bubbleslist "charm.land/bubbles/v2/list"
tea "charm.land/bubbletea/v2"
"github.com/anotherhadi/ilovetui/bubbles"
"github.com/anotherhadi/ilovetui/examples/layout/fullapp/router"
"github.com/anotherhadi/ilovetui/layout"
)
// page is a sidebar entry: just an id (matching one of router.Model's
// own page keys) and a label. Model has no idea what a page actually is or
// how it's built - that's router's business entirely, this only needs
// to name it.
type page struct {
id string
title string
}
func (p page) Title() string { return p.title }
func (p page) Description() string { return "" }
func (p page) FilterValue() string { return p.title }
type Model struct {
id string // learned from SizeMsg.ID, needed as RequestFocusMsg.Source
list bubbleslist.Model
w, h int
focused bool
}
func New() *Model {
items := []bubbleslist.Item{
page{id: "first", title: "First"},
page{id: "second", title: "Second"},
page{id: "third", title: "Third"},
}
list := bubbles.NewList(items, 0, 0)
list.Title = "Sidebar"
// list's own built-in help footer is redundant - Model already feeds
// layout's single centralized help bar via HelpBindings below - and at
// a narrow sidebar width it word-wraps onto multiple lines, which
// list.SetSize doesn't account for: list.View() ends up taller than
// the height it was given, breaking layout's "render exactly h" rule
// and pushing the border past the bottom of the pane.
list.SetShowHelp(false)
return &Model{list: list}
}
func (m *Model) Init() tea.Cmd { return nil }
func (m *Model) Update(msg tea.Msg) (layout.Pane, tea.Cmd) {
switch msg := msg.(type) {
case layout.SizeMsg:
m.id, m.w, m.h = msg.ID, msg.Width, msg.Height
m.list.SetSize(m.w-2, m.h-2)
case layout.FocusMsg:
m.focused = true
case layout.BlurMsg:
m.focused = false
}
if !m.focused {
return m, nil
}
if key, ok := msg.(tea.KeyPressMsg); ok && key.String() == "enter" {
if selected, ok := m.list.SelectedItem().(page); ok {
// tea.Sequence, not tea.Batch: the page switch must land
// before the focus jump, or RequestFocusMsg could cascade
// into router while it's still showing the previous page
// (Batch runs both concurrently with no ordering guarantee).
return m, tea.Sequence(
func() tea.Msg {
return layout.SendMsg{Target: "router", Msg: router.SelectMsg{Page: selected.id}}
},
func() tea.Msg {
return layout.RequestFocusMsg{Source: m.id, Target: "router"}
},
)
}
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m *Model) View() string {
return layout.Bordered(m.focused, m.w, m.h, m.list.View())
}
func (m *Model) HelpBindings() []key.Binding {
return []key.Binding{
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "open page")),
key.NewBinding(key.WithKeys("j"), key.WithHelp("j", "go down")),
key.NewBinding(key.WithKeys("k"), key.WithHelp("k", "go up")),
}
}