Change style package, new components, ...

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-08-18 20:10:32 +02:00
parent 499d61bddf
commit 21b574eb6f
56 changed files with 2741 additions and 363 deletions
+17
View File
@@ -0,0 +1,17 @@
# bubbles
Themed constructors for every official `bubbles/v2` component: `help`, `textarea`, `textinput`,
`list`, `table`, `filepicker`, `spinner`, `progress`, `paginator`, `viewport`. Each wraps the
official `New`, then applies `style.S` colors on top.
- `spinner`, `table`, `progress` forward any `opts` to the upstream `New` before the theme is
applied, so the theme's colors always win over anything conflicting in `opts`.
- `ViewportView` renders a `viewport.Model` with a themed scrollbar thumb in the left gutter,
shown only when content overflows.
- `NewList`/`NewDefaultDelegate` theme both the list chrome and the selection/filter colors of its
default delegate.
Custom components in this repo (`tabs`, `modal`, `drawer`, `notification`, `helpbar`) build any
official component they need through here, never through `charm.land/bubbles/v2` directly.
See `examples/bubbles`.
+1
View File
@@ -0,0 +1 @@
package bubbles
+25
View File
@@ -0,0 +1,25 @@
package bubbles
import (
"charm.land/bubbles/v2/filepicker"
"github.com/anotherhadi/ilovetui/style"
)
func NewFilePicker() filepicker.Model {
f := filepicker.New()
s := filepicker.DefaultStyles()
s.Cursor = s.Cursor.Foreground(style.S.Primary)
s.DisabledCursor = s.DisabledCursor.Foreground(style.S.Subtle)
s.Symlink = s.Symlink.Foreground(style.S.Warning)
s.Directory = s.Directory.Foreground(style.S.Primary)
s.File = s.File.Foreground(style.S.Text)
s.DisabledFile = s.DisabledFile.Foreground(style.S.Subtle)
s.Permission = s.Permission.Foreground(style.S.Muted)
s.Selected = s.Selected.Foreground(style.S.Primary)
s.DisabledSelected = s.DisabledSelected.Foreground(style.S.Subtle)
s.FileSize = s.FileSize.Foreground(style.S.Muted)
s.EmptyDirectory = s.EmptyDirectory.Foreground(style.S.Subtle)
f.Styles = s
return f
}
+20
View File
@@ -0,0 +1,20 @@
package bubbles
import (
"charm.land/bubbles/v2/help"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
)
func NewHelp() help.Model {
h := help.New()
h.Styles.ShortKey = lipgloss.NewStyle().Foreground(style.S.Primary)
h.Styles.ShortDesc = lipgloss.NewStyle().Foreground(style.S.Muted)
h.Styles.ShortSeparator = lipgloss.NewStyle().Foreground(style.S.Subtle)
h.Styles.FullKey = lipgloss.NewStyle().Foreground(style.S.Primary)
h.Styles.FullDesc = lipgloss.NewStyle().Foreground(style.S.Muted)
h.Styles.FullSeparator = lipgloss.NewStyle().Foreground(style.S.Subtle)
h.Styles.Ellipsis = lipgloss.NewStyle().Foreground(style.S.Subtle)
return h
}
+47
View File
@@ -0,0 +1,47 @@
package bubbles
import (
"charm.land/bubbles/v2/list"
"github.com/anotherhadi/ilovetui/style"
)
func NewList(items []list.Item, width, height int) list.Model {
m := list.New(items, NewDefaultDelegate(), width, height)
m.Styles = themedListStyles()
return m
}
func themedListStyles() list.Styles {
s := list.DefaultStyles(true)
s.Title = s.Title.Background(style.S.Primary).Foreground(style.S.Background)
s.Spinner = s.Spinner.Foreground(style.S.Primary)
s.Filter = themedTextInputStyles()
s.DefaultFilterCharacterMatch = s.DefaultFilterCharacterMatch.Foreground(style.S.Primary)
s.StatusBar = s.StatusBar.Foreground(style.S.Muted)
s.StatusEmpty = s.StatusEmpty.Foreground(style.S.Subtle)
s.StatusBarActiveFilter = s.StatusBarActiveFilter.Foreground(style.S.Text)
s.StatusBarFilterCount = s.StatusBarFilterCount.Foreground(style.S.Subtle)
s.NoItems = s.NoItems.Foreground(style.S.Subtle)
s.ArabicPagination = s.ArabicPagination.Foreground(style.S.Subtle)
s.ActivePaginationDot = s.ActivePaginationDot.Foreground(style.S.Primary)
s.InactivePaginationDot = s.InactivePaginationDot.Foreground(style.S.Subtle)
s.DividerDot = s.DividerDot.Foreground(style.S.Subtle)
return s
}
func NewDefaultDelegate() list.DefaultDelegate {
d := list.NewDefaultDelegate()
d.Styles.NormalTitle = d.Styles.NormalTitle.Foreground(style.S.Text)
d.Styles.NormalDesc = d.Styles.NormalDesc.Foreground(style.S.Muted)
d.Styles.SelectedTitle = d.Styles.SelectedTitle.
BorderForeground(style.S.Primary).
Foreground(style.S.Primary)
d.Styles.SelectedDesc = d.Styles.SelectedDesc.
BorderForeground(style.S.Primary).
Foreground(style.S.Primary)
d.Styles.DimmedTitle = d.Styles.DimmedTitle.Foreground(style.S.Subtle)
d.Styles.DimmedDesc = d.Styles.DimmedDesc.Foreground(style.S.SubtleBg)
d.Styles.FilterMatch = d.Styles.FilterMatch.Foreground(style.S.Primary)
return d
}
+16
View File
@@ -0,0 +1,16 @@
package bubbles
import (
"charm.land/bubbles/v2/paginator"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
)
func NewPaginator() paginator.Model {
p := paginator.New()
p.Type = paginator.Dots
p.ActiveDot = lipgloss.NewStyle().Foreground(style.S.Primary).Render("•")
p.InactiveDot = lipgloss.NewStyle().Foreground(style.S.Subtle).Render("•")
return p
}
+16
View File
@@ -0,0 +1,16 @@
package bubbles
import (
"charm.land/bubbles/v2/progress"
"github.com/anotherhadi/ilovetui/style"
)
func NewProgress(opts ...progress.Option) progress.Model {
allOpts := append([]progress.Option{
progress.WithColors(style.S.Subtle, style.S.Primary),
}, opts...)
p := progress.New(allOpts...)
p.EmptyColor = style.S.SubtleBg
return p
}
+14
View File
@@ -0,0 +1,14 @@
package bubbles
import (
"charm.land/bubbles/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
)
func NewSpinner(opts ...spinner.Option) spinner.Model {
s := spinner.New(opts...)
s.Style = lipgloss.NewStyle().Foreground(style.S.Primary)
return s
}
+17
View File
@@ -0,0 +1,17 @@
package bubbles
import (
"charm.land/bubbles/v2/table"
"github.com/anotherhadi/ilovetui/style"
)
func NewTable(opts ...table.Option) table.Model {
t := table.New(opts...)
s := table.DefaultStyles()
s.Header = s.Header.Foreground(style.S.Primary)
s.Cell = s.Cell.Foreground(style.S.Text)
s.Selected = s.Selected.Foreground(style.S.Primary)
t.SetStyles(s)
return t
}
+31
View File
@@ -0,0 +1,31 @@
package bubbles
import (
"charm.land/bubbles/v2/textarea"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
)
func NewTextarea(showLineNumbers bool) textarea.Model {
ta := textarea.New()
ta.Prompt = ""
ta.ShowLineNumbers = showLineNumbers
ta.CharLimit = 0
ta.EndOfBufferCharacter = '~'
ts := ta.Styles()
ts.Focused.Base = lipgloss.NewStyle()
ts.Blurred.Base = lipgloss.NewStyle()
ts.Focused.Text = lipgloss.NewStyle().Foreground(style.S.Text)
ts.Focused.CursorLine = lipgloss.NewStyle().Background(style.S.Selection).Foreground(style.S.Text)
ts.Focused.CursorLineNumber = lipgloss.NewStyle().Background(style.S.Selection).Foreground(style.S.Primary).Bold(true)
ts.Focused.LineNumber = lipgloss.NewStyle().Foreground(style.S.Subtle)
ts.Focused.Placeholder = lipgloss.NewStyle().Foreground(style.S.Subtle)
ts.Focused.EndOfBuffer = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
ts.Blurred.Text = lipgloss.NewStyle().Foreground(style.S.Muted)
ts.Blurred.LineNumber = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
ts.Blurred.Placeholder = lipgloss.NewStyle().Foreground(style.S.Subtle)
ts.Blurred.EndOfBuffer = lipgloss.NewStyle().Foreground(style.S.SubtleBg)
ta.SetStyles(ts)
return ta
}
+27
View File
@@ -0,0 +1,27 @@
package bubbles
import (
"charm.land/bubbles/v2/textinput"
"github.com/anotherhadi/ilovetui/style"
)
func NewTextInput() textinput.Model {
t := textinput.New()
t.SetStyles(themedTextInputStyles())
return t
}
func themedTextInputStyles() textinput.Styles {
s := textinput.DefaultStyles(true)
s.Focused.Text = s.Focused.Text.Foreground(style.S.Text)
s.Focused.Placeholder = s.Focused.Placeholder.Foreground(style.S.Subtle)
s.Focused.Suggestion = s.Focused.Suggestion.Foreground(style.S.Subtle)
s.Focused.Prompt = s.Focused.Prompt.Foreground(style.S.Primary)
s.Blurred.Text = s.Blurred.Text.Foreground(style.S.Muted)
s.Blurred.Placeholder = s.Blurred.Placeholder.Foreground(style.S.Subtle)
s.Blurred.Suggestion = s.Blurred.Suggestion.Foreground(style.S.Subtle)
s.Blurred.Prompt = s.Blurred.Prompt.Foreground(style.S.Subtle)
s.Cursor.Color = style.S.Primary
return s
}
+47
View File
@@ -0,0 +1,47 @@
package bubbles
import (
"charm.land/bubbles/v2/viewport"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
)
const ViewportGutterWidth = 2
func NewViewport() viewport.Model {
vp := viewport.New()
vp.MouseWheelEnabled = false
return vp
}
func ViewportView(vp *viewport.Model) string {
height := vp.Height()
total := vp.TotalLineCount()
blank := lipgloss.NewStyle().Width(ViewportGutterWidth).Render("")
if height <= 0 || total <= height {
vp.LeftGutterFunc = func(viewport.GutterContext) string { return blank }
return vp.View()
}
yOffset := vp.YOffset()
thumbSize := max(1, height*height/total)
thumbStart := 0
if maxOffset := total - height; maxOffset > 0 {
thumbStart = yOffset * (height - thumbSize) / maxOffset
}
trackStyle := lipgloss.NewStyle().Foreground(style.S.Subtle)
thumbStyle := lipgloss.NewStyle().Foreground(style.S.Primary)
vp.LeftGutterFunc = func(ctx viewport.GutterContext) string {
pos := ctx.Index - yOffset
if pos >= thumbStart && pos < thumbStart+thumbSize {
return thumbStyle.Render("█") + " "
}
return trackStyle.Render("│") + " "
}
return vp.View()
}