Merge pull request 'feat(tui): add 13 preloaded themes matching web design system' (#39) from feat/tui-theme into main
Reviewed-on: #39
This commit was merged in pull request #39.
This commit is contained in:
@@ -115,7 +115,7 @@ func (d detailModel) previewView(width int) string {
|
||||
bodyWidth = 20
|
||||
}
|
||||
r, _ := glamour.NewTermRenderer(
|
||||
glamour.WithStylePath("dark"),
|
||||
glamour.WithStylePath(glamourStyle()),
|
||||
glamour.WithWordWrap(bodyWidth),
|
||||
)
|
||||
rendered, err := r.Render(e.Body)
|
||||
|
||||
@@ -33,6 +33,7 @@ func renderHelp(width, height int) string {
|
||||
{"2", "cards view"},
|
||||
{"s", "cycle sort (cards)"},
|
||||
{"i", "cycle intent (cards)"},
|
||||
{"T", "cycle theme"},
|
||||
}},
|
||||
{"Actions", [][2]string{
|
||||
{"d", "delete (with confirm)"},
|
||||
|
||||
@@ -34,6 +34,7 @@ type keyMap struct {
|
||||
Tab key.Binding
|
||||
ToggleRail key.Binding
|
||||
Stumble key.Binding
|
||||
Theme key.Binding
|
||||
}
|
||||
|
||||
var keys = keyMap{
|
||||
@@ -68,4 +69,5 @@ var keys = keyMap{
|
||||
Tab: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "focus cycle")),
|
||||
ToggleRail: key.NewBinding(key.WithKeys("ctrl+b"), key.WithHelp("ctrl+b", "toggle tag rail")),
|
||||
Stumble: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "stumble")),
|
||||
Theme: key.NewBinding(key.WithKeys("T"), key.WithHelp("T", "theme")),
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ type model struct {
|
||||
}
|
||||
|
||||
func newModel(store *db.Store) model {
|
||||
loadTheme()
|
||||
applyTheme()
|
||||
inp := newInputModel()
|
||||
inp.ti.Focus()
|
||||
return model{
|
||||
@@ -565,6 +567,10 @@ func (m model) updateBrowse(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case "T":
|
||||
t := cycleTheme()
|
||||
return m, m.setStatus("theme: " + t.Name)
|
||||
|
||||
case "i":
|
||||
if m.mode == modeCards && m.state == stateList {
|
||||
m.cards.setIntent(m.cards.intent.next())
|
||||
|
||||
@@ -111,7 +111,7 @@ func (s stumbleModel) view() string {
|
||||
bodyWidth = 20
|
||||
}
|
||||
r, _ := glamour.NewTermRenderer(
|
||||
glamour.WithStylePath("dark"),
|
||||
glamour.WithStylePath(glamourStyle()),
|
||||
glamour.WithWordWrap(bodyWidth),
|
||||
)
|
||||
rendered, err := r.Render(e.Body)
|
||||
|
||||
+93
-143
@@ -3,147 +3,97 @@ package tui
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
var (
|
||||
subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
|
||||
highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
|
||||
dim = lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}
|
||||
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(highlight).
|
||||
PaddingLeft(1)
|
||||
|
||||
statusStyle = lipgloss.NewStyle().
|
||||
Foreground(dim).
|
||||
PaddingLeft(1)
|
||||
|
||||
listItemStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(4)
|
||||
|
||||
selectedItemStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(1).
|
||||
Bold(true).
|
||||
Foreground(highlight).
|
||||
SetString("›")
|
||||
|
||||
glyphStyle = lipgloss.NewStyle().
|
||||
Width(2)
|
||||
|
||||
completedGlyphStyle = lipgloss.NewStyle().
|
||||
Width(2).
|
||||
Foreground(dim)
|
||||
|
||||
tagStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"})
|
||||
|
||||
idStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
inputPromptStyle = lipgloss.NewStyle().
|
||||
Foreground(highlight).
|
||||
Bold(true)
|
||||
|
||||
detailHeaderStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(highlight).
|
||||
MarginBottom(1)
|
||||
|
||||
detailBodyStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
PaddingTop(1)
|
||||
|
||||
helpStyle = lipgloss.NewStyle().
|
||||
Foreground(dim).
|
||||
PaddingLeft(1)
|
||||
|
||||
errorStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#FF0000")).
|
||||
PaddingLeft(1)
|
||||
|
||||
dateHeaderStyle = lipgloss.NewStyle().
|
||||
Foreground(dim).
|
||||
PaddingLeft(1)
|
||||
|
||||
pinnedStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#D4A017", Dark: "#FFD700"})
|
||||
|
||||
filterPillStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"}).
|
||||
Bold(true)
|
||||
|
||||
helpKeyStyle = lipgloss.NewStyle().
|
||||
Foreground(highlight).
|
||||
Bold(true).
|
||||
Width(18)
|
||||
|
||||
helpDescStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
affordanceStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#5B8EF0", Dark: "#7AAFFF"}).
|
||||
Bold(true)
|
||||
|
||||
useCountStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#B07D3A", Dark: "#D4A54A"})
|
||||
|
||||
modeStyle = lipgloss.NewStyle().
|
||||
Foreground(dim).
|
||||
Bold(true)
|
||||
|
||||
detailLabelStyle = lipgloss.NewStyle().
|
||||
Foreground(highlight).
|
||||
Bold(true)
|
||||
|
||||
detailValueStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#555555", Dark: "#BBBBBB"})
|
||||
|
||||
checkDoneStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"})
|
||||
|
||||
checkPendingStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
searchPillStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#E06C75", Dark: "#E06C75"}).
|
||||
Bold(true)
|
||||
|
||||
gutterStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
drawerBorderStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
drawerHintsStyle = lipgloss.NewStyle().
|
||||
Foreground(dim).
|
||||
PaddingLeft(2)
|
||||
|
||||
drawerPreviewStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#555555", Dark: "#AAAAAA"}).
|
||||
PaddingLeft(2)
|
||||
|
||||
separatorStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
hintKeyStyle = lipgloss.NewStyle().
|
||||
Foreground(highlight).
|
||||
Bold(true)
|
||||
|
||||
hintDescStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
railHeaderStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(dim)
|
||||
|
||||
railTagStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"})
|
||||
|
||||
railActiveTagStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"}).
|
||||
Bold(true)
|
||||
|
||||
railCountStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
stumbleAgeStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "#cc4400", Dark: "#fab387"})
|
||||
titleStyle lipgloss.Style
|
||||
statusStyle lipgloss.Style
|
||||
listItemStyle lipgloss.Style
|
||||
selectedItemStyle lipgloss.Style
|
||||
glyphStyle lipgloss.Style
|
||||
completedGlyphStyle lipgloss.Style
|
||||
tagStyle lipgloss.Style
|
||||
idStyle lipgloss.Style
|
||||
inputPromptStyle lipgloss.Style
|
||||
detailHeaderStyle lipgloss.Style
|
||||
detailBodyStyle lipgloss.Style
|
||||
helpStyle lipgloss.Style
|
||||
errorStyle lipgloss.Style
|
||||
dateHeaderStyle lipgloss.Style
|
||||
pinnedStyle lipgloss.Style
|
||||
filterPillStyle lipgloss.Style
|
||||
helpKeyStyle lipgloss.Style
|
||||
helpDescStyle lipgloss.Style
|
||||
affordanceStyle lipgloss.Style
|
||||
useCountStyle lipgloss.Style
|
||||
modeStyle lipgloss.Style
|
||||
detailLabelStyle lipgloss.Style
|
||||
detailValueStyle lipgloss.Style
|
||||
checkDoneStyle lipgloss.Style
|
||||
checkPendingStyle lipgloss.Style
|
||||
searchPillStyle lipgloss.Style
|
||||
gutterStyle lipgloss.Style
|
||||
drawerBorderStyle lipgloss.Style
|
||||
drawerHintsStyle lipgloss.Style
|
||||
drawerPreviewStyle lipgloss.Style
|
||||
separatorStyle lipgloss.Style
|
||||
hintKeyStyle lipgloss.Style
|
||||
hintDescStyle lipgloss.Style
|
||||
railHeaderStyle lipgloss.Style
|
||||
railTagStyle lipgloss.Style
|
||||
railActiveTagStyle lipgloss.Style
|
||||
railCountStyle lipgloss.Style
|
||||
stumbleAgeStyle lipgloss.Style
|
||||
)
|
||||
|
||||
func init() {
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
func applyTheme() {
|
||||
t := activeTheme()
|
||||
accent := lipgloss.Color(t.Accent)
|
||||
dim := lipgloss.Color(t.Dim)
|
||||
muted := lipgloss.Color(t.Muted)
|
||||
ok := lipgloss.Color(t.Ok)
|
||||
todo := lipgloss.Color(t.Todo)
|
||||
event := lipgloss.Color(t.Event)
|
||||
remind := lipgloss.Color(t.Remind)
|
||||
danger := lipgloss.Color(t.Danger)
|
||||
|
||||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(accent).PaddingLeft(1)
|
||||
statusStyle = lipgloss.NewStyle().Foreground(dim).PaddingLeft(1)
|
||||
listItemStyle = lipgloss.NewStyle().PaddingLeft(4)
|
||||
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(1).Bold(true).Foreground(accent).SetString("›")
|
||||
glyphStyle = lipgloss.NewStyle().Width(2)
|
||||
completedGlyphStyle = lipgloss.NewStyle().Width(2).Foreground(dim)
|
||||
tagStyle = lipgloss.NewStyle().Foreground(ok)
|
||||
idStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
inputPromptStyle = lipgloss.NewStyle().Foreground(accent).Bold(true)
|
||||
detailHeaderStyle = lipgloss.NewStyle().Bold(true).Foreground(accent).MarginBottom(1)
|
||||
detailBodyStyle = lipgloss.NewStyle().PaddingLeft(2).PaddingTop(1)
|
||||
helpStyle = lipgloss.NewStyle().Foreground(dim).PaddingLeft(1)
|
||||
errorStyle = lipgloss.NewStyle().Foreground(danger).PaddingLeft(1)
|
||||
dateHeaderStyle = lipgloss.NewStyle().Foreground(dim).PaddingLeft(1)
|
||||
pinnedStyle = lipgloss.NewStyle().Foreground(todo)
|
||||
filterPillStyle = lipgloss.NewStyle().Foreground(ok).Bold(true)
|
||||
helpKeyStyle = lipgloss.NewStyle().Foreground(accent).Bold(true).Width(18)
|
||||
helpDescStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
affordanceStyle = lipgloss.NewStyle().Foreground(event).Bold(true)
|
||||
useCountStyle = lipgloss.NewStyle().Foreground(remind)
|
||||
modeStyle = lipgloss.NewStyle().Foreground(dim).Bold(true)
|
||||
detailLabelStyle = lipgloss.NewStyle().Foreground(accent).Bold(true)
|
||||
detailValueStyle = lipgloss.NewStyle().Foreground(muted)
|
||||
checkDoneStyle = lipgloss.NewStyle().Foreground(ok)
|
||||
checkPendingStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
searchPillStyle = lipgloss.NewStyle().Foreground(danger).Bold(true)
|
||||
gutterStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
drawerBorderStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
drawerHintsStyle = lipgloss.NewStyle().Foreground(dim).PaddingLeft(2)
|
||||
drawerPreviewStyle = lipgloss.NewStyle().Foreground(muted).PaddingLeft(2)
|
||||
separatorStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
hintKeyStyle = lipgloss.NewStyle().Foreground(accent).Bold(true)
|
||||
hintDescStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
railHeaderStyle = lipgloss.NewStyle().Bold(true).Foreground(dim)
|
||||
railTagStyle = lipgloss.NewStyle().Foreground(ok)
|
||||
railActiveTagStyle = lipgloss.NewStyle().Foreground(ok).Bold(true)
|
||||
railCountStyle = lipgloss.NewStyle().Foreground(dim)
|
||||
stumbleAgeStyle = lipgloss.NewStyle().Foreground(remind)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/lerko/nib/internal/db"
|
||||
)
|
||||
|
||||
@@ -78,7 +79,7 @@ func (r tagRailModel) view(focused bool) string {
|
||||
|
||||
headerStyle := railHeaderStyle
|
||||
if focused {
|
||||
headerStyle = headerStyle.Foreground(highlight)
|
||||
headerStyle = headerStyle.Foreground(lipgloss.Color(activeTheme().Accent))
|
||||
}
|
||||
b.WriteString(headerStyle.Render("tags"))
|
||||
b.WriteString("\n")
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
Name string
|
||||
Dark bool
|
||||
Accent string
|
||||
Dim string
|
||||
Muted string
|
||||
Ok string
|
||||
Todo string
|
||||
Event string
|
||||
Remind string
|
||||
Danger string
|
||||
}
|
||||
|
||||
var themes = []Theme{
|
||||
{Name: "dark", Dark: true, Accent: "#c8942a", Dim: "#504840", Muted: "#8c8070", Ok: "#7aab72", Todo: "#d4a84b", Event: "#6898c8", Remind: "#c8784a", Danger: "#b85858"},
|
||||
{Name: "tinycard", Dark: true, Accent: "#ad8ee6", Dim: "#555a6a", Muted: "#8b90a0", Ok: "#4ade80", Todo: "#fbbf24", Event: "#22d3ee", Remind: "#e8845a", Danger: "#ef4444"},
|
||||
{Name: "catppuccin", Dark: true, Accent: "#cba6f7", Dim: "#6c7086", Muted: "#a6adc8", Ok: "#a6e3a1", Todo: "#f9e2af", Event: "#89b4fa", Remind: "#fab387", Danger: "#f38ba8"},
|
||||
{Name: "nord", Dark: true, Accent: "#88c0d0", Dim: "#4c566a", Muted: "#d8dee9", Ok: "#a3be8c", Todo: "#ebcb8b", Event: "#81a1c1", Remind: "#d08770", Danger: "#bf616a"},
|
||||
{Name: "dracula", Dark: true, Accent: "#bd93f9", Dim: "#6272a4", Muted: "#bfbfbf", Ok: "#50fa7b", Todo: "#f1fa8c", Event: "#8be9fd", Remind: "#ffb86c", Danger: "#ff5555"},
|
||||
{Name: "gruvbox", Dark: true, Accent: "#fabd2f", Dim: "#665c54", Muted: "#a89984", Ok: "#b8bb26", Todo: "#fabd2f", Event: "#83a598", Remind: "#fe8019", Danger: "#fb4934"},
|
||||
{Name: "rosepine", Dark: true, Accent: "#c4a7e7", Dim: "#6e6a86", Muted: "#908caa", Ok: "#a6da95", Todo: "#f6c177", Event: "#31748f", Remind: "#ea9a97", Danger: "#eb6f92"},
|
||||
{Name: "tokyonight", Dark: true, Accent: "#7aa2f7", Dim: "#565f89", Muted: "#a9b1d6", Ok: "#9ece6a", Todo: "#e0af68", Event: "#7aa2f7", Remind: "#ff9e64", Danger: "#f7768e"},
|
||||
{Name: "solarized", Dark: true, Accent: "#268bd2", Dim: "#586e75", Muted: "#657b83", Ok: "#859900", Todo: "#b58900", Event: "#268bd2", Remind: "#cb4b16", Danger: "#dc322f"},
|
||||
{Name: "paper", Dark: false, Accent: "#8a6018", Dim: "#a09080", Muted: "#6a5e50", Ok: "#2a6828", Todo: "#7a5c00", Event: "#245890", Remind: "#984020", Danger: "#882030"},
|
||||
{Name: "catppuccin-latte", Dark: false, Accent: "#8839ef", Dim: "#9ca0b0", Muted: "#6c6f85", Ok: "#40a02b", Todo: "#df8e1d", Event: "#1e66f5", Remind: "#fe640b", Danger: "#d20f39"},
|
||||
{Name: "rosepine-dawn", Dark: false, Accent: "#907aa9", Dim: "#9893a5", Muted: "#797593", Ok: "#56949f", Todo: "#ea9d34", Event: "#286983", Remind: "#d7827e", Danger: "#b4637a"},
|
||||
{Name: "solarized-light", Dark: false, Accent: "#268bd2", Dim: "#93a1a1", Muted: "#586e75", Ok: "#859900", Todo: "#b58900", Event: "#268bd2", Remind: "#cb4b16", Danger: "#dc322f"},
|
||||
}
|
||||
|
||||
var activeThemeIndex int
|
||||
|
||||
func activeTheme() Theme {
|
||||
return themes[activeThemeIndex]
|
||||
}
|
||||
|
||||
func cycleTheme() Theme {
|
||||
activeThemeIndex = (activeThemeIndex + 1) % len(themes)
|
||||
applyTheme()
|
||||
saveTheme()
|
||||
return themes[activeThemeIndex]
|
||||
}
|
||||
|
||||
func glamourStyle() string {
|
||||
if themes[activeThemeIndex].Dark {
|
||||
return "dark"
|
||||
}
|
||||
return "light"
|
||||
}
|
||||
|
||||
func themePath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, ".nib", "theme")
|
||||
}
|
||||
|
||||
func loadTheme() {
|
||||
p := themePath()
|
||||
if p == "" {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := strings.TrimSpace(string(data))
|
||||
for i, t := range themes {
|
||||
if t.Name == name {
|
||||
activeThemeIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveTheme() {
|
||||
p := themePath()
|
||||
if p == "" {
|
||||
return
|
||||
}
|
||||
_ = os.WriteFile(p, []byte(themes[activeThemeIndex].Name+"\n"), 0o644)
|
||||
}
|
||||
Reference in New Issue
Block a user