Skip to content

Commit

Permalink
Rename header to column for clarity (#9)
Browse files Browse the repository at this point in the history
Also includes some basic documentation.
  • Loading branch information
Evertras committed Feb 13, 2022
1 parent b89a479 commit 8785c21
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 43 deletions.
6 changes: 3 additions & 3 deletions examples/dimensions/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ type Model struct {
}

func genTable(columnCount int, rowCount int) table.Model {
headers := []table.Header{}
columns := []table.Column{}

for column := 0; column < columnCount; column++ {
columnStr := fmt.Sprintf("%d", column+1)
headers = append(headers, table.NewHeader(columnStr, columnStr, 4))
columns = append(columns, table.NewColumn(columnStr, columnStr, 4))
}

rows := []table.Row{}
Expand All @@ -39,7 +39,7 @@ func genTable(columnCount int, rowCount int) table.Model {
rows = append(rows, table.NewRow(rowData))
}

return table.New(headers).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true))
return table.New(columns).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true))
}

func NewModel() Model {
Expand Down
12 changes: 6 additions & 6 deletions examples/features/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ type Model struct {
}

func NewModel() Model {
headers := []table.Header{
table.NewHeader(columnKeyID, "ID", 5),
table.NewHeader(columnKeyName, "Name", 10),
table.NewHeader(columnKeyDescription, "Description", 30),
table.NewHeader(columnKeyCount, "#", 5),
columns := []table.Column{
table.NewColumn(columnKeyID, "ID", 5),
table.NewColumn(columnKeyName, "Name", 10),
table.NewColumn(columnKeyDescription, "Description", 30),
table.NewColumn(columnKeyCount, "#", 5),
}

rows := []table.Row{
Expand All @@ -75,7 +75,7 @@ func NewModel() Model {
}

return Model{
tableModel: table.New(headers).
tableModel: table.New(columns).
WithRows(rows).
HeaderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true)).
SelectableRows(true).
Expand Down
3 changes: 3 additions & 0 deletions table/border.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,16 @@ func (b *Border) generateStyles() {
)
}

// BorderDefault uses the basic square border, useful to reset the border if
// it was changed somehow
func (m Model) BorderDefault() Model {
// Already generated styles
m.border = borderDefault

return m
}

// Border uses the given border components to render the table
func (m Model) Border(border Border) Model {
border.generateStyles()

Expand Down
8 changes: 5 additions & 3 deletions table/header.go → table/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import (
"fmt"
)

type Header struct {
// Column is a column in the table
type Column struct {
Title string
Key string
Width int

fmtString string
}

func NewHeader(key, title string, width int) Header {
return Header{
// NewColumn creates a new column with the given information
func NewColumn(key, title string, width int) Column {
return Column{
Key: key,
Title: title,
Width: width,
Expand Down
22 changes: 13 additions & 9 deletions table/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import (

type RowData map[string]interface{}

// Row represents a row in the table with some data keyed to the table columns>
// Can have a style applied to it such as color/bold. Create using NewRow()
type Row struct {
Style lipgloss.Style
Data RowData

selected bool
}

// NewRow creates a new row and copies the given row data
func NewRow(data RowData) Row {
d := Row{
Data: make(map[string]interface{}),
Expand All @@ -28,14 +31,15 @@ func NewRow(data RowData) Row {
return d
}

// WithStyle uses the given style for the text in the row
func (r Row) WithStyle(style lipgloss.Style) Row {
r.Style = style
r.Style = style.Copy()

return r
}

func (m Model) renderRow(i int) string {
numHeaders := len(m.headers)
numColumns := len(m.columns)
row := m.rows[i]
last := i == len(m.rows)-1
highlighted := i == m.rowCursorIndex
Expand All @@ -58,7 +62,7 @@ func (m Model) renderRow(i int) string {
rowLastStyleRight lipgloss.Style
)

if numHeaders == 1 {
if numColumns == 1 {
rowStyleLeft = m.border.styleSingleColumnInner

rowLastStyleLeft = m.border.styleSingleColumnBottom
Expand All @@ -72,16 +76,16 @@ func (m Model) renderRow(i int) string {
rowLastStyleRight = m.border.styleMultiBottomRight
}

for i, header := range m.headers {
for i, column := range m.columns {
var str string

if header.Key == columnKeySelect {
if column.Key == columnKeySelect {
if row.selected {
str = "[x]"
} else {
str = "[ ]"
}
} else if entry, exists := row.Data[header.Key]; exists {
} else if entry, exists := row.Data[column.Key]; exists {
str = fmt.Sprintf("%v", entry)
}

Expand All @@ -90,22 +94,22 @@ func (m Model) renderRow(i int) string {
if !last {
if i == 0 {
cellStyle = cellStyle.Inherit(rowStyleLeft)
} else if i < numHeaders-1 {
} else if i < numColumns-1 {
cellStyle = cellStyle.Inherit(rowStyleInner)
} else {
cellStyle = cellStyle.Inherit(rowStyleRight)
}
} else {
if i == 0 {
cellStyle = cellStyle.Inherit(rowLastStyleLeft)
} else if i < numHeaders-1 {
} else if i < numColumns-1 {
cellStyle = cellStyle.Inherit(rowLastStyleInner)
} else {
cellStyle = cellStyle.Inherit(rowLastStyleRight)
}
}

cellStr := cellStyle.Render(fmt.Sprintf(header.fmtString, limitStr(str, header.Width)))
cellStr := cellStyle.Render(fmt.Sprintf(column.fmtString, limitStr(str, column.Width)))

columnStrings = append(columnStrings, cellStr)
}
Expand Down
48 changes: 31 additions & 17 deletions table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/charmbracelet/lipgloss"
)

type MovementMode int

const (
columnKeySelect = "___select___"
)
Expand All @@ -18,8 +16,9 @@ var (
defaultHighlightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#334"))
)

// Model is the main table model. Create using New()
type Model struct {
headers []Header
columns []Column
headerStyle lipgloss.Style

rows []Row
Expand All @@ -37,47 +36,53 @@ type Model struct {
border Border
}

func New(headers []Header) Model {
// New creates a new table ready for further modifications
func New(columns []Column) Model {
m := Model{
headers: make([]Header, len(headers)),
columns: make([]Column, len(columns)),
highlightStyle: defaultHighlightStyle.Copy(),
border: borderDefault,
}

// Do a full deep copy to avoid unexpected edits
copy(m.headers, headers)
copy(m.columns, columns)

return m
}

// HeaderStyle sets the style to apply to the header text, such as color or bold
func (m Model) HeaderStyle(style lipgloss.Style) Model {
m.headerStyle = style.Copy()
return m
}

// WithRows sets the rows to show as data in the table
func (m Model) WithRows(rows []Row) Model {
m.rows = rows
return m
}

// SelectableRows sets whether or not rows are selectable. If set, adds a column
// in the front that acts as a checkbox and responds to controls if Focused
func (m Model) SelectableRows(selectable bool) Model {
m.selectableRows = selectable

hasSelectHeader := m.headers[0].Key == columnKeySelect
hasSelectColumn := m.columns[0].Key == columnKeySelect

if hasSelectHeader != selectable {
if hasSelectColumn != selectable {
if selectable {
m.headers = append([]Header{
NewHeader(columnKeySelect, "[x]", 3),
}, m.headers...)
m.columns = append([]Column{
NewColumn(columnKeySelect, "[x]", 3),
}, m.columns...)
} else {
m.headers = m.headers[1:]
m.columns = m.columns[1:]
}
}

return m
}

// HighlightedRow returns the full Row that's currently highlighted by the user
func (m Model) HighlightedRow() Row {
if len(m.rows) > 0 {
return m.rows[m.rowCursorIndex]
Expand All @@ -87,24 +92,31 @@ func (m Model) HighlightedRow() Row {
return Row{}
}

// SelectedRows returns all rows that have been set as selected by the user
func (m Model) SelectedRows() []Row {
return m.selectedRows
}

// HighlightStyle sets a custom style to use when the row is being highlighted
// by the cursor
func (m Model) HighlightStyle(style lipgloss.Style) Model {
m.highlightStyle = style
return m
}

// Focused allows the table to show highlighted rows and take in controls of
// up/down/space/etc to let the user navigate the table and interact with it
func (m Model) Focused(focused bool) Model {
m.focused = focused
return m
}

// Init initializes the table per the Bubble Tea architecture
func (m Model) Init() tea.Cmd {
return nil
}

// Update responds to input from the user or other messages from Bubble Tea
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if !m.focused {
return m, nil
Expand Down Expand Up @@ -153,12 +165,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}

// View renders the table. It does not end in a newline, so that it can be
// composed with other elements more consistently.
func (m Model) View() string {
numHeaders := len(m.headers)
numColumns := len(m.columns)
hasRows := len(m.rows) > 0

// Safety valve for empty tables
if numHeaders == 0 {
if numColumns == 0 {
return ""
}

Expand All @@ -172,7 +186,7 @@ func (m Model) View() string {
headerStyleRight lipgloss.Style
)

if numHeaders == 1 {
if numColumns == 1 {
if hasRows {
headerStyleLeft = m.border.styleSingleColumnTop
} else {
Expand All @@ -196,13 +210,13 @@ func (m Model) View() string {
headerStyleRight = headerStyleRight.Copy().Inherit(m.headerStyle)
}

for i, header := range m.headers {
for i, header := range m.columns {
headerSection := fmt.Sprintf(header.fmtString, header.Title)
var borderStyle lipgloss.Style

if i == 0 {
borderStyle = headerStyleLeft
} else if i < len(m.headers)-1 {
} else if i < len(m.columns)-1 {
borderStyle = headerStyleInner
} else {
borderStyle = headerStyleRight
Expand Down
10 changes: 5 additions & 5 deletions table/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ func TestBasicTableShowsAllHeaders(t *testing.T) {
secondWidth = 20
)

headers := []Header{
NewHeader(firstKey, firstTitle, firstWidth),
NewHeader(secondKey, secondTitle, secondWidth),
columns := []Column{
NewColumn(firstKey, firstTitle, firstWidth),
NewColumn(secondKey, secondTitle, secondWidth),
}

table := New(headers)
table := New(columns)

rendered := table.View()

Expand All @@ -33,7 +33,7 @@ func TestBasicTableShowsAllHeaders(t *testing.T) {
assert.False(t, strings.HasSuffix(rendered, "\n"), "Should not end in newline")
}

func TestNilHeadersSafelyReturnsEmptyView(t *testing.T) {
func TestNilColumnsSafelyReturnsEmptyView(t *testing.T) {
table := New(nil)

assert.Equal(t, "", table.View())
Expand Down

0 comments on commit 8785c21

Please sign in to comment.