From 6548f4e5012260621d3bae420f1f4686d2ea6df9 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Wed, 25 Sep 2024 13:41:35 -0700 Subject: [PATCH] feat(table): add option for custom StyleFunc --- table/table.go | 51 ++++++---- table/table_test.go | 97 ++++++++++++++++++- .../single_cell_styling.golden | 7 ++ .../cell_styling_by_content.golden | 7 ++ .../single_cell_styling.golden | 7 ++ 5 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 table/testdata/TestSetStyleFunc/single_cell_styling.golden create mode 100644 table/testdata/TestWithStyleFunc/cell_styling_by_content.golden create mode 100644 table/testdata/TestWithStyleFunc/single_cell_styling.golden diff --git a/table/table.go b/table/table.go index 0ae4f57..620631d 100644 --- a/table/table.go +++ b/table/table.go @@ -16,13 +16,14 @@ type Model struct { KeyMap KeyMap Help help.Model - yoffset int - height int - headers []string - rows [][]string - cursor int - focus bool - styles Styles + yoffset int + height int + headers []string + rows [][]string + cursor int + focus bool + styles Styles + styleFunc table.StyleFunc table *table.Table start int @@ -138,6 +139,12 @@ func (m *Model) SetStyles(s Styles) { m.table.BorderHeader(s.BorderHeader) } +// SetStyleFunc sets the table's custom StyleFunc. Use this for conditional +// styling e.g. styling a cell by its contents or by index. +func (m *Model) SetStyleFunc(s table.StyleFunc) { + m.styleFunc = s +} + // SetBorder is a shorthand function for setting or unsetting borders on a // table. The arguments work as follows: // @@ -311,6 +318,13 @@ func WithStyles(s Styles) Option { } } +// WithStyleFunc sets the table StyleFunc for conditional styling. +func WithStyleFunc(s table.StyleFunc) Option { + return func(m *Model) { + m.SetStyleFunc(s) + } +} + // WithKeyMap sets the key map. func WithKeyMap(km KeyMap) Option { return func(m *Model) { @@ -369,16 +383,19 @@ func (m *Model) Blur() { // View renders the component. func (m Model) View() string { - m.table.StyleFunc(func(row, col int) lipgloss.Style { - if row == table.HeaderRow { - return m.styles.Header - } - if row == m.cursor { - return m.styles.Selected - } - return m.styles.Cell - }) - + if m.styleFunc != nil { + m.table.StyleFunc(m.styleFunc) + } else { + m.table.StyleFunc(func(row, col int) lipgloss.Style { + if row == table.HeaderRow { + return m.styles.Header + } + if row == m.cursor { + return m.styles.Selected + } + return m.styles.Cell + }) + } return m.table.String() } diff --git a/table/table_test.go b/table/table_test.go index d40132e..80dcc69 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -4,6 +4,8 @@ import ( "reflect" "testing" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/exp/golden" ) @@ -136,7 +138,7 @@ func TestTableAlignment(t *testing.T) { s := DefaultStyles() s.BorderHeader = false biscuits := New( - WithHeight(5), + WithHeight(10), WithColumns([]Column{ {Title: "Name", Width: 25}, {Title: "Country of Origin", Width: 16}, @@ -174,3 +176,96 @@ func TestTableAlignment(t *testing.T) { golden.RequireEqual(t, []byte(got)) }) } + +func TestSetStyleFunc(t *testing.T) { + t.Run("single cell styling", func(t *testing.T) { + s := DefaultStyles() + s.BorderHeader = false + biscuits := New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + ) + biscuits.SetStyleFunc(func(row, col int) lipgloss.Style { + if row == table.HeaderRow { + // TODO this should be exported to be usable outside of the lib. + return s.Header + } + if row == 1 && col == 1 { + return s.Cell.Bold(true) + } + return s.Cell + }) + golden.RequireEqual(t, []byte(biscuits.View())) + }) +} + +func TestWithStyleFunc(t *testing.T) { + t.Run("single cell styling", func(t *testing.T) { + s := DefaultStyles() + s.BorderHeader = false + biscuits := New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows([]Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + }), + WithStyleFunc(func(row, col int) lipgloss.Style { + if row == table.HeaderRow { + return s.Header + } + // TODO we should probably make it possible to retrieve Style + // from the model in case it has been modified from the + // defaults. + if row == 1 && col == 1 { + return s.Cell.Bold(true) + } + return s.Cell + })) + golden.RequireEqual(t, []byte(biscuits.View())) + }) + t.Run("cell styling by content", func(t *testing.T) { + rows := []Row{ + {"Chocolate Digestives", "UK", "Yes"}, + {"Tim Tams", "Australia", "No"}, + {"Hobnobs", "UK", "Yes"}, + } + s := DefaultStyles() + s.BorderHeader = false + biscuits := New( + WithColumns([]Column{ + {Title: "Name", Width: 25}, + {Title: "Country of Origin", Width: 16}, + {Title: "Dunk-able", Width: 12}, + }), + WithRows(rows), + WithStyleFunc(func(row, col int) lipgloss.Style { + if row == table.HeaderRow { + return s.Header + } + // TODO we should probably make it possible to retrieve Style + // from the model in case it has been modified from the + // defaults. + + // you need to pre-define the rows for this to be accessible in + // WithStyleFunc + if rows[row][col] == "Yes" { + return s.Cell.Bold(true) + } + return s.Cell + })) + golden.RequireEqual(t, []byte(biscuits.View())) + }) +} diff --git a/table/testdata/TestSetStyleFunc/single_cell_styling.golden b/table/testdata/TestSetStyleFunc/single_cell_styling.golden new file mode 100644 index 0000000..908d313 --- /dev/null +++ b/table/testdata/TestSetStyleFunc/single_cell_styling.golden @@ -0,0 +1,7 @@ +╭──────────────────────┬───────────────────┬───────────╮ +│ Name │ Country of Origin │ Dunk-able │ +├──────────────────────┼───────────────────┼───────────┤ +│ Chocolate Digestives │ UK │ Yes │ +│ Tim Tams │ Australia │ No │ +│ Hobnobs │ UK │ Yes │ +╰──────────────────────┴───────────────────┴───────────╯ \ No newline at end of file diff --git a/table/testdata/TestWithStyleFunc/cell_styling_by_content.golden b/table/testdata/TestWithStyleFunc/cell_styling_by_content.golden new file mode 100644 index 0000000..d52cef3 --- /dev/null +++ b/table/testdata/TestWithStyleFunc/cell_styling_by_content.golden @@ -0,0 +1,7 @@ +╭──────────────────────┬───────────────────┬───────────╮ +│ Name │ Country of Origin │ Dunk-able │ +├──────────────────────┼───────────────────┼───────────┤ +│ Chocolate Digestives │ UK │ Yes │ +│ Tim Tams │ Australia │ No │ +│ Hobnobs │ UK │ Yes │ +╰──────────────────────┴───────────────────┴───────────╯ \ No newline at end of file diff --git a/table/testdata/TestWithStyleFunc/single_cell_styling.golden b/table/testdata/TestWithStyleFunc/single_cell_styling.golden new file mode 100644 index 0000000..908d313 --- /dev/null +++ b/table/testdata/TestWithStyleFunc/single_cell_styling.golden @@ -0,0 +1,7 @@ +╭──────────────────────┬───────────────────┬───────────╮ +│ Name │ Country of Origin │ Dunk-able │ +├──────────────────────┼───────────────────┼───────────┤ +│ Chocolate Digestives │ UK │ Yes │ +│ Tim Tams │ Australia │ No │ +│ Hobnobs │ UK │ Yes │ +╰──────────────────────┴───────────────────┴───────────╯ \ No newline at end of file