Skip to content

Commit

Permalink
refactor: first step to new api, multiple components and simplified s…
Browse files Browse the repository at this point in the history
…tyles
  • Loading branch information
indaco committed Apr 30, 2024
1 parent 30ada22 commit 738ce72
Show file tree
Hide file tree
Showing 26 changed files with 722 additions and 1,248 deletions.
36 changes: 33 additions & 3 deletions _examples/a-h-templ/home.templ
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,27 @@ package main

import "github.com/indaco/gropdown"

templ HomePage(dropdown *gropdown.DropdownBuilder) {
const (
profileIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
`
settingsIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12a7.5 7.5 0 0 0 15 0m-15 0a7.5 7.5 0 1 1 15 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077 1.41-.513m14.095-5.13 1.41-.513M5.106 17.785l1.15-.964m11.49-9.642 1.149-.964M7.501 19.795l.75-1.3m7.5-12.99.75-1.3m-6.063 16.658.26-1.477m2.605-14.772.26-1.477m0 17.726-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205 12 12m6.894 5.785-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495" />
</svg>
`

globeIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
</svg>`

clickIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.042 21.672 13.684 16.6m0 0-2.51 2.225.569-9.47 5.227 7.917-3.286-.672ZM12 2.25V4.5m5.834.166-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243-1.59-1.59" />
</svg>
`
)

templ HomePage() {
<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -24,10 +44,20 @@ templ HomePage(dropdown *gropdown.DropdownBuilder) {
<body class="parent-container">
<div class="centered">
<!-- display the dropdown -->
@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');"}})
}
}
</div>
<!-- inject dropdown javascript -->
@gropdown.GropdownJS(dropdown.Dropdown())
@gropdown.GropdownJS(gropdown.GetConfigMapFromContext(ctx))
</body>
</html>
}
41 changes: 9 additions & 32 deletions _examples/a-h-templ/main.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,24 @@
package main

import (
"context"
"log"
"net/http"
"os"

"github.com/a-h/templ"
"github.com/indaco/gropdown"
)

const (
profileIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
`
settingsIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12a7.5 7.5 0 0 0 15 0m-15 0a7.5 7.5 0 1 1 15 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077 1.41-.513m14.095-5.13 1.41-.513M5.106 17.785l1.15-.964m11.49-9.642 1.149-.964M7.501 19.795l.75-1.3m7.5-12.99.75-1.3m-6.063 16.658.26-1.477m2.605-14.772.26-1.477m0 17.726-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205 12 12m6.894 5.785-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495" />
</svg>
`

globeIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
</svg>`

clickIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.042 21.672 13.684 16.6m0 0-2.51 2.225.569-9.47 5.227 7.917-3.286-.672ZM12 2.25V4.5m5.834.166-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243-1.59-1.59" />
</svg>
`
)

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() {
Expand Down
72 changes: 72 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
65 changes: 65 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -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()
}
15 changes: 8 additions & 7 deletions gohtml.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
80 changes: 26 additions & 54 deletions gropdown-button.templ
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,40 @@ const defaultButtonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" v
</svg>
`

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) {
<button
id={ buttonId(btn.Label) }
class={ gddButton(), templ.KV(gddButton_IconOnly(), btn.Label == "") }
aria-label={ btn.Label }
id={ buttonId(label) }
class={ "gdd_button", templ.KV("gdd_button_icon_only", label == "") }
aria-label={ label }
aria-haspopup="true"
aria-expanded="false"
aria-controls={ menuId(btn.Label) }
{ btn.Attrs... }
aria-controls={ menuId(label) }
>
if btn.Label != "" {
{ btn.Label }
if label != "" {
{ label }
}
<span class={ gttSrOnly() }>Open/Close icon</span>
<span class={ gddButton_Icon() }>
if btn.Icon != "" {
@templ.Raw(btn.Icon)
<span class="gdd_sr_only">Open/Close icon</span>
<span class="gdd_button_icon">
if icon.value != "" {
@templ.Raw(icon.value)
} else {
@templ.Raw(defaultButtonIcon)
}
</span>
</button>
}

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;
}
Loading

0 comments on commit 738ce72

Please sign in to comment.