From 8fdc81c836c0c2e4a2926d02427fd31c6f7f94d5 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 29 Oct 2024 15:52:14 -0400 Subject: [PATCH] feat: add a Styler interface for styling bubbles This change introduces a Styler interface that allows users to define custom styles for bubbles. The interface is implemented by the StylerFunc type, which is a function type that takes a boolean argument and returns a value of any type. This allows a user to define a custom StylerFunc that returns a custom style for a bubble based on the background color of the terminal. --- bubbles.go | 14 ++++++++++++++ help/help.go | 17 ++++------------- help/help_test.go | 2 +- list/defaultitem.go | 10 ++++------ list/list.go | 8 +++----- list/list_test.go | 10 +++++----- textarea/textarea.go | 4 ++-- textarea/textarea_test.go | 2 +- 8 files changed, 34 insertions(+), 33 deletions(-) diff --git a/bubbles.go b/bubbles.go index 9b9e8d4..4119935 100644 --- a/bubbles.go +++ b/bubbles.go @@ -1 +1,15 @@ package bubbles + +// Styler represents an interface for styling bubbles. +type Styler[T any] interface { + Styles(isDark bool) T +} + +// StylerFunc is a function type that implements the Styler interface. +type StylerFunc[T any] func(isDark bool) T + +// Styles calls the function. +// It implements the Styler interface. +func (f StylerFunc[T]) Styles(isDark bool) T { + return f(isDark) +} diff --git a/help/help.go b/help/help.go index d9e814b..2d1201f 100644 --- a/help/help.go +++ b/help/help.go @@ -41,7 +41,8 @@ type Styles struct { FullSeparator lipgloss.Style } -func newStyles(isDark bool) Styles { +// DefaultStyles returns a set of default styles for the help bubble. +func DefaultStyles(isDark bool) Styles { lightDark := lipgloss.LightDark(isDark) keyStyle := lipgloss.NewStyle().Foreground(lightDark("#909090", "#626262")) @@ -59,16 +60,6 @@ func newStyles(isDark bool) Styles { } } -// DefaultDarkStyles returns a set of default styles for dark backgrounds. -func DefaultDarkStyles() Styles { - return newStyles(true) -} - -// DefaultLightStyles returns a set of default styles for light backgrounds. -func DefaultLightStyles() Styles { - return newStyles(false) -} - // Model contains the state of the help view. type Model struct { Width int @@ -85,12 +76,12 @@ type Model struct { } // New creates a new help view with some useful defaults. -func New() Model { +func New(isDark bool) Model { return Model{ ShortSeparator: " • ", FullSeparator: " ", Ellipsis: "…", - Styles: DefaultDarkStyles(), + Styles: DefaultStyles(isDark), } } diff --git a/help/help_test.go b/help/help_test.go index 9214e0f..a76c3db 100644 --- a/help/help_test.go +++ b/help/help_test.go @@ -10,7 +10,7 @@ import ( ) func TestFullHelp(t *testing.T) { - m := New() + m := New(true) m.FullSeparator = " | " k := key.WithKeys("x") kb := [][]key.Binding{ diff --git a/list/defaultitem.go b/list/defaultitem.go index e28d1a0..6a417a8 100644 --- a/list/defaultitem.go +++ b/list/defaultitem.go @@ -94,16 +94,14 @@ type DefaultDelegate struct { } // NewDefaultDelegate creates a new delegate with default styles. -func NewDefaultDelegate() DefaultDelegate { +func NewDefaultDelegate(isDark bool) DefaultDelegate { const defaultHeight = 2 const defaultSpacing = 1 return DefaultDelegate{ ShowDescription: true, - // XXX: Let the user choose between light and dark colors. We've - // temporarily hardcoded the dark colors here. - Styles: NewDefaultItemStyles(true), - height: defaultHeight, - spacing: defaultSpacing, + Styles: NewDefaultItemStyles(isDark), + height: defaultHeight, + spacing: defaultSpacing, } } diff --git a/list/list.go b/list/list.go index 671e153..f802b8a 100644 --- a/list/list.go +++ b/list/list.go @@ -196,10 +196,8 @@ type Model struct { } // New returns a new model with sensible defaults. -func New(items []Item, delegate ItemDelegate, width, height int) Model { - // XXX: Let the user choose between light and dark colors. We've - // temporarily hardcoded the dark colors here. - styles := DefaultStyles(true) +func New(items []Item, delegate ItemDelegate, width, height int, isDark bool) Model { + styles := DefaultStyles(isDark) sp := spinner.New() sp.Spinner = spinner.Line @@ -239,7 +237,7 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { items: items, Paginator: p, spinner: sp, - Help: help.New(), + Help: help.New(isDark), } m.updatePagination() diff --git a/list/list_test.go b/list/list_test.go index d1d8524..e1f78e7 100644 --- a/list/list_test.go +++ b/list/list_test.go @@ -30,7 +30,7 @@ func (d itemDelegate) Render(w io.Writer, m Model, index int, listItem Item) { } func TestStatusBarItemName(t *testing.T) { - list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10, true) expected := "2 items" if !strings.Contains(list.statusView(), expected) { t.Fatalf("Error: expected view to contain %s", expected) @@ -44,7 +44,7 @@ func TestStatusBarItemName(t *testing.T) { } func TestStatusBarWithoutItems(t *testing.T) { - list := New([]Item{}, itemDelegate{}, 10, 10) + list := New([]Item{}, itemDelegate{}, 10, 10, true) expected := "No items" if !strings.Contains(list.statusView(), expected) { @@ -53,7 +53,7 @@ func TestStatusBarWithoutItems(t *testing.T) { } func TestCustomStatusBarItemName(t *testing.T) { - list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10, true) list.SetStatusBarItemName("connection", "connections") expected := "2 connections" @@ -77,7 +77,7 @@ func TestCustomStatusBarItemName(t *testing.T) { func TestSetFilterText(t *testing.T) { tc := []Item{item("foo"), item("bar"), item("baz")} - list := New(tc, itemDelegate{}, 10, 10) + list := New(tc, itemDelegate{}, 10, 10, true) list.SetFilterText("ba") list.SetFilterState(Unfiltered) @@ -102,7 +102,7 @@ func TestSetFilterText(t *testing.T) { func TestSetFilterState(t *testing.T) { tc := []Item{item("foo"), item("bar"), item("baz")} - list := New(tc, itemDelegate{}, 10, 10) + list := New(tc, itemDelegate{}, 10, 10, true) list.SetFilterText("ba") list.SetFilterState(Unfiltered) diff --git a/textarea/textarea.go b/textarea/textarea.go index 71694a3..a3ec8b7 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -283,12 +283,12 @@ type Model struct { } // New creates a new model with default settings. -func New() Model { +func New(isDark bool) Model { vp := viewport.New(0, 0) vp.KeyMap = viewport.KeyMap{} cur := cursor.New() - styles := DefaultStyles(true) + styles := DefaultStyles(isDark) m := Model{ CharLimit: defaultCharLimit, diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 1572265..3f3a643 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -1703,7 +1703,7 @@ func TestView(t *testing.T) { } func newTextArea() Model { - textarea := New() + textarea := New(true) textarea.Prompt = "> " textarea.Placeholder = "Hello, World!"