Skip to content

Commit e47fc7b

Browse files
committed
feat: enhance TUI design with modern lipgloss styling
- Add styles.go with modern color palette (purple, green, amber) - Status badges showing repo counts (total, dirty, clean) - Styled loading screen with path indicators - Enhanced error display with bordered box - Improved table with status indicators (● dirty, ○ clean) - Better help footer with styled key descriptions
1 parent 2d89875 commit e47fc7b

File tree

3 files changed

+324
-100
lines changed

3 files changed

+324
-100
lines changed

internal/tui/model.go

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,41 @@ type Model struct {
3535
// NewModel creates a new TUI model
3636
func NewModel(cfg *config.Config) Model {
3737
columns := []table.Column{
38-
{Title: "Repo", Width: 20},
39-
{Title: "Path", Width: 35},
40-
{Title: "Branch", Width: 12},
41-
{Title: "Stg", Width: 4},
42-
{Title: "Unst", Width: 5},
43-
{Title: "Untrk", Width: 5},
44-
{Title: "Last Commit", Width: 19},
38+
{Title: "⬤", Width: 2}, // Status indicator
39+
{Title: "Repository", Width: 20},
40+
{Title: "Path", Width: 30},
41+
{Title: "Branch", Width: 15},
42+
{Title: "Staged", Width: 6},
43+
{Title: "Modified", Width: 8},
44+
{Title: "Untracked", Width: 9},
45+
{Title: "Last Commit", Width: 16},
4546
}
4647

4748
t := table.New(
4849
table.WithColumns(columns),
4950
table.WithRows([]table.Row{}),
5051
table.WithFocused(true),
51-
table.WithHeight(10),
52+
table.WithHeight(12),
5253
)
5354

54-
// Apply styles
55+
// Apply modern table styles
5556
s := table.DefaultStyles()
5657
s.Header = s.Header.
57-
BorderStyle(lipgloss.NormalBorder()).
58-
BorderForeground(lipgloss.Color("240")).
58+
BorderStyle(lipgloss.ThickBorder()).
59+
BorderForeground(lipgloss.Color("#7C3AED")).
5960
BorderBottom(true).
60-
Bold(true)
61+
Bold(true).
62+
Foreground(lipgloss.Color("#F9FAFB")).
63+
Background(lipgloss.Color("#374151"))
64+
6165
s.Selected = s.Selected.
62-
Foreground(lipgloss.Color("229")).
63-
Background(lipgloss.Color("57")).
66+
Foreground(lipgloss.Color("#FFFFFF")).
67+
Background(lipgloss.Color("#7C3AED")).
6468
Bold(true)
69+
70+
s.Cell = s.Cell.
71+
Foreground(lipgloss.Color("#F9FAFB"))
72+
6573
t.SetStyles(s)
6674

6775
return Model{
@@ -76,7 +84,7 @@ func (m Model) Init() tea.Cmd {
7684
return scanReposCmd(m.cfg)
7785
}
7886

79-
// reposToRows converts repos to table rows
87+
// reposToRows converts repos to table rows with status indicators
8088
func reposToRows(repos []model.Repo) []table.Row {
8189
// Sort by dirty first, then by name
8290
sorted := make([]model.Repo, len(repos))
@@ -94,16 +102,23 @@ func reposToRows(repos []model.Repo) []table.Row {
94102
for _, r := range sorted {
95103
lastCommit := "N/A"
96104
if !r.Status.LastCommit.IsZero() {
97-
lastCommit = r.Status.LastCommit.Format("2006-01-02 15:04")
105+
lastCommit = r.Status.LastCommit.Format("Jan 02 15:04")
106+
}
107+
108+
// Status indicator
109+
indicator := "○" // Clean
110+
if r.Status.IsDirty {
111+
indicator = "●" // Dirty
98112
}
99113

100114
rows = append(rows, table.Row{
101-
r.Name,
102-
truncatePath(r.Path, 35),
103-
r.Status.Branch,
104-
fmt.Sprintf("%d", r.Status.Staged),
105-
fmt.Sprintf("%d", r.Status.Unstaged),
106-
fmt.Sprintf("%d", r.Status.Untracked),
115+
indicator,
116+
truncateString(r.Name, 20),
117+
truncatePath(r.Path, 30),
118+
truncateString(r.Status.Branch, 15),
119+
colorNumber(r.Status.Staged, "#10B981"), // Green
120+
colorNumber(r.Status.Unstaged, "#F59E0B"), // Amber
121+
colorNumber(r.Status.Untracked, "#9CA3AF"), // Gray
107122
lastCommit,
108123
})
109124
}
@@ -115,5 +130,21 @@ func truncatePath(path string, maxLen int) string {
115130
if len(path) <= maxLen {
116131
return path
117132
}
118-
return "..." + path[len(path)-maxLen+3:]
133+
return "…" + path[len(path)-maxLen+1:]
134+
}
135+
136+
// truncateString shortens a string with ellipsis
137+
func truncateString(s string, maxLen int) string {
138+
if len(s) <= maxLen {
139+
return s
140+
}
141+
return s[:maxLen-1] + "…"
142+
}
143+
144+
// colorNumber returns a string representation of a number
145+
func colorNumber(n int, _ string) string {
146+
if n == 0 {
147+
return "—"
148+
}
149+
return fmt.Sprintf("%d", n)
119150
}

internal/tui/styles.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package tui
2+
3+
import (
4+
"github.com/charmbracelet/lipgloss"
5+
)
6+
7+
// Color palette - Modern, vibrant theme
8+
var (
9+
// Primary colors
10+
primaryColor = lipgloss.Color("#7C3AED") // Purple
11+
secondaryColor = lipgloss.Color("#10B981") // Green
12+
accentColor = lipgloss.Color("#F59E0B") // Amber
13+
dangerColor = lipgloss.Color("#EF4444") // Red
14+
15+
// Neutral colors
16+
bgColor = lipgloss.Color("#1F2937") // Dark gray
17+
surfaceColor = lipgloss.Color("#374151") // Medium gray
18+
textColor = lipgloss.Color("#F9FAFB") // White
19+
mutedColor = lipgloss.Color("#9CA3AF") // Gray
20+
21+
// Status colors
22+
cleanColor = lipgloss.Color("#10B981") // Green
23+
dirtyColor = lipgloss.Color("#F59E0B") // Amber
24+
errorColor = lipgloss.Color("#EF4444") // Red
25+
)
26+
27+
// Application styles
28+
var (
29+
// App container
30+
appStyle = lipgloss.NewStyle().
31+
Padding(1, 2)
32+
33+
// Header / Title
34+
titleStyle = lipgloss.NewStyle().
35+
Bold(true).
36+
Foreground(lipgloss.Color("#FFFFFF")).
37+
Background(primaryColor).
38+
Padding(0, 2).
39+
MarginBottom(1)
40+
41+
// Logo ASCII art style
42+
logoStyle = lipgloss.NewStyle().
43+
Foreground(primaryColor).
44+
Bold(true)
45+
46+
// Subtitle with stats
47+
subtitleStyle = lipgloss.NewStyle().
48+
Foreground(mutedColor).
49+
MarginBottom(1)
50+
51+
// Stats badges
52+
statsBadgeStyle = lipgloss.NewStyle().
53+
Foreground(textColor).
54+
Background(surfaceColor).
55+
Padding(0, 1).
56+
MarginRight(1)
57+
58+
dirtyBadgeStyle = lipgloss.NewStyle().
59+
Foreground(lipgloss.Color("#000000")).
60+
Background(dirtyColor).
61+
Padding(0, 1).
62+
Bold(true)
63+
64+
cleanBadgeStyle = lipgloss.NewStyle().
65+
Foreground(lipgloss.Color("#000000")).
66+
Background(cleanColor).
67+
Padding(0, 1).
68+
Bold(true)
69+
70+
// Table styles
71+
tableContainerStyle = lipgloss.NewStyle().
72+
BorderStyle(lipgloss.RoundedBorder()).
73+
BorderForeground(surfaceColor).
74+
Padding(0, 1)
75+
76+
// Help footer
77+
helpStyle = lipgloss.NewStyle().
78+
Foreground(mutedColor).
79+
MarginTop(1)
80+
81+
helpKeyStyle = lipgloss.NewStyle().
82+
Foreground(primaryColor).
83+
Bold(true)
84+
85+
helpDescStyle = lipgloss.NewStyle().
86+
Foreground(mutedColor)
87+
88+
// Status message
89+
statusStyle = lipgloss.NewStyle().
90+
Foreground(accentColor).
91+
MarginTop(1)
92+
93+
// Error styling
94+
errorTitleStyle = lipgloss.NewStyle().
95+
Foreground(errorColor).
96+
Bold(true)
97+
98+
errorBoxStyle = lipgloss.NewStyle().
99+
BorderStyle(lipgloss.RoundedBorder()).
100+
BorderForeground(errorColor).
101+
Padding(1, 2).
102+
MarginTop(1)
103+
104+
// Loading styling
105+
loadingStyle = lipgloss.NewStyle().
106+
Foreground(secondaryColor).
107+
Bold(true)
108+
109+
loadingSpinnerStyle = lipgloss.NewStyle().
110+
Foreground(primaryColor)
111+
112+
// Scanning paths list
113+
pathStyle = lipgloss.NewStyle().
114+
Foreground(textColor).
115+
PaddingLeft(2)
116+
117+
pathBulletStyle = lipgloss.NewStyle().
118+
Foreground(primaryColor).
119+
Bold(true)
120+
121+
// Repo row indicators
122+
dirtyIndicator = lipgloss.NewStyle().
123+
Foreground(dirtyColor).
124+
Bold(true).
125+
Render("●")
126+
127+
cleanIndicator = lipgloss.NewStyle().
128+
Foreground(cleanColor).
129+
Render("○")
130+
)
131+
132+
// Help item creates a styled help key-description pair
133+
func helpItem(key, desc string) string {
134+
return helpKeyStyle.Render(key) + helpDescStyle.Render(" "+desc)
135+
}
136+
137+
// Logo returns the ASCII art logo
138+
func logo() string {
139+
return logoStyle.Render(`
140+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
141+
┃ ╔═╗╦╔╦╗ ╔═╗╔═╗╔═╗╔═╗╔═╗ ┃
142+
┃ ║ ╦║ ║───╚═╗║ ║ ║╠═╝║╣ ┃
143+
┃ ╚═╝╩ ╩ ╚═╝╚═╝╚═╝╩ ╚═╝ ┃
144+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`)
145+
}
146+
147+
// Simpler logo for compact mode
148+
func compactLogo() string {
149+
return titleStyle.Render(" 🔍 git-scope ")
150+
}

0 commit comments

Comments
 (0)