feat: add Responsive multi-variant layout compositor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
40da0d8b1d
commit
e041f7681f
2 changed files with 98 additions and 0 deletions
39
responsive.go
Normal file
39
responsive.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package html
|
||||
|
||||
import "strings"
|
||||
|
||||
// Responsive wraps multiple Layout variants for breakpoint-aware rendering.
|
||||
// Each variant is rendered inside a container with data-variant for CSS targeting.
|
||||
type Responsive struct {
|
||||
variants []responsiveVariant
|
||||
}
|
||||
|
||||
type responsiveVariant struct {
|
||||
name string
|
||||
layout *Layout
|
||||
}
|
||||
|
||||
// NewResponsive creates a new multi-variant responsive compositor.
|
||||
func NewResponsive() *Responsive {
|
||||
return &Responsive{}
|
||||
}
|
||||
|
||||
// Variant adds a named layout variant (e.g., "desktop", "tablet", "mobile").
|
||||
// Variants render in insertion order.
|
||||
func (r *Responsive) Variant(name string, layout *Layout) *Responsive {
|
||||
r.variants = append(r.variants, responsiveVariant{name: name, layout: layout})
|
||||
return r
|
||||
}
|
||||
|
||||
// Render produces HTML with each variant in a data-variant container.
|
||||
func (r *Responsive) Render(ctx *Context) string {
|
||||
var b strings.Builder
|
||||
for _, v := range r.variants {
|
||||
b.WriteString(`<div data-variant="`)
|
||||
b.WriteString(v.name)
|
||||
b.WriteString(`">`)
|
||||
b.WriteString(v.layout.Render(ctx))
|
||||
b.WriteString(`</div>`)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
59
responsive_test.go
Normal file
59
responsive_test.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package html
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResponsive_SingleVariant(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
r := NewResponsive().
|
||||
Variant("desktop", NewLayout("HLCRF").
|
||||
H(Raw("header")).L(Raw("nav")).C(Raw("main")).R(Raw("aside")).F(Raw("footer")))
|
||||
got := r.Render(ctx)
|
||||
|
||||
if !strings.Contains(got, `data-variant="desktop"`) {
|
||||
t.Errorf("responsive should contain data-variant, got:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, `data-block="H-0"`) {
|
||||
t.Errorf("responsive should contain layout content, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsive_MultiVariant(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
r := NewResponsive().
|
||||
Variant("desktop", NewLayout("HLCRF").H(Raw("h")).L(Raw("l")).C(Raw("c")).R(Raw("r")).F(Raw("f"))).
|
||||
Variant("tablet", NewLayout("HCF").H(Raw("h")).C(Raw("c")).F(Raw("f"))).
|
||||
Variant("mobile", NewLayout("C").C(Raw("c")))
|
||||
|
||||
got := r.Render(ctx)
|
||||
|
||||
for _, v := range []string{"desktop", "tablet", "mobile"} {
|
||||
if !strings.Contains(got, `data-variant="`+v+`"`) {
|
||||
t.Errorf("responsive missing variant %q in:\n%s", v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsive_VariantOrder(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
r := NewResponsive().
|
||||
Variant("desktop", NewLayout("HLCRF").C(Raw("d"))).
|
||||
Variant("mobile", NewLayout("C").C(Raw("m")))
|
||||
|
||||
got := r.Render(ctx)
|
||||
|
||||
di := strings.Index(got, `data-variant="desktop"`)
|
||||
mi := strings.Index(got, `data-variant="mobile"`)
|
||||
if di < 0 || mi < 0 {
|
||||
t.Fatalf("missing variants in:\n%s", got)
|
||||
}
|
||||
if di >= mi {
|
||||
t.Errorf("desktop should appear before mobile (insertion order), desktop=%d mobile=%d", di, mi)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsive_ImplementsNode(t *testing.T) {
|
||||
var _ Node = NewResponsive()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue