feat(13-04): port modal, empty-state, table components with CSS and templ (GREEN)

- modal.css + modal.templ: backdrop/panel structure, Title/Body/Actions props
- empty-state.css + empty_state.templ: dashed border, nil-guarded Icon/Action
- table.css + table.templ: ui-table-shell wrapper, Head/Body typed props
- All 6 TestModal/TestEmptyState/TestTable tests pass; full suite green
This commit is contained in:
Arthur Belleville 2026-05-16 14:05:11 +02:00
parent 4bdb78debf
commit fbdf188f5f
No known key found for this signature in database
6 changed files with 180 additions and 0 deletions

View file

@ -0,0 +1,40 @@
.ui-empty-state {
align-items: center;
border: 1px dashed var(--color-border-subtle);
border-radius: 1rem;
color: var(--color-text-muted);
display: flex;
flex-direction: column;
gap: 0.75rem;
justify-content: center;
padding: 3rem 1.5rem;
text-align: center;
}
.ui-empty-state-title {
color: var(--color-text-primary);
font-size: 1.125rem;
font-weight: 700;
margin: 0;
}
.ui-empty-state-icon {
align-items: center;
background: var(--color-surface-muted);
border-radius: 999px;
color: var(--color-text-faint);
display: inline-flex;
height: 4rem;
justify-content: center;
width: 4rem;
}
.ui-empty-state-icon svg {
height: 2rem;
width: 2rem;
}
.ui-empty-state-description {
margin: 0;
max-width: 32rem;
}

View file

@ -0,0 +1,27 @@
package ui
type EmptyStateProps struct {
Title string
Description string
Icon templ.Component
Action templ.Component
}
templ EmptyState(props EmptyStateProps) {
<section class="ui-empty-state">
if props.Icon != nil {
<div class="ui-empty-state-icon">
@props.Icon
</div>
}
<h3 class="ui-empty-state-title">{ props.Title }</h3>
if props.Description != "" {
<p class="ui-empty-state-description">{ props.Description }</p>
}
if props.Action != nil {
<div class="ui-empty-state-action">
@props.Action
</div>
}
</section>
}

View file

@ -0,0 +1,53 @@
.ui-modal-backdrop {
align-items: center;
background: var(--overlay-backdrop-default);
display: flex;
inset: 0;
justify-content: center;
padding: 1rem;
position: fixed;
z-index: 40;
}
.ui-modal-panel {
background: var(--color-surface-default);
border: 1px solid var(--color-border-default);
border-radius: 1rem;
box-shadow: var(--shadow-surface-lg);
max-width: 32rem;
width: min(100%, 32rem);
}
.ui-modal-header,
.ui-modal-body,
.ui-modal-actions {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.ui-modal-header {
border-bottom: 1px solid var(--color-border-default);
padding-bottom: 1rem;
padding-top: 1.25rem;
}
.ui-modal-header h2 {
color: var(--color-text-primary);
font-size: 1.125rem;
font-weight: 700;
margin: 0;
}
.ui-modal-body {
padding-bottom: 1.25rem;
padding-top: 1.25rem;
}
.ui-modal-actions {
border-top: 1px solid var(--color-border-default);
display: flex;
gap: 0.75rem;
justify-content: flex-end;
padding-bottom: 1rem;
padding-top: 1rem;
}

View file

@ -0,0 +1,27 @@
package ui
type ModalProps struct {
Title string
Body templ.Component
Actions templ.Component
}
templ Modal(props ModalProps) {
<div class="ui-modal-backdrop">
<div class="ui-modal-panel">
<div class="ui-modal-header">
<h2>{ props.Title }</h2>
</div>
if props.Body != nil {
<div class="ui-modal-body">
@props.Body
</div>
}
if props.Actions != nil {
<div class="ui-modal-actions">
@props.Actions
</div>
}
</div>
</div>
}

View file

@ -0,0 +1,10 @@
.ui-table-shell {
overflow-x: auto;
width: 100%;
}
.ui-table {
border-collapse: collapse;
min-width: 100%;
width: 100%;
}

View file

@ -0,0 +1,23 @@
package ui
type TableProps struct {
Head templ.Component
Body templ.Component
}
templ Table(props TableProps) {
<div class="ui-table-shell">
<table class="ui-table">
<thead>
if props.Head != nil {
@props.Head
}
</thead>
<tbody>
if props.Body != nil {
@props.Body
}
</tbody>
</table>
</div>
}