go-htmx-gsd #1
1 changed files with 74 additions and 3 deletions
|
|
@ -19,7 +19,7 @@ created: 2026-05-14
|
|||
|----------|-------|
|
||||
| Tool | none (no shadcn — Go/templ/HTMX stack, no React) |
|
||||
| Preset | not applicable |
|
||||
| Component library | none (raw templ components + Tailwind utilities) |
|
||||
| Component library | **custom — small in-house templ design-system under `backend/internal/web/ui/`** (modeled on the reference at `go-backend/internal/web/ui/`). Phase 1 ships a minimal core; later phases extend the same package. |
|
||||
| Icon library | none in Phase 1 (deferred — inline SVG when needed in Phase 3+) |
|
||||
| Font | System font stack: `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` (Tailwind default `font-sans`) |
|
||||
|
||||
|
|
@ -243,6 +243,69 @@ Minimum bar Phase 1 must meet. Later phases inherit and extend.
|
|||
|
||||
---
|
||||
|
||||
## Component Library Contract
|
||||
|
||||
Phase 1 establishes a small custom templ design-system at `backend/internal/web/ui/`. The shape follows the reference at `go-backend/internal/web/ui/` (props struct + `templ` component + variant/tone/size enums + per-component CSS layer). Layouts and pages MUST consume these components instead of hand-rolling Tailwind classes for primitives that already exist in the package — this is how token discipline is enforced across phases.
|
||||
|
||||
**Package layout (Phase 1):**
|
||||
|
||||
```
|
||||
backend/internal/web/ui/
|
||||
├── tokens.go // semantic token constants (TokenPrimary, TokenDanger, …) — start small
|
||||
├── variants.go // Size / ButtonVariant / ButtonTone / BadgeVariant enums + class builders
|
||||
├── helpers.go // shared helpers (e.g. attribute merging, normalizers)
|
||||
├── base.css // resets, focus-ring base, html/body defaults (imported into tailwind.input.css)
|
||||
├── button.templ // <Button props={...}>; emits class via buttonClass(...)
|
||||
├── button.css // .ui-button + .ui-button-{solid,soft}-{default,…}-{sm,md,lg}
|
||||
├── badge.templ // <Badge props={Variant: ...}>
|
||||
├── badge.css
|
||||
├── card.templ // <Card> { children... } — used for the demo section
|
||||
├── card.css
|
||||
└── ui_test.go // smoke tests: each component renders with each variant without panicking
|
||||
```
|
||||
|
||||
**What Phase 1 ships (and ONLY this):**
|
||||
|
||||
| Component | Props (struct) | Variants in scope | Notes |
|
||||
|-----------|---------------|-------------------|-------|
|
||||
| `Button` | `Label string`, `Variant ButtonVariant`, `Tone ButtonTone`, `Size Size`, `Type string`, `Attrs templ.Attributes` | `Variant: default` only; `Tone: solid` only; `Size: md` only | `Attrs` is the HTMX escape hatch — `hx-get`, `hx-target`, `hx-swap`, `hx-indicator` flow through it. Other variants are *declared in `variants.go`* so later phases inherit the enum surface, but only the default/solid/md combination has a CSS rule in Phase 1. |
|
||||
| `Card` | `Attrs templ.Attributes` + `children` | n/a | Rounded slate-50 panel with slate-200 border (the demo section frame). |
|
||||
| `Badge` | `Label string`, `Variant BadgeVariant` | `info`, `success`, `danger` declared; `info` and `success` rendered in Phase 1 (used by an optional `/healthz` UI badge). `warning` enum value exists with no CSS yet. |
|
||||
| (no `Input`, `Select`, `Modal`, `Table`, `EmptyState`, `IconButton`, `Space`, `Textarea`, `FormField` in Phase 1) | — | — | These live in the reference package but are explicitly deferred. They land in their first-needed phase (forms → Phase 2, modals/tables → Phase 3, etc.). |
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Pages/layouts MUST NOT use raw `<button class="bg-blue-600 …">` — they MUST use `@ui.Button(ui.ButtonProps{...})`. The same goes for the demo section frame: use `@ui.Card`, not a hand-rolled `<section class="rounded-lg border …">`.
|
||||
- The raw demo markup shown in the **HTMX Interaction Pattern** section above is the *rendered output target* — the templ source must produce it via the components.
|
||||
- CSS for each component lives in its own `*.css` file and is imported into `backend/web/tailwind.input.css` via `@import "../internal/web/ui/{name}.css";`. Tailwind utility classes used inside `*.templ` files are picked up by the `@source "../internal/web/**/*.templ"` directive (Research Pitfall 3).
|
||||
- Variant/tone/size enums declared in `variants.go` MUST mirror the reference package's enum names (`SizeSM/MD/LG`, `ButtonVariantDefault/Neutral/Warning/Success/Danger`, `ButtonToneSolid/Soft`, `BadgeVariantInfo/Warning/Success/Danger`). Later phases extend the *CSS rules* for these enums; they do not redefine the enums.
|
||||
- A normalizer pattern (`normalizedSize`, `normalizedButtonVariant`, …) is required for every enum, so zero-value props default to a safe variant (matches the reference at `variants.go:90-150`).
|
||||
- `ui_test.go` MUST render each shipped component with every variant declared in this contract (Phase 1 = Button-default-solid-md, Card, Badge-info, Badge-success) and assert no panic and that the expected root class name (`ui-button`, `ui-card`, `ui-badge-info`, …) appears in the output. This is the Wave 0 test surface for the UI package.
|
||||
|
||||
**Canonical Button usage (the demo CTA, rewritten via the component):**
|
||||
|
||||
```templ
|
||||
@ui.Button(ui.ButtonProps{
|
||||
Label: "Fetch server time",
|
||||
Variant: ui.ButtonVariantDefault,
|
||||
Tone: ui.ButtonToneSolid,
|
||||
Size: ui.SizeMD,
|
||||
Type: "button",
|
||||
Attrs: templ.Attributes{
|
||||
"hx-get": "/demo/time",
|
||||
"hx-target": "#demo-out",
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-indicator": "#demo-spinner",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This must render the exact HTML shown in the **HTMX Interaction Pattern** section (same class soup, same attributes, same accessibility floor). The Tailwind classes themselves live in `button.css` under `.ui-button-solid-default-md` — pages do not repeat them.
|
||||
|
||||
**Deferred from the reference package (do NOT implement in Phase 1):** `Input`, `Textarea`, `Select`, `FormField`, `Modal`, `Table`, `EmptyState`, `IconButton`, `Space`. Each lands in the phase that first needs it, extending this same package.
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|
|
@ -264,7 +327,13 @@ Phase 1 commits to Tailwind v4 standalone CLI (CONTEXT D-12). The input CSS file
|
|||
@import "tailwindcss";
|
||||
|
||||
@source "../templates/**/*.templ";
|
||||
@source "../internal/web/**/*.templ";
|
||||
@source "../internal/web/**/*.go";
|
||||
|
||||
@import "../internal/web/ui/base.css";
|
||||
@import "../internal/web/ui/button.css";
|
||||
@import "../internal/web/ui/card.css";
|
||||
@import "../internal/web/ui/badge.css";
|
||||
```
|
||||
|
||||
No `tailwind.config.js`. No custom theme extensions in Phase 1 — every token in this contract is reachable through Tailwind defaults. Custom tokens land only when a later phase has a justified need.
|
||||
|
|
@ -275,8 +344,10 @@ No `tailwind.config.js`. No custom theme extensions in Phase 1 — every token i
|
|||
|
||||
- Dark mode (deferred — no FOUND requirement)
|
||||
- Icons (deferred — first need lands in Phase 3 tablo list)
|
||||
- Form components (deferred — first form is the login form in Phase 2)
|
||||
- Modal/dialog patterns (deferred — first modal is "create tablo" in Phase 3)
|
||||
- Form components — `Input`, `Textarea`, `Select`, `FormField` (deferred — first form is the login form in Phase 2; extend the `ui` package then)
|
||||
- `IconButton`, `Space` helper (deferred — first need lands in Phase 3)
|
||||
- `Table`, `EmptyState` (deferred — first need lands in Phase 3 tablo list)
|
||||
- Modal/dialog patterns — `Modal` component (deferred — first modal is "create tablo" in Phase 3)
|
||||
- Toasts / flash messages (deferred — first need lands in Phase 2 for login failures)
|
||||
- Empty-state illustrations (deferred — first list-empty state lands in Phase 3)
|
||||
- Kanban board layout (Phase 4)
|
||||
|
|
|
|||
Loading…
Reference in a new issue