[OCaml] High Intensity Training Online
style one standard button group, centered bold titles
Align every modal (delete, feedback, cancel workout, override, profile edits) on a single bottom button group: primary action left, secondary Cancel right. Remove the top-right discard/close controls. Titles are bold and centered at the top. Style all button-group controls (button, submit input, anchor) uniformly, so the feedback submit is no longer unstyled. Labels default to Submit/Cancel, overridden with meaningful verbs where clearer.
Changed files
lib/presenter/pages_logbook.ml
@@ -60,8 +60,17 @@
60
60
feedback_factors
61
61
@ [
62
62
tag "div"
63
Removed:
[ class_ "feedback-actions" ]
64
Removed:
[ tag "button" [ type_ "submit" ] [ txt "Submit feedback" ] ];
63
Added:
[ class_ "button-group" ]
64
Added:
[
65
Added:
tag "button" [ type_ "submit" ] [ txt "Submit feedback" ];
66
Added:
tag "a"
67
Added:
[
68
Added:
href Routes.logbook;
69
Added:
class_ "button secondary";
70
Added:
Dream_html.attr "data-hito-feedback-dismiss";
71
Added:
]
72
Added:
[ txt "Cancel" ];
73
Added:
];
65
74
]);
66
75
]
67
76
@@ -72,24 +81,7 @@
72
81
[
73
82
tag "div"
74
83
[ class_ "feedback-modal-content" ]
75
Removed:
[
76
Removed:
tag "div"
77
Removed:
[ class_ "feedback-modal-heading" ]
78
Removed:
[
79
Removed:
tag "h2" [] [ txt "How are you feeling?" ];
80
Removed:
(* Discard is a plain link back to the logbook. Nothing is stored
81
Removed:
until Submit, so there is no flow state to discard on the
82
Removed:
server. With script, the client closes the dialog in place. *)
83
Removed:
tag "a"
84
Removed:
[
85
Removed:
href Routes.logbook;
86
Removed:
class_ "button outline feedback-modal-close";
87
Removed:
Dream_html.attr "data-hito-feedback-dismiss";
88
Removed:
]
89
Removed:
[ txt "Discard" ];
90
Removed:
];
91
Removed:
feedback_form request;
92
Removed:
];
84
Added:
[ tag "h2" [] [ txt "How are you feeling?" ]; feedback_form request ];
93
85
]
94
86
95
87
let feedback_list (reports : View_model.Feedback.report list) =
@@ -110,28 +102,14 @@
110
102
tag "dialog"
111
103
[ class_ "confirm-dialog"; Dream_html.attr "open" ]
112
104
[
113
Removed:
tag "div"
114
Removed:
[ class_ "confirm-dialog-heading" ]
115
Removed:
[
116
Removed:
tag "h2" [] [ txt "Delete this workout log?" ];
117
Removed:
(* Discard: an oxblood-outline control top-right. The client closes
118
Removed:
the modal in place; without script it is a link back to the
119
Removed:
logbook. *)
120
Removed:
tag "a"
121
Removed:
[
122
Removed:
href Routes.logbook;
123
Removed:
class_ "button outline confirm-dialog-close";
124
Removed:
Dream_html.attr "data-hito-confirm-dismiss";
125
Removed:
]
126
Removed:
[ txt "Discard" ];
127
Removed:
];
105
Added:
tag "h2" [] [ txt "Delete this workout log?" ];
128
106
tag "p" []
129
107
[
130
108
txt "Delete %s from %s. This cannot be undone." record.workout_name
131
109
record.date;
132
110
];
133
111
tag "div"
134
Removed:
[ class_ "confirm-dialog-actions" ]
112
Added:
[ class_ "button-group" ]
135
113
[
136
114
tag "form"
137
115
[
@@ -145,6 +123,13 @@
145
123
[ type_ "submit"; class_ "danger" ]
146
124
[ txt "Delete workout" ];
147
125
];
126
Added:
tag "a"
127
Added:
[
128
Added:
href Routes.logbook;
129
Added:
class_ "button secondary";
130
Added:
Dream_html.attr "data-hito-confirm-dismiss";
131
Added:
]
132
Added:
[ txt "Cancel" ];
148
133
];
149
134
]
150
135
lib/presenter/pages_profile.ml
@@ -89,8 +89,10 @@
89
89
];
90
90
];
91
91
tag "div"
92
Removed:
[ class_ "profile-dialog-actions" ]
92
Added:
[ class_ "button-group" ]
93
93
[
94
Added:
void "input"
95
Added:
[ type_ "submit"; value "Change username" ];
94
96
tag "button"
95
97
[
96
98
type_ "button";
@@ -98,8 +100,6 @@
98
100
Dream_html.attr "data-hito-dialog-close";
99
101
]
100
102
[ txt "Cancel" ];
101
Removed:
void "input"
102
Removed:
[ type_ "submit"; value "Change username" ];
103
103
];
104
104
];
105
105
]
@@ -176,8 +176,10 @@
176
176
];
177
177
];
178
178
tag "div"
179
Removed:
[ class_ "profile-dialog-actions" ]
179
Added:
[ class_ "button-group" ]
180
180
[
181
Added:
void "input"
182
Added:
[ type_ "submit"; value "Change password" ];
181
183
tag "button"
182
184
[
183
185
type_ "button";
@@ -185,8 +187,6 @@
185
187
Dream_html.attr "data-hito-dialog-close";
186
188
]
187
189
[ txt "Cancel" ];
188
Removed:
void "input"
189
Removed:
[ type_ "submit"; value "Change password" ];
190
190
];
191
191
];
192
192
]
lib/presenter/pages_routine.ml
@@ -99,15 +99,15 @@
99
99
[ class_ "button-group" ]
100
100
[
101
101
tag "button"
102
Added:
[ type_ "button"; Dream_html.attr "data-hito-confirm-accept" ]
103
Added:
[ txt "Begin under override" ];
104
Added:
tag "button"
102
105
[
103
106
type_ "button";
104
107
class_ "secondary";
105
108
Dream_html.attr "data-hito-confirm-dismiss";
106
109
]
107
110
[ txt "Wait to recover" ];
108
Removed:
tag "button"
109
Removed:
[ type_ "button"; Dream_html.attr "data-hito-confirm-accept" ]
110
Removed:
[ txt "Begin under override" ];
111
111
];
112
112
];
113
113
]
lib/presenter/pages_workout.ml
@@ -374,16 +374,16 @@
374
374
[
375
375
tag "button"
376
376
[
377
Added:
type_ "button"; Dream_html.attr "data-hito-confirm-accept";
378
Added:
]
379
Added:
[ txt "Cancel workout" ];
380
Added:
tag "button"
381
Added:
[
377
382
type_ "button";
378
383
class_ "secondary";
379
384
Dream_html.attr "data-hito-confirm-dismiss";
380
385
]
381
386
[ txt "Keep logging" ];
382
Removed:
tag "button"
383
Removed:
[
384
Removed:
type_ "button"; Dream_html.attr "data-hito-confirm-accept";
385
Removed:
]
386
Removed:
[ txt "Cancel workout" ];
387
387
];
388
388
];
389
389
]
lib/web/assets/hito.css
@@ -536,6 +536,41 @@
536
536
background: var(--oxblood);
537
537
}
538
538
539
Added:
/* Every control in a button group — an anchor, a form button, or a submit
540
Added:
input — shares the primary look, so modals and action rows read alike. The
541
Added:
group is a two-column grid: primary action left, secondary right. */
542
Added:
.button-group button,
543
Added:
.button-group input[type="submit"] {
544
Added:
display: inline-flex;
545
Added:
align-items: center;
546
Added:
justify-content: center;
547
Added:
width: 100%;
548
Added:
min-height: 3rem;
549
Added:
border: 2px solid var(--oxblood-dark);
550
Added:
border-radius: var(--radius);
551
Added:
color: var(--on-primary);
552
Added:
background: var(--oxblood);
553
Added:
cursor: pointer;
554
Added:
font: 700 var(--font-size-small)/1 var(--sans);
555
Added:
letter-spacing: 0.055em;
556
Added:
text-transform: uppercase;
557
Added:
transition: background-color 150ms ease, transform 150ms ease;
558
Added:
}
559
Added:
.button-group button:hover,
560
Added:
.button-group input[type="submit"]:hover {
561
Added:
background: var(--oxblood-hover);
562
Added:
transform: translateY(-2px);
563
Added:
}
564
Added:
.button-group button.secondary {
565
Added:
color: var(--brass);
566
Added:
background: transparent;
567
Added:
border-color: var(--brass);
568
Added:
}
569
Added:
.button-group button.secondary:hover {
570
Added:
color: var(--on-brass);
571
Added:
background: var(--brass);
572
Added:
}
573
Added:
539
574
/* The logbook is one real table at every width. The columns — date, workout
540
575
name, duration, and the row menu — are few and narrow enough to share a
541
576
single table layout, so there is no mobile card reflow: the name column takes
@@ -686,25 +721,7 @@
686
721
}
687
722
.confirm-dialog button.danger:hover { background: var(--oxblood); }
688
723
.confirm-dialog .delete-record-form { margin: 0; }
689
Removed:
/* Heading row: title left, an oxblood-outline Discard control top-right. */
690
Removed:
.confirm-dialog-heading {
691
Removed:
display: flex;
692
Removed:
align-items: flex-start;
693
Removed:
justify-content: space-between;
694
Removed:
gap: 1rem;
695
Removed:
}
696
Removed:
.confirm-dialog-heading h2 { margin: 0; }
697
Removed:
.confirm-dialog-close { flex: 0 0 auto; }
698
Removed:
/* The delete action sits centred at the bottom of the modal. */
699
Removed:
.confirm-dialog-actions {
700
Removed:
display: flex;
701
Removed:
justify-content: center;
702
Removed:
margin-top: 1.25rem;
703
Removed:
}
704
Removed:
.confirm-dialog-actions .delete-record-form button {
705
Removed:
width: auto;
706
Removed:
min-width: 12rem;
707
Removed:
}
724
Added:
.confirm-dialog h2 { margin-top: 0; text-align: center; }
708
725
709
726
.logbook-detail-row {
710
727
display: block;
@@ -1141,39 +1158,7 @@
1141
1158
.feedback-modal::backdrop { background: rgba(26, 26, 22, 0.45); }
1142
1159
.feedback-modal[open] { animation: modal-enter 180ms ease both; }
1143
1160
.feedback-modal-content { padding: 1.25rem; }
1144
Removed:
.feedback-modal-heading {
1145
Removed:
display: flex;
1146
Removed:
align-items: start;
1147
Removed:
justify-content: space-between;
1148
Removed:
gap: 1rem;
1149
Removed:
}
1150
Removed:
.feedback-modal-heading h2 { margin-top: 0; }
1151
Removed:
/* Discard sits in the heading row, so it is shorter than a full-height button. */
1152
Removed:
.feedback-modal-close {
1153
Removed:
min-height: 2.5rem;
1154
Removed:
padding: 0 0.85rem;
1155
Removed:
}
1156
Removed:
.feedback-actions {
1157
Removed:
display: flex;
1158
Removed:
flex-wrap: wrap;
1159
Removed:
gap: 0.6rem;
1160
Removed:
margin-top: 1rem;
1161
Removed:
}
1162
Removed:
.feedback-actions button {
1163
Removed:
flex: 1 1 10rem;
1164
Removed:
min-height: 3rem;
1165
Removed:
border: 2px solid var(--oxblood-dark);
1166
Removed:
border-radius: var(--radius);
1167
Removed:
color: var(--on-primary);
1168
Removed:
background: var(--oxblood);
1169
Removed:
cursor: pointer;
1170
Removed:
font: 700 var(--font-size-small)/1 var(--sans);
1171
Removed:
letter-spacing: 0.055em;
1172
Removed:
text-transform: uppercase;
1173
Removed:
}
1174
Removed:
.feedback-actions button:hover {
1175
Removed:
background: var(--oxblood-hover);
1176
Removed:
}
1161
Added:
.feedback-modal-content h2 { margin-top: 0; text-align: center; }
1177
1162
1178
1163
.feedback-list {
1179
1164
margin-top: 1rem;
@@ -1263,12 +1248,7 @@
1263
1248
}
1264
1249
.profile-dialog[open] { animation: modal-enter 180ms ease both; }
1265
1250
.profile-dialog::backdrop { background: rgba(26, 26, 22, 0.45); }
1266
Removed:
.profile-dialog h3 { margin-top: 0; }
1267
Removed:
.profile-dialog-actions {
1268
Removed:
display: flex;
1269
Removed:
gap: 0.75rem;
1270
Removed:
margin-top: 1rem;
1271
Removed:
}
1251
Added:
.profile-dialog h3 { margin-top: 0; text-align: center; }
1272
1252
1273
1253
.done {
1274
1254
color: var(--evergreen);
test/web/logbook.ml
@@ -5,22 +5,24 @@
5
5
[
6
6
( "web.logbook",
7
7
[
8
Removed:
( "the logbook shows an empty table state without top action buttons",
8
Added:
( "the empty logbook shows the actions and the empty state",
9
9
`Quick,
10
10
fun () ->
11
11
let c = client () in
12
12
let _ = sign_in_new c in
13
13
let page = body (get c "/logbook") in
14
Added:
(* The action bar is always present. *)
14
15
Alcotest.(check bool)
15
Removed:
"no Log feedback top action" false
16
Removed:
(Option.is_some (index_from ~needle:"Log feedback" page 0));
16
Added:
"View progress action shown" true
17
Added:
(Option.is_some (index_from ~needle:"View progress" page 0));
17
18
Alcotest.(check bool)
18
Removed:
"no Edit logbook top action" false
19
Removed:
(Option.is_some (index_from ~needle:"Edit logbook" page 0));
19
Added:
"Record feedback action shown" true
20
Added:
(Option.is_some (index_from ~needle:"Record feedback" page 0));
21
Added:
(* No workout table, just the empty-state line. *)
20
22
Alcotest.(check bool)
21
Removed:
"the top action group is absent" false
23
Added:
"no workout table" false
22
24
(Option.is_some
23
Removed:
(index_from ~needle:"class=\"button-group\"" page 0));
25
Added:
(index_from ~needle:"class=\"logbook-table\"" page 0));
24
26
Alcotest.(check bool)
25
27
"empty state when nothing is logged" true
26
28
(Option.is_some (index_from ~needle:"Nothing logged yet." page 0))