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.

Commit
784e203bc2b29ca8e32fc4e187b66eec4dd6ed07
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ENHANCEMENTS.org
index 57819522..c59919e0 100644..100644
@@ -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
index ce326b36..0ff5f2e2 100644..100644
@@ -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
index 9a870641..792a1ffa 100644..100644
@@ -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"