@stealthscale/component-messaging 0.1.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/LICENSE +21 -0
- package/README.md +392 -0
- package/dist/cyclonedx/bom.json +1 -0
- package/dist/index.d.ts +826 -0
- package/dist/index.js +2248 -0
- package/dist/recipe-f6w25oLs.js +693 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +22 -0
- package/package.json +82 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { FIELD_EDGE, PALETTES, axis, defineSlotRecipe, dense, flatVariants, onSlot, onSlots, paletteVariants, sizeVariants, surface, truncate, wrappedField, wrappedFieldVariants } from "@stealthscale/theme/authoring";
|
|
2
|
+
//#region src/attachment/recipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* Declares the attachment's slot recipe: a file attached to a message or waiting in a composer, and
|
|
5
|
+
* the list that groups several.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A horizontal attachment is a row: the media square, the title over the description, and the
|
|
9
|
+
* actions at the end. A vertical one is a tile: the media across the top, the words under it,
|
|
10
|
+
* and the actions over the media's corner. The root writes its state as `data-state`: an `idle`
|
|
11
|
+
* attachment, not yet sent, has a dashed edge, and a failed one has the error edge and inks its
|
|
12
|
+
* media and description in the error ink. The title and the description keep one line and end in
|
|
13
|
+
* an ellipsis, so a long file name never widens the list. A group stacks rows and wraps tiles.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Class name of the recipe, which a selector across parts reads.
|
|
17
|
+
*/
|
|
18
|
+
const CLASS$1 = "attachment";
|
|
19
|
+
/**
|
|
20
|
+
* Selects the media of an attachment, from a rule on its root.
|
|
21
|
+
*/
|
|
22
|
+
const MEDIA = `.${CLASS$1}__media`;
|
|
23
|
+
/**
|
|
24
|
+
* Selects the description of an attachment, from a rule on its root.
|
|
25
|
+
*/
|
|
26
|
+
const DESCRIPTION = `.${CLASS$1}__description`;
|
|
27
|
+
/**
|
|
28
|
+
* Sizes the attachment offers.
|
|
29
|
+
*/
|
|
30
|
+
const SIZES$2 = [
|
|
31
|
+
"xs",
|
|
32
|
+
"sm",
|
|
33
|
+
"md"
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Maps each size to its steps and text styles.
|
|
37
|
+
*/
|
|
38
|
+
const METRICS$2 = {
|
|
39
|
+
md: {
|
|
40
|
+
detail: "body.xs",
|
|
41
|
+
gap: "sm",
|
|
42
|
+
inset: "sm",
|
|
43
|
+
media: "md",
|
|
44
|
+
text: "label.sm"
|
|
45
|
+
},
|
|
46
|
+
sm: {
|
|
47
|
+
detail: "body.xs",
|
|
48
|
+
gap: "sm",
|
|
49
|
+
inset: "xs",
|
|
50
|
+
media: "sm",
|
|
51
|
+
text: "label.xs"
|
|
52
|
+
},
|
|
53
|
+
xs: {
|
|
54
|
+
detail: "body.xs",
|
|
55
|
+
gap: "xs",
|
|
56
|
+
inset: "xs",
|
|
57
|
+
media: "xs",
|
|
58
|
+
text: "label.xs"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Styles a horizontal attachment at the middle size.
|
|
63
|
+
*/
|
|
64
|
+
const recipe$4 = defineSlotRecipe({
|
|
65
|
+
base: {
|
|
66
|
+
actions: {
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
display: "flex",
|
|
69
|
+
flexShrink: "0",
|
|
70
|
+
gap: dense("{spacing.gap.xs}")
|
|
71
|
+
},
|
|
72
|
+
content: {
|
|
73
|
+
display: "flex",
|
|
74
|
+
flex: "1",
|
|
75
|
+
flexDirection: "column",
|
|
76
|
+
minInlineSize: "0"
|
|
77
|
+
},
|
|
78
|
+
description: {
|
|
79
|
+
...truncate(),
|
|
80
|
+
color: "fg.muted"
|
|
81
|
+
},
|
|
82
|
+
group: {
|
|
83
|
+
display: "flex",
|
|
84
|
+
gap: dense("{spacing.gap.sm}"),
|
|
85
|
+
listStyle: "none",
|
|
86
|
+
margin: "0",
|
|
87
|
+
minInlineSize: "0",
|
|
88
|
+
padding: "0"
|
|
89
|
+
},
|
|
90
|
+
media: {
|
|
91
|
+
"& > img": {
|
|
92
|
+
blockSize: "full",
|
|
93
|
+
inlineSize: "full",
|
|
94
|
+
objectFit: "cover"
|
|
95
|
+
},
|
|
96
|
+
"& > svg": {
|
|
97
|
+
blockSize: "50%",
|
|
98
|
+
inlineSize: "50%"
|
|
99
|
+
},
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
aspectRatio: "square",
|
|
102
|
+
backgroundColor: "bg.muted",
|
|
103
|
+
borderRadius: "l1",
|
|
104
|
+
color: "fg.muted",
|
|
105
|
+
display: "flex",
|
|
106
|
+
flexShrink: "0",
|
|
107
|
+
justifyContent: "center",
|
|
108
|
+
overflow: "hidden"
|
|
109
|
+
},
|
|
110
|
+
root: {
|
|
111
|
+
"&[data-state=error]": {
|
|
112
|
+
[`& ${DESCRIPTION}, & ${MEDIA}`]: { color: "fg.error" },
|
|
113
|
+
borderColor: "border.error"
|
|
114
|
+
},
|
|
115
|
+
"&[data-state=idle]": { borderStyle: "dashed" },
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
backgroundColor: "bg.panel",
|
|
118
|
+
borderColor: "border",
|
|
119
|
+
borderRadius: "l2",
|
|
120
|
+
borderStyle: "solid",
|
|
121
|
+
borderWidth: "hairline",
|
|
122
|
+
display: "flex",
|
|
123
|
+
maxInlineSize: "full",
|
|
124
|
+
minInlineSize: "0",
|
|
125
|
+
position: "relative"
|
|
126
|
+
},
|
|
127
|
+
title: {
|
|
128
|
+
...truncate(),
|
|
129
|
+
color: "fg",
|
|
130
|
+
fontWeight: "medium"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
className: CLASS$1,
|
|
134
|
+
compoundVariants: [{
|
|
135
|
+
css: { media: { inlineSize: "full" } },
|
|
136
|
+
name: "tiled",
|
|
137
|
+
orientation: "vertical"
|
|
138
|
+
}],
|
|
139
|
+
defaultVariants: {
|
|
140
|
+
orientation: "horizontal",
|
|
141
|
+
size: "md"
|
|
142
|
+
},
|
|
143
|
+
jsx: [/^Attachment\.\w+$/u],
|
|
144
|
+
slots: [
|
|
145
|
+
"group",
|
|
146
|
+
"root",
|
|
147
|
+
"media",
|
|
148
|
+
"content",
|
|
149
|
+
"title",
|
|
150
|
+
"description",
|
|
151
|
+
"actions"
|
|
152
|
+
],
|
|
153
|
+
variants: {
|
|
154
|
+
/**
|
|
155
|
+
* Whether an attachment is a row or a tile, and whether a group stacks or wraps them.
|
|
156
|
+
*/
|
|
157
|
+
orientation: {
|
|
158
|
+
horizontal: {
|
|
159
|
+
group: { flexDirection: "column" },
|
|
160
|
+
root: {
|
|
161
|
+
flexDirection: "row",
|
|
162
|
+
minInlineSize: "min({sizes.40}, 100%)"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
vertical: {
|
|
166
|
+
actions: {
|
|
167
|
+
insetBlockStart: dense("{spacing.inset.xs}"),
|
|
168
|
+
insetInlineEnd: dense("{spacing.inset.xs}"),
|
|
169
|
+
position: "absolute"
|
|
170
|
+
},
|
|
171
|
+
group: {
|
|
172
|
+
flexDirection: "row",
|
|
173
|
+
flexWrap: "wrap"
|
|
174
|
+
},
|
|
175
|
+
root: {
|
|
176
|
+
alignItems: "stretch",
|
|
177
|
+
flexDirection: "column",
|
|
178
|
+
inlineSize: "{sizes.32}"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
size: onSlots({
|
|
183
|
+
description: sizeVariants((size) => ({ textStyle: METRICS$2[size].detail }), SIZES$2),
|
|
184
|
+
media: sizeVariants((size) => ({ inlineSize: dense(`{sizes.control.${METRICS$2[size].media}}`) }), SIZES$2),
|
|
185
|
+
root: sizeVariants((size) => ({
|
|
186
|
+
gap: dense(`{spacing.gap.${METRICS$2[size].gap}}`),
|
|
187
|
+
padding: dense(`{spacing.inset.${METRICS$2[size].inset}}`)
|
|
188
|
+
}), SIZES$2),
|
|
189
|
+
title: sizeVariants((size) => ({ textStyle: METRICS$2[size].text }), SIZES$2)
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/composer/recipe.ts
|
|
195
|
+
/**
|
|
196
|
+
* Declares the composer's slot recipe: the box a reader writes a message in, with a strip for the
|
|
197
|
+
* message it replies to, the attachments, the text and a row of controls.
|
|
198
|
+
*
|
|
199
|
+
* @remarks
|
|
200
|
+
* The root is the theme's wrapped field, so its edge, surface, focus ring and states come from
|
|
201
|
+
* the textarea inside it, as in the input group. The text is a bare `textarea` that grows with
|
|
202
|
+
* its content through `field-sizing: content` and stops at the number of lines the input writes
|
|
203
|
+
* as {@link ROWS}, after which it scrolls. While files are dragged over it the box takes the focus
|
|
204
|
+
* edge. The submit control is pushed to the end of the row of controls. The strip sizes its
|
|
205
|
+
* leading glyph to its text and pushes a control after its words, such as a dismiss button, to
|
|
206
|
+
* its end.
|
|
207
|
+
*/
|
|
208
|
+
/**
|
|
209
|
+
* Custom property the input writes: the most lines it grows to before it scrolls.
|
|
210
|
+
*/
|
|
211
|
+
const ROWS = "--composer-rows";
|
|
212
|
+
/**
|
|
213
|
+
* Custom property the size axis sets: the inset around the text and the controls.
|
|
214
|
+
*/
|
|
215
|
+
const INSET = "--composer-inset";
|
|
216
|
+
/**
|
|
217
|
+
* The inset, read from {@link INSET}.
|
|
218
|
+
*/
|
|
219
|
+
const PADDED = `var(${INSET})`;
|
|
220
|
+
/**
|
|
221
|
+
* Sizes the composer offers.
|
|
222
|
+
*/
|
|
223
|
+
const SIZES$1 = [
|
|
224
|
+
"sm",
|
|
225
|
+
"md",
|
|
226
|
+
"lg"
|
|
227
|
+
];
|
|
228
|
+
/**
|
|
229
|
+
* Maps each size to its inset and text style.
|
|
230
|
+
*/
|
|
231
|
+
const METRICS$1 = {
|
|
232
|
+
lg: {
|
|
233
|
+
inset: "md",
|
|
234
|
+
text: "body.lg"
|
|
235
|
+
},
|
|
236
|
+
md: {
|
|
237
|
+
inset: "sm",
|
|
238
|
+
text: "body.md"
|
|
239
|
+
},
|
|
240
|
+
sm: {
|
|
241
|
+
inset: "xs",
|
|
242
|
+
text: "body.sm"
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Styles an outlined composer at the middle size.
|
|
247
|
+
*/
|
|
248
|
+
const recipe$3 = defineSlotRecipe({
|
|
249
|
+
base: {
|
|
250
|
+
attachments: {
|
|
251
|
+
paddingBlockStart: PADDED,
|
|
252
|
+
paddingInline: PADDED
|
|
253
|
+
},
|
|
254
|
+
context: {
|
|
255
|
+
"& > :not(button) + button": { marginInlineStart: "auto" },
|
|
256
|
+
"& > svg": {
|
|
257
|
+
blockSize: "1em",
|
|
258
|
+
flexShrink: "0",
|
|
259
|
+
inlineSize: "1em"
|
|
260
|
+
},
|
|
261
|
+
alignItems: "center",
|
|
262
|
+
backgroundColor: "bg.subtle",
|
|
263
|
+
borderStartEndRadius: "inherit",
|
|
264
|
+
borderStartStartRadius: "inherit",
|
|
265
|
+
color: "fg.muted",
|
|
266
|
+
display: "flex",
|
|
267
|
+
gap: dense("{spacing.gap.sm}"),
|
|
268
|
+
minInlineSize: "0",
|
|
269
|
+
paddingBlock: dense("{spacing.gap.xs}"),
|
|
270
|
+
paddingInline: PADDED,
|
|
271
|
+
textStyle: "label.sm"
|
|
272
|
+
},
|
|
273
|
+
detail: {
|
|
274
|
+
color: "fg.muted",
|
|
275
|
+
textStyle: "label.xs"
|
|
276
|
+
},
|
|
277
|
+
input: {
|
|
278
|
+
_placeholder: { color: "fg.muted" },
|
|
279
|
+
appearance: "none",
|
|
280
|
+
background: "transparent",
|
|
281
|
+
borderStyle: "none",
|
|
282
|
+
color: "fg",
|
|
283
|
+
display: "block",
|
|
284
|
+
fieldSizing: "content",
|
|
285
|
+
font: "inherit",
|
|
286
|
+
inlineSize: "full",
|
|
287
|
+
letterSpacing: "inherit",
|
|
288
|
+
maxBlockSize: `calc(var(${ROWS}) * 1lh + ${PADDED} * 2)`,
|
|
289
|
+
outline: "none",
|
|
290
|
+
padding: PADDED,
|
|
291
|
+
resize: "none"
|
|
292
|
+
},
|
|
293
|
+
root: {
|
|
294
|
+
...wrappedField(),
|
|
295
|
+
"&[data-dragging]": { [FIELD_EDGE]: "{colors.border.focus}" },
|
|
296
|
+
borderRadius: "l3",
|
|
297
|
+
display: "flex",
|
|
298
|
+
flexDirection: "column",
|
|
299
|
+
inlineSize: "full",
|
|
300
|
+
margin: "0",
|
|
301
|
+
minInlineSize: "0",
|
|
302
|
+
position: "relative"
|
|
303
|
+
},
|
|
304
|
+
submit: { marginInlineStart: "auto" },
|
|
305
|
+
suggestion: {
|
|
306
|
+
"&[aria-selected=true]": {
|
|
307
|
+
_highContrast: {
|
|
308
|
+
background: "Highlight",
|
|
309
|
+
color: "HighlightText",
|
|
310
|
+
forcedColorAdjust: "none"
|
|
311
|
+
},
|
|
312
|
+
layerStyle: "fill.muted"
|
|
313
|
+
},
|
|
314
|
+
alignItems: "baseline",
|
|
315
|
+
borderRadius: "l1",
|
|
316
|
+
cursor: "option",
|
|
317
|
+
display: "flex",
|
|
318
|
+
gap: dense("{spacing.gap.sm}"),
|
|
319
|
+
paddingBlock: dense("{spacing.gap.xs}"),
|
|
320
|
+
paddingInline: dense("{spacing.inset.sm}"),
|
|
321
|
+
textStyle: "label.sm"
|
|
322
|
+
},
|
|
323
|
+
suggestions: {
|
|
324
|
+
...surface("md"),
|
|
325
|
+
display: "flex",
|
|
326
|
+
flexDirection: "column",
|
|
327
|
+
insetBlockEnd: "100%",
|
|
328
|
+
insetInline: "0",
|
|
329
|
+
marginBlockEnd: dense("{spacing.gap.xs}"),
|
|
330
|
+
padding: dense("{spacing.inset.xs}"),
|
|
331
|
+
position: "absolute",
|
|
332
|
+
zIndex: "dropdown"
|
|
333
|
+
},
|
|
334
|
+
toolbar: {
|
|
335
|
+
alignItems: "center",
|
|
336
|
+
display: "flex",
|
|
337
|
+
gap: dense("{spacing.gap.xs}"),
|
|
338
|
+
paddingBlockEnd: PADDED,
|
|
339
|
+
paddingInline: PADDED
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
className: "composer",
|
|
343
|
+
defaultVariants: {
|
|
344
|
+
size: "md",
|
|
345
|
+
variant: "outline"
|
|
346
|
+
},
|
|
347
|
+
jsx: [/^Composer\.\w+$/u],
|
|
348
|
+
slots: [
|
|
349
|
+
"root",
|
|
350
|
+
"context",
|
|
351
|
+
"attachments",
|
|
352
|
+
"input",
|
|
353
|
+
"suggestions",
|
|
354
|
+
"suggestion",
|
|
355
|
+
"detail",
|
|
356
|
+
"toolbar",
|
|
357
|
+
"submit"
|
|
358
|
+
],
|
|
359
|
+
variants: {
|
|
360
|
+
size: onSlots({
|
|
361
|
+
input: sizeVariants((size) => ({ textStyle: METRICS$1[size].text }), SIZES$1),
|
|
362
|
+
root: sizeVariants((size) => ({ [INSET]: dense(`{spacing.inset.${METRICS$1[size].inset}}`) }), SIZES$1)
|
|
363
|
+
}),
|
|
364
|
+
variant: onSlot("root", wrappedFieldVariants())
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/conversation/recipe.ts
|
|
369
|
+
/**
|
|
370
|
+
* Declares the conversation's slot recipe: the transcript of turns, the control that jumps to the
|
|
371
|
+
* latest message, and the row that shows another person typing.
|
|
372
|
+
*
|
|
373
|
+
* @remarks
|
|
374
|
+
* The root and the viewport are the primitives scroll area's, so the bar, the focus ring and the
|
|
375
|
+
* keyboard scrolling are its own. The content stacks the turns in a column. The jump trigger is
|
|
376
|
+
* placed over the turns at the middle of the transcript's bottom edge. The viewport contains its
|
|
377
|
+
* overscroll, so a wheel at either end of the transcript leaves the page where it is. The typing
|
|
378
|
+
* row's three dots pulse at the theme's ambient pace, each 200ms after the one before, and rest
|
|
379
|
+
* under reduced motion. The dots fill with `CanvasText` under forced colors, which remove the
|
|
380
|
+
* dots' `currentcolor` fill in Chromium.
|
|
381
|
+
*/
|
|
382
|
+
/**
|
|
383
|
+
* Styles a transcript of turns 16px apart at the foundation's metrics.
|
|
384
|
+
*/
|
|
385
|
+
const recipe$2 = defineSlotRecipe({
|
|
386
|
+
base: {
|
|
387
|
+
content: {
|
|
388
|
+
display: "flex",
|
|
389
|
+
flexDirection: "column",
|
|
390
|
+
gap: dense("{spacing.gap.lg}")
|
|
391
|
+
},
|
|
392
|
+
dots: {
|
|
393
|
+
"& > span": {
|
|
394
|
+
_highContrast: {
|
|
395
|
+
background: "CanvasText",
|
|
396
|
+
forcedColorAdjust: "none"
|
|
397
|
+
},
|
|
398
|
+
_motionSafe: {
|
|
399
|
+
"&:nth-child(2)": { animationDelay: "{durations.moderate}" },
|
|
400
|
+
"&:nth-child(3)": { animationDelay: "{durations.slower}" }
|
|
401
|
+
},
|
|
402
|
+
animationStyle: "pulse",
|
|
403
|
+
backgroundColor: "currentcolor",
|
|
404
|
+
blockSize: "0.375em",
|
|
405
|
+
borderRadius: "full",
|
|
406
|
+
inlineSize: "0.375em"
|
|
407
|
+
},
|
|
408
|
+
alignItems: "center",
|
|
409
|
+
display: "inline-flex",
|
|
410
|
+
gap: "0.25em"
|
|
411
|
+
},
|
|
412
|
+
jumpTrigger: {
|
|
413
|
+
insetBlockEnd: dense("{spacing.inset.md}"),
|
|
414
|
+
left: "50%",
|
|
415
|
+
position: "absolute",
|
|
416
|
+
translate: "-50% 0",
|
|
417
|
+
zIndex: "docked"
|
|
418
|
+
},
|
|
419
|
+
root: { position: "relative" },
|
|
420
|
+
typing: {
|
|
421
|
+
alignItems: "center",
|
|
422
|
+
color: "fg.muted",
|
|
423
|
+
display: "flex",
|
|
424
|
+
gap: dense("{spacing.gap.sm}"),
|
|
425
|
+
textStyle: "label.sm"
|
|
426
|
+
},
|
|
427
|
+
viewport: { overscrollBehavior: "contain" }
|
|
428
|
+
},
|
|
429
|
+
className: "conversation",
|
|
430
|
+
jsx: [/^Conversation\.\w+$/u],
|
|
431
|
+
slots: [
|
|
432
|
+
"root",
|
|
433
|
+
"viewport",
|
|
434
|
+
"content",
|
|
435
|
+
"jumpTrigger",
|
|
436
|
+
"typing",
|
|
437
|
+
"dots"
|
|
438
|
+
]
|
|
439
|
+
});
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/message/recipe.ts
|
|
442
|
+
/**
|
|
443
|
+
* Declares the message's slot recipe, which lays out one turn of a conversation.
|
|
444
|
+
*
|
|
445
|
+
* @remarks
|
|
446
|
+
* A turn is the run of messages one person sent together. It has one avatar, one header and a
|
|
447
|
+
* bubble per message. `align="end"` puts the reader's own turns at the end of the line: the row
|
|
448
|
+
* runs the other way and the column aligns its bubbles to the end. A bubble takes the flat looks,
|
|
449
|
+
* because it is read and never pressed. It is at most 80% of the column wide, so the two sides of
|
|
450
|
+
* a conversation are apart. The plain look has no fill and no padding and takes the whole column,
|
|
451
|
+
* for an answer that reads as a document. A bubble has a hairline outline under forced colors,
|
|
452
|
+
* where every fill is replaced.
|
|
453
|
+
*/
|
|
454
|
+
/**
|
|
455
|
+
* Class name of the recipe, which a selector across parts reads.
|
|
456
|
+
*/
|
|
457
|
+
const CLASS = "message";
|
|
458
|
+
/**
|
|
459
|
+
* Selects the actions of a turn, from a rule on the root.
|
|
460
|
+
*/
|
|
461
|
+
const ACTIONS = `.${CLASS}__actions`;
|
|
462
|
+
/**
|
|
463
|
+
* Sizes the message offers.
|
|
464
|
+
*/
|
|
465
|
+
const SIZES = [
|
|
466
|
+
"sm",
|
|
467
|
+
"md",
|
|
468
|
+
"lg"
|
|
469
|
+
];
|
|
470
|
+
/**
|
|
471
|
+
* Maps each size to its steps and text styles.
|
|
472
|
+
*/
|
|
473
|
+
const METRICS = {
|
|
474
|
+
lg: {
|
|
475
|
+
gap: "lg",
|
|
476
|
+
inline: "md",
|
|
477
|
+
meta: "label.sm",
|
|
478
|
+
padding: "md",
|
|
479
|
+
text: "body.lg"
|
|
480
|
+
},
|
|
481
|
+
md: {
|
|
482
|
+
gap: "md",
|
|
483
|
+
inline: "sm",
|
|
484
|
+
meta: "label.xs",
|
|
485
|
+
padding: "sm",
|
|
486
|
+
text: "body.md"
|
|
487
|
+
},
|
|
488
|
+
sm: {
|
|
489
|
+
gap: "sm",
|
|
490
|
+
inline: "xs",
|
|
491
|
+
meta: "label.xs",
|
|
492
|
+
padding: "xs",
|
|
493
|
+
text: "body.sm"
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Ends of the line a turn can belong to, in the order a documentation page shows them.
|
|
498
|
+
*/
|
|
499
|
+
const SIDES = ["start", "end"];
|
|
500
|
+
/**
|
|
501
|
+
* Styles a turn at the start of the line in the neutral palette's subtle look, at the middle size.
|
|
502
|
+
*/
|
|
503
|
+
const recipe$1 = defineSlotRecipe({
|
|
504
|
+
base: {
|
|
505
|
+
actions: {
|
|
506
|
+
alignItems: "center",
|
|
507
|
+
display: "flex",
|
|
508
|
+
gap: dense("{spacing.gap.xs}")
|
|
509
|
+
},
|
|
510
|
+
avatar: {
|
|
511
|
+
alignItems: "center",
|
|
512
|
+
display: "flex",
|
|
513
|
+
flexShrink: "0"
|
|
514
|
+
},
|
|
515
|
+
bubble: {
|
|
516
|
+
_highContrast: {
|
|
517
|
+
outlineColor: "CanvasText",
|
|
518
|
+
outlineStyle: "solid",
|
|
519
|
+
outlineWidth: "hairline"
|
|
520
|
+
},
|
|
521
|
+
"&[data-failed]": { colorPalette: "error" },
|
|
522
|
+
borderRadius: "l3",
|
|
523
|
+
maxInlineSize: "80%",
|
|
524
|
+
minInlineSize: "0",
|
|
525
|
+
overflowWrap: "anywhere"
|
|
526
|
+
},
|
|
527
|
+
content: {
|
|
528
|
+
display: "flex",
|
|
529
|
+
flex: "1",
|
|
530
|
+
flexDirection: "column",
|
|
531
|
+
gap: dense("{spacing.gap.xs}"),
|
|
532
|
+
minInlineSize: "0"
|
|
533
|
+
},
|
|
534
|
+
footer: {
|
|
535
|
+
alignItems: "center",
|
|
536
|
+
color: "fg.muted",
|
|
537
|
+
display: "flex",
|
|
538
|
+
flexWrap: "wrap",
|
|
539
|
+
gap: dense("{spacing.gap.sm}")
|
|
540
|
+
},
|
|
541
|
+
header: {
|
|
542
|
+
alignItems: "baseline",
|
|
543
|
+
color: "fg.muted",
|
|
544
|
+
display: "flex",
|
|
545
|
+
flexWrap: "wrap",
|
|
546
|
+
gap: dense("{spacing.gap.sm}")
|
|
547
|
+
},
|
|
548
|
+
root: {
|
|
549
|
+
alignItems: "flex-start",
|
|
550
|
+
display: "flex",
|
|
551
|
+
inlineSize: "full",
|
|
552
|
+
minInlineSize: "0"
|
|
553
|
+
},
|
|
554
|
+
status: {
|
|
555
|
+
"&[data-status=failed]": { color: "fg.error" },
|
|
556
|
+
"&[data-status=read]": {
|
|
557
|
+
color: "colorPalette.fg",
|
|
558
|
+
colorPalette: "primary"
|
|
559
|
+
},
|
|
560
|
+
"& > svg": {
|
|
561
|
+
blockSize: "1em",
|
|
562
|
+
flexShrink: "0",
|
|
563
|
+
inlineSize: "1em"
|
|
564
|
+
},
|
|
565
|
+
alignItems: "center",
|
|
566
|
+
display: "inline-flex",
|
|
567
|
+
gap: "0.25em"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
className: CLASS,
|
|
571
|
+
compoundVariants: [{
|
|
572
|
+
css: { bubble: {
|
|
573
|
+
_highContrast: { outlineStyle: "none" },
|
|
574
|
+
maxInlineSize: "full",
|
|
575
|
+
paddingBlock: "0",
|
|
576
|
+
paddingInline: "0"
|
|
577
|
+
} },
|
|
578
|
+
look: "plain",
|
|
579
|
+
name: "document"
|
|
580
|
+
}],
|
|
581
|
+
defaultVariants: {
|
|
582
|
+
align: "start",
|
|
583
|
+
look: "subtle",
|
|
584
|
+
palette: "neutral",
|
|
585
|
+
reveal: "hover",
|
|
586
|
+
size: "md"
|
|
587
|
+
},
|
|
588
|
+
jsx: [/^Message\.\w+$/u],
|
|
589
|
+
slots: [
|
|
590
|
+
"root",
|
|
591
|
+
"avatar",
|
|
592
|
+
"content",
|
|
593
|
+
"header",
|
|
594
|
+
"bubble",
|
|
595
|
+
"footer",
|
|
596
|
+
"status",
|
|
597
|
+
"actions"
|
|
598
|
+
],
|
|
599
|
+
staticCss: [{ palette: [...PALETTES] }],
|
|
600
|
+
variants: {
|
|
601
|
+
/**
|
|
602
|
+
* Which end of the line the turn belongs to: the row runs the other way at the end, and the
|
|
603
|
+
* column aligns its bubbles to that end.
|
|
604
|
+
*/
|
|
605
|
+
align: onSlots({
|
|
606
|
+
content: axis(SIDES, (side) => ({ alignItems: side === "start" ? "flex-start" : "flex-end" }))(),
|
|
607
|
+
root: axis(SIDES, (side) => ({ flexDirection: side === "start" ? "row" : "row-reverse" }))()
|
|
608
|
+
}),
|
|
609
|
+
look: onSlot("bubble", flatVariants()),
|
|
610
|
+
palette: onSlot("root", paletteVariants()),
|
|
611
|
+
/**
|
|
612
|
+
* When the actions of the turn are visible.
|
|
613
|
+
*
|
|
614
|
+
* @remarks
|
|
615
|
+
* With `hover`, the actions are transparent until a pointer is over the turn or focus is
|
|
616
|
+
* inside it. A coarse pointer cannot hover, so under one they are always opaque. They are in
|
|
617
|
+
* the tab order under both values, so Tab reveals them.
|
|
618
|
+
*/
|
|
619
|
+
reveal: {
|
|
620
|
+
always: { actions: { opacity: "1" } },
|
|
621
|
+
hover: {
|
|
622
|
+
actions: {
|
|
623
|
+
_touch: { opacity: "1" },
|
|
624
|
+
opacity: "0",
|
|
625
|
+
transitionDuration: "press",
|
|
626
|
+
transitionProperty: "common",
|
|
627
|
+
transitionTimingFunction: "press"
|
|
628
|
+
},
|
|
629
|
+
root: { [`&:focus-within ${ACTIONS}, &:hover ${ACTIONS}`]: { opacity: "1" } }
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
size: onSlots({
|
|
633
|
+
bubble: sizeVariants((size) => ({
|
|
634
|
+
paddingBlock: dense(`{spacing.gap.${METRICS[size].padding}}`),
|
|
635
|
+
paddingInline: dense(`{spacing.inset.${METRICS[size].inline}}`),
|
|
636
|
+
textStyle: METRICS[size].text
|
|
637
|
+
}), SIZES),
|
|
638
|
+
footer: sizeVariants((size) => ({ textStyle: METRICS[size].meta }), SIZES),
|
|
639
|
+
header: sizeVariants((size) => ({ textStyle: METRICS[size].meta }), SIZES),
|
|
640
|
+
root: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${METRICS[size].gap}}`) }), SIZES)
|
|
641
|
+
})
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/reactions/recipe.ts
|
|
646
|
+
/**
|
|
647
|
+
* Declares the reactions' slot recipe: a row of reactions under a message, and the choices of the
|
|
648
|
+
* picker that adds one.
|
|
649
|
+
*
|
|
650
|
+
* @remarks
|
|
651
|
+
* Each reaction and each choice is the actions `Button`, so its looks, its pressed state and its
|
|
652
|
+
* focus ring are the button's own. The recipe lays out the row and the choices, and sets the
|
|
653
|
+
* glyphs and counts inside the buttons. The root is a `fieldset`, whose border, margin and
|
|
654
|
+
* padding the recipe removes. A count is set in tabular figures, so a count that changes keeps
|
|
655
|
+
* its width.
|
|
656
|
+
*/
|
|
657
|
+
/**
|
|
658
|
+
* Styles a row of reactions 4px apart at the foundation's metrics.
|
|
659
|
+
*/
|
|
660
|
+
const recipe = defineSlotRecipe({
|
|
661
|
+
base: {
|
|
662
|
+
choices: {
|
|
663
|
+
display: "flex",
|
|
664
|
+
flexWrap: "wrap",
|
|
665
|
+
gap: dense("{spacing.gap.xs}")
|
|
666
|
+
},
|
|
667
|
+
count: { fontVariantNumeric: "tabular-nums" },
|
|
668
|
+
glyph: {
|
|
669
|
+
display: "inline-flex",
|
|
670
|
+
lineHeight: "1"
|
|
671
|
+
},
|
|
672
|
+
root: {
|
|
673
|
+
alignItems: "center",
|
|
674
|
+
borderWidth: "0",
|
|
675
|
+
display: "flex",
|
|
676
|
+
flexWrap: "wrap",
|
|
677
|
+
gap: dense("{spacing.gap.xs}"),
|
|
678
|
+
margin: "0",
|
|
679
|
+
minInlineSize: "0",
|
|
680
|
+
padding: "0"
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
className: "reactions",
|
|
684
|
+
jsx: [/^Reactions\.\w+$/u],
|
|
685
|
+
slots: [
|
|
686
|
+
"root",
|
|
687
|
+
"glyph",
|
|
688
|
+
"count",
|
|
689
|
+
"choices"
|
|
690
|
+
]
|
|
691
|
+
});
|
|
692
|
+
//#endregion
|
|
693
|
+
export { recipe$3 as a, ROWS as i, recipe$1 as n, recipe$4 as o, recipe$2 as r, recipe as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/theme.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Registers every recipe in the package in the preset an application's style compiler installs.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The list is written by hand. `theme.spec.ts` fails when a recipe file is missing from it, so no
|
|
7
|
+
* generator writes it.
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: import("@stealthscale/theme/authoring").Preset;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { _default as default };
|