feat Replace routine table with accordions

Present each workout as a summary/description accordion. The summary names the workout, and the description lists its exercises with numeric rep bands. Remove the literal "reps" label from displayed values while keeping the exercise and prescription data unchanged.

Commit
bf6390bdf40fd54a39a8796c0a7c85015d4ed9cb
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/web/assets/hito.css
index 65eb3845..4ac7351b 100644..100644
@@ -894,38 +894,54 @@
894 894 }
895 895 li li { color: var(--muted-ink); font-size: 0.92rem; }
896 896
897 Removed: /* The routine's workouts as a basic table: one row per workout of the cycle,
898 Removed: with its name, prescribed-stimuli count, and the prescription. The row header
899 Removed: names the workout; the prescription cell lists each stimulus. */
900 Removed: .routine-table {
901 Removed: width: 100%;
897 Added: /* The routine's workouts as summary/description accordions. Each summary
898 Added: names one workout. Its description lists the prescribed exercises and numeric
899 Added: rep bands. */
900 Added: .routine-accordions {
901 Added: display: grid;
902 Added: gap: 0.65rem;
902 903 margin: 1.25rem 0 0;
903 Removed: border-collapse: collapse;
904 904 }
905 Removed:
906 Removed: .routine-table th,
907 Removed: .routine-table td {
908 Removed: padding: 0.6rem 0.7rem;
909 Removed: border-bottom: 1px solid var(--rule);
910 Removed: text-align: left;
911 Removed: vertical-align: top;
905 Added: .routine-accordion {
906 Added: border: 1px solid var(--rule);
907 Added: border-radius: var(--radius);
908 Added: background: var(--paper-raised);
909 Added: overflow: hidden;
912 910 }
913 Removed:
914 Removed: .routine-table thead th {
911 Added: .routine-accordion > summary {
912 Added: padding: 0.8rem 1rem;
915 913 color: var(--ink);
916 Removed: font: 700 var(--font-size-small)/1.2 var(--sans);
917 Removed: letter-spacing: 0.02em;
918 Removed: border-bottom: 2px solid var(--rule-strong);
919 Removed: }
920 Removed:
921 Removed: .routine-table tbody th {
922 Removed: color: var(--ink);
923 914 font-weight: 700;
915 Added: cursor: pointer;
916 Added: list-style: none;
924 917 }
925 Removed:
926 Removed: .routine-table ul {
927 Removed: margin: 0;
928 Removed: padding-left: 1.1rem;
918 Added: .routine-accordion > summary::-webkit-details-marker { display: none; }
919 Added: .routine-accordion > summary::marker { content: ""; }
920 Added: .routine-accordion > summary::after {
921 Added: float: right;
922 Added: content: "+";
923 Added: color: var(--muted-ink);
924 Added: }
925 Added: .routine-accordion[open] > summary::after { content: "−"; }
926 Added: .routine-description {
927 Added: padding: 0 1rem 0.8rem;
928 Added: border-top: 1px solid var(--rule);
929 Added: }
930 Added: .routine-description ul {
931 Added: margin: 0.7rem 0 0;
932 Added: padding-left: 1.2rem;
933 Added: }
934 Added: .routine-description li {
935 Added: display: flex;
936 Added: justify-content: space-between;
937 Added: gap: 1rem;
938 Added: padding: 0.35rem 0;
939 Added: }
940 Added: .routine-exercise { color: var(--ink); }
941 Added: .routine-reps {
942 Added: color: var(--muted-ink);
943 Added: font-family: var(--mono);
944 Added: white-space: nowrap;
929 945 }
930 946
931 947 @media (max-width: 42rem) {
lib/web/handlers.ml
index aa22eb7e..34cbee69 100644..100644
@@ -422,7 +422,7 @@
422 422 Prescription.Rep_range.bounds
423 423 (Prescription.Stimulus.rep_range prescription)
424 424 in
425 Removed: Printf.sprintf "%d-%d reps" min_reps max_reps
425 Added: Printf.sprintf "%d-%d" min_reps max_reps
426 426
427 427 let routine_view r : View_model.Routine.t =
428 428 {
lib/web/pages.ml
index a876ce84..5d62ee98 100644..100644
@@ -551,54 +551,33 @@
551 551 html_page ~viewer ~request ~active:"routine" ~logging "Routine"
552 552 [
553 553 tag "h1" [] [ txt "%s" vm.name ];
554 Removed: tag "table"
555 Removed: [ class_ "routine-table" ]
556 Removed: [
557 Removed: tag "thead" []
558 Removed: [
559 Removed: tag "tr" []
560 Removed: [
561 Removed: tag "th"
562 Removed: [ Dream_html.string_attr "scope" "col" ]
563 Removed: [ txt "Workout" ];
564 Removed: tag "th"
565 Removed: [ Dream_html.string_attr "scope" "col" ]
566 Removed: [ txt "Exercise" ];
567 Removed: tag "th"
568 Removed: [ Dream_html.string_attr "scope" "col" ]
569 Removed: [ txt "Reps" ];
570 Removed: ];
571 Removed: ];
572 Removed: (* One sub-row per prescribed exercise. The workout name spans its
573 Removed: exercises through a row-group heading, so each exercise gets its
574 Removed: own name and rep-band cells and no stimuli count is shown. *)
575 Removed: tag "tbody" []
576 Removed: (List.concat_map
577 Removed: (fun (workout : View_model.Routine.workout) ->
578 Removed: List.mapi
579 Removed: (fun index (exercise : View_model.Routine.exercise) ->
580 Removed: let workout_cell =
581 Removed: if index = 0 then
582 Removed: [
583 Removed: tag "th"
584 Removed: [
585 Removed: Dream_html.string_attr "scope" "rowgroup";
586 Removed: Dream_html.string_attr "rowspan" "%d"
587 Removed: (List.length workout.exercises);
588 Removed: ]
589 Removed: [ txt "%s" workout.name ];
590 Removed: ]
591 Removed: else []
592 Removed: in
593 Removed: tag "tr" []
594 Removed: (workout_cell
595 Removed: @ [
596 Removed: tag "td" [] [ txt "%s" exercise.name ];
597 Removed: tag "td" [] [ txt "%s" exercise.reps ];
598 Removed: ]))
599 Removed: workout.exercises)
600 Removed: vm.workouts);
601 Removed: ];
554 Added: tag "div"
555 Added: [ class_ "routine-accordions" ]
556 Added: (List.map
557 Added: (fun (workout : View_model.Routine.workout) ->
558 Added: tag "details"
559 Added: [ class_ "routine-accordion" ]
560 Added: [
561 Added: tag "summary" [] [ txt "%s" workout.name ];
562 Added: tag "div"
563 Added: [ class_ "routine-description" ]
564 Added: [
565 Added: tag "ul" []
566 Added: (List.map
567 Added: (fun (exercise : View_model.Routine.exercise) ->
568 Added: tag "li" []
569 Added: [
570 Added: tag "span"
571 Added: [ class_ "routine-exercise" ]
572 Added: [ txt "%s" exercise.name ];
573 Added: tag "span"
574 Added: [ class_ "routine-reps" ]
575 Added: [ txt "%s" exercise.reps ];
576 Added: ])
577 Added: workout.exercises);
578 Added: ];
579 Added: ])
580 Added: vm.workouts);
602 581 ]
603 582
604 583 let error_for ~input_id (field : string) (errors : (string * string) list) =
lib/web/view_model.ml
index e5b3b96c..afe53fa5 100644..100644
@@ -50,8 +50,8 @@
50 50 type workout = { name : string; exercises : exercise list }
51 51
52 52 type t = { name : string; workouts : workout list }
53 Removed: (** A routine as the table renders it: names, rep bands, and grouping only. No
54 Removed: [Prescription] entity. *)
53 Added: (** A routine as the accordion descriptions render it: names, numeric rep
54 Added: bands, and workout grouping only. No [Prescription] entity. *)
55 55 end
56 56
57 57 module Routine_choice = struct