diff --git a/form.go b/form.go index 76e27889..9e50e5f4 100644 --- a/form.go +++ b/form.go @@ -65,6 +65,7 @@ type Form struct { // options width int height int + theme *Theme keymap *KeyMap timeout time.Duration teaOptions []tea.ProgramOption @@ -240,9 +241,10 @@ func (f *Form) WithShowErrors(v bool) *Form { // can be applied to each group and field individually for more granular // control. func (f *Form) WithTheme(theme *Theme) *Form { - if theme == nil { + if f.theme != nil { return f } + f.theme = theme for _, group := range f.groups { group.WithTheme(theme) } @@ -575,13 +577,23 @@ func (f *Form) isGroupHidden(page int) bool { return hide() } +func (f *Form) styles() *FormStyles { + theme := f.theme + if theme == nil { + theme = ThemeCharm() + } + return &theme.Form +} + // View renders the form. func (f *Form) View() string { if f.quitting { return "" } - return f.layout.View(f) + styles := f.styles() + + return styles.Base.Render(f.layout.View(f)) } // Run runs the form. diff --git a/group.go b/group.go index d9ef2c08..b9e361e9 100644 --- a/group.go +++ b/group.go @@ -38,6 +38,7 @@ type Group struct { // group options width int height int + theme *Theme keymap *KeyMap hide func() bool active bool @@ -92,6 +93,10 @@ func (g *Group) WithShowErrors(show bool) *Group { // WithTheme sets the theme on a group. func (g *Group) WithTheme(t *Theme) *Group { + if g.theme != nil { + return g + } + g.theme = t g.help.Styles = t.Help for _, field := range g.fields { field.WithTheme(t) @@ -265,6 +270,14 @@ func (g *Group) fullHeight() int { return height } +func (g *Group) styles() *GroupStyles { + theme := g.theme + if theme == nil { + theme = ThemeCharm() + } + return &theme.Group +} + func (g *Group) getContent() (int, string) { var fields strings.Builder offset := 0 @@ -312,6 +325,8 @@ func (g *Group) Content() string { // Footer renders the group's footer only (no content). func (g *Group) Footer() string { + styles := g.styles() + var view strings.Builder view.WriteRune('\n') errors := g.Errors() @@ -320,8 +335,8 @@ func (g *Group) Footer() string { } if g.showErrors { for _, err := range errors { - view.WriteString(ThemeCharm().Focused.ErrorMessage.Render(err.Error())) + view.WriteString(styles.ErrorMessage.Render(err.Error())) } } - return view.String() + return styles.Base.Render(view.String()) } diff --git a/theme.go b/theme.go index 32982a1b..35403fdd 100644 --- a/theme.go +++ b/theme.go @@ -9,21 +9,31 @@ import ( // Theme is a collection of styles for components of the form. // Themes can be applied to a form using the WithTheme option. type Theme struct { - Form lipgloss.Style - Group lipgloss.Style + Form FormStyles + Group GroupStyles FieldSeparator lipgloss.Style Blurred FieldStyles Focused FieldStyles Help help.Styles } +// FormStyles are the styles for form. +type FormStyles struct { + Base lipgloss.Style +} + +// GroupStyles are the styles for form groups. +type GroupStyles struct { + Base lipgloss.Style + ErrorMessage lipgloss.Style +} + // FieldStyles are the styles for input fields. type FieldStyles struct { Base lipgloss.Style Title lipgloss.Style Description lipgloss.Style ErrorIndicator lipgloss.Style - ErrorMessage lipgloss.Style // Select styles. SelectSelector lipgloss.Style // Selection indicator @@ -74,6 +84,13 @@ const ( func ThemeBase() *Theme { var t Theme + // Form styles. + t.Form.Base = lipgloss.NewStyle() + + // Group styles. + t.Group.Base = lipgloss.NewStyle() + t.Group.ErrorMessage = lipgloss.NewStyle().SetString(" *") + t.FieldSeparator = lipgloss.NewStyle().SetString("\n\n") button := lipgloss.NewStyle(). @@ -84,7 +101,6 @@ func ThemeBase() *Theme { t.Focused.Base = lipgloss.NewStyle().PaddingLeft(1).BorderStyle(lipgloss.ThickBorder()).BorderLeft(true) t.Focused.Card = lipgloss.NewStyle().PaddingLeft(1) t.Focused.ErrorIndicator = lipgloss.NewStyle().SetString(" *") - t.Focused.ErrorMessage = lipgloss.NewStyle().SetString(" *") t.Focused.SelectSelector = lipgloss.NewStyle().SetString("> ") t.Focused.NextIndicator = lipgloss.NewStyle().MarginLeft(1).SetString("→") t.Focused.PrevIndicator = lipgloss.NewStyle().MarginRight(1).SetString("←") @@ -120,13 +136,14 @@ func ThemeCharm() *Theme { red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"} ) + t.Group.ErrorMessage = t.Group.ErrorMessage.Foreground(red) + t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("238")) t.Focused.Title = t.Focused.Title.Foreground(indigo).Bold(true) t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(indigo).Bold(true).MarginBottom(1) t.Focused.Directory = t.Focused.Directory.Foreground(indigo) t.Focused.Description = t.Focused.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}) t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red) - t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red) t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(fuchsia) t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(fuchsia) t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(fuchsia) @@ -167,6 +184,8 @@ func ThemeDracula() *Theme { yellow = lipgloss.AdaptiveColor{Dark: "#f1fa8c"} ) + t.Group.ErrorMessage = t.Group.ErrorMessage.Foreground(red) + t.Focused.Base = t.Focused.Base.BorderForeground(selection) t.Focused.Title = t.Focused.Title.Foreground(purple) t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(purple) @@ -174,7 +193,6 @@ func ThemeDracula() *Theme { t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red) t.Focused.Directory = t.Focused.Directory.Foreground(purple) t.Focused.File = t.Focused.File.Foreground(foreground) - t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red) t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(yellow) t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(yellow) t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(yellow) @@ -203,13 +221,14 @@ func ThemeDracula() *Theme { func ThemeBase16() *Theme { t := ThemeBase() + t.Group.ErrorMessage = t.Group.ErrorMessage.Foreground(lipgloss.Color("9")) + t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("8")) t.Focused.Title = t.Focused.Title.Foreground(lipgloss.Color("6")) t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(lipgloss.Color("6")) t.Focused.Directory = t.Focused.Directory.Foreground(lipgloss.Color("6")) t.Focused.Description = t.Focused.Description.Foreground(lipgloss.Color("8")) t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(lipgloss.Color("9")) - t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(lipgloss.Color("9")) t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(lipgloss.Color("3")) t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(lipgloss.Color("3")) t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(lipgloss.Color("3")) @@ -259,13 +278,14 @@ func ThemeCatppuccin() *Theme { cursor = lipgloss.AdaptiveColor{Light: light.Rosewater().Hex, Dark: dark.Rosewater().Hex} ) + t.Group.ErrorMessage = t.Group.ErrorMessage.Foreground(red) + t.Focused.Base = t.Focused.Base.BorderForeground(subtext1) t.Focused.Title = t.Focused.Title.Foreground(mauve) t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(mauve) t.Focused.Directory = t.Focused.Directory.Foreground(mauve) t.Focused.Description = t.Focused.Description.Foreground(subtext0) t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red) - t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red) t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(pink) t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(pink) t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(pink)