[OCaml] High Intensity Training Online
feat share load and reps column headings
In a multi-exercise (pre-exhaust) logging group, show Load and Reps once as column headings above the movement rows instead of repeating the labels above every input. The per-input labels remain for assistive technology, visually hidden, so the form stays accessible.
Changed files
ENHANCEMENTS.org
@@ -68,7 +68,7 @@
68
68
Style form dropdown menus so they match the overall UI aesthetic.
69
69
** 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
Removed:
** TODO Use shared load and reps column headings
71
Added:
** DONE 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.
73
73
** TODO Hide exercise-selector submit buttons in SPA mode
74
74
When JavaScript progressive enhancement is active, do not show the submit button for exercise selection.
lib/web/assets/hito.css
@@ -363,6 +363,30 @@
363
363
.exercise-fields .exercise-name { margin: 0; align-self: center; }
364
364
.exercise-fields .field { margin-top: 0; }
365
365
366
Added:
/* A shared heading row: the Load and Reps column headings shown once above a
367
Added:
multi-exercise group, so the labels are not repeated above every input. */
368
Added:
.exercise-fields-head { margin-top: 1rem; align-items: end; }
369
Added:
.col-heading {
370
Added:
color: var(--ink);
371
Added:
font-size: var(--font-size-small);
372
Added:
font-weight: 700;
373
Added:
}
374
Added:
.exercise-fields-head + .exercise-fields { margin-top: 0.4rem; }
375
Added:
376
Added:
/* Visually hidden but available to assistive technology: the per-input labels
377
Added:
stay for screen readers while the visible headings carry the column names. */
378
Added:
.sr-only {
379
Added:
position: absolute;
380
Added:
width: 1px;
381
Added:
height: 1px;
382
Added:
margin: -1px;
383
Added:
padding: 0;
384
Added:
overflow: hidden;
385
Added:
clip: rect(0, 0, 0, 0);
386
Added:
white-space: nowrap;
387
Added:
border: 0;
388
Added:
}
389
Added:
366
390
/* The Home card shown while a workout is in progress. A full-width pressable
367
391
panel that returns the user to the current workout. It reads as a raised
368
392
surface with an oxblood accent, echoing the page surface. */
lib/web/pages.ml
@@ -467,8 +467,8 @@
467
467
[ txt "%s" (Decode.errors_to_text [ (field, error) ]) ];
468
468
]
469
469
470
Removed:
let input_row ?value:v ?(invalid = false) ?placeholder ~input_id
471
Removed:
(field : string) label =
470
Added:
let input_row ?value:v ?(invalid = false) ?placeholder ?(hide_label = false)
471
Added:
~input_id (field : string) label =
472
472
let error_attrs =
473
473
if invalid then
474
474
[
@@ -486,7 +486,10 @@
486
486
[ class_ "field" ]
487
487
[
488
488
tag "label"
489
Removed:
[ Dream_html.string_attr "for" "%s" input_id ]
489
Added:
[
490
Added:
Dream_html.string_attr "for" "%s" input_id;
491
Added:
class_ (if hide_label then "sr-only" else "");
492
Added:
]
490
493
[ txt "%s" label ];
491
494
void "input"
492
495
([
@@ -508,19 +511,32 @@
508
511
keeps all three on one line so a set fits without scrolling; it widens on
509
512
larger screens. The name reads as a heading, the inputs as labelled fields. *)
510
513
let movement_row ~(errors : (string * string) list) ~name ~load_id ~load_field
511
Removed:
~reps_id ~reps_field ?load_value ?reps_value ~reps_label () =
514
Added:
~reps_id ~reps_field ?load_value ?reps_value ~reps_label
515
Added:
?(hide_labels = false) () =
512
516
tag "div"
513
517
[ class_ "exercise-fields" ]
514
518
[
515
519
tag "p" [ class_ "exercise-name" ] [ txt "%s" name ];
516
Removed:
input_row ?value:load_value ~placeholder:"kg"
520
Added:
input_row ?value:load_value ~placeholder:"kg" ~hide_label:hide_labels
517
521
~invalid:(List.mem_assoc load_field errors)
518
522
~input_id:load_id load_field "Load (kg)";
519
Removed:
input_row ?value:reps_value ~placeholder:"reps"
523
Added:
input_row ?value:reps_value ~placeholder:"reps" ~hide_label:hide_labels
520
524
~invalid:(List.mem_assoc reps_field errors)
521
525
~input_id:reps_id reps_field reps_label;
522
526
]
523
527
528
Added:
(* A shared heading row for a multi-exercise group: empty name cell, then the
529
Added:
Load and Reps column headings. Rendered once above the movement rows so the
530
Added:
labels are not repeated above every input. *)
531
Added:
let exercise_fields_head ~reps_label () =
532
Added:
tag "div"
533
Added:
[ class_ "exercise-fields exercise-fields-head" ]
534
Added:
[
535
Added:
tag "span" [ class_ "exercise-name" ] [ txt "" ];
536
Added:
tag "span" [ class_ "col-heading" ] [ txt "Load (kg)" ];
537
Added:
tag "span" [ class_ "col-heading" ] [ txt "%s" reps_label ];
538
Added:
]
539
Added:
524
540
(* A recorded number, rendered the way a numeric input expects. *)
525
541
let load_string load = Printf.sprintf "%g" load
526
542
let reps_string reps = string_of_int reps
@@ -572,16 +588,17 @@
572
588
]
573
589
| Prescription.Stimulus.Pre_exhaust { isolation; compound } ->
574
590
[
591
Added:
exercise_fields_head ~reps_label:"Reps" ();
575
592
movement_row ~errors ~name:(Exercise.name isolation)
576
593
~load_id:(field_id "iso_load") ~load_field:"iso_load"
577
594
~reps_id:(field_id "iso_reps") ~reps_field:"iso_reps"
578
595
?load_value:(load_of 0) ?reps_value:(reps_of 0) ~reps_label:"Reps"
579
Removed:
();
596
Added:
~hide_labels:true ();
580
597
movement_row ~errors ~name:(Exercise.name compound)
581
598
~load_id:(field_id "comp_load") ~load_field:"comp_load"
582
599
~reps_id:(field_id "comp_reps") ~reps_field:"comp_reps"
583
600
?load_value:(load_of 1) ?reps_value:(reps_of 1) ~reps_label:"Reps"
584
Removed:
();
601
Added:
~hide_labels:true ();
585
602
]
586
603
in
587
604
tag "form"