feat(theme): token semantik Catppuccin (Mocha/Latte/terminal) + mode auto via tea.BackgroundColorMsg + tombol T
CI / test (push) Successful in 11s
CI / test (push) Successful in 11s
This commit is contained in:
1 parent
c65d2cb75d
commit
8b2a86877e
13 files changed
+428
-37
No files matched your search
@@ -0,0 +1,120 @@
|
||||
// Package theme — token warna semantik (PRD §5.4).
|
||||
//
|
||||
// Komponen TIDAK boleh memakai kode warna langsung; semua lewat token
|
||||
// (Base, Surface, Text, Accent, Success, …) yang dipetakan ke palet aktif
|
||||
// (Mocha/Latte/ANSI terminal), sehingga ganti mode tidak mengubah view.
|
||||
package theme
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
ModeAuto Mode = "auto" // deteksi background → Mocha (gelap) / Latte (terang)
|
||||
ModeDark Mode = "dark" // Catppuccin Mocha
|
||||
ModeLight Mode = "light" // Catppuccin Latte
|
||||
ModeTerminal Mode = "terminal" // palet ANSI terminal pengguna
|
||||
)
|
||||
|
||||
// Color — "#rrggbb", indeks ANSI ("1".."255"), atau "" = default terminal
|
||||
// (dipakai mode terminal: background & warna dasar tidak diubah).
|
||||
type Color string
|
||||
|
||||
// Paint mewarnai teks; Color kosong = teks apa adanya (tanpa escape).
|
||||
func (c Color) Paint(s string) string {
|
||||
if c == "" {
|
||||
return s
|
||||
}
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(string(c))).Render(s)
|
||||
}
|
||||
|
||||
// Theme — token semantik aktif.
|
||||
type Theme struct {
|
||||
Mode Mode
|
||||
AccentName string
|
||||
|
||||
Base, Surface, Overlay Color
|
||||
Text, Subtext Color
|
||||
Accent Color
|
||||
Success, Warning, Danger Color
|
||||
Info, Hover, Selection Color
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
current = build(ModeDark, "mauve", true)
|
||||
// detectedDark diisi lewat SetDetectedDark saat tea.BackgroundColorMsg
|
||||
// tiba (mode auto). Default gelap = fallback PRD §5.4.
|
||||
detectedDark = true
|
||||
)
|
||||
|
||||
// Init dipanggil sekali saat startup (dari config).
|
||||
func Init(mode Mode, accent string) { Set(mode, accent) }
|
||||
|
||||
// Set mengganti tema aktif (validasi nilai; dipakai juga saat runtime).
|
||||
func Set(mode Mode, accent string) {
|
||||
m := normalizeMode(mode)
|
||||
mu.RLock()
|
||||
dark := detectedDark
|
||||
mu.RUnlock()
|
||||
|
||||
t := build(m, accent, dark)
|
||||
mu.Lock()
|
||||
current = t
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// SetDetectedDark — dipanggil dari Update saat tea.BackgroundColorMsg tiba;
|
||||
// merakit ulang tema bila mode aktif adalah auto.
|
||||
// (Query background dilakukan oleh Bubble Tea sendiri — bukan lipgloss langsung,
|
||||
// karena hanya framework yang boleh membaca stdin di dalam loop input.)
|
||||
func SetDetectedDark(dark bool) {
|
||||
mu.Lock()
|
||||
detectedDark = dark
|
||||
cur := current
|
||||
mu.Unlock()
|
||||
if cur.Mode == ModeAuto {
|
||||
Set(ModeAuto, cur.AccentName)
|
||||
}
|
||||
}
|
||||
|
||||
func Current() Theme {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return current
|
||||
}
|
||||
|
||||
// CycleMode — urutan tombol cepat `T`: auto → dark → light → terminal.
|
||||
func CycleMode(m Mode) Mode {
|
||||
switch m {
|
||||
case ModeAuto:
|
||||
return ModeDark
|
||||
case ModeDark:
|
||||
return ModeLight
|
||||
case ModeLight:
|
||||
return ModeTerminal
|
||||
default:
|
||||
return ModeAuto
|
||||
}
|
||||
}
|
||||
|
||||
// Style — style dasar berwarna token; Color kosong = tanpa warna.
|
||||
func Style(c Color) lipgloss.Style {
|
||||
if c == "" {
|
||||
return lipgloss.NewStyle()
|
||||
}
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(string(c)))
|
||||
}
|
||||
|
||||
func normalizeMode(m Mode) Mode {
|
||||
switch m {
|
||||
case ModeAuto, ModeDark, ModeLight, ModeTerminal:
|
||||
return m
|
||||
default:
|
||||
return ModeDark
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user