feat(01-02): add Button, Card, Badge templ components + CSS
- button.templ: Button(ButtonProps) renders <button type=...> with class from ui.ButtonClass(); Attrs spread for hx-* pass-through - button.css: .ui-button base + .ui-button-solid-default-md variant with non-nested :hover and :focus-visible (Codex concern #7) - card.templ: Card(attrs) accepts children via templ child syntax - card.css: slate-50 panel, slate-200 border - badge.templ: Badge(BadgeProps) renders <span class=...> - badge.css: info / success / danger variants (warning deferred)
This commit is contained in:
parent
1ff8e681da
commit
d056b33241
6 changed files with 129 additions and 0 deletions
28
backend/internal/web/ui/badge.css
Normal file
28
backend/internal/web/ui/badge.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
12
backend/internal/web/ui/badge.templ
Normal file
12
backend/internal/web/ui/badge.templ
Normal file
|
|
@ -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) }}
|
||||
<span class={ class }>{ props.Label }</span>
|
||||
}
|
||||
47
backend/internal/web/ui/button.css
Normal file
47
backend/internal/web/ui/button.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
23
backend/internal/web/ui/button.templ
Normal file
23
backend/internal/web/ui/button.templ
Normal file
|
|
@ -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) }}
|
||||
<button type={ btnType } class={ class } { props.Attrs... }>{ props.Label }</button>
|
||||
}
|
||||
8
backend/internal/web/ui/card.css
Normal file
8
backend/internal/web/ui/card.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
11
backend/internal/web/ui/card.templ
Normal file
11
backend/internal/web/ui/card.templ
Normal file
|
|
@ -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) { <p>...</p> }`.
|
||||
//
|
||||
// `attrs` is a pass-through for arbitrary attributes (id, data-*, hx-*).
|
||||
templ Card(attrs templ.Attributes) {
|
||||
<section class="ui-card" { attrs... }>
|
||||
{ children... }
|
||||
</section>
|
||||
}
|
||||
Loading…
Reference in a new issue