Fix layout quirks in the admin Contacts view
Three of these come from generic Gloss rules leaking into places they
weren't meant for: field.css's `form { max-width: 28rem }` (sized for
the settings-form pattern) squeezed the search box narrower than the
contact list below it, and typography.css's `li + li` prose-list rule
added a stray gap under every card row but the last.
The detail card gets breathing room between the name and its
properties, added conditionally (`.detail-header:has(+ .detail-grid)`)
so a contact with no properties — which now renders no grid at all
instead of an empty one — keeps its name centered rather than pinned
near the top with dead space below it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYiigu3aFNaiTP9SyKW9mX
diff --git a/lib/pro_tacts/admin/contacts_index.rb b/lib/pro_tacts/admin/contacts_index.rb
index edb2556..43f95a6 100644
--- a/lib/pro_tacts/admin/contacts_index.rb
+++ b/lib/pro_tacts/admin/contacts_index.rb
@@ -26,7 +26,7 @@ module ProTacts
def view_template
render Layout.new(title: "Contacts") do
h1(class: "type-h1") { "Contacts" }
- form(action: "/", method: "get") do
+ form(action: "/", method: "get", class: "search-form") do
input(type: "search", name: "q", value: @query, placeholder: "Search contacts", autofocus: @query.empty?)
end
span(class: "type-label") { @query.empty? ? "recently updated" : "results" }
diff --git a/lib/pro_tacts/admin/contacts_show.rb b/lib/pro_tacts/admin/contacts_show.rb
index deffaa7..ecca1e6 100644
--- a/lib/pro_tacts/admin/contacts_show.rb
+++ b/lib/pro_tacts/admin/contacts_show.rb
@@ -28,11 +28,15 @@ module ProTacts
a(href: "/", class: "type-label") { "‹ contacts" }
div(class: "card") do
div(class: "card-body") do
- div(style: "display: flex; align-items: center; gap: var(--gl-space-s);") do
+ div(class: "detail-header") do
span(class: "avatar", data_size: "lg") { Format.initials(@fields.name || @contact.id) }
h1(class: "type-h2", style: "margin: 0;") { @fields.name || @contact.id }
end
- dl(class: "detail-grid") { rows }
+ # Only rendered when there's something to show: an empty
+ # <dl> would still take up the gap card-body puts between
+ # its children, leaving the header off-center in a card
+ # with nothing else in it.
+ dl(class: "detail-grid") { rows } if has_data?
end
end
end
@@ -40,6 +44,12 @@ module ProTacts
private
+ #: () -> bool
+ def has_data?
+ @fields.phones.any? || @fields.emails.any? || @fields.addresses.any? ||
+ !@fields.birthday.nil? || !@fields.notes.nil?
+ end
+
def rows
@fields.phones.each { |phone| row(phone.type, phone.value) }
@fields.emails.each { |email| row(email.type, email.value) }
diff --git a/public/admin.css b/public/admin.css
index 96d14ef..23754a0 100644
--- a/public/admin.css
+++ b/public/admin.css
@@ -24,6 +24,34 @@
gap: var(--gl-space-m);
}
+/* field.css's `form { max-width: 28rem; }` is sized for the
+ settings-form pattern; the search box is a single input that should
+ span the same width as the list below it. */
+.search-form {
+ max-width: none;
+}
+
+/* base/typography.css's `li + li { margin-top: var(--gl-space-3xs); }`
+ is a prose-list rule (Markdown bullet lists) that also matches
+ Card's row list, adding a stray gap below every row but the last. */
+.card > li {
+ margin: 0;
+}
+
+.detail-header {
+ display: flex;
+ align-items: center;
+ gap: var(--gl-space-s);
+}
+/* Breathing room before the property grid — but only when there's a
+ grid to make room for. A contact with no properties renders no
+ .detail-grid at all (see ContactsShow#has_data?), and this rule
+ staying silent then is what keeps its name centered in the card
+ rather than pinned to the top with dead space below it. */
+.detail-header:has(+ .detail-grid) {
+ margin-bottom: var(--gl-space-m);
+}
+
/* The detail card's load-bearing rule: type in one column, value in
the other, every row aligned to the same grid regardless of what
kind of attribute it is. See docs/DESIGN.md, "Alignment is the
diff --git a/test/pro_tacts/admin/test_contacts_pages.rb b/test/pro_tacts/admin/test_contacts_pages.rb
index d0e2443..50d2a5f 100644
--- a/test/pro_tacts/admin/test_contacts_pages.rb
+++ b/test/pro_tacts/admin/test_contacts_pages.rb
@@ -120,6 +120,19 @@ class AdminContactsPagesTest < Minitest::Test
end
end
+ # No properties at all means no grid, not an empty one — an empty
+ # <dl> would still eat the gap card-body puts before it, leaving the
+ # name pinned near the top of the card instead of centered in it.
+ def test_show_of_a_bare_contact_renders_no_grid
+ bare = "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Bare Contact\r\nUID:bare\r\nEND:VCARD\r\n"
+
+ with_contacts({"bare" => bare}) do
+ get "/contacts/bare"
+
+ refute_includes last_response.body, "detail-grid"
+ end
+ end
+
def test_show_of_an_unknown_contact_is_404
with_contacts({}) { get "/contacts/nope" }