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:
parent
fa2405938a
commit
c80ebcb9b1
6 changed files with 193 additions and 1 deletions
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
backend/internal/web/ui/icon-button.css
Normal file
50
backend/internal/web/ui/icon-button.css
Normal 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;
|
||||||
|
}
|
||||||
74
backend/internal/web/ui/icon_button.templ
Normal file
74
backend/internal/web/ui/icon_button.templ
Normal 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>
|
||||||
|
}
|
||||||
|
}
|
||||||
13
backend/internal/web/ui/space.templ
Normal file
13
backend/internal/web/ui/space.templ
Normal 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>
|
||||||
|
}
|
||||||
48
backend/internal/web/ui/spacing.css
Normal file
48
backend/internal/web/ui/spacing.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue