feat(html): support attributes on responsive wrappers
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-03 17:45:51 +00:00
parent d0e7f60dab
commit 4c0669ef1a
2 changed files with 51 additions and 2 deletions

View file

@ -1,6 +1,8 @@
package html
import (
"maps"
"slices"
"strconv"
"strings"
)
@ -10,6 +12,7 @@ import (
// Each variant is rendered inside a container with data-variant for CSS targeting.
type Responsive struct {
variants []responsiveVariant
attrs map[string]string
}
type responsiveVariant struct {
@ -20,7 +23,19 @@ type responsiveVariant struct {
// responsive.go: NewResponsive creates a new multi-variant responsive compositor.
// Example: r := NewResponsive().
func NewResponsive() *Responsive {
return &Responsive{}
return &Responsive{
attrs: make(map[string]string),
}
}
func (r *Responsive) setAttr(key, value string) {
if r == nil {
return
}
if r.attrs == nil {
r.attrs = make(map[string]string)
}
r.attrs[key] = value
}
// escapeCSSString escapes a string for safe use inside a double-quoted CSS
@ -85,7 +100,19 @@ func (r *Responsive) Render(ctx *Context) string {
var b strings.Builder
for _, v := range r.variants {
b.WriteString(`<div data-variant="`)
b.WriteString(`<div`)
if len(r.attrs) > 0 {
keys := slices.Collect(maps.Keys(r.attrs))
slices.Sort(keys)
for _, key := range keys {
b.WriteByte(' ')
b.WriteString(escapeHTML(key))
b.WriteString(`="`)
b.WriteString(escapeAttr(r.attrs[key]))
b.WriteByte('"')
}
}
b.WriteString(` data-variant="`)
b.WriteString(escapeAttr(v.name))
b.WriteString(`">`)
b.WriteString(Render(v.layout, ctx))

View file

@ -115,6 +115,28 @@ func TestResponsive_NilLayoutVariant(t *testing.T) {
}
}
func TestResponsive_Attributes(t *testing.T) {
ctx := NewContext()
r := Attr(NewResponsive().
Variant("desktop", NewLayout("C").C(Raw("main"))).
Variant("mobile", NewLayout("C").C(Raw("main"))),
"aria-label", "Responsive content",
)
r = Attr(r, "class", "responsive-shell")
got := r.Render(ctx)
if count := strings.Count(got, `aria-label="Responsive content"`); count != 2 {
t.Fatalf("responsive attrs should apply to each wrapper, got %d in:\n%s", count, got)
}
if count := strings.Count(got, `class="responsive-shell"`); count != 2 {
t.Fatalf("responsive class should apply to each wrapper, got %d in:\n%s", count, got)
}
if !strings.Contains(got, `aria-label="Responsive content" class="responsive-shell" data-variant="desktop"`) {
t.Fatalf("responsive wrapper attrs should be sorted and preserved, got:\n%s", got)
}
}
func TestVariantSelector(t *testing.T) {
tests := []struct {
name string