Repository: Evertras/bubble-table
Pull request #34 (2022-02-26 13:53:27)

Add base style
Note that this currently does not work with alignment due to unexpected behavior in lipgloss: https://github.com/charmbracelet/lipgloss/issues/70


Patch:
diff --git a/README.md b/README.md
index 48cf7aa..33dcdad 100644
--- a/README.md
+++ b/README.md
@@ -19,11 +19,14 @@ If you want to get started with a simple default table, [check the simplest exam
 
 Displays a table with a header, rows, footer, and borders.
 
-Border shape is customizable with a basic thick square default, and style can
-be applied for color.
-
-Styles can be applied to columns, rows, and individual cells.  Column style is
-applied first, then row, then cell when determining overrides.
+Border shape is customizable with a basic thick square default.  The color can
+be modified by applying a base style with `lipgloss.NewStyle().BorderForeground(...)`.
+
+Styles can be applied globally and to columns, rows, and individual cells.
+The base style is applied first, then column, then row, then cell when
+determining overrides.  The default base style is a basic right-alignment.
+[See the main feature example](./examples/features/main.go) to see styles and
+how they override each other.
 
 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/features/main.go b/examples/features/main.go
index bfea838..cecfc74 100644
--- a/examples/features/main.go
+++ b/examples/features/main.go
@@ -38,8 +38,6 @@ var (
 		InnerJunction:  "╫",
 
 		InnerDivider: "║",
-
-		StyleBase: lipgloss.NewStyle().BorderForeground(lipgloss.Color("#a38")),
 	}
 )
 
@@ -53,7 +51,7 @@ func NewModel() Model {
 			lipgloss.NewStyle().
 				Faint(true).
 				Foreground(lipgloss.Color("#88f")).
-				Align(lipgloss.Left)),
+				Align(lipgloss.Center)),
 		table.NewColumn(columnKeyName, "Name", 10),
 		table.NewColumn(columnKeyDescription, "Description", 30),
 		table.NewColumn(columnKeyCount, "#", 5),
@@ -110,6 +108,12 @@ func NewModel() Model {
 			WithStaticFooter("Footer!").
 			WithPageSize(3).
 			WithSelectedText(" ", "✓").
+			WithBaseStyle(
+				lipgloss.NewStyle().
+					BorderForeground(lipgloss.Color("#a38")).
+					Foreground(lipgloss.Color("#a7a")).
+					Align(lipgloss.Left),
+			).
 			SortByAsc(columnKeyID),
 	}
 
diff --git a/table/border.go b/table/border.go
index 405e84e..51fa8ac 100644
--- a/table/border.go
+++ b/table/border.go
@@ -22,8 +22,6 @@ type Border struct {
 
 	InnerDivider string
 
-	StyleBase lipgloss.Style
-
 	// Styles for 2x2 tables and larger
 	styleMultiTopLeft     lipgloss.Style
 	styleMultiTop         lipgloss.Style
@@ -118,7 +116,7 @@ func (b *Border) styleBothWithFooter(original lipgloss.Style) lipgloss.Style {
 // This function is long, but it's just repetitive...
 // nolint:funlen
 func (b *Border) generateMultiStyles() {
-	b.styleMultiTopLeft = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiTopLeft = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			TopLeft:     b.TopLeft,
 			Top:         b.Top,
@@ -131,7 +129,7 @@ func (b *Border) generateMultiStyles() {
 		},
 	)
 
-	b.styleMultiTop = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiTop = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Right:  b.InnerDivider,
@@ -142,7 +140,7 @@ func (b *Border) generateMultiStyles() {
 		},
 	).BorderTop(true).BorderBottom(true).BorderRight(true)
 
-	b.styleMultiTopRight = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiTopRight = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Right:  b.Right,
@@ -153,26 +151,26 @@ func (b *Border) generateMultiStyles() {
 		},
 	).BorderTop(true).BorderBottom(true).BorderRight(true)
 
-	b.styleMultiLeft = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiLeft = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Left:  b.Left,
 			Right: b.InnerDivider,
 		},
 	).BorderRight(true).BorderLeft(true)
 
-	b.styleMultiRight = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiRight = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Right: b.Right,
 		},
 	).BorderRight(true)
 
-	b.styleMultiInner = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiInner = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Right: b.InnerDivider,
 		},
 	).BorderRight(true)
 
-	b.styleMultiBottomLeft = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiBottomLeft = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Left:   b.Left,
 			Right:  b.InnerDivider,
