@stealthscale/component-feedback 0.1.0 → 0.2.2

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.
@@ -0,0 +1,1290 @@
1
+ import { PALETTES, SERIES, axis, below, cornerVariants, defineRecipe, defineSlotRecipe, dense, flatVariants, iconSizes, interactive, motionVariants, onSlot, onSlots, paletteVariants, sizeVariants, statusEmitted, statusVariants, touchTarget } from "@stealthscale/theme/authoring";
2
+ //#region src/alert/recipe.ts
3
+ /**
4
+ * Declares the alert slot recipe for a notice with an icon, a title, a description, trailing
5
+ * controls and a close trigger.
6
+ *
7
+ * @remarks
8
+ * `status` sets `colorPalette` and nothing else, so it combines freely with `variant` and `size`
9
+ * and a theme retints every alert through the palettes. The title states the severity in words,
10
+ * because WCAG 1.4.1 rejects a status told by color alone. `variant` reads the `flat.*` layer
11
+ * styles, whose fill and ink are the pairs the contrast gate measures. The indicator and the
12
+ * close trigger inherit the root's ink, so a solid alert draws both in the contrast ink. The
13
+ * recipe has no `effect` axis, because `motion` already animates the root.
14
+ */
15
+ /**
16
+ * Sizes of the icon box and the root's gap, inset and text style.
17
+ */
18
+ const SIZES$3 = [
19
+ "sm",
20
+ "md",
21
+ "lg"
22
+ ];
23
+ /**
24
+ * Maps each edge to the border that draws it, in the palette's solid at the indicator width.
25
+ */
26
+ const EDGES = {
27
+ bottom: {
28
+ borderBlockEndColor: "colorPalette.solid",
29
+ borderBlockEndWidth: "indicator"
30
+ },
31
+ end: {
32
+ borderInlineEndColor: "colorPalette.solid",
33
+ borderInlineEndWidth: "indicator"
34
+ },
35
+ top: {
36
+ borderBlockStartColor: "colorPalette.solid",
37
+ borderBlockStartWidth: "indicator"
38
+ }
39
+ };
40
+ /**
41
+ * Looks whose layer style sets the `border-width` and `border-color` shorthands.
42
+ */
43
+ const BORDERED = ["outline", "surface"];
44
+ /**
45
+ * Alert slot recipe, a subtle info notice at the md size by default.
46
+ *
47
+ * @remarks
48
+ * The indicator, the content and the trailing controls are centred on the notice's full height,
49
+ * so the icon aligns with the middle of a text block of any length.
50
+ */
51
+ const recipe$8 = defineSlotRecipe({
52
+ base: {
53
+ aside: {
54
+ alignItems: "center",
55
+ display: "flex",
56
+ flex: "0 0 auto"
57
+ },
58
+ /**
59
+ * The close trigger is a square of 1.5em, at least 24px, with a 1em glyph. A negative end
60
+ * margin of half the box less the glyph puts the glyph on the padding edge, level with the
61
+ * indicator at the start: 12, 16 and 20px from the edge at `sm`, `md` and `lg`. Its focus ring
62
+ * is drawn inside, because a ring outside it falls on the alert's fill.
63
+ */
64
+ closeTrigger: {
65
+ ...interactive(),
66
+ ...touchTarget(),
67
+ focusVisibleRing: "inside",
68
+ _hover: { background: "colorPalette.emphasized" },
69
+ "& > svg": { boxSize: "1em" },
70
+ alignItems: "center",
71
+ appearance: "none",
72
+ background: "transparent",
73
+ borderRadius: "l1",
74
+ borderStyle: "none",
75
+ boxSize: "max({sizes.6}, 1.5em)",
76
+ color: "currentcolor",
77
+ display: "inline-flex",
78
+ flex: "0 0 auto",
79
+ justifyContent: "center",
80
+ marginInlineEnd: "calc((1em - max({sizes.6}, 1.5em)) / 2)",
81
+ padding: "0"
82
+ },
83
+ content: {
84
+ display: "flex",
85
+ flex: "1",
86
+ minInlineSize: "0"
87
+ },
88
+ description: { color: "inherit" },
89
+ indicator: {
90
+ "& > svg": { boxSize: "100%" },
91
+ alignItems: "center",
92
+ display: "inline-flex",
93
+ flex: "0 0 auto"
94
+ },
95
+ /**
96
+ * The root draws a hairline outline in forced colors, where the browser replaces the fill and
97
+ * the solid, subtle and plain looks have no border to mark the box.
98
+ */
99
+ root: {
100
+ _highContrast: {
101
+ outlineColor: "CanvasText",
102
+ outlineOffset: "calc({borderWidths.hairline} * -1)",
103
+ outlineStyle: "solid",
104
+ outlineWidth: "hairline"
105
+ },
106
+ alignItems: "center",
107
+ display: "flex",
108
+ inlineSize: "full"
109
+ },
110
+ title: { fontWeight: "medium" }
111
+ },
112
+ className: "alert",
113
+ compoundVariants: [
114
+ (
115
+ /**
116
+ * The close trigger on a solid alert hovers to a tint of the contrast ink and draws its focus
117
+ * ring in the contrast ink. The `emphasized` role is lighter than the solid fill, and the
118
+ * `focusRing` role is as dark as it.
119
+ */
120
+ {
121
+ css: { closeTrigger: {
122
+ _hover: { background: "colorPalette.contrast/20" },
123
+ focusRingColor: "colorPalette.contrast"
124
+ } },
125
+ name: "contrasted",
126
+ variant: "solid"
127
+ }),
128
+ (
129
+ /**
130
+ * The outline and surface looks restate the edge. Their layer style sets the border shorthands
131
+ * in the `variant` axis, which the compiler emits after `edge` and which reset the edge's
132
+ * width and color.
133
+ */
134
+ {
135
+ css: { root: EDGES.top },
136
+ edge: "top",
137
+ name: "edged-top",
138
+ variant: BORDERED
139
+ }),
140
+ {
141
+ css: { root: EDGES.bottom },
142
+ edge: "bottom",
143
+ name: "edged-bottom",
144
+ variant: BORDERED
145
+ },
146
+ {
147
+ css: { root: EDGES.end },
148
+ edge: "end",
149
+ name: "edged-end",
150
+ variant: BORDERED
151
+ }
152
+ ],
153
+ defaultVariants: {
154
+ layout: "stacked",
155
+ radius: "l3",
156
+ size: "md",
157
+ status: "info",
158
+ variant: "subtle"
159
+ },
160
+ jsx: [/^Alert(\.\w+)?$/u],
161
+ slots: [
162
+ "root",
163
+ "indicator",
164
+ "content",
165
+ "title",
166
+ "description",
167
+ "aside",
168
+ "closeTrigger"
169
+ ],
170
+ staticCss: [statusEmitted(), { status: ["neutral"] }],
171
+ variants: {
172
+ /**
173
+ * The edge with a rule in the palette's solid color. Unset by default.
174
+ *
175
+ * @remarks
176
+ * The rule is a border on the root, so it follows the root's rounded corners, tapers into
177
+ * the side border and shows under forced colors. The theme's `indicator.*` layer styles are
178
+ * not used, because their bar is a 2px pseudo-element whose corner radius the browser scales
179
+ * down to fit. `end` is logical: the right edge in a left-to-right document and the left edge
180
+ * in a right-to-left one.
181
+ */
182
+ edge: {
183
+ top: { root: EDGES.top },
184
+ bottom: { root: EDGES.bottom },
185
+ end: { root: EDGES.end }
186
+ },
187
+ /**
188
+ * Whether the title is above the description or on the same line with it.
189
+ */
190
+ layout: {
191
+ inline: { content: {
192
+ alignItems: "baseline",
193
+ columnGap: dense("{spacing.gap.xs}"),
194
+ flexWrap: "wrap"
195
+ } },
196
+ stacked: { content: {
197
+ flexDirection: "column",
198
+ rowGap: dense("{spacing.gap.xs}")
199
+ } }
200
+ },
201
+ motion: onSlot("root", motionVariants([
202
+ "fade",
203
+ "rise",
204
+ "reveal"
205
+ ])),
206
+ radius: onSlot("root", cornerVariants()),
207
+ size: onSlots({
208
+ indicator: iconSizes(SIZES$3),
209
+ root: sizeVariants((size) => ({
210
+ textStyle: `body.${size}`,
211
+ gap: dense(`{spacing.gap.${size}}`),
212
+ padding: dense(`{spacing.inset.${size}}`)
213
+ }), SIZES$3)
214
+ }),
215
+ /**
216
+ * The severity of the alert. It sets `colorPalette` and nothing else.
217
+ */
218
+ status: {
219
+ ...onSlot("root", statusVariants()),
220
+ neutral: { root: { colorPalette: "neutral" } }
221
+ },
222
+ /**
223
+ * The fill and edge of the alert, from the `flat.*` layer styles.
224
+ */
225
+ variant: onSlot("root", flatVariants())
226
+ }
227
+ });
228
+ //#endregion
229
+ //#region src/empty-state/recipe.ts
230
+ /**
231
+ * Declares the empty state slot recipe for the panel a surface renders when it has no content.
232
+ *
233
+ * @remarks
234
+ * `size` sets the panel's inset, the column's gap, the mark's box and the title's text style
235
+ * together. The mark is 32, 40 and 50px at `sm`, `md` and `lg`, above a 16, 18 and 20px title, so
236
+ * the mark is always larger than the title's line. The mark has twice the column's gap below it,
237
+ * so the title and the description read as one group. The description stays at `body.sm` at every
238
+ * size. The recipe has no `palette` axis, because the panel is muted, and no `effect` axis,
239
+ * because it has no fill or border.
240
+ */
241
+ /**
242
+ * Sizes the recipe offers.
243
+ */
244
+ const SIZES$2 = [
245
+ "sm",
246
+ "md",
247
+ "lg"
248
+ ];
249
+ /**
250
+ * Maps each size to the gap step one size larger: 8, 12 and 16px.
251
+ */
252
+ const GAPS = {
253
+ lg: "xl",
254
+ md: "lg",
255
+ sm: "md"
256
+ };
257
+ /**
258
+ * Maps each size to the icon and inset step of the mark and the panel: 32, 40 and 50px marks in
259
+ * 24, 32 and 40px of inset.
260
+ */
261
+ const MARKS = {
262
+ lg: "3xl",
263
+ md: "2xl",
264
+ sm: "xl"
265
+ };
266
+ /**
267
+ * Empty state slot recipe, at the md size by default.
268
+ */
269
+ const recipe$7 = defineSlotRecipe({
270
+ base: {
271
+ content: {
272
+ alignItems: "center",
273
+ display: "flex",
274
+ flexDirection: "column",
275
+ justifyContent: "center",
276
+ textAlign: "center"
277
+ },
278
+ description: {
279
+ textStyle: "body.sm",
280
+ color: "fg.muted"
281
+ },
282
+ indicator: {
283
+ "& svg": { boxSize: "100%" },
284
+ alignItems: "center",
285
+ color: "fg.muted",
286
+ display: "flex",
287
+ justifyContent: "center"
288
+ },
289
+ root: {
290
+ alignItems: "center",
291
+ display: "flex",
292
+ justifyContent: "center",
293
+ width: "full"
294
+ },
295
+ title: { fontWeight: "semibold" }
296
+ },
297
+ className: "empty-state",
298
+ defaultVariants: { size: "md" },
299
+ jsx: [/^EmptyState(\.\w+)?$/u],
300
+ slots: [
301
+ "root",
302
+ "content",
303
+ "indicator",
304
+ "title",
305
+ "description"
306
+ ],
307
+ variants: {
308
+ /**
309
+ * The inset of the panel, the gap in the column, the box of the mark and the title's text
310
+ * style.
311
+ */
312
+ size: onSlots({
313
+ content: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${GAPS[size]}}`) }), SIZES$2),
314
+ indicator: sizeVariants((size) => ({
315
+ boxSize: dense(`{sizes.icon.${MARKS[size]}}`),
316
+ marginBlockEnd: dense(`{spacing.gap.${GAPS[size]}}`)
317
+ }), SIZES$2),
318
+ root: sizeVariants((size) => ({ padding: dense(`{spacing.inset.${MARKS[size]}}`) }), SIZES$2),
319
+ title: sizeVariants((size) => ({ textStyle: `heading.${below(size)}` }), SIZES$2)
320
+ }) }
321
+ });
322
+ //#endregion
323
+ //#region src/loader/recipe.ts
324
+ /**
325
+ * Declares the loader's slot recipe: a spinner beside its words, a spinner over content it hides,
326
+ * and the overlay that covers a positioned container while it loads.
327
+ *
328
+ * @remarks
329
+ * With words, the root is an inline flex row, so the spinner and the words render on one line in
330
+ * any container. Over content, the root is an inline grid, and the hidden content and the
331
+ * spinner share its single cell. The spinner is centred on the content and the box has the
332
+ * content's size. The hidden content leaves the accessibility tree, so a visually hidden label
333
+ * replaces it. The loader has no box of its own and offers no `effect` axis. A caller who needs
334
+ * one passes a spinner with its own `effect`.
335
+ */
336
+ /**
337
+ * The recipe's class name, used to build the selector from the root to the content.
338
+ */
339
+ const CLASS = "loader";
340
+ /**
341
+ * Writes the `palette` axis of the indicator: the palette's `solid` as the spinner's ink.
342
+ *
343
+ * @remarks
344
+ * The spinner inside is at `current`, so it draws in the indicator's ink. Without a palette the
345
+ * indicator inherits the surrounding ink, which keeps the spinner readable on a solid button.
346
+ */
347
+ const tinted = axis(PALETTES, (palette) => ({
348
+ color: "colorPalette.solid",
349
+ colorPalette: palette
350
+ }));
351
+ /**
352
+ * Styles the loader and its overlay, with a veiled overlay by default.
353
+ */
354
+ const recipe$6 = defineSlotRecipe({
355
+ base: {
356
+ content: {
357
+ gridArea: "1 / 1",
358
+ visibility: "hidden"
359
+ },
360
+ indicator: {
361
+ alignItems: "center",
362
+ display: "inline-flex",
363
+ gridArea: "1 / 1"
364
+ },
365
+ label: { srOnly: true },
366
+ overlay: {
367
+ alignItems: "center",
368
+ borderRadius: "inherit",
369
+ display: "flex",
370
+ gap: "gap.sm",
371
+ inset: "0",
372
+ justifyContent: "center",
373
+ position: "absolute"
374
+ },
375
+ root: {
376
+ [`&:has(> .${CLASS}__content)`]: {
377
+ display: "inline-grid",
378
+ placeItems: "center"
379
+ },
380
+ alignItems: "center",
381
+ display: "inline-flex",
382
+ gap: "0.5em",
383
+ verticalAlign: "middle"
384
+ }
385
+ },
386
+ className: CLASS,
387
+ defaultVariants: { scrim: "veil" },
388
+ jsx: [/^Loader(Overlay)?$/u],
389
+ slots: [
390
+ "root",
391
+ "indicator",
392
+ "content",
393
+ "label",
394
+ "overlay"
395
+ ],
396
+ variants: {
397
+ /**
398
+ * The spinner's ink, as a palette's `solid` role.
399
+ */
400
+ palette: onSlot("indicator", tinted()),
401
+ /**
402
+ * The fill of the overlay over the content it covers.
403
+ *
404
+ * @remarks
405
+ * `veil` fills the overlay with the panel color at 80% opacity. The covered content shows
406
+ * through it faintly, and the spinner and its words have contrast against it. `glass` blurs
407
+ * the content behind a 70% panel fill. `none` sets no fill.
408
+ */
409
+ scrim: onSlot("overlay", {
410
+ glass: { layerStyle: "glass" },
411
+ none: { background: "transparent" },
412
+ veil: { background: "bg.panel/80" }
413
+ })
414
+ }
415
+ });
416
+ //#endregion
417
+ //#region src/spinner/recipe.ts
418
+ /**
419
+ * Declares the spinner recipe, a ring whose arc turns while work of unknown length runs.
420
+ *
421
+ * @remarks
422
+ * The block start and inline end borders draw the arc and the other two draw the track, so the
423
+ * stroke is a border width and keeps its weight at every size. The turn is the foundation's
424
+ * `spin` animation style, which a theme retimes and which stops under reduced motion. Each border
425
+ * side is a longhand, so the compiled rule contains no shorthand that overrides a side. In forced
426
+ * colors the browser paints all four sides in `CanvasText`, transparent ones included, so
427
+ * `_highContrast` sets `forcedColorAdjust: none` and draws the arc in `CanvasText` itself.
428
+ */
429
+ /**
430
+ * Styles a spinner, defaulting to the medium icon size and the indicator stroke in the current
431
+ * ink.
432
+ */
433
+ const recipe$5 = defineRecipe({
434
+ base: {
435
+ animationStyle: "spin",
436
+ _highContrast: {
437
+ borderBlockEndColor: "transparent",
438
+ borderBlockStartColor: "CanvasText",
439
+ borderInlineEndColor: "CanvasText",
440
+ borderInlineStartColor: "transparent",
441
+ forcedColorAdjust: "none"
442
+ },
443
+ borderBlockEndColor: "transparent",
444
+ borderBlockStartColor: "currentcolor",
445
+ borderInlineEndColor: "currentcolor",
446
+ borderInlineStartColor: "transparent",
447
+ borderRadius: "full",
448
+ borderStyle: "solid",
449
+ color: "colorPalette.solid",
450
+ display: "inline-block",
451
+ flexShrink: "0",
452
+ position: "relative",
453
+ verticalAlign: "middle"
454
+ },
455
+ className: "spinner",
456
+ defaultVariants: {
457
+ palette: "current",
458
+ size: "md",
459
+ stroke: "indicator"
460
+ },
461
+ jsx: [/Spinner$/u],
462
+ variants: {
463
+ /**
464
+ * The halo drawn around the ring, in the palette's solid at half opacity.
465
+ *
466
+ * @remarks
467
+ * `pulse` runs on `::after`, because its keyframe animates `box-shadow` through the
468
+ * `animation` property, which the element already uses for the spin. Both effects stop
469
+ * animating when the reader prefers reduced motion.
470
+ */
471
+ effect: {
472
+ glow: { layerStyle: "glow.md" },
473
+ pulse: { _after: {
474
+ animationStyle: "pulse-glow",
475
+ borderRadius: "inherit",
476
+ boxShadowColor: "colorPalette.solid/50",
477
+ content: "\"\"",
478
+ inset: "0",
479
+ position: "absolute"
480
+ } }
481
+ },
482
+ /**
483
+ * The palette the arc is drawn in, as its `solid` role.
484
+ *
485
+ * @remarks
486
+ * `current` draws the arc in the surrounding ink instead, so a spinner inside a solid button
487
+ * takes the button's label ink.
488
+ */
489
+ palette: {
490
+ ...paletteVariants(),
491
+ current: { color: "currentcolor" }
492
+ },
493
+ size: {
494
+ ...iconSizes(),
495
+ inherit: { boxSize: "1em" }
496
+ },
497
+ /**
498
+ * Border width of the ring. `hairline`, `control` and `indicator` read the theme's stroke
499
+ * tokens, and `heavy` reads the 4px `lg` width.
500
+ */
501
+ stroke: {
502
+ control: { borderWidth: "control" },
503
+ hairline: { borderWidth: "hairline" },
504
+ heavy: { borderWidth: "lg" },
505
+ indicator: { borderWidth: "indicator" }
506
+ },
507
+ /**
508
+ * Whether the two track sides draw the ring behind the arc, in the palette's `muted` role.
509
+ */
510
+ track: { true: {
511
+ _highContrast: {
512
+ borderBlockEndColor: "GrayText",
513
+ borderInlineStartColor: "GrayText"
514
+ },
515
+ borderBlockEndColor: "colorPalette.muted",
516
+ borderInlineStartColor: "colorPalette.muted"
517
+ } }
518
+ }
519
+ });
520
+ //#endregion
521
+ //#region src/bar.ts
522
+ /**
523
+ * Styles a bar that fills a track to a value, which the progress recipe renders for the progress
524
+ * bar and the meter.
525
+ *
526
+ * @remarks
527
+ * The root is a grid. The label starts the first row and the value ends it, and the track spans
528
+ * the row below, so a bar without words is the track alone. The `inline` layout puts the three on
529
+ * one row, with the track taking the room the words leave. The size is the track's thickness on
530
+ * the gap scale, 4, 6, 8, 12 and 16px from `xs` to `xl`, and the words keep the label style at
531
+ * every size. The range fills in the palette's solid. Under forced colors the range fills with
532
+ * `Highlight` and the track keeps a hairline `CanvasText` outline. Segments fill the track in the
533
+ * order they render, each in the theme's series color at its place unless it states a series
534
+ * color or a palette, and keep their colors under forced colors. A marker crosses the track at a
535
+ * value, 150% of its thickness, in the ink with a panel-colored edge.
536
+ */
537
+ /**
538
+ * Sizes the track offers.
539
+ */
540
+ const SIZES$1 = [
541
+ "xs",
542
+ "sm",
543
+ "md",
544
+ "lg",
545
+ "xl"
546
+ ];
547
+ /**
548
+ * Custom property of a marker's place along the track, a percentage from the track's start.
549
+ */
550
+ const MARKER = "--progress-marker";
551
+ /**
552
+ * Custom property of a segment's share of the track, a percentage.
553
+ */
554
+ const SHARE = "--progress-share";
555
+ /**
556
+ * Attribute a segment writes the color it states to.
557
+ */
558
+ const TINT = "data-color";
559
+ /**
560
+ * Width of a marker: the width of a line that indicates a state.
561
+ */
562
+ const TICK = "{borderWidths.indicator}";
563
+ /**
564
+ * Styles each segment in the series color at its place: the first in `series.1`, the ninth in
565
+ * `series.1` again.
566
+ *
567
+ * @remarks
568
+ * The rules are inside `:where()`, which counts nothing towards specificity, so a stated color
569
+ * applies over them whatever order the rules are written in.
570
+ */
571
+ const PLACES = Object.fromEntries(SERIES.map((step) => [`&:where(:nth-of-type(${String(SERIES.length)}n + ${step}))`, { backgroundColor: `series.${step}` }]));
572
+ /**
573
+ * Styles a segment in the series color or the palette's solid it states.
574
+ */
575
+ const STATED = Object.fromEntries([...SERIES.map((step) => [`&[${TINT}="series.${step}"]`, { backgroundColor: `series.${step}` }]), ...PALETTES.map((palette) => [`&[${TINT}=${palette}]`, {
576
+ backgroundColor: "colorPalette.solid",
577
+ colorPalette: palette
578
+ }])]);
579
+ /**
580
+ * Styles the track in each look: the palette's muted role, or a muted ground with an inset line.
581
+ */
582
+ const TRACKS$1 = {
583
+ outline: {
584
+ backgroundColor: "bg.muted",
585
+ boxShadow: "inset"
586
+ },
587
+ subtle: { backgroundColor: "colorPalette.muted" }
588
+ };
589
+ /**
590
+ * Lists the looks in the order of the shared set of looks, loudest first.
591
+ */
592
+ const LOOKS$1 = axis(["subtle", "outline"], (look) => TRACKS$1[look]);
593
+ /**
594
+ * Styles the root, the label, the value, the track, the range, the segments and the marker of a
595
+ * bar.
596
+ */
597
+ const BASE = {
598
+ label: {
599
+ textStyle: "label.sm",
600
+ color: "fg",
601
+ gridColumn: "1",
602
+ minInlineSize: "0",
603
+ overflowX: "clip",
604
+ overflowY: "visible",
605
+ textOverflow: "ellipsis",
606
+ whiteSpace: "nowrap"
607
+ },
608
+ marker: {
609
+ _highContrast: {
610
+ backgroundColor: "CanvasText",
611
+ boxShadow: "0 0 0 {borderWidths.hairline} Canvas",
612
+ forcedColorAdjust: "none"
613
+ },
614
+ backgroundColor: "fg",
615
+ boxShadow: "0 0 0 {borderWidths.hairline} {colors.bg.panel}",
616
+ inlineSize: TICK,
617
+ insetBlock: "-25%",
618
+ insetInlineStart: `clamp(0%, calc(var(${MARKER}) - ${TICK} / 2), calc(100% - ${TICK}))`,
619
+ pointerEvents: "none",
620
+ position: "absolute"
621
+ },
622
+ range: {
623
+ _highContrast: {
624
+ backgroundColor: "Highlight",
625
+ color: "HighlightText",
626
+ forcedColorAdjust: "none"
627
+ },
628
+ _motionReduce: { transitionDuration: "0s" },
629
+ backgroundColor: "colorPalette.solid",
630
+ blockSize: "full",
631
+ borderRadius: "inherit",
632
+ position: "relative",
633
+ transitionDuration: "move",
634
+ transitionProperty: "width",
635
+ transitionTimingFunction: "move"
636
+ },
637
+ root: {
638
+ alignItems: "center",
639
+ columnGap: dense("{spacing.gap.sm}"),
640
+ display: "grid",
641
+ gridAutoFlow: "dense",
642
+ rowGap: dense("{spacing.gap.xs}")
643
+ },
644
+ segment: {
645
+ ...PLACES,
646
+ ...STATED,
647
+ _highContrast: { forcedColorAdjust: "none" },
648
+ _motionReduce: { transitionDuration: "0s" },
649
+ "&:first-of-type": {
650
+ borderEndStartRadius: "inherit",
651
+ borderStartStartRadius: "inherit"
652
+ },
653
+ "&:last-of-type": {
654
+ borderEndEndRadius: "inherit",
655
+ borderStartEndRadius: "inherit"
656
+ },
657
+ "& + &": { _highContrast: {
658
+ borderInlineStartColor: "Canvas",
659
+ borderInlineStartStyle: "solid",
660
+ borderInlineStartWidth: "hairline"
661
+ } },
662
+ blockSize: "full",
663
+ inlineSize: `var(${SHARE})`,
664
+ minInlineSize: "0",
665
+ transitionDuration: "move",
666
+ transitionProperty: "inline-size",
667
+ transitionTimingFunction: "move"
668
+ },
669
+ track: {
670
+ _highContrast: {
671
+ outlineColor: "CanvasText",
672
+ outlineOffset: "calc({borderWidths.hairline} * -1)",
673
+ outlineStyle: "solid",
674
+ outlineWidth: "hairline"
675
+ },
676
+ display: "flex",
677
+ minInlineSize: "0",
678
+ position: "relative"
679
+ },
680
+ valueText: {
681
+ textStyle: "label.sm",
682
+ color: "fg.muted",
683
+ fontVariantNumeric: "tabular-nums",
684
+ gridColumn: "-2 / -1",
685
+ justifySelf: "end",
686
+ whiteSpace: "nowrap"
687
+ }
688
+ };
689
+ /**
690
+ * Offers the axes every bar takes: where the words go, the palette, the ends, the thickness and
691
+ * the look of the track.
692
+ */
693
+ const VARIANTS = {
694
+ /**
695
+ * Where the words go: above the track, or on its row at either end.
696
+ */
697
+ layout: {
698
+ inline: {
699
+ root: { gridTemplateColumns: "auto minmax(0, 1fr) auto" },
700
+ track: { gridColumn: "2" }
701
+ },
702
+ stacked: {
703
+ root: { gridTemplateColumns: "minmax(0, 1fr) auto" },
704
+ track: { gridColumn: "1 / -1" }
705
+ }
706
+ },
707
+ palette: onSlot("root", paletteVariants()),
708
+ /**
709
+ * The ends of the track, which the range takes too.
710
+ */
711
+ shape: onSlot("track", {
712
+ full: { borderRadius: "full" },
713
+ rounded: { borderRadius: "l1" },
714
+ square: { borderRadius: "none" }
715
+ }),
716
+ /**
717
+ * The thickness of the track.
718
+ */
719
+ size: onSlot("track", sizeVariants((size) => ({ blockSize: dense(`{spacing.gap.${size}}`) }), SIZES$1)),
720
+ /**
721
+ * Fills the track with the palette's muted role, or with a muted ground and an inset line.
722
+ */
723
+ variant: onSlot("track", LOOKS$1())
724
+ };
725
+ //#endregion
726
+ //#region src/progress/recipe.ts
727
+ /**
728
+ * Styles a track, a range that fills it to the value, a label and the value in words, for the
729
+ * progress bar and the meter.
730
+ *
731
+ * @remarks
732
+ * The bar's grid, its words, its track and its range come from `bar.ts`. The meter renders the
733
+ * same parts, so it takes the same classes and offers every axis but the stripes, their movement
734
+ * and the effect. Stripes are a band in the palette's contrast ink at a quarter strength over the
735
+ * range, and move at 40px a second. For a value the machine does not know, a segment of the range
736
+ * crosses the track once per 1.2s. For a reader who asked for less motion both stop, and the
737
+ * segment spreads over the whole track, so the bar never reads as a value it does not have.
738
+ */
739
+ /**
740
+ * Styles the stripes: a band at 45 degrees in the range's ink, repeated on a square tile.
741
+ */
742
+ const STRIPES = {
743
+ backgroundImage: "linear-gradient(45deg, currentColor 25%, transparent 25%, transparent 50%, currentColor 50%, currentColor 75%, transparent 75%, transparent)",
744
+ backgroundSize: "{sizes.4} {sizes.4}",
745
+ color: "colorPalette.contrast/25"
746
+ };
747
+ /**
748
+ * Moves the stripes three tiles towards the start per 1.2s loop, 40px a second, and the loop has
749
+ * no seam because it moves whole tiles.
750
+ *
751
+ * @remarks
752
+ * The `shimmer` motion moves a background and stops under reduced motion. Its pace is the slow
753
+ * ambient one, 5s. The stripes state the ambient pace where motion is allowed, because a pace
754
+ * stated beside the motion does not apply over it.
755
+ */
756
+ const MOVING = {
757
+ animationStyle: "shimmer",
758
+ _motionSafe: { animationDuration: "ambient" },
759
+ "--animate-from": "calc({sizes.4} * 3)",
760
+ "--animate-to": "0"
761
+ };
762
+ /**
763
+ * Moves a segment half the track's width from beyond its start to beyond its end once per 1.2s.
764
+ *
765
+ * @remarks
766
+ * The segment is a gradient in the palette's solid that fades out at both ends. Its position runs
767
+ * from -100% to 200% of the room it leaves, which puts it wholly outside the track at both ends
768
+ * of the loop.
769
+ */
770
+ const SWEEP = {
771
+ animationStyle: "shimmer",
772
+ _highContrast: { color: "Highlight" },
773
+ _motionReduce: { backgroundSize: "100% 100%" },
774
+ _motionSafe: {
775
+ animationDuration: "ambient",
776
+ animationTimingFunction: "in-out"
777
+ },
778
+ _rtl: {
779
+ "--animate-from": "200%",
780
+ "--animate-to": "-100%"
781
+ },
782
+ "--animate-from": "-100%",
783
+ "--animate-to": "200%",
784
+ backgroundColor: "transparent",
785
+ backgroundImage: "linear-gradient(to right, transparent, currentColor, transparent)",
786
+ backgroundRepeat: "no-repeat",
787
+ backgroundSize: "50% 100%",
788
+ color: "colorPalette.solid",
789
+ inlineSize: "full"
790
+ };
791
+ /**
792
+ * Styles a bar in the primary palette, in the outline look, at the middle size, with round ends.
793
+ */
794
+ const recipe$4 = defineSlotRecipe({
795
+ base: {
796
+ ...BASE,
797
+ range: {
798
+ ...BASE.range,
799
+ "&[data-state=indeterminate]": SWEEP
800
+ }
801
+ },
802
+ className: "progress",
803
+ defaultVariants: {
804
+ layout: "stacked",
805
+ palette: "primary",
806
+ shape: "full",
807
+ size: "md",
808
+ variant: "outline"
809
+ },
810
+ jsx: [/^Progress\.\w+$/u, /^Meter\.\w+$/u],
811
+ slots: [
812
+ "root",
813
+ "label",
814
+ "valueText",
815
+ "track",
816
+ "range",
817
+ "segment",
818
+ "marker"
819
+ ],
820
+ staticCss: [{ palette: [...PALETTES] }],
821
+ variants: {
822
+ /**
823
+ * Moves the stripes, adding them where `striped` is not set.
824
+ */
825
+ animated: { true: { range: {
826
+ ...STRIPES,
827
+ ...MOVING
828
+ } } },
829
+ /**
830
+ * The halo around the range, in the palette's solid.
831
+ *
832
+ * @remarks
833
+ * `pulse` animates the halo on a layer over the range, because the range's own animation
834
+ * moves its stripes. Both stop under reduced motion.
835
+ */
836
+ effect: onSlot("range", {
837
+ glow: { layerStyle: "glow.sm" },
838
+ pulse: { _after: {
839
+ animationStyle: "pulse-glow",
840
+ borderRadius: "inherit",
841
+ boxShadowColor: "colorPalette.solid/50",
842
+ content: "\"\"",
843
+ inset: "0",
844
+ position: "absolute"
845
+ } }
846
+ }),
847
+ layout: VARIANTS.layout,
848
+ palette: VARIANTS.palette,
849
+ shape: VARIANTS.shape,
850
+ size: VARIANTS.size,
851
+ striped: { true: { range: STRIPES } },
852
+ variant: VARIANTS.variant
853
+ }
854
+ });
855
+ //#endregion
856
+ //#region src/progress-circle/recipe.ts
857
+ /**
858
+ * Styles a ring whose range fills to the value, with the value in its middle and a label.
859
+ *
860
+ * @remarks
861
+ * The root is a grid as wide as its content, so the ring's column is as wide as the ring in any
862
+ * parent. The ring and the value text share one cell. The label takes the row below it or,
863
+ * inline, the column beside it. The size sets the `--size` and `--thickness` properties the
864
+ * machine's circles read: 24, 32, 40, 48 and 64px wide, and 4, 4, 4, 6 and 8px thick. From `md`
865
+ * up the value text takes the largest text style whose "100%" fits inside the ring. The recipe
866
+ * hides it at `xs` and `sm`. For a value the machine does not know, a quarter arc turns once per
867
+ * 1.2s. Under reduced motion the ring stops and the range is a dashed line around the whole ring.
868
+ * Under forced colors the range strokes in `Highlight` and the track is a hairline `CanvasText`
869
+ * circle at the ring's outer edge.
870
+ */
871
+ /**
872
+ * Sizes the ring offers.
873
+ */
874
+ const SIZES = [
875
+ "xs",
876
+ "sm",
877
+ "md",
878
+ "lg",
879
+ "xl"
880
+ ];
881
+ /**
882
+ * Maps each size to the sizes token the ring is as wide as: 24, 32, 40, 48 and 64px.
883
+ */
884
+ const WIDTHS = {
885
+ lg: "12",
886
+ md: "10",
887
+ sm: "8",
888
+ xl: "16",
889
+ xs: "6"
890
+ };
891
+ /**
892
+ * Maps each size to the gap token the ring is as thick as: 4, 4, 4, 6 and 8px.
893
+ */
894
+ const THICKNESSES = {
895
+ lg: "sm",
896
+ md: "xs",
897
+ sm: "xs",
898
+ xl: "md",
899
+ xs: "xs"
900
+ };
901
+ /**
902
+ * Maps each size to the value text's styles. `xs` and `sm` hide it. From `md` up it takes the
903
+ * largest text style whose "100%" fits inside the hole: 32, 36 and 48px.
904
+ */
905
+ const FIGURES = {
906
+ lg: { textStyle: "label.xs" },
907
+ md: { textStyle: "2xs" },
908
+ sm: { display: "none" },
909
+ xl: { textStyle: "label.md" },
910
+ xs: { display: "none" }
911
+ };
912
+ /**
913
+ * Styles the track under forced colors as a hairline `CanvasText` circle at the ring's outer edge.
914
+ *
915
+ * @remarks
916
+ * The range is a thick `Highlight` arc, so the two differ in weight whatever the system colors
917
+ * are. The track's own `--thickness` sets both its stroke and its radius. Chromium's `Highlight`
918
+ * is translucent, so a track as thick as the arc would show through it.
919
+ */
920
+ const FORCED = {
921
+ "--thickness": "{borderWidths.hairline}",
922
+ stroke: "CanvasText"
923
+ };
924
+ /**
925
+ * Strokes the track in each look: the palette's muted role, or the muted ground. Each look states
926
+ * the forced track, because a look applies over the base.
927
+ */
928
+ const TRACKS = {
929
+ outline: {
930
+ _highContrast: FORCED,
931
+ stroke: "bg.muted"
932
+ },
933
+ subtle: {
934
+ _highContrast: FORCED,
935
+ stroke: "colorPalette.muted"
936
+ }
937
+ };
938
+ /**
939
+ * Lists the looks in the order of the shared set of looks, loudest first.
940
+ */
941
+ const LOOKS = axis(["subtle", "outline"], (look) => TRACKS[look]);
942
+ /**
943
+ * Defines the progress circle recipe, which defaults to the outline look at the middle size in the
944
+ * primary palette, with round ends.
945
+ */
946
+ const recipe$3 = defineSlotRecipe({
947
+ base: {
948
+ circle: {
949
+ _highContrast: { forcedColorAdjust: "none" },
950
+ "&[data-state=indeterminate]": { animationStyle: "spin" },
951
+ display: "block",
952
+ gridArea: "ring"
953
+ },
954
+ label: {
955
+ textStyle: "label.sm",
956
+ color: "fg",
957
+ gridArea: "label"
958
+ },
959
+ range: {
960
+ _highContrast: { stroke: "Highlight" },
961
+ _motionReduce: { transitionDuration: "0s" },
962
+ "&[data-state=indeterminate]": {
963
+ _motionReduce: { strokeDasharray: "var(--thickness) calc(var(--thickness) * 2)" },
964
+ strokeDasharray: "calc(var(--circumference) / 4) var(--circumference)"
965
+ },
966
+ stroke: "colorPalette.solid",
967
+ transitionDuration: "move",
968
+ transitionProperty: "stroke-dashoffset",
969
+ transitionTimingFunction: "move"
970
+ },
971
+ root: {
972
+ alignItems: "center",
973
+ columnGap: dense("{spacing.gap.sm}"),
974
+ display: "inline-grid",
975
+ inlineSize: "fit",
976
+ rowGap: dense("{spacing.gap.xs}")
977
+ },
978
+ track: { stroke: "bg.muted" },
979
+ valueText: {
980
+ alignSelf: "center",
981
+ color: "fg",
982
+ fontVariantNumeric: "tabular-nums",
983
+ fontWeight: "medium",
984
+ gridArea: "ring",
985
+ justifySelf: "center",
986
+ whiteSpace: "nowrap"
987
+ }
988
+ },
989
+ className: "progress-circle",
990
+ defaultVariants: {
991
+ layout: "stacked",
992
+ palette: "primary",
993
+ shape: "full",
994
+ size: "md",
995
+ variant: "outline"
996
+ },
997
+ jsx: [/^ProgressCircle\.\w+$/u],
998
+ slots: [
999
+ "root",
1000
+ "label",
1001
+ "circle",
1002
+ "track",
1003
+ "range",
1004
+ "valueText"
1005
+ ],
1006
+ staticCss: [{ palette: [...PALETTES] }],
1007
+ variants: {
1008
+ /**
1009
+ * Where the label goes: under the ring, or beside it.
1010
+ */
1011
+ layout: {
1012
+ inline: { root: {
1013
+ gridTemplateAreas: "\"ring label\"",
1014
+ justifyItems: "start"
1015
+ } },
1016
+ stacked: { root: {
1017
+ gridTemplateAreas: "\"ring\" \"label\"",
1018
+ justifyItems: "center"
1019
+ } }
1020
+ },
1021
+ palette: onSlot("root", paletteVariants()),
1022
+ /**
1023
+ * The ends of the range.
1024
+ */
1025
+ shape: onSlot("range", {
1026
+ full: { strokeLinecap: "round" },
1027
+ square: { strokeLinecap: "butt" }
1028
+ }),
1029
+ /**
1030
+ * The ring's width and thickness, and the value text's style.
1031
+ */
1032
+ size: onSlots({
1033
+ root: sizeVariants((size) => ({
1034
+ "--size": dense(`{sizes.${WIDTHS[size]}}`),
1035
+ "--thickness": dense(`{spacing.gap.${THICKNESSES[size]}}`)
1036
+ }), SIZES),
1037
+ valueText: sizeVariants((size) => FIGURES[size], SIZES)
1038
+ }),
1039
+ /**
1040
+ * Strokes the track in the palette's muted role, or in the muted ground.
1041
+ */
1042
+ variant: onSlot("track", LOOKS())
1043
+ }
1044
+ });
1045
+ //#endregion
1046
+ //#region src/skeleton-text/recipe.ts
1047
+ /**
1048
+ * Declares the recipe of the skeleton text column: one bar per line, spaced by half a line.
1049
+ *
1050
+ * @remarks
1051
+ * Every length is in `lh`, the line height of the text the bars replace, so the placeholder
1052
+ * takes the size of the text around it without an absolute length. Each bar is `1lh` tall and
1053
+ * the column's gap is `0.5lh`. The gap is on the column and not a clipped inset on the bar,
1054
+ * because the skeleton's `loading` variant sets `background` and `background-clip: padding-box`
1055
+ * in the variants layer, which overrides any clip this base sets on a bar. With two or more bars
1056
+ * the last is capped at 80% width, so the stack reads as a paragraph and not as a table.
1057
+ */
1058
+ /**
1059
+ * Skeleton text recipe: a full-width flex column of line-height bars.
1060
+ */
1061
+ const recipe$2 = defineRecipe({
1062
+ base: {
1063
+ "& > *": { blockSize: "1lh" },
1064
+ "& > *:last-child:not(:only-child)": { maxWidth: "80%" },
1065
+ display: "flex",
1066
+ flexDirection: "column",
1067
+ gap: "0.5lh",
1068
+ width: "full"
1069
+ },
1070
+ className: "skeleton-text",
1071
+ jsx: [/^SkeletonText$/u]
1072
+ });
1073
+ //#endregion
1074
+ //#region src/skeleton/recipe.ts
1075
+ /**
1076
+ * Declares the skeleton recipe, which paints a placeholder over content that is still loading.
1077
+ *
1078
+ * @remarks
1079
+ * A skeleton wraps the content it covers, so a caller writes one tree and toggles `loading`.
1080
+ * While loading, the element keeps the box of its content and hides everything inside it, so the
1081
+ * placeholder has the content's size without a declared width. The fade-in is in the base,
1082
+ * because the compiler emits no class for the `false` value of a boolean axis. Each motion is
1083
+ * nested under the loading class, so a finished skeleton runs no animation. Every motion is a
1084
+ * theme animation style, so the theme applies the reduced-motion preference once. The fill is the
1085
+ * neutral palette's `muted` role, which is lighter than a dark page. Under forced colors the
1086
+ * browser replaces the fill with Canvas, so a loading skeleton draws a `GrayText` hairline
1087
+ * outline inside its box. An outline leaves the layout unchanged. The recipe has no `palette`
1088
+ * axis, because a placeholder signals loading and not a category. It has no `effect` axis,
1089
+ * because `motion` already animates it.
1090
+ */
1091
+ /**
1092
+ * Selector for a skeleton that is loading, under which each motion nests its rules.
1093
+ *
1094
+ * @remarks
1095
+ * The selector uses the compiler's variant class name, the axis and the value joined by `_`, so
1096
+ * the naming plugin rewrites it together with the class.
1097
+ */
1098
+ const WHILE_LOADING = "&.skeleton--loading_true";
1099
+ /**
1100
+ * Skeleton recipe, loading with the pulse motion at the l2 radius by default.
1101
+ */
1102
+ const recipe$1 = defineRecipe({
1103
+ base: { animationStyle: "fade.in" },
1104
+ className: "skeleton",
1105
+ defaultVariants: {
1106
+ loading: true,
1107
+ motion: "pulse",
1108
+ radius: "l2"
1109
+ },
1110
+ jsx: [/^Skeleton$/u],
1111
+ variants: {
1112
+ /**
1113
+ * Whether the content is still loading. `true` paints the placeholder and hides the content.
1114
+ */
1115
+ loading: { true: {
1116
+ _highContrast: {
1117
+ outlineColor: "GrayText",
1118
+ outlineOffset: "calc(-1 * {borderWidths.hairline})",
1119
+ outlineStyle: "solid",
1120
+ outlineWidth: "hairline"
1121
+ },
1122
+ "&::before, &::after, *": { visibility: "hidden" },
1123
+ background: "colorPalette.muted",
1124
+ backgroundClip: "padding-box",
1125
+ boxShadow: "none",
1126
+ color: "transparent",
1127
+ colorPalette: "neutral",
1128
+ flexShrink: "0",
1129
+ pointerEvents: "none",
1130
+ userSelect: "none"
1131
+ } },
1132
+ /**
1133
+ * Animation of the placeholder while loading.
1134
+ */
1135
+ motion: {
1136
+ none: { [WHILE_LOADING]: { animation: "none" } },
1137
+ pulse: { [WHILE_LOADING]: { animationStyle: "pulse" } },
1138
+ shimmer: { [WHILE_LOADING]: {
1139
+ animationStyle: "shimmer",
1140
+ backgroundImage: "linear-gradient(270deg, var(--colors-color-palette-muted), var(--colors-color-palette-emphasized))",
1141
+ backgroundSize: "400% 100%"
1142
+ } }
1143
+ },
1144
+ radius: cornerVariants()
1145
+ }
1146
+ });
1147
+ //#endregion
1148
+ //#region src/toast/recipe.ts
1149
+ /**
1150
+ * Recipe for the toast: a card raised into a corner of the window, with a mark, a title, a
1151
+ * description, an action and a close button.
1152
+ *
1153
+ * @remarks
1154
+ * The machine places each toast in its region and writes the toast's motion as custom
1155
+ * properties: `--x`, `--y`, `--scale`, `--opacity`, `--height` and `--z-index`. The root reads
1156
+ * them and moves between them on the theme's `move` pace, and on the `leave` pace once it is
1157
+ * dismissed. The toast's type sets `colorPalette`, `neutral` for `loading`, and the indicator
1158
+ * renders the caller's glyph in the palette's solid. A focused toast shows the theme's focus
1159
+ * ring outside its edge, in the palette's `focusRing` color. The root is at most `sizes.sm` wide
1160
+ * and never wider than the window less the region's offsets. The recipe has no axes, because a
1161
+ * toast has one look. A theme restyles it by extending the recipe.
1162
+ */
1163
+ /**
1164
+ * Maps each type the machine writes on a toast to the palette the toast reads.
1165
+ */
1166
+ const TYPES = {
1167
+ error: "error",
1168
+ info: "info",
1169
+ loading: "neutral",
1170
+ success: "success",
1171
+ warning: "warning"
1172
+ };
1173
+ /**
1174
+ * Size of the close trigger's box: 1.5em, and never less than the 24px a target needs.
1175
+ */
1176
+ const CLOSE = "max({sizes.6}, 1.5em)";
1177
+ /**
1178
+ * Defines the toast recipe.
1179
+ */
1180
+ const recipe = defineSlotRecipe({
1181
+ base: {
1182
+ actionTrigger: {
1183
+ ...interactive(),
1184
+ textStyle: "label.sm",
1185
+ _hover: { background: "bg.muted" },
1186
+ alignItems: "center",
1187
+ alignSelf: "center",
1188
+ background: "transparent",
1189
+ borderColor: "border",
1190
+ borderRadius: "l2",
1191
+ borderStyle: "solid",
1192
+ borderWidth: "hairline",
1193
+ color: "fg",
1194
+ display: "inline-flex",
1195
+ flexShrink: "0",
1196
+ fontWeight: "medium",
1197
+ height: dense("{sizes.control.xs}"),
1198
+ paddingInline: dense("{spacing.inset.sm}")
1199
+ },
1200
+ /**
1201
+ * The close trigger centres on the first line of the title, and a negative end margin puts
1202
+ * its glyph on the root's padding edge.
1203
+ */
1204
+ closeTrigger: {
1205
+ ...interactive(),
1206
+ ...touchTarget(),
1207
+ _hover: {
1208
+ background: "bg.muted",
1209
+ color: "fg"
1210
+ },
1211
+ "& > svg": { boxSize: "1em" },
1212
+ alignItems: "center",
1213
+ background: "transparent",
1214
+ borderRadius: "l1",
1215
+ borderStyle: "none",
1216
+ boxSize: CLOSE,
1217
+ color: "fg.muted",
1218
+ display: "inline-flex",
1219
+ flexShrink: "0",
1220
+ justifyContent: "center",
1221
+ marginBlock: `calc((1lh - ${CLOSE}) / 2)`,
1222
+ marginInlineEnd: `calc((1em - ${CLOSE}) / 2)`,
1223
+ padding: "0"
1224
+ },
1225
+ content: {
1226
+ display: "flex",
1227
+ flex: "1",
1228
+ flexDirection: "column",
1229
+ gap: dense("{spacing.gap.xs}"),
1230
+ minInlineSize: "0"
1231
+ },
1232
+ description: { color: "fg.muted" },
1233
+ indicator: {
1234
+ "& > svg": { boxSize: "{sizes.icon.sm}" },
1235
+ alignItems: "center",
1236
+ blockSize: "1lh",
1237
+ color: "colorPalette.solid",
1238
+ display: "inline-flex",
1239
+ flexShrink: "0"
1240
+ },
1241
+ region: {},
1242
+ root: {
1243
+ ...Object.fromEntries(Object.entries(TYPES).map(([type, palette]) => [`&[data-type=${type}]`, { colorPalette: palette }])),
1244
+ focusVisibleRing: "outside",
1245
+ textStyle: "body.sm",
1246
+ _closed: {
1247
+ transitionDuration: "leave",
1248
+ transitionTimingFunction: "leave"
1249
+ },
1250
+ _motionReduce: { transitionDuration: "0s" },
1251
+ alignItems: "flex-start",
1252
+ background: "bg.panel",
1253
+ borderColor: "border",
1254
+ borderRadius: "l3",
1255
+ borderStyle: "solid",
1256
+ borderWidth: "hairline",
1257
+ boxShadow: "lg",
1258
+ color: "fg",
1259
+ display: "flex",
1260
+ focusRingColor: "colorPalette.focusRing",
1261
+ gap: dense("{spacing.gap.md}"),
1262
+ height: "var(--height)",
1263
+ inlineSize: "min({sizes.sm}, calc(100vw - var(--viewport-offset-left) - var(--viewport-offset-right)))",
1264
+ opacity: "var(--opacity)",
1265
+ padding: dense("{spacing.inset.md}"),
1266
+ scale: "var(--scale)",
1267
+ transitionDuration: "move",
1268
+ transitionProperty: "translate, scale, opacity, height, box-shadow",
1269
+ transitionTimingFunction: "move",
1270
+ translate: "var(--x) var(--y)",
1271
+ willChange: "translate, opacity, scale",
1272
+ zIndex: "var(--z-index)"
1273
+ },
1274
+ title: { fontWeight: "medium" }
1275
+ },
1276
+ className: "toast",
1277
+ jsx: [/^Toast(\.\w+)?$/u],
1278
+ slots: [
1279
+ "region",
1280
+ "root",
1281
+ "indicator",
1282
+ "content",
1283
+ "title",
1284
+ "description",
1285
+ "actionTrigger",
1286
+ "closeTrigger"
1287
+ ]
1288
+ });
1289
+ //#endregion
1290
+ export { recipe$4 as a, TINT as c, recipe$7 as d, recipe$8 as f, recipe$3 as i, recipe$5 as l, recipe$1 as n, MARKER as o, recipe$2 as r, SHARE as s, recipe as t, recipe$6 as u };