feat make logging fields compact on mobile

Lay each movement's exercise name, load input, and reps input out in one compact grid row so a set fits on one line on mobile without scrolling. The name takes the remaining space; the two numeric inputs take fixed, tap-friendly columns. Replaces the two-column load/reps row.

Commit
116d288fc631366c6149e8469bb6f45fd82f89d0
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ENHANCEMENTS.org
index 1ad413e7..45203a46 100644..100644
@@ -56,7 +56,7 @@
56 56 Animate page transitions between Home, Current Workout, and Logbook.
57 57 ** DONE Confirm workout cancellation in a modal
58 58 Show a modal menu that confirms workout logging cancellation before cancelling.
59 Removed: ** TODO Make workout logging fields compact on mobile
59 Added: ** DONE Make workout logging fields compact on mobile
60 60 Use a grid layout so the exercise name, load input, and reps input appear on one row on mobile.
61 61 ** TODO Allow spontaneous logbook feedback
62 62 Allow users to submit subjective feedback at any time from the Logbook. Suggest this feedback flow at the end of each workout, while keeping it available independently.
lib/web/assets/hito.css
index 87f53a01..89d475f5 100644..100644
@@ -308,7 +308,21 @@
308 308 .button-group form { margin: 0; }
309 309 .button-group input[type="submit"] { width: 100%; }
310 310
311 Removed: .row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem; }
311 Added: /* A movement's fields as one compact grid row: exercise name, load, reps. On
312 Added: mobile all three sit on one line — the name takes the remaining space and the
313 Added: two numeric inputs take fixed, tap-friendly columns — so a set fits without
314 Added: scrolling. The [.field] top margin is dropped here; the grid handles spacing.
315 Added: The name column spans the full width above the inputs only when it would
316 Added: otherwise be too cramped, at the narrowest widths. */
317 Added: .exercise-fields {
318 Added: display: grid;
319 Added: grid-template-columns: minmax(0, 1fr) 5.5rem 5.5rem;
320 Added: align-items: end;
321 Added: gap: 0.6rem;
322 Added: margin-top: 1rem;
323 Added: }
324 Added: .exercise-fields .exercise-name { margin: 0; align-self: center; }
325 Added: .exercise-fields .field { margin-top: 0; }
312 326
313 327 /* The Home card shown while a workout is in progress. A full-width pressable
314 328 panel that returns the user to the current workout. It reads as a raised
@@ -533,7 +547,6 @@
533 547 }
534 548
535 549 @media (max-width: 26rem) {
536 Removed: .row,
537 550 .button-group { grid-template-columns: 1fr; }
538 551 }
539 552
lib/web/pages.ml
index 0e00349b..8e7372ad 100644..100644
@@ -485,15 +485,16 @@
485 485 | Some v -> [ Dream_html.string_attr "value" "%s" v ]);
486 486 ]
487 487
488 Removed: (* Load and reps sit side by side. The [.row] class is a two-column grid that
489 Removed: collapses to one column on a narrow screen. Each movement of a pre-exhaust
490 Removed: pair gets its own row, so both stay paired. The placeholders hint the field's
491 Removed: unit, not a target: a set ends at failure, never at a number. *)
492 Removed: let load_reps_row ~(errors : (string * string) list) ~load_id ~load_field
488 Added: (* A movement's fields as one compact grid row: the exercise name, the load
489 Added: input, and the reps input side by side. On mobile the [.exercise-fields] grid
490 Added: keeps all three on one line so a set fits without scrolling; it widens on
491 Added: larger screens. The name reads as a heading, the inputs as labelled fields. *)
492 Added: let movement_row ~(errors : (string * string) list) ~name ~load_id ~load_field
493 493 ~reps_id ~reps_field ?load_value ?reps_value ~reps_label () =
494 494 tag "div"
495 Removed: [ class_ "row" ]
495 Added: [ class_ "exercise-fields" ]
496 496 [
497 Added: tag "p" [ class_ "exercise-name" ] [ txt "%s" name ];
497 498 input_row ?value:load_value ~placeholder:"kg"
498 499 ~invalid:(List.mem_assoc load_field errors)
499 500 ~input_id:load_id load_field "Load (kg)";
@@ -543,29 +544,26 @@
543 544 in
544 545 let fields =
545 546 match Prescription.Stimulus.delivery prescription with
546 Removed: | Prescription.Stimulus.Single _ ->
547 Added: | Prescription.Stimulus.Single exercise ->
547 548 [
548 Removed: load_reps_row ~errors ~load_id:(field_id "load") ~load_field:"load"
549 Added: movement_row ~errors ~name:(Exercise.name exercise)
550 Added: ~load_id:(field_id "load") ~load_field:"load"
549 551 ~reps_id:(field_id "reps") ~reps_field:"reps"
550 552 ?load_value:(load_of 0) ?reps_value:(reps_of 0)
551 553 ~reps_label:"Reps to failure" ();
552 554 ]
553 555 | Prescription.Stimulus.Pre_exhaust { isolation; compound } ->
554 556 [
555 Removed: tag "p"
556 Removed: [ class_ "exercise-name" ]
557 Removed: [ txt "%s" (Exercise.name isolation) ];
558 Removed: load_reps_row ~errors ~load_id:(field_id "iso_load")
559 Removed: ~load_field:"iso_load" ~reps_id:(field_id "iso_reps")
560 Removed: ~reps_field:"iso_reps" ?load_value:(load_of 0)
561 Removed: ?reps_value:(reps_of 0) ~reps_label:"Reps" ();
562 Removed: tag "p"
563 Removed: [ class_ "exercise-name" ]
564 Removed: [ txt "%s" (Exercise.name compound) ];
565 Removed: load_reps_row ~errors ~load_id:(field_id "comp_load")
566 Removed: ~load_field:"comp_load" ~reps_id:(field_id "comp_reps")
567 Removed: ~reps_field:"comp_reps" ?load_value:(load_of 1)
568 Removed: ?reps_value:(reps_of 1) ~reps_label:"Reps" ();
557 Added: movement_row ~errors ~name:(Exercise.name isolation)
558 Added: ~load_id:(field_id "iso_load") ~load_field:"iso_load"
559 Added: ~reps_id:(field_id "iso_reps") ~reps_field:"iso_reps"
560 Added: ?load_value:(load_of 0) ?reps_value:(reps_of 0) ~reps_label:"Reps"
561 Added: ();
562 Added: movement_row ~errors ~name:(Exercise.name compound)
563 Added: ~load_id:(field_id "comp_load") ~load_field:"comp_load"
564 Added: ~reps_id:(field_id "comp_reps") ~reps_field:"comp_reps"
565 Added: ?load_value:(load_of 1) ?reps_value:(reps_of 1) ~reps_label:"Reps"
566 Added: ();
569 567 ]
570 568 in
571 569 tag "form"
test/test_web.ml
index 7f4b0f83..56885e51 100644..100644
@@ -508,7 +508,7 @@
508 508 Alcotest.(check bool)
509 509 "shows bottom navigation" true
510 510 (contains ~substring:"bottom-nav" page) );
511 Removed: ( "effort forms lay load and reps out side by side",
511 Added: ( "effort forms lay name, load, and reps out in one compact row",
512 512 `Quick,
513 513 fun () ->
514 514 let c = client () in
@@ -521,8 +521,8 @@
521 521 in
522 522 let page = body (get c "/workout") in
523 523 Alcotest.(check bool)
524 Removed: "uses the two-column row class" true
525 Removed: (contains ~substring:"class=\"row\"" page);
524 Added: "uses the compact exercise-fields grid" true
525 Added: (contains ~substring:"class=\"exercise-fields\"" page);
526 526 Alcotest.(check bool)
527 527 "still labels load" true
528 528 (contains ~substring:"Load (kg)" page);