feat(ui): add mouse click handling to lazy list items

Ayman Bagabas created

Change summary

internal/ui/lazylist/item.go | 12 +++++++++++-
internal/ui/lazylist/list.go |  7 +++++++
internal/ui/model/items.go   |  7 ++++---
3 files changed, 22 insertions(+), 4 deletions(-)

Detailed changes

internal/ui/lazylist/item.go 🔗

@@ -1,6 +1,9 @@
 package lazylist
 
-import "charm.land/lipgloss/v2"
+import (
+	"charm.land/lipgloss/v2"
+	"github.com/charmbracelet/x/ansi"
+)
 
 // Item represents a single item in the lazy-loaded list.
 type Item interface {
@@ -22,3 +25,10 @@ type HighlightStylable interface {
 	// HighlightStyle returns the style to apply for highlighted regions.
 	HighlightStyle() lipgloss.Style
 }
+
+// MouseClickable represents an item that can handle mouse click events.
+type MouseClickable interface {
+	// HandleMouseClick processes a mouse click event at the given coordinates.
+	// It returns true if the event was handled, false otherwise.
+	HandleMouseClick(btn ansi.MouseButton, x, y int) bool
+}

internal/ui/lazylist/list.go 🔗

@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"charm.land/lipgloss/v2"
+	"github.com/charmbracelet/x/ansi"
 )
 
 // List represents a list of items that can be lazily rendered. A list is
@@ -550,6 +551,12 @@ func (l *List) HandleMouseDown(x, y int) bool {
 	// Select the clicked item
 	l.SetSelected(itemIdx)
 
+	if clickable, ok := l.items[itemIdx].(MouseClickable); ok {
+		clickable.HandleMouseClick(ansi.MouseButton1, x, itemY)
+		l.items[itemIdx] = clickable.(Item)
+		l.invalidateItem(itemIdx)
+	}
+
 	return true
 }
 

internal/ui/model/items.go 🔗

@@ -351,6 +351,7 @@ type SectionHeaderItem struct {
 	duration        time.Duration
 	isSectionHeader bool
 	sty             *styles.Styles
+	content         string
 }
 
 // NewSectionHeaderItem creates a new section header item.
@@ -387,13 +388,13 @@ func (s *SectionHeaderItem) BlurStyle() lipgloss.Style {
 
 // Render implements lazylist.Item.
 func (s *SectionHeaderItem) Render(width int) string {
-	content := s.sty.Chat.Message.SectionHeader.Render(fmt.Sprintf("%s %s %s",
+	content := fmt.Sprintf("%s %s %s",
 		s.sty.Subtle.Render(styles.ModelIcon),
 		s.sty.Muted.Render(s.modelName),
 		s.sty.Subtle.Render(s.duration.String()),
-	))
+	)
 
-	return content
+	return s.sty.Chat.Message.SectionHeader.Render(content)
 }
 
 // GetMessageItems extracts [MessageItem]s from a [message.Message]. It returns