@wtfalch/email 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mailbox/fake/mailbox.js +61 -1
- package/dist/mailbox/fake/sample.d.ts +0 -9
- package/dist/mailbox/fake/sample.js +224 -4
- package/dist/mailbox/index.d.ts +2 -0
- package/dist/mailbox/index.js +1 -0
- package/dist/mailbox/mail.css +693 -47
- package/dist/mailbox/mutate.d.ts +51 -0
- package/dist/mailbox/mutate.js +132 -0
- package/dist/mailbox/react/Composer.d.ts +5 -0
- package/dist/mailbox/react/Composer.js +11 -2
- package/dist/mailbox/react/Mail.js +308 -32
- package/dist/mailbox/react/MailboxTree.d.ts +3 -1
- package/dist/mailbox/react/MailboxTree.js +43 -13
- package/dist/mailbox/react/ThreadList.d.ts +46 -14
- package/dist/mailbox/react/ThreadList.js +109 -8
- package/dist/mailbox/react/ThreadView.d.ts +29 -4
- package/dist/mailbox/react/ThreadView.js +85 -18
- package/dist/mailbox/react/index.d.ts +5 -3
- package/dist/mailbox/react/index.js +3 -2
- package/dist/mailbox/react/layout.d.ts +31 -0
- package/dist/mailbox/react/layout.js +42 -0
- package/dist/postmaster/objects.d.ts +22 -3
- package/dist/postmaster/objects.js +2 -2
- package/package.json +3 -3
package/dist/mailbox/mail.css
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
/* The mail client's own layout. Everything visual comes from
|
|
2
2
|
`@wtfalch/design`'s tokens; nothing here defines a colour, a radius or a
|
|
3
|
-
space of its own. Import it after the design stylesheet.
|
|
3
|
+
space of its own. Import it after the design stylesheet.
|
|
4
|
+
|
|
5
|
+
Two rules this file is written to, both from design's CLAUDE.md and both
|
|
6
|
+
learned the hard way there:
|
|
7
|
+
|
|
8
|
+
- A token, never a value. No hex, no pixel spacing, no font size. The one
|
|
9
|
+
exception is `--rail`, declared below: it is a 3px optical detail, like
|
|
10
|
+
the system's own `--nudge`, and a spacing step it is not.
|
|
11
|
+
- Nothing drawn over a surface names the colour it sits on. The row's
|
|
12
|
+
hover and selection are `currentColor` at low alpha, so they are a step
|
|
13
|
+
from whatever is behind them in whatever theme is on. */
|
|
4
14
|
|
|
5
15
|
.mail {
|
|
16
|
+
/* Declared here rather than inline: the unread bar and the row's padding
|
|
17
|
+
have to agree, and an optical width is not a spacing step. */
|
|
18
|
+
--rail: 3px;
|
|
19
|
+
|
|
6
20
|
display: flex;
|
|
7
21
|
flex-direction: column;
|
|
8
22
|
height: 100%;
|
|
@@ -12,29 +26,196 @@
|
|
|
12
26
|
font-size: var(--text-sm);
|
|
13
27
|
}
|
|
14
28
|
|
|
29
|
+
/* ---------------------------------------------------------------- the bar */
|
|
30
|
+
|
|
15
31
|
.mail-bar {
|
|
16
|
-
display:
|
|
32
|
+
display: grid;
|
|
33
|
+
/* Three tracks, and the middle one centred in the *window* rather than in
|
|
34
|
+
the space the other two leave over. A flex bar puts the search wherever
|
|
35
|
+
the account name's length happens to leave it, which moves every time
|
|
36
|
+
somebody with a longer address signs in. */
|
|
37
|
+
grid-template-columns: 1fr minmax(0, 34rem) 1fr;
|
|
17
38
|
align-items: center;
|
|
18
39
|
gap: var(--space-3);
|
|
19
|
-
padding: var(--space-3);
|
|
40
|
+
padding: var(--space-2) var(--space-3);
|
|
20
41
|
border-bottom: var(--border-width) solid var(--border);
|
|
42
|
+
background: var(--panel);
|
|
21
43
|
flex: none;
|
|
22
44
|
}
|
|
23
45
|
|
|
46
|
+
.mail-bar-start {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: var(--space-2);
|
|
50
|
+
min-width: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.mail-bar-end {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: flex-end;
|
|
57
|
+
gap: var(--space-2);
|
|
58
|
+
min-width: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mail-wordmark {
|
|
62
|
+
font-size: var(--text-sm);
|
|
63
|
+
font-weight: var(--weight-strong);
|
|
64
|
+
letter-spacing: 0.01em;
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* The search reads as a field and behaves as one, but it is quiet: it is the
|
|
69
|
+
thing you reach for, not the thing you look at. It used to be a full-width
|
|
70
|
+
input, which made the most prominent object on the screen the one nobody
|
|
71
|
+
uses until they need it. */
|
|
24
72
|
.mail-search {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--space-2);
|
|
76
|
+
min-width: 0;
|
|
77
|
+
padding: 0 var(--space-2);
|
|
78
|
+
border: var(--border-width) solid var(--border);
|
|
79
|
+
border-radius: var(--radius-pill);
|
|
80
|
+
background: var(--control);
|
|
81
|
+
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.mail-search:focus-within {
|
|
85
|
+
border-color: var(--accent);
|
|
86
|
+
box-shadow: var(--focus-ring);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.mail-search-icon {
|
|
90
|
+
flex: none;
|
|
91
|
+
color: var(--muted);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mail-search-field {
|
|
25
95
|
flex: 1 1 auto;
|
|
26
96
|
min-width: 0;
|
|
97
|
+
padding: var(--space-2) 0;
|
|
98
|
+
border: none;
|
|
99
|
+
background: transparent;
|
|
100
|
+
color: var(--text);
|
|
101
|
+
font: inherit;
|
|
102
|
+
font-size: var(--text-sm);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.mail-search-field:focus {
|
|
106
|
+
outline: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Safari draws its own cross inside a `type=search`, in its own colours and
|
|
110
|
+
at its own size, beside the one this draws. */
|
|
111
|
+
.mail-search-field::-webkit-search-cancel-button {
|
|
112
|
+
appearance: none;
|
|
27
113
|
}
|
|
114
|
+
|
|
115
|
+
.mail-search-key {
|
|
116
|
+
flex: none;
|
|
117
|
+
padding: 0 var(--space-1);
|
|
118
|
+
border: var(--border-width) solid var(--border);
|
|
119
|
+
border-radius: var(--radius-sm);
|
|
120
|
+
color: var(--muted);
|
|
121
|
+
font-family: var(--font-mono);
|
|
122
|
+
font-size: var(--text-2xs);
|
|
123
|
+
line-height: 1.6;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.mail-search-clear {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
flex: none;
|
|
130
|
+
padding: var(--space-1);
|
|
131
|
+
border: none;
|
|
132
|
+
border-radius: var(--radius-pill);
|
|
133
|
+
background: transparent;
|
|
134
|
+
color: var(--muted);
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.mail-search-clear:hover {
|
|
139
|
+
color: var(--text);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.mail-search-clear:focus-visible {
|
|
143
|
+
outline: none;
|
|
144
|
+
box-shadow: var(--focus-ring);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* The word goes when the bar gets tight; the pen stays, and the button keeps
|
|
148
|
+
its accessible name from the icon's title. */
|
|
149
|
+
.mail-compose-word {
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
}
|
|
152
|
+
|
|
28
153
|
.mail-panes {
|
|
154
|
+
display: flex;
|
|
29
155
|
flex: 1 1 auto;
|
|
30
156
|
min-height: 0;
|
|
157
|
+
/* A flex item's `min-width` is `auto`, which means "no smaller than your
|
|
158
|
+
content" -- and this one's content is a list of rows full of `nowrap`
|
|
159
|
+
text. Without the override the list sized itself to the longest subject
|
|
160
|
+
in the mailbox and pushed the whole page wider than the window, which is
|
|
161
|
+
a horizontal scrollbar on every phone and the one layout rule with no
|
|
162
|
+
exceptions. It did not show on the wide layout because `SplitPane` gives
|
|
163
|
+
both panes an explicit width. */
|
|
164
|
+
min-width: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.mail-panes > * {
|
|
168
|
+
min-width: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.mail-panes-split {
|
|
172
|
+
flex: 1 1 auto;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* What just happened. Pinned to the bottom corner, out of the way of the
|
|
177
|
+
panes, and gone in four seconds. */
|
|
178
|
+
.mail-note {
|
|
179
|
+
position: fixed;
|
|
180
|
+
left: 50%;
|
|
181
|
+
bottom: var(--space-4);
|
|
182
|
+
z-index: 40;
|
|
183
|
+
padding: var(--space-2) var(--space-4);
|
|
184
|
+
border: var(--border-width) solid var(--border);
|
|
185
|
+
border-radius: var(--radius-pill);
|
|
186
|
+
background: var(--panel);
|
|
187
|
+
box-shadow: var(--shadow-3);
|
|
188
|
+
color: var(--text);
|
|
189
|
+
font-size: var(--text-xs);
|
|
190
|
+
/* Out of the layout and out of the hit-testing when there is nothing to
|
|
191
|
+
say, but still in the accessibility tree, which is the point of it
|
|
192
|
+
always being rendered. */
|
|
193
|
+
opacity: 0;
|
|
194
|
+
transform: translate(-50%, var(--space-2));
|
|
195
|
+
pointer-events: none;
|
|
196
|
+
transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.mail-note.is-shown {
|
|
200
|
+
opacity: 1;
|
|
201
|
+
transform: translate(-50%, 0);
|
|
31
202
|
}
|
|
32
203
|
|
|
33
204
|
/* ---------------------------------------------------------------- mailboxes */
|
|
205
|
+
|
|
34
206
|
.mailboxes {
|
|
35
207
|
height: 100%;
|
|
208
|
+
padding: var(--space-2);
|
|
36
209
|
background: var(--panel);
|
|
210
|
+
border-right: var(--border-width) solid var(--border);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.mailboxes.is-rail {
|
|
214
|
+
flex: none;
|
|
215
|
+
width: calc(var(--space-10) + var(--space-4));
|
|
216
|
+
padding: var(--space-2) var(--space-1);
|
|
37
217
|
}
|
|
218
|
+
|
|
38
219
|
.mailboxes-quiet,
|
|
39
220
|
.threads-quiet,
|
|
40
221
|
.message-quiet {
|
|
@@ -49,8 +230,28 @@
|
|
|
49
230
|
margin: 0;
|
|
50
231
|
padding: 0;
|
|
51
232
|
}
|
|
52
|
-
|
|
53
|
-
|
|
233
|
+
|
|
234
|
+
.mailboxes-heading {
|
|
235
|
+
margin: var(--space-4) 0 var(--space-1);
|
|
236
|
+
padding: 0 var(--space-2);
|
|
237
|
+
color: var(--muted);
|
|
238
|
+
font-size: var(--text-2xs);
|
|
239
|
+
font-weight: var(--weight-strong);
|
|
240
|
+
letter-spacing: 0.06em;
|
|
241
|
+
text-transform: uppercase;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* On the rail the heading has no room for its word, so it becomes the line
|
|
245
|
+
the word was there to draw. */
|
|
246
|
+
.is-rail .mailboxes-heading {
|
|
247
|
+
margin: var(--space-2) var(--space-2);
|
|
248
|
+
padding: 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.is-rail .mailboxes-heading .rule {
|
|
252
|
+
display: block;
|
|
253
|
+
height: var(--border-width);
|
|
254
|
+
background: var(--border);
|
|
54
255
|
}
|
|
55
256
|
|
|
56
257
|
.mailbox {
|
|
@@ -58,29 +259,45 @@
|
|
|
58
259
|
align-items: center;
|
|
59
260
|
gap: var(--space-2);
|
|
60
261
|
width: 100%;
|
|
61
|
-
padding: var(--space-2)
|
|
62
|
-
padding-left: calc(var(--space-
|
|
262
|
+
padding: var(--space-2);
|
|
263
|
+
padding-left: calc(var(--space-2) + var(--depth, 0) * var(--space-4));
|
|
63
264
|
border: none;
|
|
265
|
+
border-radius: var(--radius);
|
|
64
266
|
background: transparent;
|
|
65
267
|
color: var(--muted);
|
|
66
268
|
font: inherit;
|
|
67
269
|
font-size: var(--text-sm);
|
|
68
270
|
text-align: left;
|
|
69
271
|
cursor: pointer;
|
|
272
|
+
transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.is-rail .mailbox {
|
|
276
|
+
justify-content: center;
|
|
277
|
+
position: relative;
|
|
278
|
+
padding: var(--space-2);
|
|
70
279
|
}
|
|
71
280
|
|
|
72
281
|
.mailbox:hover {
|
|
73
|
-
background:
|
|
282
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
74
283
|
color: var(--text);
|
|
75
284
|
}
|
|
285
|
+
|
|
76
286
|
.mailbox:focus-visible {
|
|
77
287
|
outline: none;
|
|
78
288
|
box-shadow: var(--focus-ring);
|
|
79
289
|
}
|
|
290
|
+
|
|
80
291
|
.mailbox.on {
|
|
81
|
-
background: var(--
|
|
292
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
82
293
|
color: var(--text);
|
|
294
|
+
font-weight: var(--weight-strong);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.mailbox.on .mailbox-icon {
|
|
298
|
+
color: var(--accent);
|
|
83
299
|
}
|
|
300
|
+
|
|
84
301
|
/* Unread is weight, so it survives a theme, a projector and a reader who
|
|
85
302
|
cannot tell the colours apart. */
|
|
86
303
|
.mailbox.unread {
|
|
@@ -88,11 +305,19 @@
|
|
|
88
305
|
font-weight: var(--weight-strong);
|
|
89
306
|
}
|
|
90
307
|
|
|
308
|
+
.mailbox.is-skeleton {
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
gap: var(--space-2);
|
|
312
|
+
cursor: default;
|
|
313
|
+
}
|
|
314
|
+
|
|
91
315
|
.mailbox-icon {
|
|
316
|
+
display: inline-flex;
|
|
92
317
|
flex: none;
|
|
93
|
-
|
|
94
|
-
height: var(--space-4);
|
|
318
|
+
color: var(--muted);
|
|
95
319
|
}
|
|
320
|
+
|
|
96
321
|
.mailbox-name {
|
|
97
322
|
flex: 1 1 auto;
|
|
98
323
|
min-width: 0;
|
|
@@ -100,39 +325,140 @@
|
|
|
100
325
|
text-overflow: ellipsis;
|
|
101
326
|
white-space: nowrap;
|
|
102
327
|
}
|
|
328
|
+
|
|
329
|
+
/* A fixed slot, reserved whether or not there is a number in it: a count
|
|
330
|
+
arriving must not re-measure the name beside it. That is design's
|
|
331
|
+
"content jumping" rule, and a mailbox that goes from 0 to 1 unread is the
|
|
332
|
+
commonest way to see it. */
|
|
103
333
|
.mailbox-count {
|
|
104
334
|
flex: none;
|
|
335
|
+
min-width: var(--space-5);
|
|
336
|
+
text-align: right;
|
|
337
|
+
color: var(--muted);
|
|
105
338
|
font-size: var(--text-2xs);
|
|
106
339
|
font-variant-numeric: tabular-nums;
|
|
107
340
|
}
|
|
108
341
|
|
|
342
|
+
.mailbox.unread .mailbox-count {
|
|
343
|
+
color: var(--accent);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* A folder gets a dot where a role gets a glyph, so the names still line up
|
|
347
|
+
without a column of identical folder icons carrying no information. */
|
|
348
|
+
.mailbox-pip {
|
|
349
|
+
display: block;
|
|
350
|
+
width: var(--space-1);
|
|
351
|
+
height: var(--space-1);
|
|
352
|
+
border-radius: var(--radius-pill);
|
|
353
|
+
background: currentColor;
|
|
354
|
+
opacity: 0.45;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* On the rail there is no room for a number, so unread becomes a dot on the
|
|
358
|
+
corner of the glyph. */
|
|
359
|
+
.mailbox-pip.is-unread {
|
|
360
|
+
position: absolute;
|
|
361
|
+
top: var(--space-1);
|
|
362
|
+
right: var(--space-1);
|
|
363
|
+
background: var(--accent);
|
|
364
|
+
opacity: 1;
|
|
365
|
+
}
|
|
366
|
+
|
|
109
367
|
/* ------------------------------------------------------------------ threads */
|
|
368
|
+
|
|
110
369
|
.threads {
|
|
111
370
|
display: flex;
|
|
112
371
|
flex-direction: column;
|
|
372
|
+
flex: 1 1 auto;
|
|
113
373
|
height: 100%;
|
|
114
374
|
min-height: 0;
|
|
375
|
+
/* See `.mail-panes`. */
|
|
376
|
+
min-width: 0;
|
|
115
377
|
background: var(--panel);
|
|
378
|
+
border-right: var(--border-width) solid var(--border);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.threads-head {
|
|
382
|
+
display: flex;
|
|
383
|
+
align-items: baseline;
|
|
384
|
+
justify-content: space-between;
|
|
385
|
+
gap: var(--space-2);
|
|
386
|
+
flex: none;
|
|
387
|
+
padding: var(--space-3) var(--space-4);
|
|
388
|
+
border-bottom: var(--border-width) solid var(--border);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.threads-title {
|
|
392
|
+
margin: 0;
|
|
393
|
+
font-size: var(--text-sm);
|
|
394
|
+
font-weight: var(--weight-strong);
|
|
395
|
+
overflow: hidden;
|
|
396
|
+
text-overflow: ellipsis;
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.threads-total {
|
|
401
|
+
flex: none;
|
|
402
|
+
color: var(--muted);
|
|
403
|
+
font-size: var(--text-2xs);
|
|
404
|
+
font-variant-numeric: tabular-nums;
|
|
116
405
|
}
|
|
406
|
+
|
|
117
407
|
.threads-scroll {
|
|
118
408
|
flex: 1 1 auto;
|
|
409
|
+
min-height: 0;
|
|
119
410
|
}
|
|
411
|
+
|
|
412
|
+
.threads-empty {
|
|
413
|
+
display: grid;
|
|
414
|
+
place-items: center;
|
|
415
|
+
flex: 1 1 auto;
|
|
416
|
+
padding: var(--space-6);
|
|
417
|
+
}
|
|
418
|
+
|
|
120
419
|
.threads-list {
|
|
420
|
+
display: flex;
|
|
421
|
+
flex-direction: column;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.threads-rows {
|
|
121
425
|
list-style: none;
|
|
122
426
|
margin: 0;
|
|
123
427
|
padding: 0;
|
|
124
428
|
}
|
|
429
|
+
|
|
125
430
|
.threads-pager {
|
|
126
431
|
flex: none;
|
|
127
432
|
padding: 0 var(--space-3);
|
|
128
433
|
border-top: var(--border-width) solid var(--border);
|
|
129
434
|
}
|
|
130
435
|
|
|
436
|
+
/* The day heading, pinned while its own rows are on screen. Scrolling a long
|
|
437
|
+
mailbox with no landmarks is how you lose your place. */
|
|
438
|
+
.threads-day {
|
|
439
|
+
position: sticky;
|
|
440
|
+
top: 0;
|
|
441
|
+
z-index: 1;
|
|
442
|
+
margin: 0;
|
|
443
|
+
padding: var(--space-2) var(--space-4);
|
|
444
|
+
/* Opaque, because rows scroll under it. This is the one place a surface
|
|
445
|
+
token is named directly rather than mixed: a translucent sticky heading
|
|
446
|
+
over moving text is unreadable at any alpha. */
|
|
447
|
+
background: var(--panel);
|
|
448
|
+
border-bottom: var(--border-width) solid var(--border);
|
|
449
|
+
color: var(--muted);
|
|
450
|
+
font-size: var(--text-2xs);
|
|
451
|
+
font-weight: var(--weight-strong);
|
|
452
|
+
letter-spacing: 0.06em;
|
|
453
|
+
text-transform: uppercase;
|
|
454
|
+
}
|
|
455
|
+
|
|
131
456
|
.thread-row {
|
|
132
|
-
display:
|
|
133
|
-
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: stretch;
|
|
459
|
+
gap: 0;
|
|
134
460
|
width: 100%;
|
|
135
|
-
padding:
|
|
461
|
+
padding: 0;
|
|
136
462
|
border: none;
|
|
137
463
|
border-bottom: var(--border-width) solid var(--border);
|
|
138
464
|
background: transparent;
|
|
@@ -140,23 +466,59 @@
|
|
|
140
466
|
font: inherit;
|
|
141
467
|
text-align: left;
|
|
142
468
|
cursor: pointer;
|
|
469
|
+
transition: background var(--dur-fast) var(--ease);
|
|
143
470
|
}
|
|
144
471
|
|
|
145
472
|
.thread-row:hover {
|
|
146
|
-
background:
|
|
473
|
+
background: color-mix(in srgb, currentColor 6%, transparent);
|
|
147
474
|
}
|
|
475
|
+
|
|
148
476
|
.thread-row:focus-visible {
|
|
149
477
|
outline: none;
|
|
150
478
|
box-shadow: var(--focus-ring);
|
|
479
|
+
/* Above the sticky day heading, or the ring is clipped by it -- WCAG 2.2's
|
|
480
|
+
"focus not obscured", and the heading is exactly the persistent UI the
|
|
481
|
+
criterion is about. */
|
|
482
|
+
position: relative;
|
|
483
|
+
z-index: 2;
|
|
151
484
|
}
|
|
485
|
+
|
|
152
486
|
.thread-row.on {
|
|
153
|
-
background: var(--
|
|
487
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
154
488
|
}
|
|
489
|
+
|
|
155
490
|
.thread-row.unread {
|
|
156
491
|
color: var(--text);
|
|
157
492
|
}
|
|
158
|
-
|
|
159
|
-
|
|
493
|
+
|
|
494
|
+
.thread-row.is-skeleton {
|
|
495
|
+
cursor: default;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* The unread bar, in the gutter rather than in the text: a read row and an
|
|
499
|
+
unread row have to start their subjects at the same x, and the bullet that
|
|
500
|
+
used to sit inline moved every unread subject two characters right. */
|
|
501
|
+
.thread-rail {
|
|
502
|
+
flex: none;
|
|
503
|
+
width: var(--rail);
|
|
504
|
+
background: transparent;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.thread-row.unread .thread-rail {
|
|
508
|
+
background: var(--accent);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.thread-row.on .thread-rail {
|
|
512
|
+
background: var(--accent);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.thread-main {
|
|
516
|
+
display: flex;
|
|
517
|
+
flex-direction: column;
|
|
518
|
+
gap: var(--space-1);
|
|
519
|
+
flex: 1 1 auto;
|
|
520
|
+
min-width: 0;
|
|
521
|
+
padding: var(--space-3) var(--space-4) var(--space-3) calc(var(--space-4) - var(--rail));
|
|
160
522
|
}
|
|
161
523
|
|
|
162
524
|
.thread-top {
|
|
@@ -165,85 +527,228 @@
|
|
|
165
527
|
gap: var(--space-2);
|
|
166
528
|
min-width: 0;
|
|
167
529
|
}
|
|
530
|
+
|
|
168
531
|
.thread-who {
|
|
169
532
|
flex: 1 1 auto;
|
|
170
533
|
min-width: 0;
|
|
534
|
+
overflow: hidden;
|
|
535
|
+
text-overflow: ellipsis;
|
|
536
|
+
white-space: nowrap;
|
|
537
|
+
font-size: var(--text-sm);
|
|
171
538
|
}
|
|
172
|
-
|
|
539
|
+
|
|
540
|
+
.thread-row.unread .thread-who {
|
|
541
|
+
font-weight: var(--weight-strong);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.thread-marks {
|
|
545
|
+
display: flex;
|
|
546
|
+
align-items: center;
|
|
547
|
+
gap: var(--space-1);
|
|
548
|
+
flex: none;
|
|
173
549
|
color: var(--muted);
|
|
174
|
-
font-style: italic;
|
|
175
550
|
}
|
|
551
|
+
|
|
552
|
+
.thread-mark.is-flag {
|
|
553
|
+
color: var(--warn);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.thread-count {
|
|
557
|
+
padding: 0 var(--space-1);
|
|
558
|
+
border: var(--border-width) solid var(--border);
|
|
559
|
+
border-radius: var(--radius-pill);
|
|
560
|
+
font-size: var(--text-2xs);
|
|
561
|
+
font-variant-numeric: tabular-nums;
|
|
562
|
+
line-height: 1.5;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.thread-draft {
|
|
566
|
+
color: var(--warn);
|
|
567
|
+
font-size: var(--text-2xs);
|
|
568
|
+
font-weight: var(--weight-strong);
|
|
569
|
+
text-transform: uppercase;
|
|
570
|
+
letter-spacing: 0.04em;
|
|
571
|
+
}
|
|
572
|
+
|
|
176
573
|
.thread-when {
|
|
177
574
|
flex: none;
|
|
178
|
-
font-size: var(--text-2xs);
|
|
179
575
|
color: var(--muted);
|
|
576
|
+
font-size: var(--text-2xs);
|
|
180
577
|
font-variant-numeric: tabular-nums;
|
|
181
578
|
}
|
|
182
579
|
|
|
183
|
-
|
|
580
|
+
/* Subject and preview share a line, which is the whole density argument:
|
|
581
|
+
the subject takes what it needs and the preview takes the rest. */
|
|
582
|
+
.thread-bottom {
|
|
184
583
|
display: flex;
|
|
185
584
|
align-items: baseline;
|
|
186
585
|
gap: var(--space-2);
|
|
187
586
|
min-width: 0;
|
|
587
|
+
font-size: var(--text-xs);
|
|
188
588
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
589
|
+
|
|
590
|
+
/* The subject takes what it needs and the preview takes the rest, which is
|
|
591
|
+
the whole reason they share a line. Both were `flex-basis: auto`, so the
|
|
592
|
+
two shrank in proportion to their content and a nine-word preview
|
|
593
|
+
truncated a two-word subject to "Engine sc…" with half the row empty.
|
|
594
|
+
The preview's basis is zero: it contributes nothing to the line's natural
|
|
595
|
+
width and only ever grows into what is left. */
|
|
596
|
+
.thread-subject {
|
|
597
|
+
flex: 0 1 auto;
|
|
598
|
+
/* Not the whole line either -- a forwarded subject is a paragraph, and one
|
|
599
|
+
that eats the row leaves no evidence of what the message says. */
|
|
600
|
+
max-width: 62%;
|
|
195
601
|
min-width: 0;
|
|
196
602
|
overflow: hidden;
|
|
197
603
|
text-overflow: ellipsis;
|
|
198
604
|
white-space: nowrap;
|
|
605
|
+
color: var(--text);
|
|
199
606
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
padding: 0 var(--space-1);
|
|
203
|
-
border: var(--border-width) solid var(--border);
|
|
204
|
-
border-radius: var(--radius-pill);
|
|
205
|
-
font-size: var(--text-2xs);
|
|
607
|
+
|
|
608
|
+
.thread-row:not(.unread) .thread-subject {
|
|
206
609
|
color: var(--muted);
|
|
207
610
|
}
|
|
208
611
|
|
|
612
|
+
.thread-row.unread .thread-subject {
|
|
613
|
+
font-weight: var(--weight-strong);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* No opacity. `--muted` is the measured token -- design holds it to 4.5:1
|
|
617
|
+
against every theme -- and knocking it back to 85% put the preview under
|
|
618
|
+
the threshold on ten rows in light and one in dark. Measured with axe, not
|
|
619
|
+
judged by eye, which is the rule. */
|
|
209
620
|
.thread-preview {
|
|
210
|
-
|
|
211
|
-
|
|
621
|
+
flex: 1 1 0;
|
|
622
|
+
min-width: 0;
|
|
212
623
|
overflow: hidden;
|
|
213
624
|
text-overflow: ellipsis;
|
|
214
625
|
white-space: nowrap;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
.thread-marks {
|
|
218
|
-
display: flex;
|
|
219
|
-
gap: var(--space-1);
|
|
220
|
-
flex-wrap: wrap;
|
|
626
|
+
color: var(--muted);
|
|
221
627
|
}
|
|
222
628
|
|
|
223
629
|
/* --------------------------------------------------------------- one thread */
|
|
630
|
+
|
|
224
631
|
.thread {
|
|
225
632
|
display: flex;
|
|
226
633
|
flex-direction: column;
|
|
227
634
|
height: 100%;
|
|
228
635
|
min-height: 0;
|
|
636
|
+
min-width: 0;
|
|
637
|
+
flex: 1 1 auto;
|
|
638
|
+
background: var(--bg);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.thread.is-empty {
|
|
642
|
+
display: grid;
|
|
643
|
+
place-items: center;
|
|
644
|
+
padding: var(--space-6);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.thread-hint {
|
|
648
|
+
display: block;
|
|
649
|
+
margin-top: var(--space-2);
|
|
650
|
+
color: var(--muted);
|
|
651
|
+
font-size: var(--text-xs);
|
|
229
652
|
}
|
|
653
|
+
|
|
654
|
+
.thread-hint kbd,
|
|
655
|
+
.keys kbd,
|
|
656
|
+
.composer-hint kbd {
|
|
657
|
+
display: inline-block;
|
|
658
|
+
min-width: var(--space-5);
|
|
659
|
+
padding: 0 var(--space-1);
|
|
660
|
+
border: var(--border-width) solid var(--border);
|
|
661
|
+
border-radius: var(--radius-sm);
|
|
662
|
+
background: var(--panel-2);
|
|
663
|
+
color: var(--text);
|
|
664
|
+
font-family: var(--font-mono);
|
|
665
|
+
font-size: var(--text-2xs);
|
|
666
|
+
text-align: center;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* Pinned, so archiving the twelfth message of a thread does not mean
|
|
670
|
+
scrolling back up to find the button. */
|
|
230
671
|
.thread-head {
|
|
672
|
+
display: flex;
|
|
673
|
+
align-items: flex-start;
|
|
674
|
+
justify-content: space-between;
|
|
675
|
+
gap: var(--space-3);
|
|
231
676
|
flex: none;
|
|
232
|
-
padding: var(--space-4);
|
|
677
|
+
padding: var(--space-3) var(--space-4);
|
|
233
678
|
border-bottom: var(--border-width) solid var(--border);
|
|
679
|
+
background: var(--panel);
|
|
234
680
|
}
|
|
681
|
+
|
|
682
|
+
.thread-headline {
|
|
683
|
+
display: flex;
|
|
684
|
+
align-items: flex-start;
|
|
685
|
+
gap: var(--space-2);
|
|
686
|
+
min-width: 0;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.thread-heading {
|
|
690
|
+
min-width: 0;
|
|
691
|
+
}
|
|
692
|
+
|
|
235
693
|
.thread-title {
|
|
236
694
|
margin: 0;
|
|
237
695
|
font-size: var(--text-lg);
|
|
238
696
|
font-weight: var(--weight-strong);
|
|
697
|
+
/* Two lines, then an ellipsis: a forwarded subject can be a paragraph, and
|
|
698
|
+
a head that grows to four lines pushes the message off the screen. */
|
|
699
|
+
display: -webkit-box;
|
|
700
|
+
-webkit-line-clamp: 2;
|
|
701
|
+
line-clamp: 2;
|
|
702
|
+
-webkit-box-orient: vertical;
|
|
703
|
+
overflow: hidden;
|
|
239
704
|
}
|
|
705
|
+
|
|
240
706
|
.thread-meta {
|
|
241
707
|
margin: var(--space-1) 0 0;
|
|
242
708
|
color: var(--muted);
|
|
243
709
|
font-size: var(--text-xs);
|
|
710
|
+
overflow: hidden;
|
|
711
|
+
text-overflow: ellipsis;
|
|
712
|
+
white-space: nowrap;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.thread-meta-sep {
|
|
716
|
+
margin: 0 var(--space-1);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.thread-tools {
|
|
720
|
+
display: flex;
|
|
721
|
+
align-items: center;
|
|
722
|
+
gap: var(--space-1);
|
|
723
|
+
flex: none;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.thread-tool {
|
|
727
|
+
display: inline-flex;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.thread-tools-rule {
|
|
731
|
+
width: var(--border-width);
|
|
732
|
+
align-self: stretch;
|
|
733
|
+
margin: 0 var(--space-1);
|
|
734
|
+
background: var(--border);
|
|
244
735
|
}
|
|
736
|
+
|
|
737
|
+
.thread-tools .is-flagged {
|
|
738
|
+
color: var(--warn);
|
|
739
|
+
}
|
|
740
|
+
|
|
245
741
|
.thread-body {
|
|
246
742
|
flex: 1 1 auto;
|
|
743
|
+
min-height: 0;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.thread-foot {
|
|
747
|
+
display: flex;
|
|
748
|
+
align-items: center;
|
|
749
|
+
gap: var(--space-2);
|
|
750
|
+
padding: var(--space-4);
|
|
751
|
+
border-top: var(--border-width) solid var(--border);
|
|
247
752
|
}
|
|
248
753
|
|
|
249
754
|
.messages {
|
|
@@ -255,6 +760,11 @@
|
|
|
255
760
|
.message {
|
|
256
761
|
border-bottom: var(--border-width) solid var(--border);
|
|
257
762
|
}
|
|
763
|
+
|
|
764
|
+
.message.open {
|
|
765
|
+
background: var(--panel);
|
|
766
|
+
}
|
|
767
|
+
|
|
258
768
|
.message-head {
|
|
259
769
|
display: flex;
|
|
260
770
|
align-items: center;
|
|
@@ -281,23 +791,55 @@
|
|
|
281
791
|
outline: none;
|
|
282
792
|
box-shadow: var(--focus-ring);
|
|
283
793
|
}
|
|
284
|
-
|
|
794
|
+
|
|
795
|
+
/* Collapsed, the preview is the only thing that says whether this is the
|
|
796
|
+
message being looked for -- but the sender outranks it, so the basis is
|
|
797
|
+
zero and the preview only ever takes what the name has left. At `auto` the
|
|
798
|
+
two shrank in proportion and a four-word preview cut "Mary Somerville"
|
|
799
|
+
down to "M..", which is the same bug the list's subject had. */
|
|
800
|
+
.message-peek {
|
|
801
|
+
flex: 1 1 0;
|
|
802
|
+
min-width: 0;
|
|
803
|
+
overflow: hidden;
|
|
804
|
+
text-overflow: ellipsis;
|
|
805
|
+
white-space: nowrap;
|
|
806
|
+
color: var(--muted);
|
|
807
|
+
font-size: var(--text-xs);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.message-aside {
|
|
811
|
+
display: flex;
|
|
812
|
+
align-items: center;
|
|
813
|
+
gap: var(--space-2);
|
|
285
814
|
flex: none;
|
|
286
815
|
margin-left: auto;
|
|
287
816
|
color: var(--muted);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.message-when {
|
|
820
|
+
color: var(--muted);
|
|
288
821
|
font-size: var(--text-2xs);
|
|
822
|
+
font-variant-numeric: tabular-nums;
|
|
823
|
+
white-space: nowrap;
|
|
289
824
|
}
|
|
825
|
+
|
|
290
826
|
.message-nobody {
|
|
291
827
|
color: var(--muted);
|
|
292
828
|
font-style: italic;
|
|
293
829
|
}
|
|
294
830
|
|
|
295
831
|
.message-body {
|
|
296
|
-
|
|
832
|
+
/* The measure. Prose past about 80 characters a line is measurably slower
|
|
833
|
+
to read, and a maximised window on a wide monitor gives a message pane
|
|
834
|
+
160. The body is capped and the chrome around it is not. */
|
|
835
|
+
max-width: 78ch;
|
|
836
|
+
padding: 0 var(--space-4) var(--space-5);
|
|
297
837
|
}
|
|
838
|
+
|
|
298
839
|
.message-lines {
|
|
299
840
|
margin-bottom: var(--space-3);
|
|
300
841
|
}
|
|
842
|
+
|
|
301
843
|
.message-line {
|
|
302
844
|
margin: 0;
|
|
303
845
|
color: var(--muted);
|
|
@@ -305,6 +847,7 @@
|
|
|
305
847
|
display: flex;
|
|
306
848
|
gap: var(--space-2);
|
|
307
849
|
}
|
|
850
|
+
|
|
308
851
|
.message-line-label {
|
|
309
852
|
min-width: var(--space-6);
|
|
310
853
|
}
|
|
@@ -360,30 +903,35 @@
|
|
|
360
903
|
font: inherit;
|
|
361
904
|
font-size: var(--text-xs);
|
|
362
905
|
cursor: pointer;
|
|
906
|
+
transition: border-color var(--dur-fast) var(--ease);
|
|
363
907
|
}
|
|
364
908
|
|
|
365
909
|
.attachment:hover:not(:disabled) {
|
|
366
910
|
border-color: var(--accent);
|
|
367
911
|
}
|
|
912
|
+
|
|
368
913
|
.attachment:focus-visible {
|
|
369
914
|
outline: none;
|
|
370
915
|
box-shadow: var(--focus-ring);
|
|
371
916
|
}
|
|
917
|
+
|
|
372
918
|
.attachment:disabled {
|
|
373
919
|
cursor: default;
|
|
374
920
|
color: var(--muted);
|
|
375
921
|
}
|
|
922
|
+
|
|
376
923
|
.attachment-icon {
|
|
377
924
|
flex: none;
|
|
378
|
-
width: var(--space-4);
|
|
379
|
-
height: var(--space-4);
|
|
380
925
|
color: var(--muted);
|
|
381
926
|
}
|
|
927
|
+
|
|
382
928
|
.attachment-size {
|
|
383
929
|
color: var(--muted);
|
|
930
|
+
font-variant-numeric: tabular-nums;
|
|
384
931
|
}
|
|
385
932
|
|
|
386
933
|
/* ----------------------------------------------------------------- composer */
|
|
934
|
+
|
|
387
935
|
.composer {
|
|
388
936
|
display: grid;
|
|
389
937
|
gap: var(--space-3);
|
|
@@ -426,11 +974,13 @@
|
|
|
426
974
|
display: grid;
|
|
427
975
|
gap: var(--space-1);
|
|
428
976
|
}
|
|
977
|
+
|
|
429
978
|
.composer-files li {
|
|
430
979
|
display: flex;
|
|
431
980
|
align-items: center;
|
|
432
981
|
gap: var(--space-2);
|
|
433
982
|
}
|
|
983
|
+
|
|
434
984
|
.composer-file {
|
|
435
985
|
font-size: var(--text-xs);
|
|
436
986
|
}
|
|
@@ -439,13 +989,109 @@
|
|
|
439
989
|
display: flex;
|
|
440
990
|
align-items: center;
|
|
441
991
|
gap: var(--space-3);
|
|
992
|
+
padding-top: var(--space-2);
|
|
993
|
+
border-top: var(--border-width) solid var(--border);
|
|
442
994
|
}
|
|
995
|
+
|
|
443
996
|
.composer-hint {
|
|
997
|
+
margin-left: auto;
|
|
444
998
|
color: var(--muted);
|
|
445
999
|
font-size: var(--text-xs);
|
|
446
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
.composer-hint kbd + kbd {
|
|
1003
|
+
margin-left: var(--space-1);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
447
1006
|
.composer-error {
|
|
448
1007
|
margin: 0;
|
|
449
1008
|
color: var(--bad);
|
|
450
1009
|
font-size: var(--text-xs);
|
|
451
1010
|
}
|
|
1011
|
+
|
|
1012
|
+
/* ---------------------------------------------------------------- shortcuts */
|
|
1013
|
+
|
|
1014
|
+
.keys {
|
|
1015
|
+
display: grid;
|
|
1016
|
+
gap: var(--space-2);
|
|
1017
|
+
margin: 0;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.keys-row {
|
|
1021
|
+
display: grid;
|
|
1022
|
+
grid-template-columns: 8rem 1fr;
|
|
1023
|
+
align-items: baseline;
|
|
1024
|
+
gap: var(--space-3);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.keys dt {
|
|
1028
|
+
margin: 0;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.keys dd {
|
|
1032
|
+
margin: 0;
|
|
1033
|
+
color: var(--muted);
|
|
1034
|
+
font-size: var(--text-xs);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/* --------------------------------------------------------------- the widths */
|
|
1038
|
+
|
|
1039
|
+
/* One column. The list and the message are the same space, so the shell
|
|
1040
|
+
swaps them; what is left here is the chrome that has to give way. */
|
|
1041
|
+
.mail.is-narrow .threads,
|
|
1042
|
+
.mail.is-narrow .thread {
|
|
1043
|
+
border-right: none;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.mail.is-narrow .mail-bar {
|
|
1047
|
+
grid-template-columns: auto 1fr auto;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/* The signed-in address goes, and the disc stays. The account slot is the
|
|
1051
|
+
application's -- `Mail` is handed it as a node and cannot re-render it
|
|
1052
|
+
narrower -- but the width is the shell's problem, so the shell hides the
|
|
1053
|
+
text. Reaching into `Identity`'s classes is the cost of that split; it is
|
|
1054
|
+
a display rule and the name stays in the accessibility tree. */
|
|
1055
|
+
.mail.is-narrow .mail-bar-end .ident-text,
|
|
1056
|
+
.mail.is-medium .mail-bar-end .ident-text {
|
|
1057
|
+
display: none;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.mail.is-narrow .mail-compose-word {
|
|
1061
|
+
/* The pen alone. The button keeps its name from the `aria-label` the icon
|
|
1062
|
+
is given, so this is a visual change and not a semantic one. */
|
|
1063
|
+
display: none;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
.mail.is-narrow .thread-head {
|
|
1067
|
+
/* The toolbar wraps under the subject rather than squeezing it to nothing:
|
|
1068
|
+
six icon buttons and a two-line subject do not fit on 390px. */
|
|
1069
|
+
flex-wrap: wrap;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/* One column has room for a name and a date, and not for a preview as well.
|
|
1073
|
+
The preview is the thing that goes: the name and the time are what a
|
|
1074
|
+
collapsed row is for. */
|
|
1075
|
+
.mail.is-narrow .message-peek {
|
|
1076
|
+
display: none;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
.mail.is-narrow .message-body {
|
|
1080
|
+
padding-inline: var(--space-3);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.mail.is-narrow .thread-main {
|
|
1084
|
+
padding-inline: var(--space-3) var(--space-3);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
@media (max-width: 520px) {
|
|
1088
|
+
.mail-bar {
|
|
1089
|
+
gap: var(--space-2);
|
|
1090
|
+
padding: var(--space-2);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.threads-head,
|
|
1094
|
+
.thread-head {
|
|
1095
|
+
padding-inline: var(--space-3);
|
|
1096
|
+
}
|
|
1097
|
+
}
|