Refactor picker
This commit is contained in:
144
components/picker/picker.go
Normal file
144
components/picker/picker.go
Normal file
@ -0,0 +1,144 @@
|
||||
package picker
|
||||
|
||||
import (
|
||||
"tasksquire/common"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
text string
|
||||
}
|
||||
|
||||
func NewItem(text string) Item { return Item{text: text} }
|
||||
func (i Item) Title() string { return i.text }
|
||||
func (i Item) Description() string { return "" }
|
||||
func (i Item) FilterValue() string { return i.text }
|
||||
|
||||
type Picker struct {
|
||||
common *common.Common
|
||||
list list.Model
|
||||
onSelect func(list.Item) tea.Cmd
|
||||
title string
|
||||
filterByDefault bool
|
||||
}
|
||||
|
||||
type PickerOption func(*Picker)
|
||||
|
||||
func WithFilterByDefault(enabled bool) PickerOption {
|
||||
return func(p *Picker) {
|
||||
p.filterByDefault = enabled
|
||||
}
|
||||
}
|
||||
|
||||
func New(
|
||||
c *common.Common,
|
||||
title string,
|
||||
items []list.Item,
|
||||
onSelect func(list.Item) tea.Cmd,
|
||||
opts ...PickerOption,
|
||||
) *Picker {
|
||||
delegate := list.NewDefaultDelegate()
|
||||
delegate.ShowDescription = false
|
||||
delegate.SetSpacing(0)
|
||||
|
||||
l := list.New(items, delegate, 0, 0)
|
||||
l.SetShowTitle(false)
|
||||
l.SetShowHelp(false)
|
||||
l.SetShowStatusBar(false)
|
||||
l.SetFilteringEnabled(true)
|
||||
|
||||
// Custom key for filtering (insert mode)
|
||||
l.KeyMap.Filter = key.NewBinding(
|
||||
key.WithKeys("i"),
|
||||
key.WithHelp("i", "filter"),
|
||||
)
|
||||
|
||||
p := &Picker{
|
||||
common: c,
|
||||
list: l,
|
||||
onSelect: onSelect,
|
||||
title: title,
|
||||
}
|
||||
|
||||
if c.TW.GetConfig().Get("uda.tasksquire.picker.filter_by_default") == "yes" {
|
||||
p.filterByDefault = true
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Picker) SetSize(width, height int) {
|
||||
// We do NOT set common.SetSize here, as we are a sub-component.
|
||||
|
||||
// Set list size. The parent is responsible for providing a reasonable size.
|
||||
// If this component is intended to fill a page, width/height will be large.
|
||||
// If it's a small embedded box, they will be small.
|
||||
// We apply a small margin for the title if needed, but for now we just pass through
|
||||
// minus a header gap if we render a title.
|
||||
|
||||
headerHeight := 2 // Title + gap
|
||||
p.list.SetSize(width, height-headerHeight)
|
||||
}
|
||||
|
||||
func (p *Picker) Init() tea.Cmd {
|
||||
if p.filterByDefault {
|
||||
return func() tea.Msg {
|
||||
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'i'}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Picker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
// If filtering, let the list handle keys (including Enter to stop filtering)
|
||||
if p.list.FilterState() == list.Filtering {
|
||||
if key.Matches(msg, p.common.Keymap.Ok) {
|
||||
items := p.list.VisibleItems()
|
||||
if len(items) == 1 {
|
||||
return p, p.onSelect(items[0])
|
||||
}
|
||||
}
|
||||
break // Pass to list.Update
|
||||
}
|
||||
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Ok):
|
||||
selectedItem := p.list.SelectedItem()
|
||||
if selectedItem == nil {
|
||||
return p, nil
|
||||
}
|
||||
return p, p.onSelect(selectedItem)
|
||||
}
|
||||
}
|
||||
|
||||
p.list, cmd = p.list.Update(msg)
|
||||
return p, cmd
|
||||
}
|
||||
|
||||
func (p *Picker) View() string {
|
||||
title := p.common.Styles.Form.Focused.Title.Render(p.title)
|
||||
return lipgloss.JoinVertical(lipgloss.Left, title, p.list.View())
|
||||
}
|
||||
|
||||
// SelectItemByFilterValue selects the item with the given filter value
|
||||
func (p *Picker) SelectItemByFilterValue(filterValue string) {
|
||||
items := p.list.Items()
|
||||
for i, item := range items {
|
||||
if item.FilterValue() == filterValue {
|
||||
p.list.Select(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user