feat(13-04): port icon-button/space components; wire UIIcon into button.templ; update tailwind.input.css (GREEN)

- icon-button.css + icon_button.templ: UIIcon switch (plus/grid3x3/list/filter/search/calendar/pencil/trash + fallback span)
- spacing.css + space.templ: SpaceX/SpaceY with SpacingStep classes
- button.templ: wire @UIIcon(props.Icon) replacing Plan 02 placeholder comment
- tailwind.input.css: add modal, empty-state, table, icon-button, form-field, spacing imports (14 total)
- All 7 new tests pass; full go test ./... green
This commit is contained in:
Arthur Belleville 2026-05-16 14:07:07 +02:00
parent fa2405938a
commit c80ebcb9b1
No known key found for this signature in database
6 changed files with 193 additions and 1 deletions

View file

@ -17,7 +17,9 @@ type ButtonProps struct {
templ Button(props ButtonProps) { templ Button(props ButtonProps) {
<button type={ buttonType(props.Type) } class={ ButtonClass(props.Variant, props.Tone, props.Size) } { props.Attrs... }> <button type={ buttonType(props.Type) } class={ ButtonClass(props.Variant, props.Tone, props.Size) } { props.Attrs... }>
// UIIcon added in Plan 04 — Icon field present, rendering wired when UIIcon exists if props.Icon != "" {
<span class="ui-button-icon">@UIIcon(props.Icon)</span>
}
{ props.Label } { props.Label }
</button> </button>
} }

View file

@ -0,0 +1,50 @@
.ui-icon-button {
align-items: center;
appearance: none;
background: transparent;
border: 0;
border-radius: 0.5rem;
cursor: pointer;
display: inline-flex;
justify-content: center;
min-height: 44px;
min-width: 44px;
padding: 0.5rem;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.ui-icon-button:focus-visible,
.borderless-icon-button:focus-visible {
box-shadow: 0 0 0 3px var(--color-focus-ring);
outline: none;
}
.ui-icon-button-solid.ui-icon-button-neutral {
color: var(--color-text-muted);
}
.ui-icon-button-solid.ui-icon-button-neutral:hover {
background: var(--color-surface-neutral-hover);
color: var(--color-text-primary);
}
.borderless-icon-button {
appearance: none;
background: transparent;
border: 0;
box-shadow: none;
cursor: pointer;
outline: none;
}
.ui-icon-button-ghost.ui-icon-button-neutral,
.ui-icon-button-ghost.ui-icon-button-danger {
color: var(--color-text-faint);
}
.borderless-icon-button svg {
height: 1rem;
width: 1rem;
}

View file

@ -0,0 +1,74 @@
package ui
type IconButtonProps struct {
Label string
Icon string
Variant IconButtonVariant
Tone IconButtonTone
Type string
Attrs templ.Attributes
}
templ IconButton(props IconButtonProps) {
<button type={ buttonType(props.Type) } class={ IconButtonClass(props.Variant, props.Tone) } aria-label={ props.Label } { props.Attrs... }>
@UIIcon(props.Icon)
</button>
}
templ UIIcon(kind string) {
switch kind {
case "plus":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
case "grid3x3":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
<path d="M3 9h18"></path>
<path d="M3 15h18"></path>
<path d="M9 3v18"></path>
<path d="M15 3v18"></path>
</svg>
case "list":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 12h.01"></path>
<path d="M3 18h.01"></path>
<path d="M3 6h.01"></path>
<path d="M8 12h13"></path>
<path d="M8 18h13"></path>
<path d="M8 6h13"></path>
</svg>
case "filter":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
</svg>
case "search":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</svg>
case "calendar":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M8 2v4"></path>
<path d="M16 2v4"></path>
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
<path d="M3 10h18"></path>
</svg>
case "pencil":
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.12 2.12 0 1 1 3 3L7 19l-4 1 1-4Z"></path>
</svg>
case "trash":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash2 w-4 h-4" aria-hidden="true">
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" x2="10" y1="11" y2="17"></line>
<line x1="14" x2="14" y1="11" y2="17"></line>
</svg>
default:
<span aria-hidden="true">{ kind }</span>
}
}

View file

@ -0,0 +1,13 @@
package ui
type SpaceProps struct {
Size SpacingStep
}
templ SpaceX(props SpaceProps) {
<span class={ SpaceXClass(props.Size) } aria-hidden="true"></span>
}
templ SpaceY(props SpaceProps) {
<div class={ SpaceYClass(props.Size) } aria-hidden="true"></div>
}

View file

@ -0,0 +1,48 @@
.ui-space-x {
display: inline-block;
flex-shrink: 0;
}
.ui-space-y {
display: block;
}
.ui-space-x-xs {
width: 0.25rem;
}
.ui-space-x-sm {
width: 0.5rem;
}
.ui-space-x-md {
width: 0.75rem;
}
.ui-space-x-lg {
width: 1rem;
}
.ui-space-x-xl {
width: 1.5rem;
}
.ui-space-y-xs {
height: 0.25rem;
}
.ui-space-y-sm {
height: 0.5rem;
}
.ui-space-y-md {
height: 0.75rem;
}
.ui-space-y-lg {
height: 1rem;
}
.ui-space-y-xl {
height: 1.5rem;
}

View file

@ -12,4 +12,9 @@
@import "./internal/web/ui/input.css"; @import "./internal/web/ui/input.css";
@import "./internal/web/ui/textarea.css"; @import "./internal/web/ui/textarea.css";
@import "./internal/web/ui/select.css"; @import "./internal/web/ui/select.css";
@import "./internal/web/ui/modal.css";
@import "./internal/web/ui/empty-state.css";
@import "./internal/web/ui/table.css";
@import "./internal/web/ui/icon-button.css";
@import "./internal/web/ui/form-field.css"; @import "./internal/web/ui/form-field.css";
@import "./internal/web/ui/spacing.css";