feat standardize logbook nav and add a profile button

The logbook tab always reads 'Logbook' rather than the username. The username moves to a dedicated profile control in the masthead that links to the logbook; on mobile it is the top-bar profile button the bottom nav had no room for, and it joins the inline nav at desktop widths.

Commit
63c00846fbc6c7d642c462e4ed8c8433c7d2980f
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ENHANCEMENTS.org
index ceff0caf..57819522 100644..100644
@@ -66,7 +66,7 @@
66 66 When cancellation is confirmed, return the Current Workout tab to the Routine tab, save the workout as a partial logbook entry, and keep it editable later.
67 67 ** DONE Style form dropdowns to match the UI
68 68 Style form dropdown menus so they match the overall UI aesthetic.
69 Removed: ** TODO Standardize Logbook navigation and add a mobile profile button
69 Added: ** DONE Standardize Logbook navigation and add a mobile profile button
70 70 Always label the logbook tab `Logbook`. Add a dedicated profile button for the user name in the mobile top navigation bar.
71 71 ** TODO Use shared load and reps column headings
72 72 In exercise logging fieldsets, show Load and Reps as column headings instead of repeating them above every input in a multi-exercise group.
lib/web/assets/hito.css
index 3aaa2b55..ce326b36 100644..100644
@@ -92,6 +92,30 @@
92 92 text-decoration: none;
93 93 }
94 94
95 Added: /* The profile control: the signed-in username, linking to the logbook. Right
96 Added: of the brand, it is the mobile top-bar profile button and joins the inline
97 Added: nav at desktop widths. It reads as a compact pill. */
98 Added: .profile {
99 Added: display: inline-flex;
100 Added: align-items: center;
101 Added: min-height: 2.75rem;
102 Added: padding: 0.5rem 0.9rem;
103 Added: border: 1px solid var(--rule-strong);
104 Added: border-radius: var(--radius);
105 Added: color: var(--ink);
106 Added: font-weight: 700;
107 Added: text-decoration: none;
108 Added: }
109 Added: .profile:hover {
110 Added: color: var(--oxblood-dark);
111 Added: background: #ece4d5;
112 Added: }
113 Added: .profile[aria-current] {
114 Added: color: var(--paper-raised);
115 Added: background: var(--oxblood);
116 Added: border-color: var(--oxblood-dark);
117 Added: }
118 Added:
95 119 .eyebrow,
96 120 .ledger-meta {
97 121 font: 700 var(--font-size-small)/1.35 var(--sans);
lib/web/pages.ml
index 0487c548..9a870641 100644..100644
@@ -40,15 +40,37 @@
40 40 in
41 41 (* Three always-available actions. The middle slot points to the routine
42 42 normally and to the workout in progress while logging, so a running
43 Removed: workout stays one tap away without adding a fourth destination. *)
44 Removed: let navigation_links username =
43 Added: workout stays one tap away without adding a fourth destination. The logbook
44 Added: tab always reads "Logbook"; the username lives on its own profile control. *)
45 Added: let navigation_links () =
45 46 [
46 47 nav_link "home" Routes.home "Home";
47 48 (if logging then nav_link "workout" Routes.workout "Current workout"
48 49 else nav_link "routine" Routes.routine "Routine");
49 Removed: nav_link "logbook" Routes.logbook username;
50 Added: nav_link "logbook" Routes.logbook "Logbook";
50 51 ]
51 52 in
53 Added: (* The profile control names the signed-in trainee and links to the logbook.
54 Added: It sits in the masthead, and on mobile it is the top-bar profile button the
55 Added: bottom nav has no room for. *)
56 Added: let profile_area =
57 Added: match trainee with
58 Added: | Some (trainee : Trainee.t) ->
59 Added: let attrs =
60 Added: [ class_ "profile"; href Routes.logbook ]
61 Added: @ if spa_client then [ Dream_html.attr "data-hito-app-link" ] else []
62 Added: in
63 Added: let attrs =
64 Added: if String.equal "logbook" active then
65 Added: Dream_html.string_attr "aria-current" "page" :: attrs
66 Added: else attrs
67 Added: in
68 Added: [
69 Added: tag "a" attrs
70 Added: [ txt "%s" (Trainee.username_to_string trainee.username) ];
71 Added: ]
72 Added: | None -> []
73 Added: in
52 74 let account_area =
53 75 match (trainee, request) with
54 76 | Some _, Some request ->
@@ -73,24 +95,20 @@
73 95 in
74 96 let primary_nav =
75 97 match (trainee, request) with
76 Removed: | Some (trainee : Trainee.t), Some _ ->
77 Removed: [
78 Removed: tag "nav"
79 Removed: [ class_ "primary-nav" ]
80 Removed: (navigation_links (Trainee.username_to_string trainee.username));
81 Removed: ]
98 Added: | Some (_ : Trainee.t), Some _ ->
99 Added: [ tag "nav" [ class_ "primary-nav" ] (navigation_links ()) ]
82 100 | _ -> []
83 101 in
84 102 let bottom_nav =
85 103 match (trainee, request) with
86 Removed: | Some (trainee : Trainee.t), Some _ ->
104 Added: | Some (_ : Trainee.t), Some _ ->
87 105 [
88 106 tag "nav"
89 107 [
90 108 class_ "bottom-nav";
91 109 Dream_html.string_attr "aria-label" "Mobile navigation";
92 110 ]
93 Removed: (navigation_links (Trainee.username_to_string trainee.username));
111 Added: (navigation_links ());
94 112 ]
95 113 | _ -> []
96 114 in
@@ -137,7 +155,7 @@
137 155 else [])
138 156 [ txt "hito" ];
139 157 ]
140 Removed: @ primary_nav @ account_area);
158 Added: @ primary_nav @ profile_area @ account_area);
141 159 tag "main"
142 160 [ id "main-content"; tabindex "-1" ]
143 161 [
test/test_web.ml
index 10a2cc6a..a2ae6449 100644..100644
@@ -173,8 +173,12 @@
173 173 "starts the overview with a page-level heading" true
174 174 (contains ~substring:"<h1>Routines</h1>" (body overview));
175 175 Alcotest.(check bool)
176 Removed: "shows the username in logbook navigation" true
177 Removed: (contains ~substring:"href=\"/logbook\"" (body overview)
176 Added: "labels the logbook tab Logbook" true
177 Added: (contains ~substring:">Logbook</a>" (body overview));
178 Added: Alcotest.(check bool)
179 Added: "shows the username on a profile control linking to the logbook"
180 Added: true
181 Added: (contains ~substring:"class=\"profile\"" (body overview)
178 182 && contains ~substring:">lifter</a>" (body overview));
179 183 Alcotest.(check bool)
180 184 "omits the header username" false