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:
parent
4bdb78debf
commit
fbdf188f5f
6 changed files with 180 additions and 0 deletions
40
backend/internal/web/ui/empty-state.css
Normal file
40
backend/internal/web/ui/empty-state.css
Normal 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;
|
||||
}
|
||||
27
backend/internal/web/ui/empty_state.templ
Normal file
27
backend/internal/web/ui/empty_state.templ
Normal 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>
|
||||
}
|
||||
53
backend/internal/web/ui/modal.css
Normal file
53
backend/internal/web/ui/modal.css
Normal 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;
|
||||
}
|
||||
27
backend/internal/web/ui/modal.templ
Normal file
27
backend/internal/web/ui/modal.templ
Normal 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>
|
||||
}
|
||||
10
backend/internal/web/ui/table.css
Normal file
10
backend/internal/web/ui/table.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.ui-table-shell {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-table {
|
||||
border-collapse: collapse;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
23
backend/internal/web/ui/table.templ
Normal file
23
backend/internal/web/ui/table.templ
Normal 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>
|
||||
}
|
||||
Loading…
Reference in a new issue