From ef5b263af036828bfcf391a04dab1498b0a36334 Mon Sep 17 00:00:00 2001 From: Brandon Fulljames Date: Sat, 18 May 2024 09:40:06 +0000 Subject: [PATCH] Add row style func (#176) --- README.md | 3 +++ examples/updates/main.go | 35 +++++++++++++++++---------- table/model.go | 1 + table/options.go | 24 +++++++++++++++++++ table/row.go | 9 +++++++ table/view_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8d2f03b..a9e3467 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ determining overrides. The default base style is a basic right-alignment. [See the main feature example](./examples/features) to see styles and how they override each other. +Styles can also be applied via a style function which can be used to apply +zebra striping, data-specific formatting, etc. + Can be focused to highlight a row and navigate with up/down (and j/k). These keys can be customized with a KeyMap. diff --git a/examples/updates/main.go b/examples/updates/main.go index 207a9ac..608b5da 100644 --- a/examples/updates/main.go +++ b/examples/updates/main.go @@ -31,9 +31,30 @@ type Model struct { data []*SomeData } +func rowStyleFunc(input table.RowStyleFuncInput) lipgloss.Style { + calculatedStyle := lipgloss.NewStyle() + + switch input.Row.Data[columnKeyStatus] { + case "Critical": + calculatedStyle = styleCritical.Copy() + case "Stable": + calculatedStyle = styleStable.Copy() + case "Good": + calculatedStyle = styleGood.Copy() + } + + if input.Index%2 == 0 { + calculatedStyle = calculatedStyle.Background(lipgloss.Color("#222")) + } else { + calculatedStyle = calculatedStyle.Background(lipgloss.Color("#444")) + } + + return calculatedStyle +} + func NewModel() Model { return Model{ - table: table.New(generateColumns(0)), + table: table.New(generateColumns(0)).WithRowStyleFunc(rowStyleFunc), updateDelay: time.Second, } } @@ -152,18 +173,6 @@ func generateRowsFromData(data []*SomeData) []table.Row { columnKeyStatus: entry.Status, }) - // Highlight different statuses - switch entry.Status { - case "Critical": - row = row.WithStyle(styleCritical) - - case "Stable": - row = row.WithStyle(styleStable) - - case "Good": - row = row.WithStyle(styleGood) - } - rows = append(rows, row) } diff --git a/table/model.go b/table/model.go index b1934f8..d5e8937 100644 --- a/table/model.go +++ b/table/model.go @@ -40,6 +40,7 @@ type Model struct { baseStyle lipgloss.Style highlightStyle lipgloss.Style headerStyle lipgloss.Style + rowStyleFunc func(RowStyleFuncInput) lipgloss.Style border Border selectedText string unselectedText string diff --git a/table/options.go b/table/options.go index 0bf43ad..eed61d9 100644 --- a/table/options.go +++ b/table/options.go @@ -5,6 +5,30 @@ import ( "github.com/charmbracelet/lipgloss" ) +// RowStyleFuncInput is the input to the style function that can +// be applied to each row. This is useful for things like zebra +// striping or other data-based styles. +// +// Note that we use a struct here to allow for future expansion +// while keeping backwards compatibility. +type RowStyleFuncInput struct { + // Index is the index of the row, starting at 0. + Index int + + // Row is the full row data. + Row Row +} + +// WithRowStyleFunc sets a function that can be used to apply a style to each row +// based on the row data. This is useful for things like zebra striping or other +// data-based styles. It can be safely set to nil to remove it later. +// This style is applied after the base style and before individual row styles. +func (m Model) WithRowStyleFunc(f func(RowStyleFuncInput) lipgloss.Style) Model { + m.rowStyleFunc = f + + return m +} + // WithHighlightedRow sets the highlighted row to the given index. func (m Model) WithHighlightedRow(index int) Model { m.rowCursorIndex = index diff --git a/table/row.go b/table/row.go index ab3dd61..f2cf5d3 100644 --- a/table/row.go +++ b/table/row.go @@ -115,6 +115,15 @@ func (m Model) renderRow(rowIndex int, last bool) string { rowStyle := row.Style.Copy() + if m.rowStyleFunc != nil { + styleResult := m.rowStyleFunc(RowStyleFuncInput{ + Index: rowIndex, + Row: row, + }) + + rowStyle = rowStyle.Inherit(styleResult) + } + if m.focused && highlighted { rowStyle = rowStyle.Inherit(m.highlightStyle) } diff --git a/table/view_test.go b/table/view_test.go index 07014b1..fe07155 100644 --- a/table/view_test.go +++ b/table/view_test.go @@ -798,6 +798,57 @@ func TestSimple3x3StyleOverridesAsBaseColumnRowCell(t *testing.T) { assert.Equal(t, expectedTable, rendered) } +func TestStyleFuncAppliesAfterBaseStyleAndColStylesAndBeforeRowStyle(t *testing.T) { + styleFunc := func(input RowStyleFuncInput) lipgloss.Style { + if input.Index%2 == 0 { + return lipgloss.NewStyle().Align(lipgloss.Left) + } + + return lipgloss.NewStyle() + } + + model := New([]Column{ + NewColumn("1", "1", 6), + // This column style should be overridden by the style func + NewColumn("2", "2", 6).WithStyle(lipgloss.NewStyle().Align(lipgloss.Right)), + NewColumn("3", "3", 6), + }). + WithBaseStyle(lipgloss.NewStyle().Align(lipgloss.Center)). + WithRowStyleFunc(styleFunc) + + rows := []Row{} + + for rowIndex := 1; rowIndex <= 5; rowIndex++ { + rowData := RowData{} + + for columnIndex := 1; columnIndex <= 3; columnIndex++ { + id := fmt.Sprintf("%d", columnIndex) + + rowData[id] = fmt.Sprintf("%d,%d", columnIndex, rowIndex) + } + + rows = append(rows, NewRow(rowData)) + } + + rows[0] = rows[0].WithStyle(lipgloss.NewStyle().Align(lipgloss.Right)) + + model = model.WithRows(rows) + + const expectedTable = `┏━━━━━━┳━━━━━━┳━━━━━━┓ +┃ 1 ┃ 2┃ 3 ┃ +┣━━━━━━╋━━━━━━╋━━━━━━┫ +┃ 1,1┃ 2,1┃ 3,1┃ +┃ 1,2 ┃ 2,2┃ 3,2 ┃ +┃1,3 ┃2,3 ┃3,3 ┃ +┃ 1,4 ┃ 2,4┃ 3,4 ┃ +┃1,5 ┃2,5 ┃3,5 ┃ +┗━━━━━━┻━━━━━━┻━━━━━━┛` + + rendered := model.View() + + assert.Equal(t, expectedTable, rendered) +} + // This is a long test due to typing and multiple big table strings, that's okay // //nolint:funlen