[OCaml] High Intensity Training Online
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.
Changed files
lib/presenter/page_kit.ml
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")) );