From c47e236713237c35fe1072682379236b129479d4 Mon Sep 17 00:00:00 2001 From: Priec Date: Fri, 7 Aug 2026 21:37:40 +0200 Subject: [PATCH] web using penguin-ui --- web/src/lib.rs | 8 ++- web/src/pages/add_table/draft.rs | 1 + web/src/pages/add_table/ui.rs | 2 +- web/static/app.css | 15 ++---- web/templates/ui/alert.html | 56 +++++++++++++-------- web/templates/ui/base.html | 65 +++++++++++++++--------- web/templates/ui/dialog.html | 84 ++++++++++++++++---------------- web/templates/ui/navbar.html | 72 +++++++++++++++++++-------- 8 files changed, 183 insertions(+), 120 deletions(-) diff --git a/web/src/lib.rs b/web/src/lib.rs index 9229ee34..7eae1e19 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -202,8 +202,10 @@ mod tests { for path in ["/", "/login", "/register"] { let (status, body) = get(path).await; assert!(status.is_success(), "{path} returned {status}"); + // Penguin UI's navbar, so what identifies it is its Alpine + // state rather than a class this app wrote. assert!( - body.contains("
"), + body.contains(r#"x-data="{ mobileMenuIsOpen: false }""#), "{path} is missing the shared navbar" ); assert!( @@ -312,6 +314,8 @@ mod tests { async fn stylesheet_is_served_once_for_every_page() { let (status, body) = get("/static/app.css").await; assert!(status.is_success()); - assert!(body.contains(".topbar")); + // The navbar and the alerts are Penguin UI's own Tailwind classes now, + // so what is left in this file is the site's layout. + assert!(body.contains(".form-card")); } } diff --git a/web/src/pages/add_table/draft.rs b/web/src/pages/add_table/draft.rs index 94b3ae2d..5869a022 100644 --- a/web/src/pages/add_table/draft.rs +++ b/web/src/pages/add_table/draft.rs @@ -331,6 +331,7 @@ impl TableDraft { .map(|link| ProtoTableLink { linked_table_name: link.linked_table_name.clone(), required: link.mode.is_required(), + name: link.linked_table_name.clone(), }) .collect(), accounting_currency: if self.creating_new_profile { diff --git a/web/src/pages/add_table/ui.rs b/web/src/pages/add_table/ui.rs index 2eed8373..696c1fb9 100644 --- a/web/src/pages/add_table/ui.rs +++ b/web/src/pages/add_table/ui.rs @@ -183,7 +183,7 @@ mod tests { state.error = Some("That column already exists.".to_string()); let html = render_builder(&state); assert!(html.contains(r#"role="dialog""#)); - assert!(html.contains("x-data=\"{ modalIsOpen: true }\"")); + assert!(html.contains("x-data=\"{ dangerModalIsOpen: true }\"")); assert_eq!(html.matches("That column already exists.").count(), 2); } diff --git a/web/static/app.css b/web/static/app.css index 972022eb..180148f8 100644 --- a/web/static/app.css +++ b/web/static/app.css @@ -25,15 +25,10 @@ textarea { resize: vertical; font: 13px/1.5 ui-monospace, monospace; } main { width: min(1500px, calc(100% - 40px)); margin: 34px auto; } - /* ---------- Navbar (components/navbar.html) ---------- */ - - .topbar { min-height: 58px; padding: 0 28px; display: flex; align-items: center; justify-content: space-between; color: #eef4ff; background: #152238; } - .topbar > div, .topbar nav { display: flex; align-items: center; gap: 18px; } - .topbar a, .link-button { color: #cbd7e8; text-decoration: none; background: transparent; border: 0; cursor: pointer; padding: 8px 2px; width: auto; } - .topbar a:hover, .link-button:hover, .topbar a.active { color: white; } - .topbar a.active { border-bottom: 2px solid #68a4ff; } - .topbar form { margin: 0; } - .role { color: #9fb0c8; font-size: 12px; } + /* ---------- Navbar ---------- + Nothing here: the navbar is Penguin UI's, styled by its own Tailwind + classes in ui/navbar.html. The dark `.topbar` bar this replaced was the + last hand-written component on the site. */ /* ---------- Page heading (components/heading.html) ---------- */ @@ -189,8 +184,6 @@ .heading { align-items: start; flex-direction: column; } .workspace { grid-template-columns: 1fr; } .pane + .pane { border-left: 0; border-top: 1px solid #e1e5eb; } - .topbar { padding: 10px 16px; align-items: start; gap: 10px; } - .topbar, .topbar nav { flex-wrap: wrap; } .analytics { grid-template-columns: 1fr; } .sidebar { position: static; max-height: none; } .query-row { grid-template-columns: 1fr; } diff --git a/web/templates/ui/alert.html b/web/templates/ui/alert.html index c582cdd8..235d0466 100644 --- a/web/templates/ui/alert.html +++ b/web/templates/ui/alert.html @@ -1,15 +1,20 @@ {# - Inline status blocks — Penguin UI's "alert" component - (https://www.penguinui.com/components/alert, modern style, with icon), minus - the dark variants because the app has no dark theme. The colours come from - the @theme block in ui/base.html. + Inline failure alert — Penguin UI's danger alert, taken from + penguinui-components/alert/alert-dismiss-functionality.html. - Failures only: a success is ui/toast.html, which says its piece and then - removes itself rather than staying in the layout. An error has to stay until + Unlike the toast, this component carries its copy in the markup, so it cannot + be `{% include %}`d from the library the way ui/toast.html's stack is: the + library file says "Invalid Email Address". This is that file's danger variant + with its two text nodes replaced by `{{ title }}` and `{{ message }}` and + nothing else touched — every class, the x-data, the leave transition and the + dismiss button are the library's. Two additions, both about this app's + content rather than the component's look: `mt-4` for the gap from the form + above, and `whitespace-pre-wrap` because the server's messages are multi-line. + + Failures only. A success is ui/toast.html, which says its piece and then + removes itself rather than staying in the layout; an error has to stay until it is read, so it is this. - Imported wherever a form reports a failure: - {% import "ui/alert.html" as alert %} {% call alert::error("Could not create the table", message) %}{% endcall %} @@ -17,18 +22,29 @@ the message in front of someone who has scrolled away from the top of a form. #} {% macro error(title, message) %} -