@tldraw/collaboration 0.0.0-bootstrap → 5.3.0-canary.04044ed9e96d
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/collaboration.css +1152 -350
- package/dist-cjs/index.js +1 -1
- package/dist-esm/index.mjs +1 -1
- package/package.json +3 -3
package/collaboration.css
CHANGED
|
@@ -2,87 +2,227 @@
|
|
|
2
2
|
/* This file is created by the copy-css-files.mjs script in packages/collaboration. */
|
|
3
3
|
/* It combines the stylesheets of the collaboration umbrella's sub-packages. */
|
|
4
4
|
|
|
5
|
+
/* avatar — a person's image, or a single-initial coloured circle. Shared by the mention picker and,
|
|
6
|
+
via @tldraw/commenting, the comment UI. */
|
|
7
|
+
.tlui-cmt-avatar {
|
|
8
|
+
width: 28px;
|
|
9
|
+
height: 28px;
|
|
10
|
+
border-radius: 50%;
|
|
11
|
+
background: var(--tl-color-selected);
|
|
12
|
+
color: #fff;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
font-size: 11px;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.tlui-cmt-avatar--image {
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
background: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* mention pill */
|
|
27
|
+
.tlui-cmt-mention {
|
|
28
|
+
color: var(--tl-color-selected);
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
background: color-mix(in srgb, var(--tl-color-selected) 12%, transparent);
|
|
31
|
+
padding: 0 4px;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* mention picker */
|
|
36
|
+
.tlui-cmt-mention-list {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 4px;
|
|
40
|
+
padding: 4px;
|
|
41
|
+
width: 100%;
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
max-height: 260px;
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
color: var(--tl-color-text-1, #1d1d1d);
|
|
46
|
+
background: var(--tl-color-panel, #fff);
|
|
47
|
+
border-radius: 10px;
|
|
48
|
+
/* shadow-3 (like the context menu) already carries a 1px inset edge — a separate `border`
|
|
49
|
+
on top of it double-outlines the popup. */
|
|
50
|
+
box-shadow: var(--tl-shadow-3, 0 2px 12px rgba(0, 0, 0, 0.14));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tlui-cmt-mention-list__item {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 8px;
|
|
57
|
+
padding: 6px 8px;
|
|
58
|
+
border: none;
|
|
59
|
+
background: transparent;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
text-align: left;
|
|
63
|
+
color: var(--tl-color-text-1);
|
|
64
|
+
font: inherit;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tlui-cmt-mention-list__item--active,
|
|
68
|
+
.tlui-cmt-mention-list__item:hover {
|
|
69
|
+
background: var(--tl-color-muted-2, rgba(0, 0, 0, 0.06));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tlui-cmt-mention-list__text {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
min-width: 0;
|
|
76
|
+
line-height: 1.3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.tlui-cmt-mention-list__name {
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
text-overflow: ellipsis;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.tlui-cmt-mention-list__you {
|
|
88
|
+
margin-left: 4px;
|
|
89
|
+
font-weight: 400;
|
|
90
|
+
color: var(--tl-color-text-3);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.tlui-cmt-mention-list__secondary {
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
color: var(--tl-color-text-3);
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
text-overflow: ellipsis;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.tlui-cmt-mention-list--empty {
|
|
102
|
+
padding: 8px 10px;
|
|
103
|
+
width: 100%;
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
color: var(--tl-color-text-3);
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* the @-picker popup — anchored under the editor field, positioned by the suggestion plugin */
|
|
110
|
+
.tlui-cmt-mention-popup {
|
|
111
|
+
position: fixed;
|
|
112
|
+
z-index: var(--tl-layer-menus);
|
|
113
|
+
}
|
|
114
|
+
|
|
5
115
|
/* Real comment UI components. Co-located so styles travel to any view (studio,
|
|
6
116
|
canvas), and themed via tldraw's --tl-color tokens so they follow light/dark. */
|
|
7
|
-
.cmt-card {
|
|
117
|
+
.tlui-cmt-card {
|
|
8
118
|
position: relative;
|
|
9
119
|
display: flex;
|
|
10
|
-
|
|
11
|
-
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
gap: 8px;
|
|
122
|
+
padding: 8px;
|
|
12
123
|
width: 260px;
|
|
13
124
|
background: var(--tl-color-panel);
|
|
14
125
|
border: 1px solid var(--tl-color-divider);
|
|
15
|
-
border-radius:
|
|
126
|
+
border-radius: var(--tl-radius-3);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* The avatar lives in the byline row (see Byline), so the body text below indents to line up with
|
|
130
|
+
the name — the avatar's width plus the head's gap. */
|
|
131
|
+
.tlui-cmt-body {
|
|
132
|
+
padding-left: calc(28px + 6px);
|
|
16
133
|
}
|
|
17
134
|
|
|
18
|
-
/* hover-revealed controls (edit…) at the card's top-right */
|
|
19
|
-
.cmt-card__actions {
|
|
135
|
+
/* hover-revealed controls (edit, react…) stacked at the card's top-right */
|
|
136
|
+
.tlui-cmt-card__actions {
|
|
20
137
|
position: absolute;
|
|
21
138
|
top: 6px;
|
|
22
139
|
right: 6px;
|
|
23
140
|
display: flex;
|
|
24
|
-
gap:
|
|
141
|
+
gap: 0;
|
|
25
142
|
opacity: 0;
|
|
26
143
|
}
|
|
27
|
-
.cmt-card:hover .cmt-card__actions,
|
|
28
|
-
.cmt-card:focus-within .cmt-card__actions
|
|
144
|
+
.tlui-cmt-card:hover .tlui-cmt-card__actions,
|
|
145
|
+
.tlui-cmt-card:focus-within .tlui-cmt-card__actions,
|
|
146
|
+
/* an open menu is portaled out of the card, so neither :hover nor :focus-within survives moving
|
|
147
|
+
the pointer into it — keep the controls up while their menu is open */
|
|
148
|
+
.tlui-cmt-card__actions:has([data-state='open']) {
|
|
29
149
|
opacity: 1;
|
|
30
150
|
}
|
|
31
151
|
|
|
32
|
-
.cmt-body {
|
|
152
|
+
.tlui-cmt-body {
|
|
33
153
|
flex: 1;
|
|
34
154
|
min-width: 0;
|
|
35
155
|
}
|
|
36
156
|
|
|
37
|
-
.cmt-head {
|
|
157
|
+
.tlui-cmt-head {
|
|
38
158
|
display: flex;
|
|
39
|
-
align-items:
|
|
159
|
+
align-items: center;
|
|
40
160
|
gap: 6px;
|
|
41
161
|
}
|
|
42
162
|
|
|
43
|
-
.cmt-author {
|
|
44
|
-
font-weight:
|
|
45
|
-
font-size:
|
|
163
|
+
.tlui-cmt-author {
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
font-size: 12px;
|
|
46
166
|
color: var(--tl-color-text-1);
|
|
167
|
+
/* a long name ellipsizes rather than pushing the time out or running under the actions */
|
|
168
|
+
min-width: 0;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
text-overflow: ellipsis;
|
|
171
|
+
white-space: nowrap;
|
|
47
172
|
}
|
|
48
173
|
|
|
49
|
-
.cmt-time {
|
|
50
|
-
font-size:
|
|
174
|
+
.tlui-cmt-time {
|
|
175
|
+
font-size: 12px;
|
|
51
176
|
color: var(--tl-color-text-3);
|
|
177
|
+
flex-shrink: 0;
|
|
178
|
+
white-space: nowrap;
|
|
52
179
|
}
|
|
53
180
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
181
|
+
/* In the hover preview a long comment truncates with an ellipsis rather than growing the card —
|
|
182
|
+
~7 lines (≈120px at this line height). Preview only. */
|
|
183
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-text {
|
|
184
|
+
display: -webkit-box;
|
|
185
|
+
-webkit-box-orient: vertical;
|
|
186
|
+
-webkit-line-clamp: 7;
|
|
187
|
+
line-clamp: 7;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.tlui-cmt-text {
|
|
192
|
+
margin: 0 0 6px;
|
|
193
|
+
font-size: 12px;
|
|
57
194
|
line-height: 1.4;
|
|
58
195
|
color: var(--tl-color-text-1);
|
|
196
|
+
/* Break long unbroken strings (URLs, keyboard-mashing) so they wrap inside the card instead of
|
|
197
|
+
overflowing it. `anywhere` also lets the flex body shrink to the card width. */
|
|
198
|
+
overflow-wrap: anywhere;
|
|
59
199
|
}
|
|
60
200
|
|
|
61
|
-
.cmt-edited {
|
|
201
|
+
.tlui-cmt-edited {
|
|
62
202
|
font-style: italic;
|
|
63
203
|
}
|
|
64
204
|
|
|
65
205
|
/* rendered rich text inside a comment body — bare tags emitted by the comment renderer
|
|
66
206
|
(paragraphs, lists, code, highlight, links), matching the limited comment extension set. */
|
|
67
|
-
.cmt-text p {
|
|
207
|
+
.tlui-cmt-text p {
|
|
68
208
|
margin: 0;
|
|
69
209
|
}
|
|
70
210
|
|
|
71
|
-
.cmt-text p + p,
|
|
72
|
-
.cmt-text p + ul,
|
|
73
|
-
.cmt-text p + ol,
|
|
74
|
-
.cmt-text ul + p,
|
|
75
|
-
.cmt-text ol + p {
|
|
211
|
+
.tlui-cmt-text p + p,
|
|
212
|
+
.tlui-cmt-text p + ul,
|
|
213
|
+
.tlui-cmt-text p + ol,
|
|
214
|
+
.tlui-cmt-text ul + p,
|
|
215
|
+
.tlui-cmt-text ol + p {
|
|
76
216
|
margin-top: 6px;
|
|
77
217
|
}
|
|
78
218
|
|
|
79
|
-
.cmt-text ul,
|
|
80
|
-
.cmt-text ol {
|
|
219
|
+
.tlui-cmt-text ul,
|
|
220
|
+
.tlui-cmt-text ol {
|
|
81
221
|
margin: 0;
|
|
82
222
|
padding-left: 18px;
|
|
83
223
|
}
|
|
84
224
|
|
|
85
|
-
.cmt-text code {
|
|
225
|
+
.tlui-cmt-text code {
|
|
86
226
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
87
227
|
font-size: 0.92em;
|
|
88
228
|
background: var(--tl-color-muted-2);
|
|
@@ -90,212 +230,272 @@
|
|
|
90
230
|
padding: 0 3px;
|
|
91
231
|
}
|
|
92
232
|
|
|
93
|
-
.cmt-text mark {
|
|
233
|
+
.tlui-cmt-text mark {
|
|
94
234
|
background: color-mix(in srgb, #ffeb3b 60%, transparent);
|
|
95
235
|
color: inherit;
|
|
96
236
|
border-radius: 2px;
|
|
97
237
|
}
|
|
98
238
|
|
|
99
|
-
.cmt-text a {
|
|
239
|
+
.tlui-cmt-text a {
|
|
100
240
|
color: var(--tl-color-selected);
|
|
101
241
|
text-decoration: underline;
|
|
102
242
|
}
|
|
103
243
|
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
height: 28px;
|
|
107
|
-
border-radius: 50%;
|
|
108
|
-
background: var(--tl-color-selected);
|
|
109
|
-
color: #fff;
|
|
110
|
-
display: flex;
|
|
111
|
-
align-items: center;
|
|
112
|
-
justify-content: center;
|
|
113
|
-
font-size: 11px;
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
flex-shrink: 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.cmt-avatar--image {
|
|
119
|
-
object-fit: cover;
|
|
120
|
-
background: none;
|
|
121
|
-
}
|
|
244
|
+
/* Avatar and mention styles live in @tldraw/mentions (src/mention.css) and are bundled into
|
|
245
|
+
commenting.css by copy-css-files.mjs. */
|
|
122
246
|
|
|
123
247
|
/* composer — an avatar beside a rounded-rect field holding the rich-text input and the send button.
|
|
124
248
|
Formatting is applied via markdown and keyboard shortcuts (no toolbar). The avatar aligns to the
|
|
125
249
|
top so it stays put as the field grows to multiple lines. */
|
|
126
|
-
.cmt-composer {
|
|
250
|
+
.tlui-cmt-composer {
|
|
127
251
|
display: flex;
|
|
128
252
|
align-items: flex-start;
|
|
129
253
|
gap: 10px;
|
|
130
254
|
padding: 4px 0 8px 4px;
|
|
131
255
|
}
|
|
132
256
|
|
|
133
|
-
.cmt-composer__field {
|
|
257
|
+
.tlui-cmt-composer__field {
|
|
134
258
|
flex: 1;
|
|
135
259
|
min-width: 0;
|
|
136
260
|
display: flex;
|
|
137
261
|
align-items: center;
|
|
138
262
|
gap: 6px;
|
|
139
|
-
padding: 3px 3px 3px
|
|
140
|
-
background: var(--tl-color-muted-
|
|
263
|
+
padding: 3px 3px 3px 8px;
|
|
264
|
+
background: var(--tl-color-muted-0);
|
|
141
265
|
border: 1px solid var(--tl-color-divider);
|
|
142
|
-
border-radius: var(--tl-radius-
|
|
266
|
+
border-radius: var(--tl-radius-2);
|
|
267
|
+
/* The whole field reads as the text input — clicking its empty area focuses the editor
|
|
268
|
+
(see CommentComposer). The send button restores its own pointer cursor. */
|
|
269
|
+
cursor: text;
|
|
143
270
|
}
|
|
144
271
|
|
|
145
|
-
.cmt-composer__field:focus-within {
|
|
272
|
+
.tlui-cmt-composer__field:focus-within {
|
|
146
273
|
border-color: var(--tl-color-selected);
|
|
274
|
+
background: var(--tl-color-panel);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Expanded: the content no longer fits on one line beside the send button, so the input takes
|
|
278
|
+
the full width and the send button drops to its own row, right-aligned. */
|
|
279
|
+
.tlui-cmt-composer__field--expanded {
|
|
280
|
+
flex-wrap: wrap;
|
|
281
|
+
/* No vertical gap between the input and the send-button row it wraps onto. */
|
|
282
|
+
row-gap: 0;
|
|
283
|
+
}
|
|
284
|
+
.tlui-cmt-composer__field--expanded .tlui-cmt-composer__input-wrap {
|
|
285
|
+
flex-basis: 100%;
|
|
286
|
+
/* The field pads 8px left but only 3px right (the send button's inset). With the input
|
|
287
|
+
spanning the full width, give the text the same 8px on the right. */
|
|
288
|
+
padding-right: 5px;
|
|
289
|
+
}
|
|
290
|
+
.tlui-cmt-composer__field--expanded .tlui-cmt-send {
|
|
291
|
+
margin-left: auto;
|
|
147
292
|
}
|
|
148
293
|
|
|
149
294
|
/* the editable area, with the placeholder overlaid while it's empty */
|
|
150
|
-
.cmt-composer__input-wrap {
|
|
295
|
+
.tlui-cmt-composer__input-wrap {
|
|
151
296
|
position: relative;
|
|
152
297
|
flex: 1;
|
|
153
298
|
min-width: 0;
|
|
154
|
-
|
|
299
|
+
/* Frames the input's line height symmetrically so the text sits centred in the field. Paired
|
|
300
|
+
with the input's min-height, which equals its line height (no gap for the text to top-align
|
|
301
|
+
into). Keeps the field's overall height. */
|
|
302
|
+
padding: 4.5px 0;
|
|
155
303
|
}
|
|
156
304
|
|
|
157
|
-
|
|
305
|
+
/* The hidden single-line measuring clone of the editor's content (see CommentComposer). */
|
|
306
|
+
.tlui-cmt-composer__mirror {
|
|
307
|
+
position: absolute;
|
|
308
|
+
top: 0;
|
|
309
|
+
left: 0;
|
|
310
|
+
visibility: hidden;
|
|
311
|
+
pointer-events: none;
|
|
312
|
+
white-space: nowrap;
|
|
313
|
+
width: max-content;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.tlui-cmt-input {
|
|
158
317
|
font: inherit;
|
|
159
|
-
font-size:
|
|
318
|
+
font-size: 12px;
|
|
160
319
|
line-height: 1.4;
|
|
161
320
|
color: var(--tl-color-text-1);
|
|
162
321
|
outline: none;
|
|
163
|
-
min-height:
|
|
164
|
-
max-height: 40vh;
|
|
165
|
-
overflow-y: auto;
|
|
322
|
+
min-height: 17px;
|
|
166
323
|
white-space: pre-wrap;
|
|
167
324
|
word-break: break-word;
|
|
168
325
|
}
|
|
169
326
|
|
|
170
|
-
.cmt-input p {
|
|
327
|
+
.tlui-cmt-input p {
|
|
171
328
|
margin: 0;
|
|
172
329
|
}
|
|
173
330
|
|
|
174
|
-
.cmt-input p + p,
|
|
175
|
-
.cmt-input p + ul,
|
|
176
|
-
.cmt-input p + ol {
|
|
331
|
+
.tlui-cmt-input p + p,
|
|
332
|
+
.tlui-cmt-input p + ul,
|
|
333
|
+
.tlui-cmt-input p + ol {
|
|
177
334
|
margin-top: 4px;
|
|
178
335
|
}
|
|
179
336
|
|
|
180
|
-
.cmt-input ul,
|
|
181
|
-
.cmt-input ol {
|
|
337
|
+
.tlui-cmt-input ul,
|
|
338
|
+
.tlui-cmt-input ol {
|
|
182
339
|
margin: 0;
|
|
183
340
|
padding-left: 18px;
|
|
184
341
|
}
|
|
185
342
|
|
|
186
|
-
.cmt-input__placeholder {
|
|
343
|
+
.tlui-cmt-input__placeholder {
|
|
187
344
|
position: absolute;
|
|
188
|
-
top:
|
|
345
|
+
top: 4.5px;
|
|
189
346
|
left: 0;
|
|
190
|
-
font-size:
|
|
347
|
+
font-size: 12px;
|
|
191
348
|
line-height: 1.4;
|
|
192
349
|
color: var(--tl-color-text-3);
|
|
193
350
|
pointer-events: none;
|
|
194
351
|
}
|
|
195
352
|
|
|
196
|
-
|
|
353
|
+
/* iOS Safari zooms the whole page in when a text input with a sub-16px font takes focus, and doesn't
|
|
354
|
+
zoom back out. Raising the font to 16px on touch devices suppresses that without disabling
|
|
355
|
+
pinch-zoom of the canvas (which a `maximum-scale` viewport lock would). Must come after both base
|
|
356
|
+
rules above: a media query adds no specificity, so it only wins the placeholder on source order. */
|
|
357
|
+
@media (pointer: coarse) {
|
|
358
|
+
.tlui-cmt-input,
|
|
359
|
+
.tlui-cmt-input__placeholder {
|
|
360
|
+
font-size: 16px;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* A bare up-arrow icon button (matching the placement composer): no fill by default, a grey square
|
|
365
|
+
on hover, grey while there's nothing to send and dark once there is. */
|
|
366
|
+
.tlui-cmt-send {
|
|
367
|
+
flex-shrink: 0;
|
|
368
|
+
display: inline-flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
justify-content: center;
|
|
371
|
+
width: 28px;
|
|
372
|
+
height: 28px;
|
|
197
373
|
border: 0;
|
|
198
374
|
border-radius: var(--tl-radius-2);
|
|
199
|
-
background:
|
|
200
|
-
color:
|
|
201
|
-
font: inherit;
|
|
202
|
-
font-size: 12px;
|
|
203
|
-
font-weight: 600;
|
|
204
|
-
padding: 6px 12px;
|
|
375
|
+
background: transparent;
|
|
376
|
+
color: var(--tl-color-text-3);
|
|
205
377
|
cursor: pointer;
|
|
206
|
-
align-self:
|
|
378
|
+
align-self: center;
|
|
207
379
|
}
|
|
208
380
|
|
|
209
|
-
.cmt-send:
|
|
210
|
-
|
|
211
|
-
background: color-mix(in srgb, var(--tl-color-selected) 88%, black);
|
|
381
|
+
.tlui-cmt-send:not(:disabled) {
|
|
382
|
+
color: var(--tl-color-text-1);
|
|
212
383
|
}
|
|
213
384
|
|
|
214
|
-
.cmt-send:
|
|
215
|
-
|
|
216
|
-
background: color-mix(in srgb, var(--tl-color-selected) 78%, black);
|
|
385
|
+
.tlui-cmt-send:hover:not(:disabled) {
|
|
386
|
+
background: var(--tl-color-muted-2);
|
|
217
387
|
}
|
|
218
388
|
|
|
219
|
-
.cmt-send:
|
|
220
|
-
.pseudo-
|
|
389
|
+
.tlui-cmt-send:active:not(:disabled),
|
|
390
|
+
.pseudo-active .tlui-cmt-send:not(:disabled) {
|
|
391
|
+
background: var(--tl-color-muted-1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.tlui-cmt-send:focus-visible,
|
|
395
|
+
.pseudo-focus-visible .tlui-cmt-send {
|
|
221
396
|
outline: 2px solid var(--tl-color-selected);
|
|
222
397
|
outline-offset: 2px;
|
|
223
398
|
}
|
|
224
399
|
|
|
225
|
-
.cmt-send:disabled {
|
|
226
|
-
|
|
400
|
+
.tlui-cmt-send:disabled {
|
|
401
|
+
background: transparent;
|
|
402
|
+
color: var(--tl-color-text-3);
|
|
227
403
|
cursor: not-allowed;
|
|
228
404
|
}
|
|
229
405
|
|
|
230
|
-
/* pin
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
406
|
+
/* The shared marker treatment. To the pointer a comment pin and a count badge are the same
|
|
407
|
+
object — a token sitting on the canvas — so they rest and lift identically; everything shared
|
|
408
|
+
lives here so the two can't drift apart. */
|
|
409
|
+
.tlui-cmt-marker {
|
|
410
|
+
box-shadow: var(--tlui-cmt-marker-shadow);
|
|
411
|
+
cursor: pointer;
|
|
412
|
+
transition:
|
|
413
|
+
transform var(--tlui-cmt-marker-transition),
|
|
414
|
+
box-shadow var(--tlui-cmt-marker-transition);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.tlui-cmt-marker:hover {
|
|
418
|
+
transform: scale(var(--tlui-cmt-marker-hover-scale));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* The hover shadow is for closed markers only — an open one keeps its selection ring, which this
|
|
422
|
+
box-shadow would otherwise override (the hover selector outranks the --open rule). */
|
|
423
|
+
.tlui-cmt-marker:not(.tlui-cmt-marker--open):hover {
|
|
424
|
+
box-shadow: var(--tlui-cmt-marker-shadow-hover);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Count badges don't react to hover — no scale, no shadow lift. Only pins do. */
|
|
428
|
+
.tlui-cmt-count-badge:hover {
|
|
429
|
+
transform: none;
|
|
430
|
+
box-shadow: var(--tlui-cmt-marker-shadow);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* Pins don't scale on hover — only the shadow lifts. */
|
|
434
|
+
.tlui-cmt-pin:hover {
|
|
435
|
+
transform: none;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* open: the thread (or a stack's list) is showing — the active marker just keeps its lift shadow,
|
|
439
|
+
no selection ring. */
|
|
440
|
+
.tlui-cmt-marker--open {
|
|
441
|
+
box-shadow: var(--tlui-cmt-marker-shadow);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* pin — a canvas marker: a white teardrop holding the author's avatar circle, so a thread is
|
|
445
|
+
identifiable at a glance. */
|
|
446
|
+
.tlui-cmt-pin {
|
|
447
|
+
--tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
|
|
448
|
+
width: var(--tlui-cmt-marker-size);
|
|
449
|
+
height: var(--tlui-cmt-marker-size);
|
|
450
|
+
border-radius: 50% 50% 50% 0;
|
|
451
|
+
background: var(--tl-color-panel);
|
|
236
452
|
color: #fff;
|
|
237
453
|
display: flex;
|
|
238
454
|
align-items: center;
|
|
239
455
|
justify-content: center;
|
|
240
456
|
font-size: 14px;
|
|
241
457
|
font-weight: 600;
|
|
242
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
243
|
-
cursor: pointer;
|
|
244
|
-
transition:
|
|
245
|
-
transform 0.08s ease,
|
|
246
|
-
box-shadow 0.08s ease;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.cmt-pin:hover,
|
|
250
|
-
.pseudo-hover .cmt-pin {
|
|
251
|
-
transform: scale(1.08);
|
|
252
|
-
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.32);
|
|
253
458
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
0 0 0 3px var(--tl-color-panel),
|
|
259
|
-
0 0 0 5px var(--tl-color-selected),
|
|
260
|
-
0 2px 6px rgba(0, 0, 0, 0.25);
|
|
459
|
+
/* The pin holds the author's 22px avatar circle, centred in its 28px face. */
|
|
460
|
+
.tlui-cmt-pin .tlui-cmt-avatar {
|
|
461
|
+
width: 22px;
|
|
462
|
+
height: 22px;
|
|
261
463
|
}
|
|
262
464
|
|
|
263
465
|
/* resolved: a solid, muted grey (the default --tl-color-muted-1 is 10% black, so it goes
|
|
264
466
|
translucent over the canvas). Dark mode gets its own grey — the token's dark value is near-black. */
|
|
265
|
-
.cmt-pin--resolved {
|
|
467
|
+
.tlui-cmt-pin--resolved {
|
|
266
468
|
background: #dfe0e1;
|
|
267
469
|
color: hsl(214, 8%, 42%);
|
|
268
470
|
}
|
|
269
|
-
.tl-theme__dark .cmt-pin--resolved {
|
|
471
|
+
.tl-theme__dark .tlui-cmt-pin--resolved {
|
|
270
472
|
background: hsl(240, 5%, 30%);
|
|
271
473
|
color: hsl(0, 0%, 72%);
|
|
272
474
|
}
|
|
273
475
|
|
|
274
|
-
/* count badge */
|
|
275
|
-
.cmt-count-badge {
|
|
276
|
-
width:
|
|
277
|
-
height:
|
|
476
|
+
/* count badge — the same marker as a pin. */
|
|
477
|
+
.tlui-cmt-count-badge {
|
|
478
|
+
width: var(--tlui-cmt-marker-size);
|
|
479
|
+
height: var(--tlui-cmt-marker-size);
|
|
278
480
|
border-radius: 50%;
|
|
279
481
|
display: flex;
|
|
280
482
|
align-items: center;
|
|
281
483
|
justify-content: center;
|
|
282
484
|
background: var(--tl-color-text-1);
|
|
283
485
|
color: var(--tl-color-panel);
|
|
284
|
-
font-size:
|
|
486
|
+
font-size: 12px;
|
|
285
487
|
font-weight: 700;
|
|
286
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
287
488
|
user-select: none;
|
|
288
489
|
}
|
|
289
490
|
|
|
290
491
|
/* thread */
|
|
291
|
-
.cmt-thread {
|
|
492
|
+
.tlui-cmt-thread {
|
|
292
493
|
display: flex;
|
|
293
494
|
flex-direction: column;
|
|
294
|
-
gap: 8px;
|
|
295
495
|
width: 300px;
|
|
296
496
|
}
|
|
297
497
|
|
|
298
|
-
.cmt-thread__head {
|
|
498
|
+
.tlui-cmt-thread__head {
|
|
299
499
|
display: flex;
|
|
300
500
|
align-items: center;
|
|
301
501
|
gap: 8px;
|
|
@@ -306,7 +506,7 @@
|
|
|
306
506
|
letter-spacing: 0.04em;
|
|
307
507
|
}
|
|
308
508
|
|
|
309
|
-
.cmt-badge {
|
|
509
|
+
.tlui-cmt-badge {
|
|
310
510
|
font-size: 10px;
|
|
311
511
|
font-weight: 600;
|
|
312
512
|
text-transform: none;
|
|
@@ -318,134 +518,111 @@
|
|
|
318
518
|
}
|
|
319
519
|
|
|
320
520
|
/* reactions */
|
|
321
|
-
.cmt-reactions {
|
|
521
|
+
.tlui-cmt-reactions {
|
|
322
522
|
display: flex;
|
|
323
|
-
|
|
523
|
+
flex-wrap: wrap;
|
|
524
|
+
gap: 4px;
|
|
525
|
+
margin-top: 6px;
|
|
324
526
|
}
|
|
325
527
|
|
|
326
|
-
.cmt-reaction {
|
|
528
|
+
.tlui-cmt-reaction {
|
|
327
529
|
display: inline-flex;
|
|
328
530
|
align-items: center;
|
|
329
|
-
gap:
|
|
330
|
-
padding:
|
|
331
|
-
border
|
|
332
|
-
border:
|
|
333
|
-
background: var(--tl-color-
|
|
531
|
+
gap: 4px;
|
|
532
|
+
padding: 2px 6px;
|
|
533
|
+
border: none;
|
|
534
|
+
border-radius: var(--tl-radius-1);
|
|
535
|
+
background: var(--tl-color-muted-2);
|
|
334
536
|
font: inherit;
|
|
335
|
-
font-size:
|
|
537
|
+
font-size: 10px;
|
|
538
|
+
font-weight: 400;
|
|
336
539
|
color: var(--tl-color-text-1);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* An inert pill renders as a span (e.g. in a notification row); only the button variant is
|
|
543
|
+
clickable, so only it gets the pointer cursor and hover shade. */
|
|
544
|
+
button.tlui-cmt-reaction {
|
|
337
545
|
cursor: pointer;
|
|
338
546
|
}
|
|
339
547
|
|
|
340
|
-
.
|
|
341
|
-
|
|
342
|
-
|
|
548
|
+
/* Hover deepens the grey — no border to tint now. Mixing toward the text colour is theme-aware:
|
|
549
|
+
it darkens the fill in light mode and lightens it in dark, tracking the pill's own contrast. */
|
|
550
|
+
button.tlui-cmt-reaction:hover {
|
|
551
|
+
background: var(--tl-color-muted-1);
|
|
343
552
|
}
|
|
344
553
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
554
|
+
/* a reaction you're part of fills with the selection colour, so "I reacted with this" stands out
|
|
555
|
+
from the grey pills of reactions you haven't joined. */
|
|
556
|
+
.tlui-cmt-reaction--active {
|
|
557
|
+
background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
|
|
348
558
|
}
|
|
349
559
|
|
|
350
|
-
.cmt-
|
|
351
|
-
|
|
560
|
+
button.tlui-cmt-reaction--active:hover {
|
|
561
|
+
background: color-mix(in srgb, var(--tl-color-selected) 38%, var(--tl-color-panel));
|
|
352
562
|
}
|
|
353
563
|
|
|
354
|
-
|
|
564
|
+
/* The emoji renders larger than the 10px count, but its pinned line-height keeps the pill the
|
|
565
|
+
count's height — the glyph overflows the line box rather than growing it. */
|
|
566
|
+
.tlui-cmt-reaction__emoji {
|
|
567
|
+
font-size: 13px;
|
|
568
|
+
line-height: 10px;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.tlui-cmt-reaction__count {
|
|
355
572
|
color: var(--tl-color-text-3);
|
|
356
|
-
font-
|
|
573
|
+
font-variant-numeric: tabular-nums;
|
|
357
574
|
}
|
|
358
575
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
color: var(--tl-color-selected);
|
|
362
|
-
font-weight: 600;
|
|
363
|
-
background: color-mix(in srgb, var(--tl-color-selected) 12%, transparent);
|
|
364
|
-
padding: 0 4px;
|
|
365
|
-
border-radius: 4px;
|
|
576
|
+
.tlui-cmt-reaction--active .tlui-cmt-reaction__count {
|
|
577
|
+
color: var(--tl-color-text-1);
|
|
366
578
|
}
|
|
367
579
|
|
|
368
|
-
/*
|
|
369
|
-
.cmt-
|
|
370
|
-
display:
|
|
371
|
-
|
|
372
|
-
gap:
|
|
580
|
+
/* emoji picker */
|
|
581
|
+
.tlui-cmt-emoji-picker {
|
|
582
|
+
display: grid;
|
|
583
|
+
grid-template-columns: repeat(5, 40px);
|
|
584
|
+
gap: 0;
|
|
373
585
|
padding: 4px;
|
|
374
|
-
width: 100%;
|
|
375
|
-
box-sizing: border-box;
|
|
376
|
-
max-height: 260px;
|
|
377
|
-
overflow-y: auto;
|
|
378
|
-
color: var(--tl-color-text-1, #1d1d1d);
|
|
379
|
-
background: var(--tl-color-panel, #fff);
|
|
380
|
-
border: 1px solid var(--tl-color-divider, rgba(0, 0, 0, 0.08));
|
|
381
|
-
border-radius: 10px;
|
|
382
|
-
box-shadow: var(--tl-shadow-2, 0 2px 12px rgba(0, 0, 0, 0.14));
|
|
383
586
|
}
|
|
384
587
|
|
|
385
|
-
.cmt-
|
|
588
|
+
.tlui-cmt-emoji-picker__item {
|
|
589
|
+
position: relative;
|
|
386
590
|
display: flex;
|
|
387
591
|
align-items: center;
|
|
388
|
-
|
|
389
|
-
|
|
592
|
+
justify-content: center;
|
|
593
|
+
width: 40px;
|
|
594
|
+
height: 40px;
|
|
595
|
+
padding: 0;
|
|
390
596
|
border: none;
|
|
391
|
-
background:
|
|
392
|
-
border-radius: 6px;
|
|
393
|
-
cursor: pointer;
|
|
394
|
-
text-align: left;
|
|
395
|
-
color: var(--tl-color-text-1);
|
|
597
|
+
background: none;
|
|
396
598
|
font: inherit;
|
|
599
|
+
font-size: 21px;
|
|
600
|
+
line-height: 1;
|
|
601
|
+
cursor: pointer;
|
|
397
602
|
}
|
|
398
603
|
|
|
399
|
-
|
|
400
|
-
.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
min-width: 0;
|
|
408
|
-
line-height: 1.3;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.cmt-mention-list__name {
|
|
412
|
-
font-size: 13px;
|
|
413
|
-
font-weight: 500;
|
|
414
|
-
white-space: nowrap;
|
|
415
|
-
overflow: hidden;
|
|
416
|
-
text-overflow: ellipsis;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.cmt-mention-list__you {
|
|
420
|
-
margin-left: 4px;
|
|
421
|
-
font-weight: 400;
|
|
422
|
-
color: var(--tl-color-text-3);
|
|
604
|
+
/* Inset wash behind the glyph — the 40px hit cells tile edge-to-edge, the squares sit 4px apart,
|
|
605
|
+
like the thread action buttons. Sits under the emoji so an opaque selected fill can't hide it. */
|
|
606
|
+
.tlui-cmt-emoji-picker__item::after {
|
|
607
|
+
content: '';
|
|
608
|
+
position: absolute;
|
|
609
|
+
inset: 2px;
|
|
610
|
+
z-index: -1;
|
|
611
|
+
border-radius: var(--tl-radius-2);
|
|
423
612
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
font-size: 12px;
|
|
427
|
-
color: var(--tl-color-text-3);
|
|
428
|
-
white-space: nowrap;
|
|
429
|
-
overflow: hidden;
|
|
430
|
-
text-overflow: ellipsis;
|
|
613
|
+
.tlui-cmt-emoji-picker__item:hover {
|
|
614
|
+
z-index: 1;
|
|
431
615
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
padding: 8px 10px;
|
|
435
|
-
width: 100%;
|
|
436
|
-
box-sizing: border-box;
|
|
437
|
-
color: var(--tl-color-text-3);
|
|
438
|
-
font-size: 13px;
|
|
616
|
+
.tlui-cmt-emoji-picker__item:hover::after {
|
|
617
|
+
background: var(--tl-color-muted-2);
|
|
439
618
|
}
|
|
440
619
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
position: fixed;
|
|
444
|
-
z-index: var(--tl-layer-menus);
|
|
620
|
+
.tlui-cmt-emoji-picker__item--active::after {
|
|
621
|
+
background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
|
|
445
622
|
}
|
|
446
623
|
|
|
447
624
|
/* empty state */
|
|
448
|
-
.cmt-empty {
|
|
625
|
+
.tlui-cmt-empty {
|
|
449
626
|
display: flex;
|
|
450
627
|
flex-direction: column;
|
|
451
628
|
align-items: center;
|
|
@@ -455,92 +632,260 @@
|
|
|
455
632
|
width: 240px;
|
|
456
633
|
}
|
|
457
634
|
|
|
458
|
-
.cmt-empty__icon {
|
|
635
|
+
.tlui-cmt-empty__icon {
|
|
459
636
|
font-size: 28px;
|
|
460
637
|
}
|
|
461
638
|
|
|
462
|
-
.cmt-empty__message {
|
|
639
|
+
.tlui-cmt-empty__message {
|
|
463
640
|
margin: 0;
|
|
464
641
|
color: var(--tl-color-text-3);
|
|
465
|
-
font-size:
|
|
642
|
+
font-size: 12px;
|
|
466
643
|
line-height: 1.4;
|
|
467
644
|
}
|
|
468
645
|
|
|
469
|
-
/* a comment thread: an elevated panel
|
|
470
|
-
|
|
471
|
-
|
|
646
|
+
/* a comment thread: an elevated panel holding a header, an optional resolved banner, the
|
|
647
|
+
comments, and a reply composer */
|
|
648
|
+
/* The thread panel's layout metrics live on the container because two separate portals need them:
|
|
649
|
+
the panel itself, and the canvas hover preview, which has to land its first comment on exactly
|
|
650
|
+
the pixel the panel would. Deriving the preview's offset from these means the two can't drift —
|
|
651
|
+
retune the padding or the header here and the preview follows. */
|
|
652
|
+
.tl-container {
|
|
653
|
+
/* Everything that has to line up against a pin — its preview's bridge, the draft composer's
|
|
654
|
+
offsets, `PIN_SIZE` in thread-view.tsx — derives from this, so the pin's size stays one
|
|
655
|
+
number. */
|
|
656
|
+
--tlui-cmt-pin-size: 28px;
|
|
657
|
+
/* A cluster badge and a stack badge are this square — the same size as a pin, so the marker
|
|
658
|
+
kinds read as one family. The hover preview measures its bridge against it, so the bridge
|
|
659
|
+
stops at the marker's edge instead of covering it. Mirrored by `MARKER_SIZE` in
|
|
660
|
+
thread-view.tsx. */
|
|
661
|
+
--tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
|
|
662
|
+
/* How a marker sits, lifts, and rings. Shared so a pin and a badge respond identically.
|
|
663
|
+
shadow-3's depth without its `inset 1px panel-contrast` edge — that inset reads as a white
|
|
664
|
+
border on a coloured pin (it's invisible on the panel-white menus shadow-3 is meant for). */
|
|
665
|
+
--tlui-cmt-marker-shadow: 0px 1px 2px hsl(0, 0%, 0%, 28%), 0px 2px 6px hsl(0, 0%, 0%, 14%);
|
|
666
|
+
--tlui-cmt-marker-shadow-hover: var(--tlui-cmt-marker-shadow);
|
|
667
|
+
--tlui-cmt-marker-hover-scale: 1.08;
|
|
668
|
+
--tlui-cmt-marker-transition: 0.08s ease;
|
|
669
|
+
--tlui-cmt-thread-padding: 12px;
|
|
670
|
+
--tlui-cmt-thread-gap: 0px;
|
|
671
|
+
/* Hit-area width of one .tlui-cmt-thread__action button (drawn as a 32px square inset 4px). */
|
|
672
|
+
--tlui-cmt-thread-action-size: 40px;
|
|
673
|
+
/* A floating pill's edge — its hairline ring is the only thing separating it from the panel it
|
|
674
|
+
overlaps, so the ring is a theme token rather than a literal black: a near-black hairline
|
|
675
|
+
vanishes on a dark panel, where the divider token lightens instead. The drop below it stays
|
|
676
|
+
literal; a soft dark blur reads on both themes. */
|
|
677
|
+
--tlui-cmt-pill-shadow: 0 0 0 1px var(--tl-color-divider), 0 1px 3px hsl(0, 0%, 0%, 0.05);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.tlui-cmt-thread {
|
|
681
|
+
position: relative;
|
|
472
682
|
display: flex;
|
|
473
683
|
flex-direction: column;
|
|
474
|
-
gap: 6px;
|
|
475
684
|
width: 300px;
|
|
476
|
-
padding: 8px;
|
|
477
685
|
background: var(--tl-color-panel);
|
|
478
686
|
border-radius: var(--tl-radius-4);
|
|
479
|
-
box-shadow: var(--tl-shadow-
|
|
687
|
+
box-shadow: var(--tl-shadow-3);
|
|
480
688
|
}
|
|
481
|
-
|
|
689
|
+
/* A pill of its own, floating above the panel's top-right corner and overlapping its top edge. */
|
|
690
|
+
.tlui-cmt-thread__header {
|
|
691
|
+
position: absolute;
|
|
692
|
+
right: 10px;
|
|
693
|
+
bottom: calc(100% - 15px);
|
|
482
694
|
display: flex;
|
|
695
|
+
gap: 2px;
|
|
696
|
+
padding: 2px;
|
|
697
|
+
background: var(--tl-color-panel);
|
|
698
|
+
border-radius: var(--tl-radius-3);
|
|
699
|
+
box-shadow: var(--tlui-cmt-pill-shadow);
|
|
700
|
+
transition: opacity 0.1s ease;
|
|
701
|
+
}
|
|
702
|
+
/* The first comment's hover actions surface right where the header pill sits, so hide the pill
|
|
703
|
+
while that comment is hovered — one pill in the corner, never two stacked. */
|
|
704
|
+
.tlui-cmt-thread:has(.tlui-cmt-thread__list > :first-child:hover)
|
|
705
|
+
.tlui-cmt-thread__header:not(:has(.tlui-cmt-thread__title)) {
|
|
706
|
+
opacity: 0;
|
|
707
|
+
pointer-events: none;
|
|
708
|
+
}
|
|
709
|
+
/* A title puts the header back in the panel, in flow. The pill above is tuned for the actions-only
|
|
710
|
+
case tldraw itself uses: right-anchored and only as wide as its buttons, so a title inside it
|
|
711
|
+
would grow leftwards off the panel's edge and float over whatever sits above. `header` is public
|
|
712
|
+
API (see `CommentThreadProps`), so it has to keep laying out sensibly for anyone passing one. */
|
|
713
|
+
.tlui-cmt-thread__header:has(.tlui-cmt-thread__title) {
|
|
714
|
+
position: static;
|
|
483
715
|
align-items: center;
|
|
484
|
-
|
|
485
|
-
|
|
716
|
+
padding: 0;
|
|
717
|
+
background: none;
|
|
718
|
+
border-radius: 0;
|
|
719
|
+
box-shadow: none;
|
|
486
720
|
}
|
|
487
|
-
.cmt-thread__title {
|
|
721
|
+
.tlui-cmt-thread__title {
|
|
722
|
+
padding: 10px 12px;
|
|
488
723
|
font-size: 12px;
|
|
489
|
-
|
|
724
|
+
line-height: 20px;
|
|
725
|
+
font-weight: 500;
|
|
490
726
|
color: var(--tl-color-text-1);
|
|
491
727
|
}
|
|
492
|
-
.cmt-thread__actions {
|
|
493
|
-
display: flex;
|
|
494
|
-
gap: 2px;
|
|
495
|
-
}
|
|
496
|
-
.cmt-thread__action {
|
|
728
|
+
.tlui-cmt-thread__actions {
|
|
497
729
|
display: flex;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
730
|
+
gap: 0;
|
|
731
|
+
/* Right-align the actions whether or not the header has a title (it currently has none, so
|
|
732
|
+
space-between alone would leave them at the left). */
|
|
733
|
+
margin-left: auto;
|
|
734
|
+
}
|
|
735
|
+
/* These are TldrawUiButtons (type="icon"), so the 40px box, the 32px ::after square carrying the
|
|
736
|
+
hover wash, and the hover z-index flip all come from .tlui-button — the hit area (not the
|
|
737
|
+
::after) is what's clickable, and it never reaches past its row. Only the bits that differ from
|
|
738
|
+
a toolbar button are set here, at .tlui-button's specificity + 1 so stylesheet order can't
|
|
739
|
+
decide the winner. */
|
|
740
|
+
.tlui-button.tlui-cmt-thread__action {
|
|
741
|
+
width: 40px;
|
|
502
742
|
padding: 0;
|
|
503
|
-
border: none;
|
|
504
743
|
border-radius: 6px;
|
|
505
|
-
background: transparent;
|
|
506
744
|
color: var(--tl-color-text-3);
|
|
507
745
|
font-size: 14px;
|
|
508
746
|
line-height: 1;
|
|
509
|
-
cursor: pointer;
|
|
510
747
|
}
|
|
511
|
-
.
|
|
512
|
-
|
|
748
|
+
/* Overlap each button 4px into the previous so the 32px visual squares sit 4px apart, not 8px.
|
|
749
|
+
Left-only, so the last button's hit area still stops at its row's right edge. */
|
|
750
|
+
.tlui-cmt-thread__action + .tlui-cmt-thread__action {
|
|
751
|
+
margin-left: -4px;
|
|
752
|
+
}
|
|
753
|
+
.tlui-button.tlui-cmt-thread__action:hover {
|
|
513
754
|
color: var(--tl-color-text-1);
|
|
514
755
|
}
|
|
515
|
-
.cmt-
|
|
756
|
+
.tlui-button.tlui-cmt-thread__action--danger:hover {
|
|
757
|
+
color: var(--tl-color-danger);
|
|
758
|
+
}
|
|
759
|
+
.tlui-cmt-thread__resolved {
|
|
760
|
+
margin: 4px 12px;
|
|
516
761
|
padding: 6px 10px;
|
|
517
762
|
border-radius: 8px;
|
|
518
763
|
background: var(--tl-color-muted-2);
|
|
519
764
|
font-size: 12px;
|
|
520
765
|
color: var(--tl-color-text-3);
|
|
521
766
|
}
|
|
522
|
-
.cmt-thread__list {
|
|
767
|
+
.tlui-cmt-thread__list {
|
|
523
768
|
display: flex;
|
|
524
769
|
flex-direction: column;
|
|
525
|
-
gap:
|
|
770
|
+
gap: var(--tlui-cmt-thread-gap);
|
|
771
|
+
}
|
|
772
|
+
/* When nothing follows the list (a resolved thread has no composer or footer) the last comment
|
|
773
|
+
would sit against the panel's rounded bottom — restore the trailing inset the composer supplies. */
|
|
774
|
+
.tlui-cmt-thread__list:last-child {
|
|
775
|
+
padding-bottom: 12px;
|
|
526
776
|
}
|
|
777
|
+
/* The footer slot sits where the composer would — its content (e.g. a sign-in prompt) sits at
|
|
778
|
+
the thread's trailing edge, like the composer's send button. */
|
|
779
|
+
.tlui-cmt-thread__footer {
|
|
780
|
+
display: flex;
|
|
781
|
+
justify-content: flex-end;
|
|
782
|
+
padding: 4px 2px 2px;
|
|
783
|
+
}
|
|
784
|
+
/* Inside a thread, author identity is a small colour dot, no initial — the letter doesn't fit
|
|
785
|
+
at this size. */
|
|
786
|
+
.tlui-cmt-thread .tlui-cmt-avatar,
|
|
787
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-avatar,
|
|
788
|
+
.tlui-cmt-canvas-preview--list .tlui-cmt-avatar,
|
|
789
|
+
.tlui-cmt-stack-list .tlui-cmt-avatar {
|
|
790
|
+
width: 12px;
|
|
791
|
+
height: 12px;
|
|
792
|
+
font-size: 0;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.tlui-cmt-thread .tlui-cmt-composer {
|
|
796
|
+
padding: 12px;
|
|
797
|
+
}
|
|
798
|
+
.tlui-cmt-thread .tlui-cmt-composer .tlui-cmt-avatar {
|
|
799
|
+
display: none;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* The sidebar's rows use the same small colour dot as the thread view. */
|
|
803
|
+
.tlui-cmt-list .tlui-cmt-avatar {
|
|
804
|
+
width: 14px;
|
|
805
|
+
height: 14px;
|
|
806
|
+
font-size: 0;
|
|
807
|
+
margin-top: 2px;
|
|
808
|
+
}
|
|
809
|
+
/* The sidebar row already leads with its own avatar (the flex row's first column); the byline's
|
|
810
|
+
avatar — added for the canvas thread views — would double it, so hide the byline's inside a row. */
|
|
811
|
+
.tlui-cmt-list .tlui-cmt-head .tlui-cmt-avatar {
|
|
812
|
+
display: none;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/* The dot treatment is for colour-only identity — an image avatar stays full size. */
|
|
816
|
+
.tlui-cmt-thread .tlui-cmt-avatar--image,
|
|
817
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-avatar--image,
|
|
818
|
+
.tlui-cmt-canvas-preview--list .tlui-cmt-avatar--image,
|
|
819
|
+
.tlui-cmt-stack-list .tlui-cmt-avatar--image {
|
|
820
|
+
/* Match the 12px colour dot's width so the body's calc(12px + 6px) indent still lines up under
|
|
821
|
+
the byline name when the author has an image rather than a dot. */
|
|
822
|
+
width: 12px;
|
|
823
|
+
height: 12px;
|
|
824
|
+
margin-top: 0;
|
|
825
|
+
}
|
|
826
|
+
.tlui-cmt-list .tlui-cmt-avatar--image {
|
|
827
|
+
width: 24px;
|
|
828
|
+
height: 24px;
|
|
829
|
+
margin-top: 0;
|
|
830
|
+
}
|
|
831
|
+
|
|
527
832
|
/* inside the panel, comments are plain content — no surrounding box; the composer's field carries
|
|
528
|
-
its own surface.
|
|
529
|
-
|
|
833
|
+
its own surface. The hover preview is the same panel without a header, so its comment is laid
|
|
834
|
+
out by the same rule rather than a copy of it. */
|
|
835
|
+
.tlui-cmt-thread .tlui-cmt-card,
|
|
836
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-card {
|
|
530
837
|
width: 100%;
|
|
531
|
-
|
|
838
|
+
gap: 0;
|
|
839
|
+
padding: 0 var(--tlui-cmt-thread-padding);
|
|
532
840
|
background: transparent;
|
|
533
841
|
border: none;
|
|
534
842
|
}
|
|
843
|
+
/* The avatar is a 12px dot, so the body lines up 12px + the head's 6px gap in from the row. */
|
|
844
|
+
.tlui-cmt-thread .tlui-cmt-body,
|
|
845
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-body,
|
|
846
|
+
.tlui-cmt-canvas-preview--list .tlui-cmt-body,
|
|
847
|
+
.tlui-cmt-stack-list .tlui-cmt-body {
|
|
848
|
+
padding-left: calc(12px + 6px);
|
|
849
|
+
}
|
|
850
|
+
/* Inside a panel the actions float as their own pill — the same pill the thread header is, in the
|
|
851
|
+
same corner (which is why the header hides while the first comment is hovered). It sits just
|
|
852
|
+
above the card's top edge rather than on the byline. */
|
|
853
|
+
/* Line the card actions up with the header actions: flush to the full-width card's right edge (the
|
|
854
|
+
panel edge, matching the header ✕) and flush on the 40px byline row, so the 40px buttons fill it. */
|
|
855
|
+
.tlui-cmt-thread .tlui-cmt-card .tlui-cmt-card__actions {
|
|
856
|
+
top: 0;
|
|
857
|
+
right: 0;
|
|
858
|
+
}
|
|
859
|
+
/* The byline row is 40px: on the opened thread it fits the action pills (react/edit/delete) that
|
|
860
|
+
float in its corner without the text running underneath; previews and collapsed cards carry the
|
|
861
|
+
same height so every comment row reads identically, solo or in a stack. */
|
|
862
|
+
.tlui-cmt-thread .tlui-cmt-card .tlui-cmt-head,
|
|
863
|
+
.tlui-cmt-canvas-preview--thread .tlui-cmt-card .tlui-cmt-head,
|
|
864
|
+
.tlui-cmt-canvas-preview--list .tlui-cmt-card .tlui-cmt-head,
|
|
865
|
+
.tlui-cmt-stack-list .tlui-cmt-card .tlui-cmt-head {
|
|
866
|
+
min-height: 40px;
|
|
867
|
+
}
|
|
868
|
+
/* Reserve head room only for pills that actually rendered (one child / two children) — pill count
|
|
869
|
+
is a permissions question (canModifyComment), not ownership, and empty slots (read-only viewers)
|
|
870
|
+
keep the full row. Sized from the 40px hit areas (4px overlap), minus the card padding the
|
|
871
|
+
right:0 pills sit over; text stops 4px shy of the visible square. */
|
|
872
|
+
.tlui-cmt-thread .tlui-cmt-card:has(.tlui-cmt-card__actions > *) .tlui-cmt-head {
|
|
873
|
+
padding-right: calc(var(--tlui-cmt-thread-action-size) - var(--tlui-cmt-thread-padding));
|
|
874
|
+
}
|
|
875
|
+
.tlui-cmt-thread .tlui-cmt-card:has(.tlui-cmt-card__actions > * + *) .tlui-cmt-head {
|
|
876
|
+
padding-right: calc(
|
|
877
|
+
2 * var(--tlui-cmt-thread-action-size) - 4px - var(--tlui-cmt-thread-padding)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
535
880
|
|
|
536
881
|
/* comments list — thread summaries, one per row (used by the canvas sidebar) */
|
|
537
|
-
.cmt-list {
|
|
882
|
+
.tlui-cmt-list {
|
|
538
883
|
display: flex;
|
|
539
884
|
flex-direction: column;
|
|
540
885
|
width: 100%;
|
|
541
886
|
min-height: 0;
|
|
542
887
|
}
|
|
543
|
-
.cmt-list__header {
|
|
888
|
+
.tlui-cmt-list__header {
|
|
544
889
|
flex-shrink: 0;
|
|
545
890
|
display: flex;
|
|
546
891
|
align-items: center;
|
|
@@ -549,73 +894,102 @@
|
|
|
549
894
|
padding: 6px 6px 6px 14px;
|
|
550
895
|
border-bottom: 1px solid var(--tl-color-divider);
|
|
551
896
|
}
|
|
552
|
-
.cmt-list__header-title {
|
|
553
|
-
font-size:
|
|
554
|
-
font-weight:
|
|
555
|
-
|
|
556
|
-
text-transform: uppercase;
|
|
557
|
-
color: var(--tl-color-text-3);
|
|
897
|
+
.tlui-cmt-list__header-title {
|
|
898
|
+
font-size: 12px;
|
|
899
|
+
font-weight: 500;
|
|
900
|
+
color: var(--tl-color-text-1);
|
|
558
901
|
}
|
|
559
902
|
|
|
560
|
-
/*
|
|
561
|
-
.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
903
|
+
/* A small square icon button in the sidebar header (filter funnel, overflow …). Also a
|
|
904
|
+
TldrawUiButton, but a bespoke 26px size — so it has to override .tlui-button's 40px box, and
|
|
905
|
+
`min-width` in particular would otherwise beat `width`. Selectors carry .tlui-button so
|
|
906
|
+
stylesheet order can't decide the winner. */
|
|
907
|
+
.tlui-button.tlui-cmt-header-btn {
|
|
565
908
|
width: 26px;
|
|
909
|
+
min-width: 26px;
|
|
566
910
|
height: 26px;
|
|
567
911
|
padding: 0;
|
|
568
|
-
border: none;
|
|
569
912
|
border-radius: 6px;
|
|
570
|
-
background: transparent;
|
|
571
913
|
color: var(--tl-color-text-2);
|
|
572
|
-
cursor: pointer;
|
|
573
914
|
}
|
|
574
|
-
|
|
575
|
-
|
|
915
|
+
/* Same hit-target expansion as the thread actions, tiled the same way — this row's gap is 2px too
|
|
916
|
+
(see `.tlui-cmt-list__header-actions`). Unlike the thread actions, the hover fill sits on the
|
|
917
|
+
button itself, so .tlui-button's ::after wash is cleared and the pseudo-element is left doing
|
|
918
|
+
nothing but widening the hit area. */
|
|
919
|
+
.tlui-button.tlui-cmt-header-btn::after {
|
|
920
|
+
inset: -6px -1px;
|
|
921
|
+
background: none;
|
|
922
|
+
}
|
|
923
|
+
.tlui-button.tlui-cmt-header-btn:first-child::after {
|
|
924
|
+
left: -6px;
|
|
925
|
+
}
|
|
926
|
+
.tlui-button.tlui-cmt-header-btn:last-child::after {
|
|
927
|
+
right: -6px;
|
|
928
|
+
}
|
|
929
|
+
.tlui-button.tlui-cmt-header-btn:hover,
|
|
930
|
+
.tlui-button.tlui-cmt-header-btn[data-state='open'] {
|
|
576
931
|
background: var(--tl-color-muted-2);
|
|
577
932
|
color: var(--tl-color-text-1);
|
|
578
933
|
}
|
|
579
|
-
.cmt-list__header-actions {
|
|
934
|
+
.tlui-cmt-list__header-actions {
|
|
580
935
|
display: flex;
|
|
581
936
|
align-items: center;
|
|
582
937
|
gap: 2px;
|
|
583
938
|
}
|
|
584
939
|
|
|
940
|
+
/* The commenting dropdowns' surface — a little air between the items and the menu edge. */
|
|
941
|
+
.tlui-cmt-menu {
|
|
942
|
+
min-width: 100px;
|
|
943
|
+
padding: 4px;
|
|
944
|
+
}
|
|
945
|
+
|
|
585
946
|
/* a menu row with a label and a right-aligned shortcut hint (overflow menu) */
|
|
586
|
-
.cmt-menu-item {
|
|
947
|
+
.tlui-cmt-menu-item {
|
|
948
|
+
position: relative;
|
|
587
949
|
display: flex;
|
|
588
950
|
align-items: center;
|
|
589
951
|
justify-content: space-between;
|
|
590
952
|
gap: 24px;
|
|
591
953
|
width: 100%;
|
|
592
|
-
|
|
954
|
+
min-height: 40px;
|
|
955
|
+
padding: 0 10px;
|
|
593
956
|
border: none;
|
|
594
|
-
border-radius: 6px;
|
|
595
957
|
background: transparent;
|
|
596
958
|
font: inherit;
|
|
597
|
-
font-size:
|
|
959
|
+
font-size: 12px;
|
|
598
960
|
color: var(--tl-color-text-1);
|
|
599
961
|
cursor: pointer;
|
|
600
962
|
}
|
|
601
|
-
|
|
963
|
+
/* 36px visual inset within the 40px row — the hover wash the action and emoji buttons use. */
|
|
964
|
+
.tlui-cmt-menu-item::after {
|
|
965
|
+
content: '';
|
|
966
|
+
position: absolute;
|
|
967
|
+
inset: 2px;
|
|
968
|
+
border-radius: var(--tl-radius-2);
|
|
969
|
+
}
|
|
970
|
+
.tlui-cmt-menu-item:hover::after {
|
|
602
971
|
background: var(--tl-color-muted-2);
|
|
603
972
|
}
|
|
604
|
-
|
|
973
|
+
/* Destructive menu entries (delete) — the same danger colour as tldraw's danger buttons. */
|
|
974
|
+
.tlui-cmt-menu-item--danger {
|
|
975
|
+
color: var(--tl-color-danger);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.tlui-cmt-menu-item__shortcut {
|
|
605
979
|
color: var(--tl-color-text-3);
|
|
606
980
|
font-size: 12px;
|
|
607
981
|
}
|
|
608
|
-
.cmt-list__items {
|
|
982
|
+
.tlui-cmt-list__items {
|
|
609
983
|
flex: 1;
|
|
610
984
|
min-height: 0;
|
|
611
985
|
overflow-y: auto;
|
|
612
986
|
}
|
|
613
|
-
.cmt-list__empty {
|
|
987
|
+
.tlui-cmt-list__empty {
|
|
614
988
|
padding: 20px 14px;
|
|
615
989
|
color: var(--tl-color-text-3);
|
|
616
|
-
font-size:
|
|
990
|
+
font-size: 12px;
|
|
617
991
|
}
|
|
618
|
-
.cmt-list__item {
|
|
992
|
+
.tlui-cmt-list__item {
|
|
619
993
|
display: flex;
|
|
620
994
|
gap: 10px;
|
|
621
995
|
width: 100%;
|
|
@@ -626,34 +1000,48 @@
|
|
|
626
1000
|
font: inherit;
|
|
627
1001
|
text-align: left;
|
|
628
1002
|
cursor: pointer;
|
|
1003
|
+
/* rows can render as anchors (for open-in-new-tab); keep them looking like list rows */
|
|
1004
|
+
color: inherit;
|
|
1005
|
+
text-decoration: none;
|
|
629
1006
|
}
|
|
630
|
-
.cmt-list__item:hover {
|
|
1007
|
+
.tlui-cmt-list__item:hover {
|
|
631
1008
|
background: var(--tl-color-muted-2);
|
|
632
1009
|
}
|
|
633
|
-
|
|
634
|
-
|
|
1010
|
+
/* Selected sits a step above hover so the open thread's row stays distinguishable while
|
|
1011
|
+
pointing at other rows — but only a step: the muted-1 token (10%) reads too heavy here.
|
|
1012
|
+
These rows sit on the opaque sidebar panel, so a translucent tint composes safely. */
|
|
1013
|
+
.tlui-cmt-list__item--selected,
|
|
1014
|
+
.tlui-cmt-list__item--selected:hover {
|
|
1015
|
+
background: hsl(0, 0%, 0%, 5%);
|
|
1016
|
+
}
|
|
1017
|
+
.tl-theme__dark .tlui-cmt-list__item--selected,
|
|
1018
|
+
.tl-theme__dark .tlui-cmt-list__item--selected:hover {
|
|
1019
|
+
background: hsl(0, 0%, 100%, 5%);
|
|
635
1020
|
}
|
|
636
1021
|
/* resolved rows mute their content but keep the "Resolved" marker legible */
|
|
637
|
-
.cmt-list__item[data-resolved] .cmt-head,
|
|
638
|
-
.cmt-list__item[data-resolved] .cmt-list__item-preview {
|
|
1022
|
+
.tlui-cmt-list__item[data-resolved] .tlui-cmt-head,
|
|
1023
|
+
.tlui-cmt-list__item[data-resolved] .tlui-cmt-list__item-preview {
|
|
639
1024
|
opacity: 0.55;
|
|
640
1025
|
}
|
|
641
|
-
.cmt-list__item-body {
|
|
1026
|
+
.tlui-cmt-list__item-body {
|
|
642
1027
|
flex: 1;
|
|
643
1028
|
min-width: 0;
|
|
644
1029
|
}
|
|
645
|
-
.cmt-list__item-preview {
|
|
1030
|
+
.tlui-cmt-list__item-preview {
|
|
646
1031
|
margin-top: 2px;
|
|
647
|
-
font-size:
|
|
1032
|
+
font-size: 12px;
|
|
648
1033
|
color: var(--tl-color-text-1);
|
|
649
1034
|
overflow: hidden;
|
|
650
1035
|
text-overflow: ellipsis;
|
|
651
1036
|
display: -webkit-box;
|
|
652
1037
|
-webkit-line-clamp: 2;
|
|
653
1038
|
-webkit-box-orient: vertical;
|
|
1039
|
+
/* Long unbroken strings (URLs, mashed keys) wrap onto the clamped lines instead of clipping
|
|
1040
|
+
mid-string — same treatment as the card body. */
|
|
1041
|
+
overflow-wrap: anywhere;
|
|
654
1042
|
}
|
|
655
1043
|
/* meta line under the preview: page label and/or a resolved marker */
|
|
656
|
-
.cmt-list__item-meta {
|
|
1044
|
+
.tlui-cmt-list__item-meta {
|
|
657
1045
|
display: flex;
|
|
658
1046
|
align-items: center;
|
|
659
1047
|
gap: 8px;
|
|
@@ -661,92 +1049,372 @@
|
|
|
661
1049
|
font-size: 11px;
|
|
662
1050
|
color: var(--tl-color-text-3);
|
|
663
1051
|
}
|
|
664
|
-
.cmt-list__item-resolved {
|
|
1052
|
+
.tlui-cmt-list__item-resolved {
|
|
665
1053
|
display: inline-flex;
|
|
666
1054
|
align-items: center;
|
|
667
1055
|
gap: 3px;
|
|
668
1056
|
font-weight: 600;
|
|
669
1057
|
}
|
|
670
|
-
.cmt-list__item-page {
|
|
1058
|
+
.tlui-cmt-list__item-page {
|
|
671
1059
|
display: inline-flex;
|
|
672
1060
|
align-items: center;
|
|
673
1061
|
padding: 1px 6px;
|
|
674
1062
|
border-radius: 4px;
|
|
675
1063
|
background: var(--tl-color-muted-2);
|
|
676
1064
|
}
|
|
677
|
-
.cmt-list__item-
|
|
678
|
-
|
|
679
|
-
align-
|
|
1065
|
+
.tlui-cmt-list__item-replies {
|
|
1066
|
+
display: inline-flex;
|
|
1067
|
+
align-items: center;
|
|
1068
|
+
}
|
|
1069
|
+
/* reply count under a preview card's opening comment — the thread continues past what's shown */
|
|
1070
|
+
.tlui-cmt-card__replies {
|
|
1071
|
+
display: block;
|
|
1072
|
+
margin-top: 6px;
|
|
680
1073
|
font-size: 12px;
|
|
681
1074
|
color: var(--tl-color-text-3);
|
|
682
1075
|
}
|
|
683
1076
|
|
|
684
|
-
/* The comments layer
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
.
|
|
1077
|
+
/* The comments layer renders through `EditorPortal`, so its pieces can pick their own layer rather
|
|
1078
|
+
than the one the component is mounted in. Pins live in the canvas-in-front layer (beneath the UI
|
|
1079
|
+
panels at z-index 300), so the toolbar and style panel draw over them; the open thread's popover
|
|
1080
|
+
goes to the menus layer instead (`.tlui-cmt-canvas-popover`) so it isn't clipped. Click-through
|
|
1081
|
+
except on its own children. */
|
|
1082
|
+
.tlui-cmt-canvas-layer {
|
|
689
1083
|
position: absolute;
|
|
690
1084
|
inset: 0;
|
|
691
|
-
|
|
1085
|
+
/* Just below the collaborator cursor layer, so cursors ride over the pins — and below the
|
|
1086
|
+
in-front content (250), so active tool UI covers both. Derived, not a literal, so the
|
|
1087
|
+
ordering can't silently drift if the cursor slot moves. */
|
|
1088
|
+
z-index: calc(var(--tl-layer-collaborators) - 5);
|
|
692
1089
|
pointer-events: none;
|
|
693
1090
|
}
|
|
694
1091
|
|
|
695
|
-
.cmt-canvas-pin,
|
|
696
|
-
.cmt-canvas-cluster,
|
|
697
|
-
.cmt-canvas-composer {
|
|
1092
|
+
.tlui-cmt-canvas-pin,
|
|
1093
|
+
.tlui-cmt-canvas-cluster,
|
|
1094
|
+
.tlui-cmt-canvas-composer {
|
|
698
1095
|
position: absolute;
|
|
699
1096
|
}
|
|
700
1097
|
|
|
701
|
-
.cmt-canvas-
|
|
702
|
-
.cmt-canvas-composer {
|
|
1098
|
+
.tlui-cmt-canvas-composer {
|
|
703
1099
|
pointer-events: auto;
|
|
704
1100
|
}
|
|
705
1101
|
|
|
706
|
-
/*
|
|
707
|
-
.
|
|
708
|
-
|
|
1102
|
+
/* The pin's only interactive surface is the square hit layer below — the wrapper itself is
|
|
1103
|
+
inert. Its box sits at the anchor point, not over the (translated) visual, so letting it take
|
|
1104
|
+
events would put a phantom hit zone beside the pin. */
|
|
1105
|
+
.tlui-cmt-canvas-pin {
|
|
1106
|
+
pointer-events: none;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.tlui-cmt-button {
|
|
1110
|
+
appearance: none;
|
|
1111
|
+
margin: 0;
|
|
1112
|
+
padding: 0;
|
|
1113
|
+
border: none;
|
|
1114
|
+
background: none;
|
|
1115
|
+
color: inherit;
|
|
1116
|
+
font: inherit;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/* Anchored by its bottom-left corner like a pin, so the badge sits up-and-right of the cluster's
|
|
1120
|
+
centroid — the same visual-to-anchor relationship a single comment pin has. */
|
|
1121
|
+
.tlui-cmt-canvas-cluster {
|
|
1122
|
+
transform: translate(0, -100%);
|
|
709
1123
|
pointer-events: auto;
|
|
710
1124
|
cursor: pointer;
|
|
711
1125
|
}
|
|
712
1126
|
|
|
713
|
-
|
|
1127
|
+
/* Keyboard focus has to be visible on the canvas, where there's no surrounding chrome to imply
|
|
1128
|
+
it. The ring goes on the marker visual (the rounded pin/badge), not the button box, so it
|
|
1129
|
+
traces the shape rather than a rectangle around it; the button's own outline is suppressed
|
|
1130
|
+
because its box is offset from what you see. */
|
|
1131
|
+
.tlui-cmt-canvas-pin__marker:focus-visible,
|
|
1132
|
+
.tlui-cmt-canvas-cluster:focus-visible {
|
|
1133
|
+
outline: none;
|
|
1134
|
+
}
|
|
1135
|
+
.tlui-cmt-canvas-pin__marker:focus-visible .tlui-cmt-marker,
|
|
1136
|
+
.tlui-cmt-canvas-cluster:focus-visible .tlui-cmt-marker {
|
|
1137
|
+
outline: 2px solid var(--tl-color-selected);
|
|
1138
|
+
outline-offset: 2px;
|
|
1139
|
+
}
|
|
1140
|
+
/* Focus should read like hover, so the pin lifts the same way when tabbed to. */
|
|
1141
|
+
.tlui-cmt-canvas-pin__marker:focus-visible .tlui-cmt-marker {
|
|
1142
|
+
transform: scale(var(--tlui-cmt-marker-hover-scale));
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
.tlui-cmt-cluster-fade {
|
|
714
1146
|
opacity: 1;
|
|
715
1147
|
transition: opacity 150ms ease;
|
|
716
1148
|
}
|
|
717
1149
|
|
|
718
|
-
.cmt-cluster-fade--entering {
|
|
1150
|
+
.tlui-cmt-cluster-fade--entering {
|
|
719
1151
|
opacity: 0;
|
|
720
1152
|
}
|
|
721
1153
|
|
|
722
|
-
.cmt-cluster-fade--present {
|
|
1154
|
+
.tlui-cmt-cluster-fade--present {
|
|
723
1155
|
opacity: 1;
|
|
724
1156
|
}
|
|
725
1157
|
|
|
726
|
-
.cmt-cluster-fade--exiting {
|
|
1158
|
+
.tlui-cmt-cluster-fade--exiting {
|
|
727
1159
|
opacity: 0;
|
|
728
1160
|
}
|
|
729
1161
|
|
|
730
|
-
.cmt-cluster-fade--entering *,
|
|
731
|
-
.cmt-cluster-fade--exiting * {
|
|
1162
|
+
.tlui-cmt-cluster-fade--entering *,
|
|
1163
|
+
.tlui-cmt-cluster-fade--exiting * {
|
|
732
1164
|
pointer-events: none !important;
|
|
733
1165
|
}
|
|
734
1166
|
|
|
735
1167
|
@media (prefers-reduced-motion: reduce) {
|
|
736
|
-
.cmt-cluster-fade {
|
|
1168
|
+
.tlui-cmt-cluster-fade {
|
|
737
1169
|
transition: none;
|
|
738
1170
|
}
|
|
739
1171
|
}
|
|
740
1172
|
|
|
741
1173
|
/* The open thread (its pin + popover) sits above the other pins, so nearby markers don't
|
|
742
1174
|
overlap its popover — they tuck behind it. */
|
|
743
|
-
.cmt-canvas-pin--open {
|
|
1175
|
+
.tlui-cmt-canvas-pin--open {
|
|
744
1176
|
z-index: 1;
|
|
745
1177
|
}
|
|
746
1178
|
|
|
1179
|
+
/* The count badge standing in for a stack of coincident pins — same face as a cluster badge,
|
|
1180
|
+
but clicking opens the stack's thread list rather than zooming (no zoom can separate them).
|
|
1181
|
+
Re-enables pointer events: the badge lives inside `.tlui-cmt-canvas-pin`, which is inert
|
|
1182
|
+
(`pointer-events: none`) so its box doesn't shadow the canvas — without this the badge renders
|
|
1183
|
+
but can't be hovered or clicked, like the cluster badge's own `pointer-events: auto`. */
|
|
1184
|
+
.tlui-cmt-canvas-stack-badge {
|
|
1185
|
+
/* Bottom-left anchored like the pins it stands in for, and like the cluster badge. */
|
|
1186
|
+
transform: translate(0, -100%);
|
|
1187
|
+
width: max-content;
|
|
1188
|
+
pointer-events: auto;
|
|
1189
|
+
cursor: pointer;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.tlui-cmt-canvas-stack-badge:focus-visible {
|
|
1193
|
+
outline: none;
|
|
1194
|
+
}
|
|
1195
|
+
.tlui-cmt-canvas-stack-badge:focus-visible .tlui-cmt-marker {
|
|
1196
|
+
outline: 2px solid var(--tl-color-selected);
|
|
1197
|
+
outline-offset: 2px;
|
|
1198
|
+
transform: scale(var(--tlui-cmt-marker-hover-scale));
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/* The stack's popover content: one card per thread, the expanded thread inline among them. */
|
|
1202
|
+
.tlui-cmt-stack-list {
|
|
1203
|
+
display: flex;
|
|
1204
|
+
flex-direction: column;
|
|
1205
|
+
gap: 10px;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/* The expanded thread is the only entry with pills floating above its panel — its header actions,
|
|
1209
|
+
and its first comment's hover actions. Those reach ~15px up, past the list's 10px gap, so give
|
|
1210
|
+
this one entry extra room: without it the pills land on the card above, and against the scroll
|
|
1211
|
+
box's clip edge. Collapsed cards keep their own actions inside, so they need none of this. */
|
|
1212
|
+
.tlui-cmt-stack-list__thread {
|
|
1213
|
+
margin-top: 8px;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/* Collapsed entries match the expanded thread's surface (same width, panel, elevation) so the
|
|
1217
|
+
list reads as one column of cards with one of them opened up. */
|
|
1218
|
+
.tlui-cmt-stack-list__card {
|
|
1219
|
+
box-sizing: border-box;
|
|
1220
|
+
position: relative;
|
|
1221
|
+
width: 300px;
|
|
1222
|
+
background: var(--tl-color-panel);
|
|
1223
|
+
border-radius: 12px;
|
|
1224
|
+
box-shadow: var(--tl-shadow-3);
|
|
1225
|
+
padding: 2px 12px 10px;
|
|
1226
|
+
cursor: pointer;
|
|
1227
|
+
text-align: left;
|
|
1228
|
+
display: block;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/* The card's keyboard affordance, covering the whole surface so it's one target rather than a
|
|
1232
|
+
control tucked in a corner. It's laid under the card's content instead of wrapping it: a comment
|
|
1233
|
+
body renders its links as anchors, which can't be nested inside a button (see the note in
|
|
1234
|
+
`StackThreadCard`). Pointer clicks anywhere still reach the card's own handler by bubbling. */
|
|
1235
|
+
.tlui-cmt-stack-list__card-action {
|
|
1236
|
+
position: absolute;
|
|
1237
|
+
inset: 0;
|
|
1238
|
+
border-radius: inherit;
|
|
1239
|
+
cursor: pointer;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.tlui-cmt-stack-list__card-action:focus-visible {
|
|
1243
|
+
outline: 2px solid var(--tl-color-selected);
|
|
1244
|
+
outline-offset: 2px;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/* The wrapper is the card's surface — inside it the comment is plain content, mirroring how the
|
|
1248
|
+
thread panel flattens its own cards. Stacked above the action overlay so the body's links stay
|
|
1249
|
+
clickable. */
|
|
1250
|
+
.tlui-cmt-stack-list__card .tlui-cmt-card {
|
|
1251
|
+
position: relative;
|
|
1252
|
+
width: 100%;
|
|
1253
|
+
gap: 0;
|
|
1254
|
+
padding: 0;
|
|
1255
|
+
background: transparent;
|
|
1256
|
+
border: none;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/* The hover preview, shown for any marker — a pin, a coincident stack, or a cluster badge. Live,
|
|
1260
|
+
not a tooltip: the pointer travels into it to hover and click the cards.
|
|
1261
|
+
|
|
1262
|
+
Two elements, deliberately: this root is the hover *region* (it reaches back over the marker via
|
|
1263
|
+
the bridge below), and `__panel` inside it is the visible surface. A pseudo-element's hover
|
|
1264
|
+
counts as its host's, so a surface on this root would light up whenever the pointer was on the
|
|
1265
|
+
marker — the pin would appear pressed just for being hovered. */
|
|
1266
|
+
.tlui-cmt-canvas-preview {
|
|
1267
|
+
position: absolute;
|
|
1268
|
+
z-index: var(--tl-layer-menus);
|
|
1269
|
+
pointer-events: auto;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/* The bridge over the gap between the marker and the panel. Moving right off the pin would
|
|
1273
|
+
otherwise cross bare canvas, and that leave would retract the panel before the pointer arrived.
|
|
1274
|
+
Invisible, part of the panel's hover region, and only present while the panel is.
|
|
1275
|
+
|
|
1276
|
+
It spans the gap and stops there. Reaching any further would put it over the marker itself, and
|
|
1277
|
+
since it sits in the menus layer — above the pins — it would take the marker's hover and clicks:
|
|
1278
|
+
the pin would go dead exactly where it looks most alive. The marker needs no covering anyway, as
|
|
1279
|
+
it carries the same hover handlers.
|
|
1280
|
+
|
|
1281
|
+
The gap is the panel's own offset (`--tlui-cmt-preview-offset`, set from `POPOVER_OFFSET` so
|
|
1282
|
+
there's one source) less however far the marker reaches toward it, per variant below. */
|
|
1283
|
+
.tlui-cmt-canvas-preview::before {
|
|
1284
|
+
content: '';
|
|
1285
|
+
position: absolute;
|
|
1286
|
+
top: 0;
|
|
1287
|
+
bottom: 0;
|
|
1288
|
+
right: 100%;
|
|
1289
|
+
width: max(0px, var(--tlui-cmt-preview-bridge, 0px));
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/* A pin is anchored at its bottom-left corner, so its full width lies between anchor and panel.
|
|
1293
|
+
The popover and preview share a first-comment position now that the actions float outside the
|
|
1294
|
+
panel rather than reserving a header row, so no vertical shift is needed. */
|
|
1295
|
+
.tlui-cmt-canvas-preview--thread {
|
|
1296
|
+
--tlui-cmt-preview-bridge: calc(var(--tlui-cmt-preview-offset) - var(--tlui-cmt-pin-size));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/* A badge is anchored at its bottom-left corner like a pin, so its full width does too. */
|
|
1300
|
+
.tlui-cmt-canvas-preview--list {
|
|
1301
|
+
--tlui-cmt-preview-bridge: calc(var(--tlui-cmt-preview-offset) - var(--tlui-cmt-marker-size));
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.tlui-cmt-canvas-preview__panel {
|
|
1305
|
+
display: flex;
|
|
1306
|
+
flex-direction: column;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* Previewing a single pin: the thread panel itself, minus the header — the same surface, width,
|
|
1310
|
+
and flattened comment (see `.tlui-cmt-thread .tlui-cmt-card`), so the hover shows exactly what
|
|
1311
|
+
the click opens. */
|
|
1312
|
+
.tlui-cmt-canvas-preview__panel--thread {
|
|
1313
|
+
box-sizing: border-box;
|
|
1314
|
+
width: 300px;
|
|
1315
|
+
gap: var(--tlui-cmt-thread-gap);
|
|
1316
|
+
/* Top/bottom only — the card pads its own sides now (12px), matching the expanded thread. A
|
|
1317
|
+
little extra below so the (often truncated) last line doesn't sit against the rounded edge. */
|
|
1318
|
+
padding: 4px 0 8px;
|
|
1319
|
+
background: var(--tl-color-panel);
|
|
1320
|
+
border-radius: var(--tl-radius-4);
|
|
1321
|
+
box-shadow: var(--tl-shadow-3);
|
|
1322
|
+
}
|
|
1323
|
+
/* The reply count is a short line that sits closer to the edge than a wrapped comment does, so give
|
|
1324
|
+
it more room below when it's the panel's last line. */
|
|
1325
|
+
.tlui-cmt-canvas-preview__panel--thread:has(.tlui-cmt-card__replies) {
|
|
1326
|
+
padding-bottom: 12px;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/* Previewing a stack or cluster: one card per thread, matching the card list a click opens. */
|
|
1330
|
+
.tlui-cmt-canvas-preview__panel--list {
|
|
1331
|
+
gap: 8px;
|
|
1332
|
+
padding: 4px 0 8px;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/* Matches the stack list's card surface, so a preview and the list it previews read as the same
|
|
1336
|
+
object seen twice — once on hover, once opened. */
|
|
1337
|
+
.tlui-cmt-preview-card {
|
|
1338
|
+
box-sizing: border-box;
|
|
1339
|
+
width: 300px;
|
|
1340
|
+
background: var(--tl-color-panel);
|
|
1341
|
+
border-radius: 12px;
|
|
1342
|
+
box-shadow: var(--tl-shadow-3);
|
|
1343
|
+
/* A 2px nudge above the byline's centring — a boxed card reads a touch better with it than the
|
|
1344
|
+
single preview's flat card, which pads 0 vertically and leans on the panel + byline. */
|
|
1345
|
+
padding: 2px 12px 10px;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
.tlui-cmt-preview-card--selectable {
|
|
1349
|
+
cursor: pointer;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/* Every card surface that tints on hover — a collapsed thread in a stack list, a preview card, and
|
|
1353
|
+
the single-comment preview panel. One rule, because they're the same object to the eye.
|
|
1354
|
+
|
|
1355
|
+
The opaque low-surface token, not a translucent muted one: these float directly over the canvas,
|
|
1356
|
+
where a translucent fill would let shapes show through.
|
|
1357
|
+
|
|
1358
|
+
The second line is why this isn't a one-liner. `--tl-shadow-3` ends with a 1px *inset* ring in
|
|
1359
|
+
`--tl-color-panel-contrast` — pure white in light mode — and an inset shadow paints over the
|
|
1360
|
+
background. At rest that's invisible (the panel is 99% white under a 100% white ring), but tint
|
|
1361
|
+
the background to grey and the ring stays white, hugging the edge as a pale outline. Re-tinting
|
|
1362
|
+
`--tl-color-panel-contrast` here wouldn't reach it: `--tl-shadow-3` is declared on the container,
|
|
1363
|
+
so its var() was already substituted there and the white is baked into what we inherit. Instead
|
|
1364
|
+
lay a ring of the new surface over the old one — first shadow in the list paints on top. */
|
|
1365
|
+
.tlui-cmt-stack-list__card:hover,
|
|
1366
|
+
.tlui-cmt-preview-card--selectable:hover,
|
|
1367
|
+
.tlui-cmt-canvas-preview__panel--thread.tlui-cmt-canvas-preview__panel--selectable:hover {
|
|
1368
|
+
--tlui-cmt-preview-hover-surface: color-mix(
|
|
1369
|
+
in srgb,
|
|
1370
|
+
var(--tl-color-low) 50%,
|
|
1371
|
+
var(--tl-color-panel)
|
|
1372
|
+
);
|
|
1373
|
+
background: var(--tlui-cmt-preview-hover-surface);
|
|
1374
|
+
box-shadow:
|
|
1375
|
+
inset 0 0 0 1px var(--tlui-cmt-preview-hover-surface),
|
|
1376
|
+
var(--tl-shadow-3);
|
|
1377
|
+
cursor: pointer;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
.tlui-cmt-preview-card .tlui-cmt-card {
|
|
1381
|
+
width: 100%;
|
|
1382
|
+
gap: 0;
|
|
1383
|
+
padding: 0;
|
|
1384
|
+
background: transparent;
|
|
1385
|
+
border: none;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/* A preview is a glance, not a list — past a few cards it says how many were left out rather than
|
|
1389
|
+
growing to the height of the cluster. */
|
|
1390
|
+
.tlui-cmt-preview-more {
|
|
1391
|
+
box-sizing: border-box;
|
|
1392
|
+
width: 300px;
|
|
1393
|
+
padding: 6px 14px;
|
|
1394
|
+
border-radius: 12px;
|
|
1395
|
+
background: var(--tl-color-panel);
|
|
1396
|
+
box-shadow: var(--tl-shadow-3);
|
|
1397
|
+
color: var(--tl-color-text-3);
|
|
1398
|
+
font-size: 12px;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
1402
|
+
.tlui-cmt-canvas-preview {
|
|
1403
|
+
animation: tlui-cmt-preview-in 120ms ease;
|
|
1404
|
+
}
|
|
1405
|
+
@keyframes tlui-cmt-preview-in {
|
|
1406
|
+
from {
|
|
1407
|
+
opacity: 0;
|
|
1408
|
+
}
|
|
1409
|
+
to {
|
|
1410
|
+
opacity: 1;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
747
1415
|
/* A region anchor's area: a dashed box under the pins, non-interactive so canvas events pass
|
|
748
1416
|
through. Drawn for the live drag draft and for a region thread while hovered or open. */
|
|
749
|
-
.cmt-canvas-region {
|
|
1417
|
+
.tlui-cmt-canvas-region {
|
|
750
1418
|
position: absolute;
|
|
751
1419
|
pointer-events: none;
|
|
752
1420
|
border: 2px dashed var(--tl-color-selected);
|
|
@@ -754,15 +1422,8 @@
|
|
|
754
1422
|
background: color-mix(in srgb, var(--tl-color-selected) 10%, transparent);
|
|
755
1423
|
}
|
|
756
1424
|
|
|
757
|
-
/* A movable region body (the 'body'/'both' move modes): draggable to translate the whole region.
|
|
758
|
-
Interactive, so it captures the interior while shown — the tradeoff of dragging the body. */
|
|
759
|
-
.cmt-canvas-region--movable {
|
|
760
|
-
pointer-events: auto;
|
|
761
|
-
cursor: move;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
1425
|
/* Corner resize handles on a region box (three corners; the pin corner is the move handle). */
|
|
765
|
-
.cmt-canvas-region-handle {
|
|
1426
|
+
.tlui-cmt-canvas-region-handle {
|
|
766
1427
|
position: absolute;
|
|
767
1428
|
width: 9px;
|
|
768
1429
|
height: 9px;
|
|
@@ -774,59 +1435,200 @@
|
|
|
774
1435
|
touch-action: none;
|
|
775
1436
|
}
|
|
776
1437
|
|
|
777
|
-
/*
|
|
778
|
-
|
|
1438
|
+
/* Anchor the pin by its bottom-left corner, so the marker's one sharp corner (the pin shape's
|
|
1439
|
+
3px bottom-left) points at the exact anchor point. */
|
|
1440
|
+
.tlui-cmt-canvas-pin__marker {
|
|
1441
|
+
position: relative;
|
|
779
1442
|
width: max-content;
|
|
780
|
-
transform: translate(
|
|
781
|
-
cursor
|
|
1443
|
+
transform: translate(0, -100%);
|
|
1444
|
+
/* Default at rest, like hovering a shape; the editor's move cursor while dragging (below). */
|
|
1445
|
+
cursor: var(--tl-cursor-default);
|
|
782
1446
|
touch-action: none;
|
|
1447
|
+
pointer-events: none;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/* Dragging a pin shows the same move cursor as dragging a shape. */
|
|
1451
|
+
.tlui-cmt-canvas-pin--dragging .tlui-cmt-canvas-pin__marker {
|
|
1452
|
+
cursor: var(--tl-cursor-move);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/* The predictable hit area: a fixed square over the marker's box, and the only part of the pin
|
|
1456
|
+
that takes pointer events. Events over it target the marker (a pseudo-element's events report
|
|
1457
|
+
its originating element), so the marker's handlers and :hover keep working — but the hit area
|
|
1458
|
+
no longer stretches with pin content or scales with the hover transform. */
|
|
1459
|
+
.tlui-cmt-canvas-pin__marker::after {
|
|
1460
|
+
content: '';
|
|
1461
|
+
position: absolute;
|
|
1462
|
+
inset: 0;
|
|
1463
|
+
pointer-events: auto;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/* With the hit layer targeting the marker, the pin visual is never the hovered element itself
|
|
1467
|
+
(`:hover` matches the target and its ancestors, and the pin is the marker's child) — so the
|
|
1468
|
+
canvas pin's hover has to key off the hit layer instead of the marker's own `:hover`. Only the
|
|
1469
|
+
selector differs: the values come from the same tokens the shared rule uses, so a pin on the
|
|
1470
|
+
canvas and a badge (which is hovered directly) lift by exactly the same amount. */
|
|
1471
|
+
.tlui-cmt-canvas-pin__marker:hover .tlui-cmt-marker {
|
|
1472
|
+
transform: scale(var(--tlui-cmt-marker-hover-scale));
|
|
783
1473
|
}
|
|
784
|
-
.cmt-canvas-pin__marker:
|
|
785
|
-
|
|
1474
|
+
.tlui-cmt-canvas-pin__marker:hover .tlui-cmt-marker:not(.tlui-cmt-marker--open) {
|
|
1475
|
+
box-shadow: var(--tlui-cmt-marker-shadow-hover);
|
|
1476
|
+
}
|
|
1477
|
+
/* The comment pin doesn't scale on hover — only the shadow lifts (badges still scale). */
|
|
1478
|
+
.tlui-cmt-canvas-pin__marker:hover .tlui-cmt-pin {
|
|
1479
|
+
transform: none;
|
|
786
1480
|
}
|
|
787
1481
|
|
|
788
1482
|
/* The open thread's popover — portaled to the menus layer (above the UI), positioned at the pin. */
|
|
789
|
-
.cmt-canvas-popover {
|
|
1483
|
+
.tlui-cmt-canvas-popover {
|
|
790
1484
|
position: absolute;
|
|
791
1485
|
z-index: var(--tl-layer-menus);
|
|
792
1486
|
pointer-events: auto;
|
|
793
1487
|
}
|
|
794
1488
|
|
|
795
1489
|
/* The placement composer — a distinct floating view (portaled to the menus layer, below the click
|
|
796
|
-
point): a
|
|
797
|
-
|
|
798
|
-
.cmt-canvas-composer {
|
|
1490
|
+
point): a draft avatar pin beside a pill field that is itself the surface (no wrapping panel).
|
|
1491
|
+
Scoped here so the in-thread reply composer keeps its own squarer look. */
|
|
1492
|
+
.tlui-cmt-canvas-composer {
|
|
799
1493
|
position: absolute;
|
|
800
1494
|
z-index: var(--tl-layer-menus);
|
|
801
1495
|
pointer-events: auto;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
|
|
1496
|
+
/* Anchor the draft avatar bottom-left like the pins it becomes: it is a pin wide and sits 4px
|
|
1497
|
+
into the row, so its bottom-left corner is 4px right of and 4px + a pin below the composer's
|
|
1498
|
+
top-left corner. */
|
|
1499
|
+
transform: translate(-4px, calc(-4px - var(--tlui-cmt-pin-size)));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/* A region placement centres the draft avatar on the region's pin corner — the pin it becomes
|
|
1503
|
+
straddles that corner. The avatar's centre sits 4px + half a pin into the composer. */
|
|
1504
|
+
.tlui-cmt-canvas-composer--region {
|
|
1505
|
+
--tlui-cmt-draft-avatar-centre: calc(4px + var(--tlui-cmt-pin-size) / 2);
|
|
1506
|
+
transform: translate(
|
|
1507
|
+
calc(-1 * var(--tlui-cmt-draft-avatar-centre)),
|
|
1508
|
+
calc(-1 * var(--tlui-cmt-draft-avatar-centre))
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/* Fallback placement (no composer to show): no draft avatar to anchor by, so no offset — a small
|
|
1513
|
+
panel hanging off the click point, sized to its content (e.g. the host's sign-in prompt). */
|
|
1514
|
+
.tlui-cmt-canvas-composer--fallback {
|
|
1515
|
+
transform: none;
|
|
1516
|
+
width: auto;
|
|
1517
|
+
display: flex;
|
|
1518
|
+
flex-direction: column;
|
|
1519
|
+
padding: 8px;
|
|
808
1520
|
background: var(--tl-color-panel);
|
|
809
|
-
border-color: transparent;
|
|
810
1521
|
border-radius: var(--tl-radius-4);
|
|
1522
|
+
box-shadow: var(--tl-shadow-3);
|
|
1523
|
+
}
|
|
1524
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer {
|
|
1525
|
+
gap: 6px;
|
|
1526
|
+
}
|
|
1527
|
+
/* The card: a fixed 300×42 surface whose uniform 3px padding derives the 294×36 focusable input. */
|
|
1528
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer__field {
|
|
1529
|
+
position: relative;
|
|
1530
|
+
flex: none;
|
|
1531
|
+
width: 300px;
|
|
1532
|
+
min-height: 42px;
|
|
1533
|
+
padding: 3px;
|
|
1534
|
+
border: none;
|
|
1535
|
+
background: var(--tl-color-panel);
|
|
1536
|
+
border-radius: var(--tl-radius-3);
|
|
1537
|
+
box-shadow: var(--tl-shadow-2);
|
|
1538
|
+
/* The pin is anchored by its bottom corner at the click point and can't move; raise the taller
|
|
1539
|
+
card onto the shorter pin's centre instead. */
|
|
1540
|
+
margin-top: -6px;
|
|
1541
|
+
}
|
|
1542
|
+
/* Focused (the placement composer autofocuses on mount): a selection-blue ring inset 3px from the
|
|
1543
|
+
card edge, so the card frames a bordered input rather than the border sitting on the card edge. */
|
|
1544
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer__field:focus-within::before {
|
|
1545
|
+
content: '';
|
|
1546
|
+
position: absolute;
|
|
1547
|
+
inset: 3px;
|
|
1548
|
+
border: 1px solid var(--tl-color-selected);
|
|
1549
|
+
border-radius: var(--tl-radius-2);
|
|
1550
|
+
pointer-events: none;
|
|
1551
|
+
}
|
|
1552
|
+
/* The text sits 12px in from the focus ring rather than against it. The placeholder is absolutely
|
|
1553
|
+
positioned (it ignores the wrap's padding), so it carries the same offset itself. */
|
|
1554
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer__input-wrap {
|
|
1555
|
+
padding: 4.5px 12px;
|
|
1556
|
+
}
|
|
1557
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer__field--expanded .tlui-cmt-composer__input-wrap {
|
|
1558
|
+
/* The base expanded rule re-pads the right edge for the squarer composer's geometry; here the
|
|
1559
|
+
12px is already symmetric. */
|
|
1560
|
+
padding-right: 12px;
|
|
1561
|
+
}
|
|
1562
|
+
.tlui-cmt-canvas-composer .tlui-cmt-input__placeholder {
|
|
1563
|
+
left: 12px;
|
|
1564
|
+
}
|
|
1565
|
+
/* The draft avatar previews the pin it becomes — a white 28px pin holding the author's 22px avatar
|
|
1566
|
+
circle — sharing the composer's lift. Not interactive: it drops the pin's pointer cursor and
|
|
1567
|
+
hover affordances (scale + shadow bump), but keeps a 40×40 hit area (the ::after below). */
|
|
1568
|
+
.tlui-cmt-canvas-composer .tlui-cmt-pin {
|
|
1569
|
+
position: relative;
|
|
1570
|
+
box-shadow: var(--tl-shadow-2);
|
|
1571
|
+
cursor: default;
|
|
1572
|
+
}
|
|
1573
|
+
.tlui-cmt-canvas-composer .tlui-cmt-pin::after {
|
|
1574
|
+
content: '';
|
|
1575
|
+
position: absolute;
|
|
1576
|
+
inset: -6px;
|
|
1577
|
+
}
|
|
1578
|
+
.tlui-cmt-canvas-composer .tlui-cmt-pin:hover {
|
|
1579
|
+
transform: none;
|
|
811
1580
|
box-shadow: var(--tl-shadow-2);
|
|
812
1581
|
}
|
|
1582
|
+
/* The placement composer's send button — a bare arrow, no pill: grey while empty, black once
|
|
1583
|
+
there's something to send. A 28px square centred in the 36px input height, so its hover wash
|
|
1584
|
+
clears the focus ring rather than butting against it; the ::after keeps a 40×40 hit area. */
|
|
1585
|
+
.tlui-cmt-canvas-composer .tlui-cmt-send {
|
|
1586
|
+
position: relative;
|
|
1587
|
+
width: 28px;
|
|
1588
|
+
height: 28px;
|
|
1589
|
+
margin-right: 4px;
|
|
1590
|
+
background: transparent;
|
|
1591
|
+
border-radius: var(--tl-radius-2);
|
|
1592
|
+
color: var(--tl-color-text-3);
|
|
1593
|
+
}
|
|
1594
|
+
.tlui-cmt-canvas-composer .tlui-cmt-send::after {
|
|
1595
|
+
content: '';
|
|
1596
|
+
position: absolute;
|
|
1597
|
+
inset: -6px;
|
|
1598
|
+
}
|
|
1599
|
+
.tlui-cmt-canvas-composer .tlui-cmt-send:not(:disabled) {
|
|
1600
|
+
color: var(--tl-color-text-1);
|
|
1601
|
+
}
|
|
1602
|
+
/* Hover and press wash grey behind the bare arrow (the same treatment as the thread actions),
|
|
1603
|
+
overriding the base button's blue fill. */
|
|
1604
|
+
.tlui-cmt-canvas-composer .tlui-cmt-send:hover:not(:disabled) {
|
|
1605
|
+
background: var(--tl-color-muted-2);
|
|
1606
|
+
}
|
|
1607
|
+
.tlui-cmt-canvas-composer .tlui-cmt-send:active:not(:disabled) {
|
|
1608
|
+
background: var(--tl-color-muted-1);
|
|
1609
|
+
}
|
|
1610
|
+
/* In the expanded (multi-line) layout the send drops to the bottom-right corner; give it the same
|
|
1611
|
+
4px gap from the focus ring on the bottom that it already keeps on the right. */
|
|
1612
|
+
.tlui-cmt-canvas-composer .tlui-cmt-composer__field--expanded .tlui-cmt-send {
|
|
1613
|
+
margin-bottom: 4px;
|
|
1614
|
+
}
|
|
813
1615
|
|
|
814
|
-
/* The comments list panel — a right-side surface shown while the
|
|
815
|
-
the popover layer, so an open thread still draws over it.
|
|
816
|
-
(top
|
|
817
|
-
than stretching the full canvas height. */
|
|
818
|
-
.cmt-canvas-sidebar {
|
|
1616
|
+
/* The comments list panel — a right-side surface shown while the comments sidebar is open. Below
|
|
1617
|
+
the popover layer, so an open thread still draws over it. Its top aligns flush with the style
|
|
1618
|
+
panel (48px from the top, clearing the share panel) and it sizes to its content, scrolling past
|
|
1619
|
+
max-height rather than stretching the full canvas height. */
|
|
1620
|
+
.tlui-cmt-canvas-sidebar {
|
|
819
1621
|
position: absolute;
|
|
820
|
-
top:
|
|
1622
|
+
top: 48px;
|
|
821
1623
|
right: 8px;
|
|
822
1624
|
width: 280px;
|
|
823
|
-
max-height: calc(100% -
|
|
1625
|
+
max-height: calc(100% - 56px);
|
|
824
1626
|
z-index: var(--tl-layer-panels);
|
|
825
1627
|
pointer-events: auto;
|
|
826
1628
|
display: flex;
|
|
827
1629
|
background: var(--tl-color-panel);
|
|
828
1630
|
border-radius: var(--tl-radius-4);
|
|
829
|
-
box-shadow: var(--tl-shadow-
|
|
1631
|
+
box-shadow: var(--tl-shadow-3);
|
|
830
1632
|
overflow: hidden;
|
|
831
1633
|
}
|
|
832
1634
|
|