@@ -183,7 +181,7 @@ func (b *Border) generateMultiStyles() {
 		},
 	).BorderLeft(true).BorderBottom(true).BorderRight(true)
 
-	b.styleMultiBottom = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiBottom = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Right:  b.InnerDivider,
 			Bottom: b.Bottom,
@@ -192,7 +190,7 @@ func (b *Border) generateMultiStyles() {
 		},
 	).BorderBottom(true).BorderRight(true)
 
-	b.styleMultiBottomRight = b.StyleBase.Copy().BorderStyle(
+	b.styleMultiBottomRight = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Right:  b.Right,
 			Bottom: b.Bottom,
@@ -203,7 +201,7 @@ func (b *Border) generateMultiStyles() {
 }
 
 func (b *Border) generateSingleColumnStyles() {
-	b.styleSingleColumnTop = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleColumnTop = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Left:   b.Left,
@@ -217,14 +215,14 @@ func (b *Border) generateSingleColumnStyles() {
 		},
 	)
 
-	b.styleSingleColumnInner = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleColumnInner = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Left:  b.Left,
 			Right: b.Right,
 		},
 	).BorderRight(true).BorderLeft(true)
 
-	b.styleSingleColumnBottom = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleColumnBottom = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Left:   b.Left,
 			Right:  b.Right,
@@ -237,7 +235,7 @@ func (b *Border) generateSingleColumnStyles() {
 }
 
 func (b *Border) generateSingleRowStyles() {
-	b.styleSingleRowLeft = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleRowLeft = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Left:   b.Left,
@@ -251,7 +249,7 @@ func (b *Border) generateSingleRowStyles() {
 		},
 	)
 
-	b.styleSingleRowInner = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleRowInner = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Right:  b.InnerDivider,
@@ -262,7 +260,7 @@ func (b *Border) generateSingleRowStyles() {
 		},
 	).BorderTop(true).BorderBottom(true).BorderRight(true)
 
