Skip to content

Commit

Permalink
Make the selectable text customizable (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evertras authored Feb 22, 2022
1 parent cc37eab commit 9a7bdba
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 9 deletions.
6 changes: 4 additions & 2 deletions examples/features/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func NewModel() Model {
keys.RowUp.SetKeys("k", "up", "w")

model := Model{
// Throw features in... the point is not to look good, it's just reference!
tableModel: table.New(columns).
WithRows(rows).
HeaderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true)).
Expand All @@ -101,7 +102,8 @@ func NewModel() Model {
Border(customBorder).
WithKeyMap(keys).
WithStaticFooter("Footer!").
WithPageSize(3),
WithPageSize(3).
WithSelectedText(" ", "✓"),
}

model.updateFooter()
Expand Down Expand Up @@ -152,7 +154,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m Model) View() string {
body := strings.Builder{}

body.WriteString("Table demo with all features enabled!\n")
body.WriteString("A (chaotic) table demo with all features enabled!\n")
body.WriteString("Press left/right or page up/down to move pages\n")
body.WriteString("Press space/enter to select a row, q or ctrl+c to quit\n")

Expand Down
5 changes: 5 additions & 0 deletions table/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type Model struct {
highlightStyle lipgloss.Style
headerStyle lipgloss.Style
border Border
selectedText string
unselectedText string

// Footers
staticFooter string
Expand All @@ -49,6 +51,9 @@ func New(columns []Column) Model {
highlightStyle: defaultHighlightStyle.Copy(),
border: borderDefault,
keyMap: DefaultKeyMap(),

selectedText: "[x]",
unselectedText: "[ ]",
}

// Do a full deep copy to avoid unexpected edits
Expand Down
21 changes: 18 additions & 3 deletions table/options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package table

import "github.com/charmbracelet/lipgloss"
import (
"github.com/charmbracelet/lipgloss"
)

// HeaderStyle sets the style to apply to the header text, such as color or bold.
func (m Model) HeaderStyle(style lipgloss.Style) Model {
Expand Down Expand Up @@ -31,15 +33,14 @@ func (m Model) KeyMap() KeyMap {
// 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 {
const selectHeader = "[x]"
m.selectableRows = selectable

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

if hasSelectColumn != selectable {
if selectable {
m.columns = append([]Column{
NewColumn(columnKeySelect, selectHeader, len(selectHeader)),
NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText))),
}, m.columns...)
} else {
m.columns = m.columns[1:]
Expand Down Expand Up @@ -102,3 +103,17 @@ func (m Model) WithNoPagination() Model {

return m
}

// WithSelectedText describes what text to show when selectable rows are enabled.
// The selectable column header will use the selected text string.
func (m Model) WithSelectedText(unselected, selected string) Model {
m.selectedText = selected
m.unselectedText = unselected

if len(m.columns) > 0 && m.columns[0].Key == columnKeySelect {
m.columns[0] = NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText)))
m.recalculateWidth()
}

return m
}
4 changes: 2 additions & 2 deletions table/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ func (m Model) renderRow(rowIndex int, last bool) string {

if column.Key == columnKeySelect {
if row.selected {
str = "[x]"
str = m.selectedText
} else {
str = "[ ]"
str = m.unselectedText
}
} else if entry, exists := row.Data[column.Key]; exists {
switch entry := entry.(type) {
Expand Down
4 changes: 2 additions & 2 deletions table/strlimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ func limitStr(str string, maxLen int) string {
return ""
}

if len(str) > maxLen {
return str[:maxLen-1] + "…"
if len([]rune(str)) > maxLen {
return string([]rune(str)[:maxLen-1]) + "…"
}

return str
Expand Down
12 changes: 12 additions & 0 deletions table/strlimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ func TestLimitStr(t *testing.T) {
max: 0,
expected: "",
},
{
name: "Unicode width",
input: "✓",
max: 1,
expected: "✓",
},
{
name: "Unicode truncated",
input: "✓✓✓",
max: 2,
expected: "✓…",
},
}

for _, test := range tests {
Expand Down
123 changes: 123 additions & 0 deletions table/view_selectable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package table

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSimple3x3WithSelectableDefaults(t *testing.T) {
model := New([]Column{
NewColumn("1", "1", 4),
NewColumn("2", "2", 4),
NewColumn("3", "3", 4),
})

rows := []Row{}

for rowIndex := 1; rowIndex <= 3; 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))
}

model = model.WithRows(rows).SelectableRows(true)

const expectedTable = `┏━━━┳━━━━┳━━━━┳━━━━┓
┃[x]┃ 1┃ 2┃ 3┃
┣━━━╋━━━━╋━━━━╋━━━━┫
┃[ ]┃ 1,1┃ 2,1┃ 3,1┃
┃[ ]┃ 1,2┃ 2,2┃ 3,2┃
┃[ ]┃ 1,3┃ 2,3┃ 3,3┃
┗━━━┻━━━━┻━━━━┻━━━━┛`

rendered := model.View()

assert.Equal(t, expectedTable, rendered)
}

func TestSimple3x3WithCustomSelectableText(t *testing.T) {
model := New([]Column{
NewColumn("1", "1", 4),
NewColumn("2", "2", 4),
NewColumn("3", "3", 4),
})

rows := []Row{}

for rowIndex := 1; rowIndex <= 3; 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))
}

model = model.WithRows(rows).
SelectableRows(true).
WithSelectedText(" ", "✓")

const expectedTable = `┏━┳━━━━┳━━━━┳━━━━┓
┃✓┃ 1┃ 2┃ 3┃
┣━╋━━━━╋━━━━╋━━━━┫
┃ ┃ 1,1┃ 2,1┃ 3,1┃
┃ ┃ 1,2┃ 2,2┃ 3,2┃
┃ ┃ 1,3┃ 2,3┃ 3,3┃
┗━┻━━━━┻━━━━┻━━━━┛`

rendered := model.View()

assert.Equal(t, expectedTable, rendered)
}

func TestSimple3x3WithCustomSelectableTextAndFooter(t *testing.T) {
model := New([]Column{
NewColumn("1", "1", 4),
NewColumn("2", "2", 4),
NewColumn("3", "3", 4),
})

rows := []Row{}

for rowIndex := 1; rowIndex <= 3; 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))
}

model = model.WithRows(rows).
SelectableRows(true).
WithSelectedText(" ", "✓").
WithStaticFooter("Footer")

const expectedTable = `┏━┳━━━━┳━━━━┳━━━━┓
┃✓┃ 1┃ 2┃ 3┃
┣━╋━━━━╋━━━━╋━━━━┫
┃ ┃ 1,1┃ 2,1┃ 3,1┃
┃ ┃ 1,2┃ 2,2┃ 3,2┃
┃ ┃ 1,3┃ 2,3┃ 3,3┃
┣━┻━━━━┻━━━━┻━━━━┫
┃ Footer┃
┗━━━━━━━━━━━━━━━━┛`

rendered := model.View()

assert.Equal(t, expectedTable, rendered)
}

0 comments on commit 9a7bdba

Please sign in to comment.