feat(01-02): add ui package enums, helpers, base CSS
- tokens.go: semantic token constants - variants.go: Size/ButtonVariant/ButtonTone/BadgeVariant enums + Normalized* - helpers.go: mergeAttrs for templ.Attributes - base.css: resets, :focus-visible ring (no nesting)
This commit is contained in:
parent
5b19a8815c
commit
1ff8e681da
5 changed files with 171 additions and 14 deletions
|
|
@ -2,17 +2,4 @@ module backend
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require github.com/a-h/templ v0.3.1020
|
||||||
github.com/a-h/templ v0.3.1020 // indirect
|
|
||||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
|
||||||
github.com/pressly/goose/v3 v3.27.1 // indirect
|
|
||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
|
||||||
golang.org/x/text v0.36.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
|
||||||
28
backend/internal/web/ui/base.css
Normal file
28
backend/internal/web/ui/base.css
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* base.css — global resets and accessibility floor for the design system.
|
||||||
|
* Plain CSS only (no @apply, no nesting) so the file is consumable by both
|
||||||
|
* Tailwind v4 standalone (with @source scanning Go files) and any other
|
||||||
|
* downstream CSS pipeline.
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||||
|
color: #0f172a;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid #2563eb;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
16
backend/internal/web/ui/helpers.go
Normal file
16
backend/internal/web/ui/helpers.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
|
||||||
|
// mergeAttrs returns a new templ.Attributes containing every key from base,
|
||||||
|
// with override keys taking precedence on collision. Either input may be nil.
|
||||||
|
func mergeAttrs(base, override templ.Attributes) templ.Attributes {
|
||||||
|
out := templ.Attributes{}
|
||||||
|
for k, v := range base {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range override {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
21
backend/internal/web/ui/tokens.go
Normal file
21
backend/internal/web/ui/tokens.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Package ui hosts the design-system primitives (Button, Card, Badge) and the
|
||||||
|
// shared enum surface (Size, ButtonVariant, ButtonTone, BadgeVariant) used by
|
||||||
|
// every templ page in the application.
|
||||||
|
//
|
||||||
|
// Phase 1 ships a minimal subset of the semantic token surface declared here.
|
||||||
|
// Later phases extend the rules in *.css files rather than restructuring the
|
||||||
|
// enum/token surface — these constants exist so consumers can refer to tokens
|
||||||
|
// by name without hard-coding string literals.
|
||||||
|
package ui
|
||||||
|
|
||||||
|
// Semantic token names. Phase 1 ships these for forward compatibility; the
|
||||||
|
// CSS rules in base.css / button.css / card.css / badge.css do not yet
|
||||||
|
// dereference these constants. Future phases will route component variants
|
||||||
|
// through tokens (e.g. ButtonVariantDanger -> TokenDanger).
|
||||||
|
const (
|
||||||
|
TokenPrimary string = "primary"
|
||||||
|
TokenNeutral string = "neutral"
|
||||||
|
TokenWarning string = "warning"
|
||||||
|
TokenSuccess string = "success"
|
||||||
|
TokenDanger string = "danger"
|
||||||
|
)
|
||||||
105
backend/internal/web/ui/variants.go
Normal file
105
backend/internal/web/ui/variants.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
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:
|
||||||
|
return variant
|
||||||
|
default:
|
||||||
|
return BadgeVariantInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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-default-md".
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue