feat(13-02): multi-class ButtonClass() + Icon field + buttonType helper in button.templ
- ButtonClass() now emits "ui-button ui-button-solid ui-button-default ui-button-md" - Ghost variant special case: "ui-button ui-button-ghost ui-button-md" (tone omitted) - button.templ: added Icon string field to ButtonProps, replaced inline btnType with buttonType() helper - ui_test.go: updated TestButton_DefaultSolidMD to multi-class slice assertions - ui_test.go: updated TestButtonClass_String and TestButtonClass_GhostVariant expectations - All 18 ui package tests pass
This commit is contained in:
parent
ab030dbe71
commit
66f23bba77
3 changed files with 31 additions and 19 deletions
|
|
@ -3,21 +3,21 @@ package ui
|
|||
// ButtonProps is the input to the Button templ component.
|
||||
//
|
||||
// Type defaults to "button" when empty. Attrs is a pass-through for arbitrary
|
||||
// attributes (notably hx-* HTMX attributes).
|
||||
// attributes (notably hx-* HTMX attributes). Icon is a named icon key that
|
||||
// will be rendered via UIIcon when Plan 04 wires up icon_button.templ.
|
||||
type ButtonProps struct {
|
||||
Label string
|
||||
Variant ButtonVariant
|
||||
Tone ButtonTone
|
||||
Size Size
|
||||
Type string
|
||||
Icon string
|
||||
Attrs templ.Attributes
|
||||
}
|
||||
|
||||
templ Button(props ButtonProps) {
|
||||
{{ btnType := props.Type }}
|
||||
if btnType == "" {
|
||||
{{ btnType = "button" }}
|
||||
}
|
||||
{{ class := ButtonClass(props.Variant, props.Tone, props.Size) }}
|
||||
<button type={ btnType } class={ class } { props.Attrs... }>{ props.Label }</button>
|
||||
<button type={ buttonType(props.Type) } class={ ButtonClass(props.Variant, props.Tone, props.Size) } { props.Attrs... }>
|
||||
// UIIcon added in Plan 04 — Icon field present, rendering wired when UIIcon exists
|
||||
{ props.Label }
|
||||
</button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,16 @@ func render(t *testing.T, ctx context.Context, c templ.Component) string {
|
|||
func TestButton_DefaultSolidMD(t *testing.T) {
|
||||
out := render(t, context.Background(), Button(ButtonProps{Label: "Fetch server time"}))
|
||||
|
||||
wantClass := `class="ui-button ui-button-solid-default-md"`
|
||||
if !strings.Contains(out, wantClass) {
|
||||
t.Errorf("output missing class %q\ngot: %s", wantClass, out)
|
||||
// Plan 02: multi-class assertions (one check per class token)
|
||||
for _, wantClass := range []string{
|
||||
`ui-button`,
|
||||
`ui-button-solid`,
|
||||
`ui-button-default`,
|
||||
`ui-button-md`,
|
||||
} {
|
||||
if !strings.Contains(out, wantClass) {
|
||||
t.Errorf("output missing class %q\ngot: %s", wantClass, out)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out, "Fetch server time") {
|
||||
t.Errorf("output missing label literal; got: %s", out)
|
||||
|
|
@ -102,7 +109,7 @@ func TestBadge_ZeroValueDefaultsToInfo(t *testing.T) {
|
|||
|
||||
func TestButtonClass_String(t *testing.T) {
|
||||
got := ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD)
|
||||
want := "ui-button ui-button-solid-default-md"
|
||||
want := "ui-button ui-button-solid ui-button-default ui-button-md"
|
||||
if got != want {
|
||||
t.Errorf("ButtonClass = %q; want %q", got, want)
|
||||
}
|
||||
|
|
@ -142,11 +149,10 @@ func TestButtonVariantGhost_Normalizer(t *testing.T) {
|
|||
|
||||
func TestButtonClass_GhostVariant(t *testing.T) {
|
||||
got := ButtonClass(ButtonVariantGhost, ButtonToneSolid, SizeMD)
|
||||
// ButtonClass uses the compound format (ui-button-{tone}-{variant}-{size}) preserved
|
||||
// from Phase 1 — Plan 02 migrates it to multi-class. Ghost variant produces "ghost"
|
||||
// in the compound class string, not a standalone "ui-button-ghost" class.
|
||||
if !strings.Contains(got, "ghost") {
|
||||
t.Errorf("ButtonClass(Ghost, Solid, MD) = %q; want to contain \"ghost\"", got)
|
||||
// Plan 02: ghost variant omits the tone class and emits "ui-button-ghost" standalone.
|
||||
want := "ui-button ui-button-ghost ui-button-md"
|
||||
if got != want {
|
||||
t.Errorf("ButtonClass(Ghost, Solid, MD) = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,12 +152,18 @@ func NormalizedSpacingStep(step SpacingStep) SpacingStep {
|
|||
// are normalized before assembly so callers can pass zero values safely.
|
||||
//
|
||||
// Example: ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD) ==
|
||||
// "ui-button ui-button-solid-default-md".
|
||||
// "ui-button ui-button-solid ui-button-default ui-button-md".
|
||||
//
|
||||
// Ghost variant is a special case: the tone class is omitted and "ui-button-ghost"
|
||||
// is emitted as a standalone modifier.
|
||||
func ButtonClass(variant ButtonVariant, tone ButtonTone, size Size) string {
|
||||
v := NormalizedButtonVariant(variant)
|
||||
t := NormalizedButtonTone(tone)
|
||||
s := NormalizedSize(size)
|
||||
return "ui-button ui-button-" + string(t) + "-" + string(v) + "-" + string(s)
|
||||
if v == ButtonVariantGhost {
|
||||
return "ui-button ui-button-ghost ui-button-" + string(s)
|
||||
}
|
||||
t := NormalizedButtonTone(tone)
|
||||
return "ui-button ui-button-" + string(t) + " ui-button-" + string(v) + " ui-button-" + string(s)
|
||||
}
|
||||
|
||||
// BadgeClass assembles the deterministic class string for a Badge.
|
||||
|
|
|
|||
Loading…
Reference in a new issue