diff --git a/backend/internal/web/ui/badge.css b/backend/internal/web/ui/badge.css new file mode 100644 index 0000000..fc03a02 --- /dev/null +++ b/backend/internal/web/ui/badge.css @@ -0,0 +1,28 @@ +/* badge.css — Phase 1 ships info / success / danger. + * `warning` is declared in variants.go for forward compatibility but has no + * CSS rule in Phase 1 — it lands when a page first needs it. + */ + +.ui-badge { + display: inline-block; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 600; + border-radius: 0.25rem; + line-height: 1.25; +} + +.ui-badge-info { + background-color: #dbeafe; + color: #1d4ed8; +} + +.ui-badge-success { + background-color: #dcfce7; + color: #16a34a; +} + +.ui-badge-danger { + background-color: #fee2e2; + color: #dc2626; +} diff --git a/backend/internal/web/ui/badge.templ b/backend/internal/web/ui/badge.templ new file mode 100644 index 0000000..93c944a --- /dev/null +++ b/backend/internal/web/ui/badge.templ @@ -0,0 +1,12 @@ +package ui + +// BadgeProps is the input to the Badge templ component. +type BadgeProps struct { + Label string + Variant BadgeVariant +} + +templ Badge(props BadgeProps) { + {{ class := BadgeClass(props.Variant) }} + { props.Label } +} diff --git a/backend/internal/web/ui/button.css b/backend/internal/web/ui/button.css new file mode 100644 index 0000000..637caea --- /dev/null +++ b/backend/internal/web/ui/button.css @@ -0,0 +1,47 @@ +/* button.css — Phase 1 ships only solid / default / md. + * Codex concern #7: no CSS nesting (`&:hover`). All pseudo-class rules are + * declared as top-level selectors so the file is portable across every + * Tailwind v4 standalone processing mode. + */ + +.ui-button { + display: inline-flex; + align-items: center; + font-family: inherit; + font-size: 1rem; + line-height: 1.25; + border: 1px solid transparent; + cursor: pointer; + user-select: none; + text-decoration: none; +} + +.ui-button:focus-visible { + outline: 2px solid #1d4ed8; + outline-offset: 2px; +} + +.ui-button.htmx-request { + opacity: 0.6; + pointer-events: none; +} + +.ui-button-solid-default-md { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + background-color: #2563eb; + padding: 0.5rem 1rem; + font-size: 1rem; + font-weight: 600; + color: #ffffff; +} + +.ui-button-solid-default-md:hover { + background-color: #1d4ed8; +} + +.ui-button-solid-default-md:focus-visible { + outline: 2px solid #1d4ed8; + outline-offset: 2px; +} diff --git a/backend/internal/web/ui/button.templ b/backend/internal/web/ui/button.templ new file mode 100644 index 0000000..df79976 --- /dev/null +++ b/backend/internal/web/ui/button.templ @@ -0,0 +1,23 @@ +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). +type ButtonProps struct { + Label string + Variant ButtonVariant + Tone ButtonTone + Size Size + Type string + Attrs templ.Attributes +} + +templ Button(props ButtonProps) { + {{ btnType := props.Type }} + if btnType == "" { + {{ btnType = "button" }} + } + {{ class := ButtonClass(props.Variant, props.Tone, props.Size) }} + +} diff --git a/backend/internal/web/ui/card.css b/backend/internal/web/ui/card.css new file mode 100644 index 0000000..74dcba6 --- /dev/null +++ b/backend/internal/web/ui/card.css @@ -0,0 +1,8 @@ +/* card.css — slate-50 panel with slate-200 border. */ + +.ui-card { + border-radius: 0.5rem; + border: 1px solid #e2e8f0; + background-color: #f8fafc; + padding: 1.5rem; +} diff --git a/backend/internal/web/ui/card.templ b/backend/internal/web/ui/card.templ new file mode 100644 index 0000000..29893a9 --- /dev/null +++ b/backend/internal/web/ui/card.templ @@ -0,0 +1,11 @@ +package ui + +// Card is a slate-50 panel with a slate-200 border. Children are rendered +// inside via templ's child-content syntax: `@ui.Card(nil) {
...
}`. +// +// `attrs` is a pass-through for arbitrary attributes (id, data-*, hx-*). +templ Card(attrs templ.Attributes) { +