Files
lerko a96c1a52f4 feat(tui): add 13 preloaded themes matching web design system
Port all web CSS token themes to TUI via shared vocabulary (accent, dim,
muted, ok, todo, event, remind, danger). Styles rebuild from active
theme on switch. Press T to cycle, persists to ~/.nib/theme. Glamour
markdown renderer respects light/dark per theme.
2026-05-20 20:13:21 -04:00

140 lines
2.6 KiB
Go

package tui
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/lerko/nib/internal/db"
)
type tagRailModel struct {
tags []db.TagCount
cursor int
offset int
height int
width int
activeTag string
}
func newTagRailModel() tagRailModel {
return tagRailModel{}
}
func (r *tagRailModel) setTags(tags []db.TagCount) {
r.tags = tags
if r.cursor >= len(tags) {
r.cursor = max(0, len(tags)-1)
}
}
func (r *tagRailModel) setSize(width, height int) {
r.width = width
r.height = height
}
func (r tagRailModel) selectedTag() string {
if len(r.tags) == 0 || r.cursor >= len(r.tags) {
return ""
}
return r.tags[r.cursor].Tag
}
func (r tagRailModel) update(key string) tagRailModel {
switch key {
case "up", "k":
if r.cursor > 0 {
r.cursor--
if r.cursor < r.offset {
r.offset = r.cursor
}
}
case "down", "j":
if r.cursor < len(r.tags)-1 {
r.cursor++
visible := r.visibleCount()
if r.cursor >= r.offset+visible {
r.offset = r.cursor - visible + 1
}
}
}
return r
}
func (r tagRailModel) visibleCount() int {
v := r.height - 2
if v <= 0 {
return 10
}
return v
}
func (r tagRailModel) view(focused bool) string {
w := r.width
if w <= 0 {
return ""
}
var b strings.Builder
headerStyle := railHeaderStyle
if focused {
headerStyle = headerStyle.Foreground(lipgloss.Color(activeTheme().Accent))
}
b.WriteString(headerStyle.Render("tags"))
b.WriteString("\n")
b.WriteString(separatorStyle.Render(strings.Repeat("─", w)))
b.WriteString("\n")
if len(r.tags) == 0 {
b.WriteString(hintDescStyle.Render(" no tags"))
return b.String()
}
visible := r.visibleCount()
end := min(r.offset+visible, len(r.tags))
countW := 0
for _, tc := range r.tags {
cw := len(fmt.Sprintf("%d", tc.Count))
if cw > countW {
countW = cw
}
}
nameW := w - countW - 3
if nameW < 4 {
nameW = 4
}
for i := r.offset; i < end; i++ {
tc := r.tags[i]
name := "#" + tc.Tag
if len(name) > nameW {
name = name[:nameW-1] + "…"
}
count := fmt.Sprintf("%*d", countW, tc.Count)
gap := w - len(name) - len(count) - 1
if gap < 1 {
gap = 1
}
var line string
if i == r.cursor && focused {
line = selectedItemStyle.Render(" " + name + strings.Repeat(" ", gap) + railCountStyle.Render(count))
} else if tc.Tag == r.activeTag {
line = " " + railActiveTagStyle.Render(name) + strings.Repeat(" ", gap) + railCountStyle.Render(count)
} else {
line = " " + railTagStyle.Render(name) + strings.Repeat(" ", gap) + railCountStyle.Render(count)
}
b.WriteString(line)
if i < end-1 {
b.WriteString("\n")
}
}
return b.String()
}