go-html/responsive.go
Virgil ba384aeb12
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
fix: add nil-safe rendering paths
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-03 16:55:48 +00:00

81 lines
1.9 KiB
Go

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{}
}
// escapeCSSString escapes a string for safe use inside a double-quoted CSS
// attribute selector.
func escapeCSSString(s string) string {
var b strings.Builder
for _, r := range s {
switch r {
case '\\', '"':
b.WriteByte('\\')
b.WriteRune(r)
case '\n':
b.WriteString(`\a `)
case '\r':
b.WriteString(`\d `)
case '\f':
b.WriteString(`\c `)
default:
b.WriteRune(r)
}
}
return b.String()
}
// VariantSelector returns a CSS attribute selector for a named responsive
// variant.
func VariantSelector(name string) string {
return `[data-variant="` + escapeCSSString(name) + `"]`
}
// ScopeVariant prefixes a selector so it only matches elements inside the
// named responsive variant.
func ScopeVariant(name, selector string) string {
scope := VariantSelector(name)
if selector == "" {
return scope
}
return scope + " " + selector
}
// 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 {
if r == nil {
return ""
}
var b strings.Builder
for _, v := range r.variants {
b.WriteString(`<div data-variant="`)
b.WriteString(escapeAttr(v.name))
b.WriteString(`">`)
b.WriteString(v.layout.Render(ctx))
b.WriteString(`</div>`)
}
return b.String()
}