feat: add TUI theme system with four color palettes
Implement the theming layer for the interactive dashboard:
- tui/theme/theme.go: Theme struct with 14 semantic color roles
(Background, Surface, Border, BorderHover, TextDim, TextMuted,
TextPrimary, Accent, Green, Orange, Red, Blue, Purple, Yellow).
Four built-in themes:
* Flexoki Dark (default): warm, low-contrast palette designed for
extended reading. Teal accent (#3AA99F) on near-black background.
* Catppuccin Mocha: pastel warm tones with blue accent (#89B4FA)
on dark surface (#1E1E2E).
* Tokyo Night: cool blue/purple palette with blue accent (#7AA2F7)
on deep navy (#1A1B26).
* Terminal: ANSI 16-color only for maximum compatibility — maps
all roles to standard terminal colors (0-15), works correctly
in any terminal emulator regardless of true-color support.
Global Active variable holds the current theme. SetActive/ByName
enable runtime theme switching from the settings tab and setup
wizard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
119
internal/tui/theme/theme.go
Normal file
119
internal/tui/theme/theme.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package theme
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
// Theme defines the color roles used throughout the TUI.
|
||||||
|
type Theme struct {
|
||||||
|
Name string
|
||||||
|
Background lipgloss.Color
|
||||||
|
Surface lipgloss.Color
|
||||||
|
Border lipgloss.Color
|
||||||
|
BorderHover lipgloss.Color
|
||||||
|
TextDim lipgloss.Color
|
||||||
|
TextMuted lipgloss.Color
|
||||||
|
TextPrimary lipgloss.Color
|
||||||
|
Accent lipgloss.Color
|
||||||
|
Green lipgloss.Color
|
||||||
|
Orange lipgloss.Color
|
||||||
|
Red lipgloss.Color
|
||||||
|
Blue lipgloss.Color
|
||||||
|
Purple lipgloss.Color
|
||||||
|
Yellow lipgloss.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active is the currently selected theme.
|
||||||
|
var Active = FlexokiDark
|
||||||
|
|
||||||
|
// FlexokiDark is the default theme.
|
||||||
|
var FlexokiDark = Theme{
|
||||||
|
Name: "flexoki-dark",
|
||||||
|
Background: lipgloss.Color("#100F0F"),
|
||||||
|
Surface: lipgloss.Color("#1C1B1A"),
|
||||||
|
Border: lipgloss.Color("#282726"),
|
||||||
|
BorderHover: lipgloss.Color("#343331"),
|
||||||
|
TextDim: lipgloss.Color("#575653"),
|
||||||
|
TextMuted: lipgloss.Color("#6F6E69"),
|
||||||
|
TextPrimary: lipgloss.Color("#FFFCF0"),
|
||||||
|
Accent: lipgloss.Color("#3AA99F"),
|
||||||
|
Green: lipgloss.Color("#879A39"),
|
||||||
|
Orange: lipgloss.Color("#DA702C"),
|
||||||
|
Red: lipgloss.Color("#D14D41"),
|
||||||
|
Blue: lipgloss.Color("#4385BE"),
|
||||||
|
Purple: lipgloss.Color("#8B7EC8"),
|
||||||
|
Yellow: lipgloss.Color("#D0A215"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// CatppuccinMocha is a warm pastel theme.
|
||||||
|
var CatppuccinMocha = Theme{
|
||||||
|
Name: "catppuccin-mocha",
|
||||||
|
Background: lipgloss.Color("#1E1E2E"),
|
||||||
|
Surface: lipgloss.Color("#313244"),
|
||||||
|
Border: lipgloss.Color("#45475A"),
|
||||||
|
BorderHover: lipgloss.Color("#585B70"),
|
||||||
|
TextDim: lipgloss.Color("#6C7086"),
|
||||||
|
TextMuted: lipgloss.Color("#A6ADC8"),
|
||||||
|
TextPrimary: lipgloss.Color("#CDD6F4"),
|
||||||
|
Accent: lipgloss.Color("#89B4FA"),
|
||||||
|
Green: lipgloss.Color("#A6E3A1"),
|
||||||
|
Orange: lipgloss.Color("#FAB387"),
|
||||||
|
Red: lipgloss.Color("#F38BA8"),
|
||||||
|
Blue: lipgloss.Color("#89B4FA"),
|
||||||
|
Purple: lipgloss.Color("#CBA6F7"),
|
||||||
|
Yellow: lipgloss.Color("#F9E2AF"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokyoNight is a cool blue/purple theme.
|
||||||
|
var TokyoNight = Theme{
|
||||||
|
Name: "tokyo-night",
|
||||||
|
Background: lipgloss.Color("#1A1B26"),
|
||||||
|
Surface: lipgloss.Color("#24283B"),
|
||||||
|
Border: lipgloss.Color("#414868"),
|
||||||
|
BorderHover: lipgloss.Color("#565F89"),
|
||||||
|
TextDim: lipgloss.Color("#565F89"),
|
||||||
|
TextMuted: lipgloss.Color("#A9B1D6"),
|
||||||
|
TextPrimary: lipgloss.Color("#C0CAF5"),
|
||||||
|
Accent: lipgloss.Color("#7AA2F7"),
|
||||||
|
Green: lipgloss.Color("#9ECE6A"),
|
||||||
|
Orange: lipgloss.Color("#FF9E64"),
|
||||||
|
Red: lipgloss.Color("#F7768E"),
|
||||||
|
Blue: lipgloss.Color("#7AA2F7"),
|
||||||
|
Purple: lipgloss.Color("#BB9AF7"),
|
||||||
|
Yellow: lipgloss.Color("#E0AF68"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal uses ANSI 16 colors only.
|
||||||
|
var Terminal = Theme{
|
||||||
|
Name: "terminal",
|
||||||
|
Background: lipgloss.Color("0"),
|
||||||
|
Surface: lipgloss.Color("0"),
|
||||||
|
Border: lipgloss.Color("8"),
|
||||||
|
BorderHover: lipgloss.Color("7"),
|
||||||
|
TextDim: lipgloss.Color("8"),
|
||||||
|
TextMuted: lipgloss.Color("7"),
|
||||||
|
TextPrimary: lipgloss.Color("15"),
|
||||||
|
Accent: lipgloss.Color("6"),
|
||||||
|
Green: lipgloss.Color("2"),
|
||||||
|
Orange: lipgloss.Color("3"),
|
||||||
|
Red: lipgloss.Color("1"),
|
||||||
|
Blue: lipgloss.Color("4"),
|
||||||
|
Purple: lipgloss.Color("5"),
|
||||||
|
Yellow: lipgloss.Color("3"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// All available themes.
|
||||||
|
var All = []Theme{FlexokiDark, CatppuccinMocha, TokyoNight, Terminal}
|
||||||
|
|
||||||
|
// ByName returns a theme by its name, defaulting to FlexokiDark.
|
||||||
|
func ByName(name string) Theme {
|
||||||
|
for _, t := range All {
|
||||||
|
if t.Name == name {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FlexokiDark
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetActive sets the active theme by name.
|
||||||
|
func SetActive(name string) {
|
||||||
|
Active = ByName(name)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user