refactor share link tab rendering

Use one full-width tab primitive for Logbook and App Feedback. Keep server-side navigation and active-page semantics in one presenter helper.

Commit
396fe77fe3f0d9e0e84d5cb68e36318d3c92fa3e
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/presenter/page_kit.ml
index 4c2e2324..be3ca2c3 100644..100644
@@ -21,3 +21,25 @@
21 21 tag "p"
22 22 [ class_ "warn"; Dream_html.string_attr "role" "alert" ]
23 23 [ Dream_html.txt "%s" message ]
24 Added:
25 Added: (* Render server-side link tabs. The active tab uses [aria-current] because
26 Added: each tab navigates to a complete page rather than switching a client panel. *)
27 Added: let link_tabs ~aria_label ~active tabs =
28 Added: let tab_link (key, label, path) =
29 Added: let attrs =
30 Added: [
31 Added: class_ "page-tab";
32 Added: Dream_html.string_attr "href" "%s" path;
33 Added: Dream_html.attr "data-hito-app-link";
34 Added: ]
35 Added: in
36 Added: let attrs =
37 Added: if String.equal key active then
38 Added: Dream_html.string_attr "aria-current" "page" :: attrs
39 Added: else attrs
40 Added: in
41 Added: tag "a" attrs [ Dream_html.txt "%s" label ]
42 Added: in
43 Added: tag "nav"
44 Added: [ class_ "page-tabs"; Dream_html.string_attr "aria-label" "%s" aria_label ]
45 Added: (List.map tab_link tabs)
lib/presenter/pages_app_feedback.ml
index a6d056a9..4e74131b 100644..100644
@@ -5,24 +5,6 @@
5 5 (* The authenticated application feedback page. It keeps writing and reviewing
6 6 feedback in separate tabs, while both tabs remain plain links for no-script
7 7 use and progressive enhancement. *)
8 Removed: let app_feedback_tab ~selected ~tab ~label =
9 Removed: let path =
10 Removed: match tab with
11 Removed: | `Write -> "/app-feedback?tab=write"
12 Removed: | `Submitted -> "/app-feedback?tab=submitted"
13 Removed: in
14 Removed: let attrs =
15 Removed: [
16 Removed: class_ "feedback-tab";
17 Removed: Dream_html.string_attr "href" "%s" path;
18 Removed: Dream_html.attr "data-hito-app-link";
19 Removed: ]
20 Removed: in
21 Removed: let attrs =
22 Removed: if selected = tab then Dream_html.string_attr "aria-current" "page" :: attrs
23 Removed: else attrs
24 Removed: in
25 Removed: tag "a" attrs [ txt "%s" label ]
26 8
27 9 let app_feedback_form request ?error () =
28 10 let error_block =
@@ -182,15 +164,12 @@
182 164 ([
183 165 tag "h1" [] [ txt "App Feedback" ];
184 166 tag "p" [] [ txt "Help us improve hito by sharing your experience." ];
185 Removed: tag "nav"
167 Added: link_tabs ~aria_label:"App feedback views"
168 Added: ~active:
169 Added: (match tab with `Write -> "write" | `Submitted -> "submitted")
186 170 [
187 Removed: class_ "feedback-tabs";
188 Removed: Dream_html.string_attr "aria-label" "App feedback tabs";
189 Removed: ]
190 Removed: [
191 Removed: app_feedback_tab ~selected:tab ~tab:`Write ~label:"Write feedback";
192 Removed: app_feedback_tab ~selected:tab ~tab:`Submitted
193 Removed: ~label:"Submitted feedback";
171 Added: ("write", "Write feedback", "/app-feedback?tab=write");
172 Added: ("submitted", "Submitted feedback", "/app-feedback?tab=submitted");
194 173 ];
195 174 ]
196 175 @ panel)
lib/presenter/pages_logbook.ml
index 9d800788..b0f8cd34 100644..100644
@@ -467,28 +467,11 @@
467 467 ];
468 468 ]
469 469
470 Removed: (* The logbook views are link tabs. They use one endpoint and remain usable
471 Removed: without JavaScript. *)
472 470 let logbook_tabs ~active_tab =
473 Removed: let tab_link ~tab ~label =
474 Removed: let active = String.equal tab active_tab in
475 Removed: tag "a"
476 Removed: ([
477 Removed: Dream_html.string_attr "href" "/logbook?tab=%s" tab;
478 Removed: class_ "logbook-tab";
479 Removed: Dream_html.attr "data-hito-app-link";
480 Removed: ]
481 Removed: @ if active then [ Dream_html.string_attr "aria-current" "page" ] else []
482 Removed: )
483 Removed: [ txt "%s" label ]
484 Removed: in
485 Removed: tag "nav"
471 Added: link_tabs ~aria_label:"Logbook views" ~active:active_tab
486 472 [
487 Removed: class_ "logbook-tabs"; Dream_html.string_attr "aria-label" "Logbook views";
488 Removed: ]
489 Removed: [
490 Removed: tab_link ~tab:"calendar" ~label:"Calendar";
491 Removed: tab_link ~tab:"progress" ~label:"Progress";
473 Added: ("calendar", "Calendar", "/logbook?tab=calendar");
474 Added: ("progress", "Progress", "/logbook?tab=progress");
492 475 ]
493 476
494 477 let progress_legend () =
lib/web/assets/hito.css
index 8344cab6..455cc558 100644..100644
@@ -300,18 +300,18 @@
300 300 }
301 301 .page-heading-link:hover { text-decoration: underline; }
302 302
303 Removed: /* The Logbook views use one endpoint with two full-width link tabs. The active
304 Removed: tab uses the same blood treatment as the application navigation. */
305 Removed: .logbook-tabs {
303 Added: /* The Logbook and App Feedback views use one endpoint with full-width link tabs.
304 Added: The active tab uses the same blood treatment as the application navigation. */
305 Added: .page-tabs {
306 306 display: grid;
307 Removed: grid-template-columns: repeat(2, minmax(0, 1fr));
307 Added: grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
308 308 width: 100%;
309 309 margin: 0 0 1.25rem;
310 310 border: 1px solid var(--rule-strong);
311 311 border-radius: var(--radius);
312 312 overflow: hidden;
313 313 }
314 Removed: .logbook-tab {
314 Added: .page-tab {
315 315 display: flex;
316 316 min-height: 2.75rem;
317 317 align-items: center;
@@ -321,9 +321,9 @@
321 321 background: transparent;
322 322 text-decoration: none;
323 323 }
324 Removed: .logbook-tab + .logbook-tab { border-left: 1px solid var(--rule-strong); }
325 Removed: .logbook-tab:hover,
326 Removed: .logbook-tab[aria-current="page"] {
324 Added: .page-tab + .page-tab { border-left: 1px solid var(--rule-strong); }
325 Added: .page-tab:hover,
326 Added: .page-tab[aria-current="page"] {
327 327 color: var(--on-primary);
328 328 background: var(--blood);
329 329 }
@@ -1396,30 +1396,6 @@
1396 1396
1397 1397 /* The app-feedback page presents one active panel at a time through native
1398 1398 links, so both tabs remain usable without JavaScript. */
1399 Removed: .feedback-tabs {
1400 Removed: display: flex;
1401 Removed: flex-wrap: wrap;
1402 Removed: gap: 0.6rem;
1403 Removed: margin: 1.25rem 0 0;
1404 Removed: border-bottom: 1px solid var(--rule);
1405 Removed: padding-bottom: 0.6rem;
1406 Removed: }
1407 Removed: .feedback-tab {
1408 Removed: display: inline-flex;
1409 Removed: min-height: 2.75rem;
1410 Removed: align-items: center;
1411 Removed: padding: 0.55rem 0.9rem;
1412 Removed: border: 1px solid var(--rule-strong);
1413 Removed: border-radius: var(--radius);
1414 Removed: color: var(--ink);
1415 Removed: text-decoration: none;
1416 Removed: }
1417 Removed: .feedback-tab:hover,
1418 Removed: .feedback-tab[aria-current] {
1419 Removed: color: var(--on-primary);
1420 Removed: background: var(--blood);
1421 Removed: border-color: var(--blood-dark);
1422 Removed: }
1423 1399 .app-feedback-form {
1424 1400 max-width: 42rem;
1425 1401 margin-top: 1.25rem;
test/web/app_feedback.ml
index f6fc2d5a..37ca75f7 100644..100644
@@ -17,6 +17,15 @@
17 17 Alcotest.(check bool)
18 18 "page names the App Feedback affordance" true
19 19 (Option.is_some (index_from ~needle:"App Feedback" page 0));
20 Added: Alcotest.(check bool)
21 Added: "write tab link is present" true
22 Added: (Option.is_some
23 Added: (index_from ~needle:"href=\"/app-feedback?tab=write\"" page 0));
24 Added: Alcotest.(check bool)
25 Added: "submitted tab link is present" true
26 Added: (Option.is_some
27 Added: (index_from ~needle:"href=\"/app-feedback?tab=submitted\"" page
28 Added: 0));
20 29 Alcotest.(check int)
21 30 "submitted tab served" 200
22 31 (status (get c "/app-feedback?tab=submitted")) );