theme.go RawHistoryFolder
1// Package styles provides reusable lipgloss-based TUI components.
2package styles
3
4import (
5 "github.com/charmbracelet/lipgloss"
6
7 "github.com/bnema/dumber/internal/domain/entity"
8 "github.com/bnema/dumber/internal/infrastructure/config"
9)
10
11// Theme holds lipgloss colors and styles derived from config.
12type Theme struct {
13 // Base colors (from config.ColorPalette)
14 Background lipgloss.Color
15 Surface lipgloss.Color
16 SurfaceVariant lipgloss.Color
17 Text lipgloss.Color
18 Muted lipgloss.Color
19 Accent lipgloss.Color
20 Border lipgloss.Color
21
22 // Additional semantic colors
23 Error lipgloss.Color
24 Warning lipgloss.Color
25 Success lipgloss.Color
26
27 // Pre-built styles
28 Title lipgloss.Style
29 Subtitle lipgloss.Style
30 Normal lipgloss.Style
31 Subtle lipgloss.Style
32 Highlight lipgloss.Style
33 ErrorStyle lipgloss.Style
34 WarningStyle lipgloss.Style
35 SuccessStyle lipgloss.Style
36
37 // Component styles
38 ActiveTab lipgloss.Style
39 InactiveTab lipgloss.Style
40 TabBar lipgloss.Style
41
42 ListItem lipgloss.Style
43 ListItemSelected lipgloss.Style
44 ListItemTitle lipgloss.Style
45 ListItemDesc lipgloss.Style
46
47 Badge lipgloss.Style
48 BadgeMuted lipgloss.Style
49
50 Input lipgloss.Style
51 InputFocused lipgloss.Style
52
53 HelpKey lipgloss.Style
54 HelpDesc lipgloss.Style
55
56 Box lipgloss.Style
57 BoxHeader lipgloss.Style
58}
59
60// DefaultDarkPalette returns hardcoded dark theme colors.
61func DefaultDarkPalette() config.ColorPalette {
62 return config.ColorPalette{
63 Background: "#0a0a0b",
64 Surface: "#1a1a1b",
65 SurfaceVariant: "#2d2d2d",
66 Text: "#ffffff",
67 Muted: "#909090",
68 Accent: "#4ade80",
69 Border: "#333333",
70 }
71}
72
73// NewTheme creates a Theme from config, using dark palette only.
74func NewTheme(cfg *config.Config) *Theme {
75 var p config.ColorPalette
76 if cfg != nil && cfg.Appearance.DarkPalette.Background != "" {
77 p = cfg.Appearance.DarkPalette
78 } else {
79 p = DefaultDarkPalette()
80 }
81
82 return NewThemeFromPalette(p)
83}
84
85// NewThemeFromResolved creates a Theme from the active resolved palette.
86func NewThemeFromResolved(resolved entity.ResolvedTheme) *Theme {
87 p := resolved.ActivePalette
88 if p == (entity.ColorPalette{}) {
89 p = resolved.DarkPalette
90 }
91 if p == (entity.ColorPalette{}) {
92 p = DefaultDarkPalette()
93 }
94 return NewThemeFromPalette(p)
95}
96
97// NewThemeFromPalette creates a Theme from a ColorPalette.
98func NewThemeFromPalette(p config.ColorPalette) *Theme {
99 t := &Theme{
100 Background: lipgloss.Color(p.Background),
101 Surface: lipgloss.Color(p.Surface),
102 SurfaceVariant: lipgloss.Color(p.SurfaceVariant),
103 Text: lipgloss.Color(p.Text),
104 Muted: lipgloss.Color(p.Muted),
105 Accent: lipgloss.Color(p.Accent),
106 Border: lipgloss.Color(p.Border),
107
108 // Semantic colors (not in config, use sensible defaults)
109 Error: lipgloss.Color("#ef4444"),
110 Warning: lipgloss.Color("#f59e0b"),
111 Success: lipgloss.Color(p.Accent), // Use accent as success
112 }
113
114 // Build derived styles
115 t.buildStyles()
116 return t
117}
118
119// buildStyles creates all derived lipgloss styles.
120func (t *Theme) buildStyles() {
121 // Text styles
122 t.Title = lipgloss.NewStyle().
123 Foreground(t.Text).
124 Bold(true)
125
126 t.Subtitle = lipgloss.NewStyle().
127 Foreground(t.Muted).
128 Bold(true)
129
130 t.Normal = lipgloss.NewStyle().
131 Foreground(t.Text)
132
133 t.Subtle = lipgloss.NewStyle().
134 Foreground(t.Muted)
135
136 t.Highlight = lipgloss.NewStyle().
137 Foreground(t.Accent).
138 Bold(true)
139
140 t.ErrorStyle = lipgloss.NewStyle().
141 Foreground(t.Error)
142
143 t.WarningStyle = lipgloss.NewStyle().
144 Foreground(t.Warning)
145
146 t.SuccessStyle = lipgloss.NewStyle().
147 Foreground(t.Success)
148
149 // Tab styles
150 t.ActiveTab = lipgloss.NewStyle().
151 Foreground(t.Background).
152 Background(t.Accent).
153 Padding(0, 2).
154 Bold(true)
155
156 t.InactiveTab = lipgloss.NewStyle().
157 Foreground(t.Muted).
158 Background(t.Surface).
159 Padding(0, 2)
160
161 t.TabBar = lipgloss.NewStyle().
162 Background(t.Surface).
163 BorderStyle(lipgloss.NormalBorder()).
164 BorderBottom(true).
165 BorderForeground(t.Border)
166
167 // List item styles
168 t.ListItem = lipgloss.NewStyle().
169 Foreground(t.Text).
170 PaddingLeft(2)
171
172 t.ListItemSelected = lipgloss.NewStyle().
173 Foreground(t.Accent).
174 Background(t.SurfaceVariant).
175 PaddingLeft(2).
176 Bold(true)
177
178 t.ListItemTitle = lipgloss.NewStyle().
179 Foreground(t.Text)
180
181 t.ListItemDesc = lipgloss.NewStyle().
182 Foreground(t.Muted)
183
184 // Badge styles
185 t.Badge = lipgloss.NewStyle().
186 Foreground(t.Background).
187 Background(t.Accent).
188 Padding(0, 1)
189
190 t.BadgeMuted = lipgloss.NewStyle().
191 Foreground(t.Text).
192 Background(t.SurfaceVariant).
193 Padding(0, 1)
194
195 // Input styles
196 t.Input = lipgloss.NewStyle().
197 Foreground(t.Text).
198 Background(t.Surface).
199 BorderStyle(lipgloss.RoundedBorder()).
200 BorderForeground(t.Border).
201 Padding(0, 1)
202
203 t.InputFocused = lipgloss.NewStyle().
204 Foreground(t.Text).
205 Background(t.Surface).
206 BorderStyle(lipgloss.RoundedBorder()).
207 BorderForeground(t.Accent).
208 Padding(0, 1)
209
210 // Help styles
211 t.HelpKey = lipgloss.NewStyle().
212 Foreground(t.Accent)
213
214 t.HelpDesc = lipgloss.NewStyle().
215 Foreground(t.Muted)
216
217 // Box/container styles
218 t.Box = lipgloss.NewStyle().
219 BorderStyle(lipgloss.RoundedBorder()).
220 BorderForeground(t.Border).
221 Padding(1, 2)
222
223 t.BoxHeader = lipgloss.NewStyle().
224 Foreground(t.Text).
225 Bold(true).
226 BorderStyle(lipgloss.NormalBorder()).
227 BorderBottom(true).
228 BorderForeground(t.Border).
229 MarginBottom(1)
230}
231
232// Renderer returns a lipgloss renderer for the current output.
233func (t *Theme) Renderer() *lipgloss.Renderer {
234 if t == nil {
235 return lipgloss.DefaultRenderer()
236 }
237 return lipgloss.DefaultRenderer()
238}