diff --git a/backend/internal/web/ui/empty-state.css b/backend/internal/web/ui/empty-state.css new file mode 100644 index 0000000..b361f0a --- /dev/null +++ b/backend/internal/web/ui/empty-state.css @@ -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; +} diff --git a/backend/internal/web/ui/empty_state.templ b/backend/internal/web/ui/empty_state.templ new file mode 100644 index 0000000..22a975d --- /dev/null +++ b/backend/internal/web/ui/empty_state.templ @@ -0,0 +1,27 @@ +package ui + +type EmptyStateProps struct { + Title string + Description string + Icon templ.Component + Action templ.Component +} + +templ EmptyState(props EmptyStateProps) { +
+ if props.Icon != nil { +
+ @props.Icon +
+ } +

{ props.Title }

+ if props.Description != "" { +

{ props.Description }

+ } + if props.Action != nil { +
+ @props.Action +
+ } +
+} diff --git a/backend/internal/web/ui/modal.css b/backend/internal/web/ui/modal.css new file mode 100644 index 0000000..854e1cd --- /dev/null +++ b/backend/internal/web/ui/modal.css @@ -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; +} diff --git a/backend/internal/web/ui/modal.templ b/backend/internal/web/ui/modal.templ new file mode 100644 index 0000000..f3ac5d4 --- /dev/null +++ b/backend/internal/web/ui/modal.templ @@ -0,0 +1,27 @@ +package ui + +type ModalProps struct { + Title string + Body templ.Component + Actions templ.Component +} + +templ Modal(props ModalProps) { +
+
+
+

{ props.Title }

+
+ if props.Body != nil { +
+ @props.Body +
+ } + if props.Actions != nil { +
+ @props.Actions +
+ } +
+
+} diff --git a/backend/internal/web/ui/table.css b/backend/internal/web/ui/table.css new file mode 100644 index 0000000..292f192 --- /dev/null +++ b/backend/internal/web/ui/table.css @@ -0,0 +1,10 @@ +.ui-table-shell { + overflow-x: auto; + width: 100%; +} + +.ui-table { + border-collapse: collapse; + min-width: 100%; + width: 100%; +} diff --git a/backend/internal/web/ui/table.templ b/backend/internal/web/ui/table.templ new file mode 100644 index 0000000..dead420 --- /dev/null +++ b/backend/internal/web/ui/table.templ @@ -0,0 +1,23 @@ +package ui + +type TableProps struct { + Head templ.Component + Body templ.Component +} + +templ Table(props TableProps) { +
+ + + if props.Head != nil { + @props.Head + } + + + if props.Body != nil { + @props.Body + } + +
+
+}