diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-05 19:34:01 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-05 19:34:01 +0200 |
| commit | 5fe164e91e40e8a3f749f4143f7562f940bf9f67 (patch) | |
| tree | d77b03c737628fa58171de28eb89720c96f203b2 /internal/tui/common | |
| parent | a44f6ee30c11963552b5b90a19698873aa9b6b6d (diff) | |
feat(tui): detect terminal theme and apply palettes
Diffstat (limited to 'internal/tui/common')
| -rw-r--r-- | internal/tui/common/styles.go | 128 | ||||
| -rw-r--r-- | internal/tui/common/styles_test.go | 39 |
2 files changed, 132 insertions, 35 deletions
diff --git a/internal/tui/common/styles.go b/internal/tui/common/styles.go index 06ed596..a71ef81 100644 --- a/internal/tui/common/styles.go +++ b/internal/tui/common/styles.go @@ -1,59 +1,117 @@ package common -import "charm.land/lipgloss/v2" +import ( + "image/color" + + "charm.land/lipgloss/v2" +) + +// Palette defines themed colors shared across the TUI package. +type Palette struct { + Background color.Color + Panel color.Color + Primary color.Color + Accent color.Color + Muted color.Color + Text color.Color + Danger color.Color +} + +// NewPalette returns a color palette for dark or light terminal backgrounds. +func NewPalette(isDark bool) Palette { + if isDark { + return Palette{ + Background: lipgloss.Color("235"), + Panel: lipgloss.Color("238"), + Primary: lipgloss.Color("75"), + Accent: lipgloss.Color("222"), + Muted: lipgloss.Color("246"), + Text: lipgloss.Color("255"), + Danger: lipgloss.Color("203"), + } + } + + return Palette{ + Background: lipgloss.Color("255"), + Panel: lipgloss.Color("250"), + Primary: lipgloss.Color("26"), + Accent: lipgloss.Color("88"), + Muted: lipgloss.Color("242"), + Text: lipgloss.Color("235"), + Danger: lipgloss.Color("160"), + } +} var ( // Palette colors shared across the TUI package. - ColorBackground = lipgloss.Color("235") - ColorPanel = lipgloss.Color("238") - ColorPrimary = lipgloss.Color("75") - ColorAccent = lipgloss.Color("222") - ColorMuted = lipgloss.Color("246") - ColorText = lipgloss.Color("255") - ColorDanger = lipgloss.Color("203") + ColorBackground color.Color + ColorPanel color.Color + ColorPrimary color.Color + ColorAccent color.Color + ColorMuted color.Color + ColorText color.Color + ColorDanger color.Color ) var ( // ScreenStyle is the base style for full-screen models. - ScreenStyle = lipgloss.NewStyle(). - Foreground(ColorText) + ScreenStyle lipgloss.Style // HeaderStyle is used by top-level titles and screen headers. - HeaderStyle = lipgloss.NewStyle(). - Bold(true). - Foreground(ColorPrimary) + HeaderStyle lipgloss.Style // TabActiveStyle is applied to the currently-selected tab. - TabActiveStyle = lipgloss.NewStyle(). - Bold(true). - Foreground(ColorBackground). - Background(ColorPrimary). - Padding(0, 1) + TabActiveStyle lipgloss.Style // TabInactiveStyle is applied to non-selected tabs. - TabInactiveStyle = lipgloss.NewStyle(). - Foreground(ColorMuted). - Padding(0, 1) + TabInactiveStyle lipgloss.Style // PanelStyle is used for boxed sections. - PanelStyle = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(ColorPanel). - Padding(0, 1) + PanelStyle lipgloss.Style // HelpBarStyle is used for keybinding hints at the bottom. - HelpBarStyle = lipgloss.NewStyle(). - Foreground(ColorMuted). - BorderTop(true). - BorderForeground(ColorPanel) + HelpBarStyle lipgloss.Style // HighlightStyle emphasizes inline values. - HighlightStyle = lipgloss.NewStyle(). - Bold(true). - Foreground(ColorAccent) + HighlightStyle lipgloss.Style // ErrorStyle is used for fatal or warning messages. - ErrorStyle = lipgloss.NewStyle(). - Bold(true). - Foreground(ColorDanger) + ErrorStyle lipgloss.Style ) + +// ApplyPalette updates shared colors and styles to match the provided theme. +func ApplyPalette(isDark bool) { + palette := NewPalette(isDark) + ColorBackground = palette.Background + ColorPanel = palette.Panel + ColorPrimary = palette.Primary + ColorAccent = palette.Accent + ColorMuted = palette.Muted + ColorText = palette.Text + ColorDanger = palette.Danger + + ScreenStyle = lipgloss.NewStyle().Foreground(ColorText) + HeaderStyle = lipgloss.NewStyle().Bold(true).Foreground(ColorPrimary) + TabActiveStyle = lipgloss.NewStyle(). + Bold(true). + Foreground(ColorBackground). + Background(ColorPrimary). + Padding(0, 1) + TabInactiveStyle = lipgloss.NewStyle(). + Foreground(ColorMuted). + Padding(0, 1) + PanelStyle = lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(ColorPanel). + Padding(0, 1) + HelpBarStyle = lipgloss.NewStyle(). + Foreground(ColorMuted). + BorderTop(true). + BorderForeground(ColorPanel) + HighlightStyle = lipgloss.NewStyle().Bold(true).Foreground(ColorAccent) + ErrorStyle = lipgloss.NewStyle().Bold(true).Foreground(ColorDanger) +} + +func init() { + ApplyPalette(true) +} diff --git a/internal/tui/common/styles_test.go b/internal/tui/common/styles_test.go new file mode 100644 index 0000000..c0900b3 --- /dev/null +++ b/internal/tui/common/styles_test.go @@ -0,0 +1,39 @@ +package common + +import ( + "testing" + + "charm.land/lipgloss/v2" +) + +func TestNewPaletteRendersDistinctThemes(t *testing.T) { + dark := NewPalette(true) + light := NewPalette(false) + + darkRender := lipgloss.NewStyle(). + Foreground(dark.Text). + Background(dark.Background). + Render("ior") + lightRender := lipgloss.NewStyle(). + Foreground(light.Text). + Background(light.Background). + Render("ior") + + if darkRender == lightRender { + t.Fatalf("expected dark and light palettes to render differently") + } +} + +func TestApplyPaletteUpdatesSharedStyles(t *testing.T) { + t.Cleanup(func() { ApplyPalette(true) }) + + ApplyPalette(true) + dark := ScreenStyle.Render("ior") + + ApplyPalette(false) + light := ScreenStyle.Render("ior") + + if dark == light { + t.Fatalf("expected ScreenStyle render to differ between dark and light palettes") + } +} |
