xtablo-source/backend/internal/web/ui/variants.go
Arthur Belleville 66f23bba77
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
2026-05-16 13:52:01 +02:00

201 lines
6.3 KiB
Go

package ui
// Size is the canonical component size enum. Phase 1 only renders SizeMD via
// CSS, but every component normalizes Size so future phases can drop in
// `.ui-button-...-sm` / `.ui-button-...-lg` rules without changing call sites.
type Size string
const (
SizeSM Size = "sm"
SizeMD Size = "md"
SizeLG Size = "lg"
)
// ButtonVariant is the semantic-color enum for Button.
type ButtonVariant string
const (
ButtonVariantDefault ButtonVariant = "default"
ButtonVariantNeutral ButtonVariant = "neutral"
ButtonVariantWarning ButtonVariant = "warning"
ButtonVariantSuccess ButtonVariant = "success"
ButtonVariantDanger ButtonVariant = "danger"
ButtonVariantGhost ButtonVariant = "ghost"
)
// ButtonTone is the visual-weight enum for Button (solid vs. soft).
type ButtonTone string
const (
ButtonToneSolid ButtonTone = "solid"
ButtonToneSoft ButtonTone = "soft"
)
// BadgeVariant is the semantic-color enum for Badge.
type BadgeVariant string
const (
BadgeVariantInfo BadgeVariant = "info"
BadgeVariantWarning BadgeVariant = "warning"
BadgeVariantSuccess BadgeVariant = "success"
BadgeVariantDanger BadgeVariant = "danger"
BadgeVariantPrimary BadgeVariant = "primary"
)
// IconButtonVariant is the semantic-color enum for IconButton.
type IconButtonVariant string
const (
IconButtonVariantNeutral IconButtonVariant = "neutral"
IconButtonVariantWarning IconButtonVariant = "warning"
IconButtonVariantSuccess IconButtonVariant = "success"
IconButtonVariantDanger IconButtonVariant = "danger"
)
// IconButtonTone is the visual-weight enum for IconButton (solid vs. ghost).
type IconButtonTone string
const (
IconButtonToneSolid IconButtonTone = "solid"
IconButtonToneGhost IconButtonTone = "ghost"
)
// SpacingStep is the spacing size enum for space components.
type SpacingStep string
const (
SpacingStepXS SpacingStep = "xs"
SpacingStepSM SpacingStep = "sm"
SpacingStepMD SpacingStep = "md"
SpacingStepLG SpacingStep = "lg"
SpacingStepXL SpacingStep = "xl"
)
// NormalizedSize returns the safe default (SizeMD) for the zero value and any
// value not in the declared set.
func NormalizedSize(size Size) Size {
switch size {
case SizeSM, SizeLG:
return size
default:
return SizeMD
}
}
// NormalizedButtonVariant returns the safe default (ButtonVariantDefault) for
// the zero value and any value not in the declared set.
func NormalizedButtonVariant(variant ButtonVariant) ButtonVariant {
switch variant {
case ButtonVariantNeutral, ButtonVariantWarning, ButtonVariantSuccess, ButtonVariantDanger, ButtonVariantGhost:
return variant
default:
return ButtonVariantDefault
}
}
// NormalizedButtonTone returns the safe default (ButtonToneSolid) for the zero
// value and any value not in the declared set.
func NormalizedButtonTone(tone ButtonTone) ButtonTone {
switch tone {
case ButtonToneSoft:
return tone
default:
return ButtonToneSolid
}
}
// NormalizedBadgeVariant returns the safe default (BadgeVariantInfo) for the
// zero value and any value not in the declared set.
func NormalizedBadgeVariant(variant BadgeVariant) BadgeVariant {
switch variant {
case BadgeVariantWarning, BadgeVariantSuccess, BadgeVariantDanger, BadgeVariantPrimary:
return variant
default:
return BadgeVariantInfo
}
}
// NormalizedIconButtonVariant returns the safe default (IconButtonVariantNeutral)
// for the zero value and any value not in the declared set.
func NormalizedIconButtonVariant(variant IconButtonVariant) IconButtonVariant {
switch variant {
case IconButtonVariantWarning, IconButtonVariantSuccess, IconButtonVariantDanger:
return variant
default:
return IconButtonVariantNeutral
}
}
// NormalizedIconButtonTone returns the safe default (IconButtonToneSolid) for
// the zero value and any value not in the declared set.
func NormalizedIconButtonTone(tone IconButtonTone) IconButtonTone {
switch tone {
case IconButtonToneGhost:
return tone
default:
return IconButtonToneSolid
}
}
// NormalizedSpacingStep returns the safe default (SpacingStepMD) for the zero
// value and any value not in the declared set.
func NormalizedSpacingStep(step SpacingStep) SpacingStep {
switch step {
case SpacingStepXS, SpacingStepSM, SpacingStepLG, SpacingStepXL:
return step
default:
return SpacingStepMD
}
}
// ButtonClass assembles the deterministic class string for a Button. Inputs
// are normalized before assembly so callers can pass zero values safely.
//
// Example: ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD) ==
// "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)
s := NormalizedSize(size)
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.
//
// Example: BadgeClass(BadgeVariantInfo) == "ui-badge ui-badge-info".
func BadgeClass(variant BadgeVariant) string {
v := NormalizedBadgeVariant(variant)
return "ui-badge ui-badge-" + string(v)
}
// IconButtonClass assembles the deterministic class string for an IconButton.
// Ghost tone uses the borderless pattern; solid tone uses the filled pattern.
func IconButtonClass(variant IconButtonVariant, tone IconButtonTone) string {
normalizedVariant := NormalizedIconButtonVariant(variant)
switch NormalizedIconButtonTone(tone) {
case IconButtonToneGhost:
return "borderless-icon-button ui-icon-button-ghost ui-icon-button-" + string(normalizedVariant)
default:
return "ui-icon-button ui-icon-button-solid ui-icon-button-" + string(normalizedVariant)
}
}
// SpaceXClass assembles the deterministic class string for a horizontal spacer.
//
// Example: SpaceXClass(SpacingStepMD) == "ui-space-x ui-space-x-md".
func SpaceXClass(step SpacingStep) string {
return "ui-space-x ui-space-x-" + string(NormalizedSpacingStep(step))
}
// SpaceYClass assembles the deterministic class string for a vertical spacer.
//
// Example: SpaceYClass(SpacingStepLG) == "ui-space-y ui-space-y-lg".
func SpaceYClass(step SpacingStep) string {
return "ui-space-y ui-space-y-" + string(NormalizedSpacingStep(step))
}