feat use inline subjective feedback choices

Commit
f09fd5db68929db6879a91f6e400c9a765f846cc
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 3de19b85..b74e03b0 100644..100644
@@ -865,3 +865,53 @@
865 865 @media (prefers-reduced-motion: reduce) {
866 866 .toast[data-hito-toast-done] { transition: none; }
867 867 }
868 Added:
869 Added: /* Subjective feedback uses one inline three-choice group per signal. The radio
870 Added: input keeps native keyboard and form behavior; its adjacent span is the
871 Added: visible button. */
872 Added: .feedback-group {
873 Added: margin: 1rem 0;
874 Added: }
875 Added: .feedback-group-label {
876 Added: margin: 0 0 0.45rem;
877 Added: font-weight: 700;
878 Added: }
879 Added: .feedback-buttons {
880 Added: display: inline-flex;
881 Added: flex-wrap: wrap;
882 Added: gap: 0.45rem;
883 Added: }
884 Added: .feedback-choice {
885 Added: position: relative;
886 Added: display: inline-flex;
887 Added: min-height: 2.75rem;
888 Added: align-items: center;
889 Added: }
890 Added: .feedback-choice input {
891 Added: position: absolute;
892 Added: width: 1px;
893 Added: height: 1px;
894 Added: opacity: 0;
895 Added: }
896 Added: .feedback-choice span {
897 Added: display: inline-flex;
898 Added: min-height: 2.75rem;
899 Added: align-items: center;
900 Added: border: 1px solid var(--rule-strong);
901 Added: border-radius: var(--radius);
902 Added: padding: 0.55rem 0.9rem;
903 Added: color: var(--ink);
904 Added: background: var(--paper-raised);
905 Added: cursor: pointer;
906 Added: font-weight: 700;
907 Added: }
908 Added: .feedback-choice span:hover,
909 Added: .feedback-choice input:focus-visible + span {
910 Added: border-color: var(--brass);
911 Added: box-shadow: 0 0 0 3px var(--brass-wash);
912 Added: }
913 Added: .feedback-choice input:checked + span {
914 Added: border-color: var(--oxblood-dark);
915 Added: color: var(--paper-raised);
916 Added: background: var(--oxblood);
917 Added: }
lib/web/pages.ml
index 8c799944..456d71d3 100644..100644
@@ -1117,21 +1117,35 @@
1117 1117 report pain, injury, and insufficient preparation. A field left blank reports
1118 1118 nothing, so the trainee submits only what they mean to. Feedback is
1119 1119 standalone — recorded from the Logbook at any time. *)
1120 Removed: let feedback_level_select field label =
1120 Added: (* Each subjective metric uses three unselected radio buttons styled as inline buttons.
1121 Added: Leaving a group untouched records no signal for that metric. *)
1122 Added: let feedback_level_buttons field label =
1123 Added: let choice code text =
1124 Added: tag "label"
1125 Added: [ class_ "feedback-choice" ]
1126 Added: [
1127 Added: void "input"
1128 Added: [
1129 Added: type_ "radio";
1130 Added: Dream_html.string_attr "name" "%s" field;
1131 Added: Dream_html.string_attr "value" "%s" code;
1132 Added: Dream_html.string_attr "id" "%s-%s" field code;
1133 Added: ];
1134 Added: tag "span" [] [ txt "%s" text ];
1135 Added: ]
1136 Added: in
1121 1137 tag "div"
1122 Removed: [ class_ "field" ]
1138 Added: [ class_ "feedback-group" ]
1123 1139 [
1124 Removed: tag "label" [ Dream_html.string_attr "for" "%s" field ] [ txt "%s" label ];
1125 Removed: tag "select"
1140 Added: tag "p" [ class_ "feedback-group-label" ] [ txt "%s" label ];
1141 Added: tag "div"
1126 1142 [
1127 Removed: Dream_html.string_attr "name" "%s" field;
1128 Removed: Dream_html.string_attr "id" "%s" field;
1143 Added: class_ "feedback-buttons";
1144 Added: Dream_html.string_attr "role" "group";
1145 Added: Dream_html.string_attr "aria-label" "%s" label;
1129 1146 ]
1130 1147 [
1131 Removed: tag "option" [ value "" ] [ txt "Not reported" ];
1132 Removed: tag "option" [ value "below" ] [ txt "Below usual" ];
1133 Removed: tag "option" [ value "usual" ] [ txt "Usual" ];
1134 Removed: tag "option" [ value "above" ] [ txt "Above usual" ];
1148 Added: choice "below" "Worse"; choice "usual" "Same"; choice "above" "Better";
1135 1149 ];
1136 1150 ]
1137 1151
@@ -1160,11 +1174,11 @@
1160 1174 Dream_html.csrf_tag request;
1161 1175 tag "fieldset" []
1162 1176 [
1163 Removed: feedback_level_select "sleep" "Sleep";
1164 Removed: feedback_level_select "appetite" "Appetite";
1165 Removed: feedback_level_select "readiness" "Readiness";
1166 Removed: feedback_level_select "motivation" "Motivation";
1167 Removed: feedback_level_select "difficulty" "Perceived difficulty";
1177 Added: feedback_level_buttons "sleep" "Sleep";
1178 Added: feedback_level_buttons "appetite" "Appetite";
1179 Added: feedback_level_buttons "readiness" "Readiness";
1180 Added: feedback_level_buttons "motivation" "Motivation";
1181 Added: feedback_level_buttons "difficulty" "Perceived difficulty";
1168 1182 tag "div"
1169 1183 [ class_ "field" ]
1170 1184 [
test/test_web.ml
index 00c751c7..890ce458 100644..100644
@@ -969,9 +969,14 @@
969 969 (contains ~substring:"class=\"feedback-toggle\"" logbook_page
970 970 && contains ~substring:"<summary" logbook_page);
971 971 Alcotest.(check bool)
972 Removed: "keeps the disclosure collapsed by default" false
973 Removed: (contains ~substring:"<details class=\"feedback-disclosure\" open"
974 Removed: logbook_page) );
972 Added: "renders inline subjective button groups" true
973 Added: (contains ~substring:"feedback-buttons" logbook_page
974 Added: && contains ~substring:">Worse</span>" logbook_page
975 Added: && contains ~substring:">Same</span>" logbook_page
976 Added: && contains ~substring:">Better</span>" logbook_page);
977 Added: Alcotest.(check bool)
978 Added: "does not render subjective selects" false
979 Added: (contains ~substring:"<select" logbook_page) );
975 980 ( "submitting feedback stores it and it appears on the logbook",
976 981 `Quick,
977 982 fun () ->