From 738ce72797ea0f335eefd54b87c4a787cdd54702 Mon Sep 17 00:00:00 2001 From: indaco Date: Tue, 30 Apr 2024 10:25:59 +0200 Subject: [PATCH] refactor: first step to new api, multiple components and simplified styles --- _examples/a-h-templ/home.templ | 36 ++++- _examples/a-h-templ/main.go | 41 ++---- config.go | 72 ++++++++++ constants.go | 3 + context.go | 65 +++++++++ gohtml.go | 15 ++- gropdown-button.templ | 80 ++++------- gropdown-button_templ.go | 190 ++++++++------------------ gropdown-content-item.templ | 52 +------ gropdown-content-item_templ.go | 210 +++-------------------------- gropdown-content.templ | 38 +----- gropdown-content_templ.go | 86 ++---------- gropdown-css.templ | 239 +++++++++++++++++++++++---------- gropdown-css_templ.go | 21 +-- gropdown-divider.templ | 6 + gropdown-divider_templ.go | 36 +++++ gropdown-js.templ | 170 ++++++++++------------- gropdown-js_templ.go | 176 ++++++++++-------------- gropdown-list.templ | 31 ----- gropdown-list_templ.go | 99 -------------- gropdown.go | 55 -------- gropdown.templ | 20 +-- gropdown_templ.go | 50 ++----- gropdown_test.go | 136 ------------------- types.go | 22 +-- utils.go | 21 +++ 26 files changed, 722 insertions(+), 1248 deletions(-) create mode 100644 config.go create mode 100644 context.go create mode 100644 gropdown-divider.templ create mode 100644 gropdown-divider_templ.go delete mode 100644 gropdown-list.templ delete mode 100644 gropdown-list_templ.go delete mode 100644 gropdown.go delete mode 100644 gropdown_test.go diff --git a/_examples/a-h-templ/home.templ b/_examples/a-h-templ/home.templ index 04405d4..d33acaa 100644 --- a/_examples/a-h-templ/home.templ +++ b/_examples/a-h-templ/home.templ @@ -2,7 +2,27 @@ package main import "github.com/indaco/gropdown" -templ HomePage(dropdown *gropdown.DropdownBuilder) { +const ( + profileIcon = ` + + +` + settingsIcon = ` + + +` + + globeIcon = ` + +` + + clickIcon = ` + + +` +) + +templ HomePage() { @@ -24,10 +44,20 @@ templ HomePage(dropdown *gropdown.DropdownBuilder) {
- @dropdown.Render() + @gropdown.Root("demo") { + @gropdown.Button("Menu") + @gropdown.Content() { + @gropdown.Item(gropdown.DropdownItem{Label: "Profile", Href: "/profile", Icon: profileIcon}) + @gropdown.Item(gropdown.DropdownItem{Label: "Settings", Href: "/settings", Icon: settingsIcon}) + @gropdown.Divider() + @gropdown.Item(gropdown.DropdownItem{Label: "GitHub", Href: "https://github.com", External: true, Icon: globeIcon}) + @gropdown.Divider() + @gropdown.Item(gropdown.DropdownItem{Label: "Button", Icon: clickIcon, Attrs: templ.Attributes{"onclick": "alert('Hello gropdown');"}}) + } + }
- @gropdown.GropdownJS(dropdown.Dropdown()) + @gropdown.GropdownJS(gropdown.GetConfigMapFromContext(ctx)) } diff --git a/_examples/a-h-templ/main.go b/_examples/a-h-templ/main.go index 29aa884..fec51da 100644 --- a/_examples/a-h-templ/main.go +++ b/_examples/a-h-templ/main.go @@ -1,47 +1,24 @@ package main import ( + "context" "log" "net/http" "os" - "github.com/a-h/templ" "github.com/indaco/gropdown" ) -const ( - profileIcon = ` - - -` - settingsIcon = ` - - -` - - globeIcon = ` - -` - - clickIcon = ` - - -` -) - func HandleHome(w http.ResponseWriter, r *http.Request) { - button := gropdown.DropdownButton{Label: "Menu"} - items := []gropdown.DropdownItem{ - {Label: "Profile", Href: "/profile", Icon: profileIcon}, - {Label: "Settings", Href: "/settings", Icon: settingsIcon}, - {Divider: true}, - {Label: "GitHub", Href: "https://github.com", External: true, Icon: globeIcon}, - {Divider: true}, - {Label: "Button", Icon: clickIcon, Attrs: templ.Attributes{"onclick": "alert('Hello gropdown');"}}, + config := gropdown.NewConfigBuilder().Build() + configMap := gropdown.NewConfigMap() + configMap.Add("demo", config) + + ctx := context.WithValue(r.Context(), gropdown.ConfigContextKey, configMap) + err := HomePage().Render(ctx, w) + if err != nil { + return } - dropdown := gropdown.NewDropdownBuilder().WithButton(button).WithItems(items) - - templ.Handler(HomePage(dropdown)).ServeHTTP(w, r) } func main() { diff --git a/config.go b/config.go new file mode 100644 index 0000000..190a6fa --- /dev/null +++ b/config.go @@ -0,0 +1,72 @@ +package gropdown + +// Config represents a dropdown menu component. +type Config struct { + Open bool // Open indicates whether the dropdown menu is currently open. + Animation bool // Animation indicates whether the dropdown button should use animations on open and close. + Position Position // Position indicates the position of the dropdown content relative to the button. +} + +// DropdownBuilder is used to construct Dropdown instances with options. +type ConfigBuilder struct { + config Config +} + +type ConfigMap struct { + Data map[string]Config +} + +// DefaultConfig returns the default configuration. +func DefaultConfig() Config { + return Config{ + Open: false, + Animation: true, + Position: Bottom, + } +} + +// NewConfigBuilder creates a new ConfigBuilder instance with default settings. +func NewConfigBuilder() *ConfigBuilder { + return &ConfigBuilder{ + config: DefaultConfig(), + } +} + +// WithOpen sets the Open field of the dropdown. +func (b *ConfigBuilder) WithOpen(open bool) *ConfigBuilder { + b.config.Open = open + return b +} + +// WithAnimation sets the animations for the dropdown button icon when open/close. +func (b *ConfigBuilder) WithAnimation(animation bool) *ConfigBuilder { + b.config.Animation = animation + return b +} + +func (b *ConfigBuilder) WithPosition(position Position) *ConfigBuilder { + b.config.Position = position + return b +} + +func (b *ConfigBuilder) Build() Config { + return b.config +} + +// Initialize a new ConfigMap instance +func NewConfigMap() *ConfigMap { + return &ConfigMap{ + Data: make(map[string]Config), + } +} + +// Add adds a configuration to the map. +func (m *ConfigMap) Add(key string, config Config) { + m.Data[key] = config +} + +// Get retrieves a configuration from the map. +func (m *ConfigMap) Get(key string) (Config, bool) { + config, ok := m.Data[key] + return config, ok +} diff --git a/constants.go b/constants.go index 5e7029d..834b455 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,8 @@ package gropdown +// ConfigContextKey is a context key for the dropdown component configurations. +var ConfigContextKey = contextKey("gropdown-config-ctx") + // Position constants define where the dropdown content will appear on the screen. const ( Top Position = "top" diff --git a/context.go b/context.go new file mode 100644 index 0000000..934c2ad --- /dev/null +++ b/context.go @@ -0,0 +1,65 @@ +package gropdown + +import ( + "context" +) + +// Define the context key type. +type contextKey string + +// GetConfigMapFromContext retrieves the tab configuration from the context. +func GetConfigMapFromContext(ctx context.Context) *ConfigMap { + if opts, ok := ctx.Value(ConfigContextKey).(*ConfigMap); ok { + return opts + } + return &ConfigMap{ + Data: make(map[string]Config), + } +} + +// GetConfigFromContextById retrieves the tab configuration from the context. +func GetConfigFromContextById(ctx context.Context, id string) *Config { + if config, ok := GetConfigMapFromContext(ctx).Get(id); ok { + return &config + } + return &Config{} +} + +// getOpenFromContextById retrieves the configured tab position from the context. +func getOpenFromContextById(ctx context.Context, id string) Position { + if config, ok := GetConfigMapFromContext(ctx).Get(id); ok { + return config.Position + } + return DefaultConfig().Position +} + +// getOpenAsStringFromContextById retrieves the configured tab position from the context. +func getOpenAsStringFromContextById(ctx context.Context, id string) string { + return getOpenFromContextById(ctx, id).String() +} + +// getAnimationFromContextById retrieves the configured tab position from the context. +func getAnimationFromContextById(ctx context.Context, id string) Position { + if config, ok := GetConfigMapFromContext(ctx).Get(id); ok { + return config.Position + } + return DefaultConfig().Position +} + +// getAnimationAsStringFromContextById retrieves the configured tab position from the context. +func getAnimationAsStringFromContextById(ctx context.Context, id string) string { + return getAnimationFromContextById(ctx, id).String() +} + +// getPositionFromContextById retrieves the configured tab position from the context. +func getPositionFromContextById(ctx context.Context, id string) Position { + if config, ok := GetConfigMapFromContext(ctx).Get(id); ok { + return config.Position + } + return DefaultConfig().Position +} + +// getPositionAsStringFromContextById retrieves the configured tab position from the context. +func getPositionAsStringFromContextById(ctx context.Context, id string) string { + return getPositionFromContextById(ctx, id).String() +} diff --git a/gohtml.go b/gohtml.go index 9df6d39..292e05d 100644 --- a/gohtml.go +++ b/gohtml.go @@ -4,8 +4,9 @@ package gropdown import ( "context" "fmt" - "github.com/a-h/templ" "html/template" + + "github.com/a-h/templ" ) // HTMLGenerator provides functions for generating HTML code for dropdown. @@ -26,20 +27,20 @@ func (g *HTMLGenerator) GropdownCSSToGoHTML() (template.HTML, error) { } // GropdownJSToGoHTML generates HTML code for the dropdown JS and returns it as a template.HTML. -func (g *HTMLGenerator) GropdownJSToGoHTML(dropdown *Dropdown) (template.HTML, error) { - html, err := templ.ToGoHTML(context.Background(), GropdownJS(dropdown)) +func (g *HTMLGenerator) GropdownJSToGoHTML(configMap *ConfigMap) (template.HTML, error) { + html, err := templ.ToGoHTML(context.Background(), GropdownJS(configMap)) if err != nil { return "", fmt.Errorf("failed to generate dropdown JS: %v", err) } return html, nil } -// Render generates HTML code for displaying the dropdown and returns it as a template.HTML. -func (g *HTMLGenerator) Render(dd *DropdownBuilder) (template.HTML, error) { +// Render generates HTML code for displaying the tabber and returns it as a template.HTML. +func (g *HTMLGenerator) Render(ctx context.Context, comp templ.Component) (template.HTML, error) { // Generate HTML code for displaying the toast. - html, err := templ.ToGoHTML(context.Background(), dd.Render()) + html, err := templ.ToGoHTML(ctx, comp) if err != nil { - return "", fmt.Errorf("failed to generate dropdown HTML: %v", err) + return "", fmt.Errorf("failed to generate tabber HTML: %v", err) } return html, nil } diff --git a/gropdown-button.templ b/gropdown-button.templ index 0c23c85..367546e 100644 --- a/gropdown-button.templ +++ b/gropdown-button.templ @@ -5,68 +5,40 @@ const defaultButtonIcon = ` ` -templ button(btn DropdownButton) { +// getIcon If icons are provided, use the first one; otherwise, empty ButtonIcon struct. +func getIcon(icons []*ButtonIcon) *ButtonIcon { + var icon *ButtonIcon + if len(icons) > 0 { + icon = icons[0] + } else { + icon = &ButtonIcon{} + } + return icon +} + +templ Button(label string, icons ...*ButtonIcon) { + @renderButton(label, getIcon(icons)) +} + +templ renderButton(label string, icon *ButtonIcon) { Open/Close icon - - if btn.Icon != "" { - @templ.Raw(btn.Icon) + Open/Close icon + + if icon.value != "" { + @templ.Raw(icon.value) } else { @templ.Raw(defaultButtonIcon) } } - -css gddButton() { - cursor: pointer; - min-width: var(--gdd-button-min-w); - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--gdd-button-icon-space); - margin: 0; - padding: var(--gdd-button-py) var(--gdd-button-px); - color: var(--gdd-button-color); - border-width: var(--gdd-button-border-width); - border-style: var(--gdd-button-border-style); - border-color: var(--gdd-button-border-color); - border-radius: var(--gdd-button-border-radius); - background-color: var(--gdd-button-bg-color); - font-family: var(--gdd-button-font-family); - font-size: var(--gdd-button-font-size); - font-weight: var(--gdd-button-font-weight); - line-height: var(--gdd-button-line-height); - letter-spacing: var(--gdd-button-letter-spacing); - text-transform: none; - text-decoration-line: var(--_text-decoration-line); - transition: var(--gdd-button-transition-property) var(--gdd-button-transition-duration) var(--gdd-button-transition-timing-function); -} - -css gddButton_Icon() { - display: inline-flex; - align-items: center; - flex-shrink: 0; - width: 1em; - height: 1em; -} - -css gddButton_IconOnly() { - min-width: 2.5rem; - padding: 0; - width: 2.5rem; - height: 2.5rem; - border-radius: 1e5px; - aspect-ratio: 1; -} diff --git a/gropdown-button_templ.go b/gropdown-button_templ.go index 90a4e5e..12bbf58 100644 --- a/gropdown-button_templ.go +++ b/gropdown-button_templ.go @@ -9,14 +9,24 @@ import "github.com/a-h/templ" import "context" import "io" import "bytes" -import "strings" const defaultButtonIcon = ` ` -func button(btn DropdownButton) templ.Component { +// getIcon If icons are provided, use the first one; otherwise, empty ButtonIcon struct. +func getIcon(icons []*ButtonIcon) *ButtonIcon { + var icon *ButtonIcon + if len(icons) > 0 { + icon = icons[0] + } else { + icon = &ButtonIcon{} + } + return icon +} + +func Button(label string, icons ...*ButtonIcon) templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -29,82 +39,98 @@ func button(btn DropdownButton) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var2 = []any{gddButton(), templ.KV(gddButton_IconOnly(), btn.Label == "")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + templ_7745c5c3_Err = renderButton(label, getIcon(icons)).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" } - -templ divider() { -
-} - -css gddContent_Item() { - position: relative; - display: flex; - width: 100%; - text-decoration: none; - gap: var(--gdd-item-icon-space); - padding: var(--gdd-item-py) var(--gdd-item-px); - color: var(--gdd-item-color); - font-family: var(--gdd-item-font-family); - font-size: var(--gdd-item-font-size); - line-height: var(--gdd-item-line-height); - letter-spacing: var(--gdd-item-letter-spacing); - background-color: var(--gdd-item-bg-color); - border-width: var(--gdd-item-border-width); - border-style: var(--gdd-item-border-style); - border-color: var(--gdd-item-border-color); - border-radius: var(--gdd-item-border-radius); -} - -css gddContent_ItemIcon() { - display: inline-flex; - align-items: center; - flex-shrink: 0; - width: 1.25em; - height: 1.25em; -} - -css gddContent_ItemDivider() { - height: 0; - margin: 0.125rem 0; - overflow: hidden; - border-top: var(--gdd-item-divider-width) var(--gdd-item-divider-style) var(--gdd-item-divider-color); -} diff --git a/gropdown-content-item_templ.go b/gropdown-content-item_templ.go index eda8898..ad4289d 100644 --- a/gropdown-content-item_templ.go +++ b/gropdown-content-item_templ.go @@ -9,11 +9,10 @@ import "github.com/a-h/templ" import "context" import "io" import "bytes" -import "strings" const defaultExternalLinkIcon = `` -func contentItem(item DropdownItem) templ.Component { +func Item(item DropdownItem) templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -30,12 +29,7 @@ func contentItem(item DropdownItem) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if item.Divider { - templ_7745c5c3_Err = divider().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else if item.Href != "" { + if item.Href != "" { templ_7745c5c3_Err = linkItem(item).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -70,30 +64,12 @@ func linkItem(item DropdownItem) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var3 = []any{gddContent_Item()} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -147,12 +105,12 @@ func linkItem(item DropdownItem) templ.Component { return templ_7745c5c3_Err } } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label) + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `gropdown-content-item.templ`, Line: 33, Col: 14} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `gropdown-content-item.templ`, Line: 31, Col: 14} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -185,30 +143,12 @@ func buttonItem(item DropdownItem) templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var10 = []any{gddContent_Item()} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("