-	b.styleSingleRowRight = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleRowRight = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Right:  b.Right,
@@ -275,7 +273,7 @@ func (b *Border) generateSingleRowStyles() {
 }
 
 func (b *Border) generateSingleCellStyle() {
-	b.styleSingleCell = b.StyleBase.Copy().BorderStyle(
+	b.styleSingleCell = lipgloss.NewStyle().BorderStyle(
 		lipgloss.Border{
 			Top:    b.Top,
 			Left:   b.Left,
diff --git a/table/column.go b/table/column.go
index 5f4b6e2..005d81e 100644
--- a/table/column.go
+++ b/table/column.go
@@ -19,14 +19,13 @@ func NewColumn(key, title string, width int) Column {
 		Key:   key,
 		Title: title,
 		Width: width,
-		style: lipgloss.NewStyle().Width(width).Align(lipgloss.Right),
+		style: lipgloss.NewStyle().Width(width),
 	}
 }
 
 // WithStyle applies a style to the column as a whole.
 func (c Column) WithStyle(style lipgloss.Style) Column {
-	//c.style = lipgloss.NewStyle().Width(c.Width).Align(lipgloss.Right).Inherit(style)
-	c.style = style.Copy().Inherit(lipgloss.NewStyle().Width(c.Width).Align(lipgloss.Right))
+	c.style = style.Copy().Width(c.Width)
 
 	return c
 }
diff --git a/table/footer.go b/table/footer.go
index 896d9d8..6cc5d04 100644
--- a/table/footer.go
+++ b/table/footer.go
@@ -21,5 +21,7 @@ func (m Model) renderFooter() string {
 		footerText = fmt.Sprintf("%d/%d", m.CurrentPage(), m.MaxPages())
 	}
 
-	return m.border.styleFooter.Width(m.totalWidth).Render(footerText)
+	styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(m.totalWidth)
+
+	return styleFooter.Render(footerText)
 }
diff --git a/table/model.go b/table/model.go
index b4ea53e..30256be 100644
--- a/table/model.go
+++ b/table/model.go
@@ -27,6 +27,7 @@ type Model struct {
 	rowCursorIndex int
 
 	// Styles
+	baseStyle      lipgloss.Style
 	highlightStyle lipgloss.Style
 	headerStyle    lipgloss.Style
 	border         Border
@@ -58,6 +59,8 @@ func New(columns []Column) Model {
 
 		selectedText:   "[x]",
 		unselectedText: "[ ]",
+
+		baseStyle: lipgloss.NewStyle().Align(lipgloss.Right),
 	}
 
 	// Do a full deep copy to avoid unexpected edits
diff --git a/table/options.go b/table/options.go
index 455bd7c..7f297b8 100644
--- a/table/options.go
+++ b/table/options.go
@@ -118,3 +118,11 @@ func (m Model) WithSelectedText(unselected, selected string) Model {
 
 	return m
 }
+
+// WithBaseStyle applies a base style as the default for everything in the table.
+// This is useful for border colors, default alignment, default color, etc.
+func (m Model) WithBaseStyle(style lipgloss.Style) Model {
+	m.baseStyle = style
+
+	return m
+}
diff --git a/table/row.go b/table/row.go
index 5c7b31d..b615dc6 100644
--- a/table/row.go
+++ b/table/row.go
@@ -49,16 +49,16 @@ func (m Model) renderRow(rowIndex int, last bool) string {
 
 	columnStrings := []string{}
 
-	baseStyle := row.Style.Copy()
+	rowStyle := row.Style.Copy()
 
 	if m.focused && highlighted {
-		baseStyle = baseStyle.Inherit(m.highlightStyle)
+		rowStyle = rowStyle.Inherit(m.highlightStyle)
 	}
 
 	stylesInner, stylesLast := m.styleRows()
 
 	for columnIndex, column := range m.columns {
-		cellStyle := baseStyle.Copy().Inherit(column.style)
+		cellStyle := rowStyle.Copy().Inherit(column.style).Inherit(m.baseStyle)
 
 		var str string
 
diff --git a/table/view.go b/table/view.go
index d3daf92..39db2e9 100644
--- a/table/view.go
+++ b/table/view.go
@@ -32,7 +32,7 @@ func (m Model) View() string {
 			borderStyle = headerStyles.right.Copy()
 		}
 
-		borderStyle = borderStyle.Inherit(column.style)
+		borderStyle = borderStyle.Inherit(column.style).Inherit(m.baseStyle)
 
 		headerStrings = append(headerStrings, borderStyle.Render(headerSection))
 	}


Test patch:
diff --git a/table/view_test.go b/table/view_test.go
index 1aff0b7..322a4ca 100644
--- a/table/view_test.go
+++ b/table/view_test.go
@@ -340,12 +340,12 @@ func TestPaged3x3WithStaticFooter(t *testing.T) {
 	assert.Equal(t, expectedTable, rendered)
 }
 
-func TestSimple3x3StyleOverridesAsColumnRowCell(t *testing.T) {
+func TestSimple3x3StyleOverridesAsBaseColumnRowCell(t *testing.T) {
 	model := New([]Column{
 		NewColumn("1", "1", 6),
 		NewColumn("2", "2", 6).WithStyle(lipgloss.NewStyle().Align(lipgloss.Left)),
 		NewColumn("3", "3", 6),
-	})
+	}).WithBaseStyle(lipgloss.NewStyle().Align(lipgloss.Center))
 
 	rows := []Row{}
 
@@ -363,17 +363,17 @@ func TestSimple3x3StyleOverridesAsColumnRowCell(t *testing.T) {
 
 	// Test overrides with alignment because it's easy to check output string
 	rows[0] = rows[0].WithStyle(lipgloss.NewStyle().Align(lipgloss.Left))
-	rows[0].Data["2"] = NewStyledCell("C", lipgloss.NewStyle().Align(lipgloss.Center))
+	rows[0].Data["2"] = NewStyledCell("R", lipgloss.NewStyle().Align(lipgloss.Right))
 
 	rows[2] = rows[2].WithStyle(lipgloss.NewStyle().Align(lipgloss.Right))
 
 	model = model.WithRows(rows)
 
 	const expectedTable = `┏━━━━━━┳━━━━━━┳━━━━━━┓
-┃     1┃2     ┃     3┃
+┃  1   ┃2     ┃  3   ┃
 ┣━━━━━━╋━━━━━━╋━━━━━━┫
-┃1,1   ┃  C   ┃3,1   ┃
-┃   1,2┃2,2   ┃   3,2┃
+┃1,1   ┃     R┃3,1   ┃
+┃ 1,2  ┃2,2   ┃ 3,2  ┃
 ┃   1,3┃   2,3┃   3,3┃
 ┗━━━━━━┻━━━━━━┻━━━━━━┛`