@stisla/style 3.0.0-beta.8
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 +9 -0
- package/README.md +16 -0
- package/dist/accordion/accordion.css +194 -0
- package/dist/alert/alert.css +138 -0
- package/dist/autocomplete/autocomplete.css +193 -0
- package/dist/avatar/avatar.css +142 -0
- package/dist/avatar-group/avatar-group.css +42 -0
- package/dist/badge/badge.css +74 -0
- package/dist/breadcrumb/breadcrumb.css +71 -0
- package/dist/button/button.css +318 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +6 -0
- package/dist/button-group/button-group.css +108 -0
- package/dist/card/card.css +219 -0
- package/dist/carousel/carousel.css +170 -0
- package/dist/checkbox/checkbox.css +98 -0
- package/dist/chunk-K45KLI3Y.js +74 -0
- package/dist/collapsible/collapsible.css +36 -0
- package/dist/combobox/combobox.css +106 -0
- package/dist/combobox/combobox.tomselect.css +251 -0
- package/dist/config-CARtrJ7I.d.ts +61 -0
- package/dist/dialog/dialog.css +258 -0
- package/dist/drawer/drawer.css +318 -0
- package/dist/empty-state/empty-state.css +138 -0
- package/dist/field/field.css +70 -0
- package/dist/icon-box/icon-box.css +64 -0
- package/dist/illustration/illustration.css +103 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +60 -0
- package/dist/indicator/indicator.css +84 -0
- package/dist/input/input.css +220 -0
- package/dist/input-group/input-group.css +141 -0
- package/dist/kbd/kbd.css +55 -0
- package/dist/link/link.css +28 -0
- package/dist/list-group/list-group.css +261 -0
- package/dist/media/media.css +115 -0
- package/dist/menu/menu.css +237 -0
- package/dist/meter/meter.css +124 -0
- package/dist/navbar/navbar.css +170 -0
- package/dist/page/page.css +95 -0
- package/dist/pagination/pagination.css +125 -0
- package/dist/placeholders/placeholders.css +58 -0
- package/dist/popover/popover.css +251 -0
- package/dist/progress/progress.css +139 -0
- package/dist/radio/radio.css +81 -0
- package/dist/scroll-area/scroll-area.css +25 -0
- package/dist/scroll-area/scroll-area.overlayscrollbars.css +42 -0
- package/dist/select/select.css +282 -0
- package/dist/separator/separator.css +26 -0
- package/dist/sidebar/sidebar.css +493 -0
- package/dist/slider/slider.css +159 -0
- package/dist/spinner/spinner.css +65 -0
- package/dist/switch/switch.css +91 -0
- package/dist/table/table.css +284 -0
- package/dist/tabs/tabs.css +137 -0
- package/dist/textarea/textarea.css +99 -0
- package/dist/timeline/timeline.css +271 -0
- package/dist/toast/toast.css +267 -0
- package/dist/toggle/toggle.css +125 -0
- package/dist/toggle-group/toggle-group.css +87 -0
- package/dist/tooltip/tooltip.css +95 -0
- package/package.json +46 -0
- package/src/theme.css +151 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/* @stisla/style — Timeline. Ported from src/scss/components/_timeline.scss. An ordered sequence of
|
|
2
|
+
* events along a vertical rail: each .timeline__item pairs a .timeline__marker (dot / icon / number /
|
|
3
|
+
* avatar) on the rail with a .timeline__body (time, title, text). Pure layout, no JS — status rides the
|
|
4
|
+
* author-set [data-state] attribute and intent rides .timeline__marker--<intent>. The rail line is a
|
|
5
|
+
* pseudo-element on the item (no DOM node). References the @theme tokens (colors var(--color-*),
|
|
6
|
+
* sizes/spacing --spacing(n), type var(--text-*) / var(--font-weight-*) / var(--leading-*)); only
|
|
7
|
+
* no-namespace customs use --st-* (border-width). Knobs are --timeline-*. @layer components.
|
|
8
|
+
* Authoring rules: ../../../../PORTING.md */
|
|
9
|
+
|
|
10
|
+
@layer components {
|
|
11
|
+
/* Reset list chrome so the root can be an <ol> (the order is meaningful). */
|
|
12
|
+
.timeline {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 0;
|
|
17
|
+
list-style: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* === Item === marker lane + body. align-items:start keeps the marker its own height; the
|
|
21
|
+
padding-block-end carries the inter-item gap so the rail reaches the next marker. */
|
|
22
|
+
.timeline__item {
|
|
23
|
+
position: relative;
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: var(--timeline-marker-size, --spacing(7)) 1fr;
|
|
26
|
+
align-items: start;
|
|
27
|
+
column-gap: var(--timeline-gutter, --spacing(4));
|
|
28
|
+
padding-block-end: var(--timeline-gap, --spacing(6));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.timeline__item:last-child {
|
|
32
|
+
padding-block-end: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* === Connector === the rail line, a pseudo-element. Runs through the gap between one marker lane and
|
|
36
|
+
the next, inset by --timeline-connector-gap at both ends so it floats clear of both markers.
|
|
37
|
+
Suppressed on the last item so the rail stops at the final marker. Centred on the marker lane via a
|
|
38
|
+
logical inline offset (no transform) so it tracks RTL. width:0 + border-inline-start lets the
|
|
39
|
+
solid/dashed switch ride one property (--timeline-connector-style). */
|
|
40
|
+
.timeline__item:not(:last-child)::before {
|
|
41
|
+
content: "";
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset-block-start: calc(
|
|
44
|
+
var(--timeline-marker-size, --spacing(7)) +
|
|
45
|
+
var(--timeline-connector-gap, --spacing(1.5))
|
|
46
|
+
);
|
|
47
|
+
inset-block-end: var(--timeline-connector-gap, --spacing(1.5));
|
|
48
|
+
inset-inline-start: calc(
|
|
49
|
+
var(--timeline-marker-size, --spacing(7)) / 2 -
|
|
50
|
+
var(--timeline-connector-width, 2px) / 2
|
|
51
|
+
);
|
|
52
|
+
width: 0;
|
|
53
|
+
border-inline-start: var(--timeline-connector-width, 2px)
|
|
54
|
+
var(--timeline-connector-style, solid)
|
|
55
|
+
var(--timeline-connector-color, var(--color-border));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* === Marker === centred in its lane. An empty marker shows a small filled dot via ::before; a marker
|
|
59
|
+
with content (icon / number / avatar) becomes a soft-tinted bordered circle. z-index keeps it above
|
|
60
|
+
the rail line in any edge overlap. */
|
|
61
|
+
.timeline__marker {
|
|
62
|
+
grid-column: 1;
|
|
63
|
+
justify-self: center;
|
|
64
|
+
align-self: start;
|
|
65
|
+
position: relative;
|
|
66
|
+
z-index: 1;
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
width: var(--timeline-marker-size, --spacing(7));
|
|
73
|
+
height: var(--timeline-marker-size, --spacing(7));
|
|
74
|
+
font-size: var(--text-xs);
|
|
75
|
+
font-weight: var(--font-weight-semibold);
|
|
76
|
+
line-height: var(--leading-none);
|
|
77
|
+
color: var(--timeline-marker-color, var(--color-primary));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Bare dot — empty marker. Small filled circle centred in the lane. */
|
|
81
|
+
.timeline__marker:empty::before {
|
|
82
|
+
content: "";
|
|
83
|
+
width: var(--timeline-dot-size, --spacing(2.75));
|
|
84
|
+
height: var(--timeline-dot-size, --spacing(2.75));
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
background: var(--timeline-marker-color, var(--color-primary));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Icon / content marker — soft-tinted circle. Tint in oklab (not oklch): mixing a saturated hue toward
|
|
90
|
+
an opaque near-white in oklch rotates the hue (a blue reads pink); oklab interpolates straight. The
|
|
91
|
+
border mixes with transparent, where premultiplied alpha keeps the hue, so it stays oklch. */
|
|
92
|
+
.timeline__marker:not(:empty) {
|
|
93
|
+
background: color-mix(
|
|
94
|
+
in oklab,
|
|
95
|
+
var(--timeline-marker-color, var(--color-primary)) 14%,
|
|
96
|
+
var(--color-surface)
|
|
97
|
+
);
|
|
98
|
+
border: var(--st-border-width) solid
|
|
99
|
+
color-mix(
|
|
100
|
+
in oklch,
|
|
101
|
+
var(--timeline-marker-color, var(--color-primary)) 32%,
|
|
102
|
+
transparent
|
|
103
|
+
);
|
|
104
|
+
border-radius: 50%;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Avatar / image markers carry their own circular shape, so shed the tint and ring and size them to
|
|
108
|
+
the lane. */
|
|
109
|
+
.timeline__marker:has(> .avatar),
|
|
110
|
+
.timeline__marker:has(> img) {
|
|
111
|
+
background: none;
|
|
112
|
+
border: 0;
|
|
113
|
+
}
|
|
114
|
+
.timeline__marker > .avatar {
|
|
115
|
+
--avatar-size: var(--timeline-marker-size, --spacing(7));
|
|
116
|
+
}
|
|
117
|
+
.timeline__marker > img {
|
|
118
|
+
width: var(--timeline-marker-size, --spacing(7));
|
|
119
|
+
height: var(--timeline-marker-size, --spacing(7));
|
|
120
|
+
object-fit: cover;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Bare glyphs pin to a sensible size so markers align across icon packs (mirrors .media__figure). The
|
|
125
|
+
:only-child gate leaves multi-element markers to size themselves. */
|
|
126
|
+
.timeline__marker > :is(svg, i):only-child {
|
|
127
|
+
width: --spacing(4);
|
|
128
|
+
height: --spacing(4);
|
|
129
|
+
flex-shrink: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* === Body === */
|
|
133
|
+
.timeline__body {
|
|
134
|
+
grid-column: 2;
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
gap: --spacing(0.5);
|
|
138
|
+
min-width: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.timeline__time {
|
|
142
|
+
font-size: var(--text-xs);
|
|
143
|
+
line-height: var(--leading-normal);
|
|
144
|
+
color: var(--color-muted-foreground);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.timeline__title {
|
|
148
|
+
font-weight: var(--font-weight-semibold);
|
|
149
|
+
line-height: var(--leading-tight);
|
|
150
|
+
color: var(--color-foreground);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.timeline__text {
|
|
154
|
+
line-height: var(--leading-normal);
|
|
155
|
+
color: var(--color-muted-foreground);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* === Status (data-state) === author-set, independent of marker colour. complete keeps the solid rail;
|
|
159
|
+
current rings the marker and dashes the rail leaving it; pending mutes the marker, hollows the dot,
|
|
160
|
+
and dashes the rail. */
|
|
161
|
+
|
|
162
|
+
/* Complete — solid-filled marker so a done step reads as filled rather than tinted. */
|
|
163
|
+
.timeline__item[data-state="complete"] .timeline__marker:not(:empty) {
|
|
164
|
+
background: var(--timeline-marker-color, var(--color-primary));
|
|
165
|
+
border-color: var(--timeline-marker-color, var(--color-primary));
|
|
166
|
+
color: var(--timeline-marker-on-color, var(--color-primary-foreground));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Current — ring halo + dashed rail below. The ping radiates a ring outward and fades, like a live
|
|
170
|
+
locator; reduced motion keeps the static halo and drops the ping. */
|
|
171
|
+
.timeline__item[data-state="current"] {
|
|
172
|
+
--timeline-connector-style: dashed;
|
|
173
|
+
}
|
|
174
|
+
.timeline__item[data-state="current"] .timeline__marker {
|
|
175
|
+
box-shadow: 0 0 0 var(--timeline-marker-ring-size, --spacing(1))
|
|
176
|
+
var(
|
|
177
|
+
--timeline-marker-ring,
|
|
178
|
+
color-mix(
|
|
179
|
+
in oklch,
|
|
180
|
+
var(--timeline-marker-color, var(--color-primary)) 22%,
|
|
181
|
+
transparent
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
.timeline__item[data-state="current"] .timeline__marker::after {
|
|
186
|
+
content: "";
|
|
187
|
+
position: absolute;
|
|
188
|
+
inset: 0;
|
|
189
|
+
border-radius: 50%;
|
|
190
|
+
border: var(--timeline-connector-width, 2px) solid
|
|
191
|
+
color-mix(
|
|
192
|
+
in oklch,
|
|
193
|
+
var(--timeline-marker-color, var(--color-primary)) 45%,
|
|
194
|
+
transparent
|
|
195
|
+
);
|
|
196
|
+
animation: timeline-ping var(--timeline-ping-duration, 1.8s)
|
|
197
|
+
cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Pending — muted marker, hollow dot, dashed rail. The colour override flows to the dot, icon, and
|
|
201
|
+
soft-circle tint at once. */
|
|
202
|
+
.timeline__item[data-state="pending"] {
|
|
203
|
+
--timeline-marker-color: var(--color-muted-foreground);
|
|
204
|
+
--timeline-connector-style: dashed;
|
|
205
|
+
}
|
|
206
|
+
.timeline__item[data-state="pending"] .timeline__marker:empty::before {
|
|
207
|
+
background: transparent;
|
|
208
|
+
border: var(--timeline-connector-width, 2px) solid
|
|
209
|
+
var(--timeline-marker-color, var(--color-muted-foreground));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* === Intent modifiers === shorthand for a palette token (fixed meaning). For a one-off colour set
|
|
213
|
+
--timeline-marker-color on the item instead. */
|
|
214
|
+
.timeline__marker--primary {
|
|
215
|
+
--timeline-marker-color: var(--color-primary);
|
|
216
|
+
}
|
|
217
|
+
.timeline__marker--success {
|
|
218
|
+
--timeline-marker-color: var(--color-success);
|
|
219
|
+
}
|
|
220
|
+
.timeline__marker--danger {
|
|
221
|
+
--timeline-marker-color: var(--color-danger);
|
|
222
|
+
}
|
|
223
|
+
.timeline__marker--warning {
|
|
224
|
+
--timeline-marker-color: var(--color-warning);
|
|
225
|
+
}
|
|
226
|
+
.timeline__marker--info {
|
|
227
|
+
--timeline-marker-color: var(--color-info);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* === Alternate layout === centre rail, items alternating sides. Needs width, so it only engages at md
|
|
231
|
+
and up; below that it falls through to the standard left-rail grid. Three columns: body / rail /
|
|
232
|
+
body. Odd items take the inline-start body, even take inline-end; the connector re-centres. */
|
|
233
|
+
@media (min-width: 48rem) {
|
|
234
|
+
.timeline--alternate .timeline__item {
|
|
235
|
+
grid-template-columns: 1fr var(--timeline-marker-size, --spacing(7)) 1fr;
|
|
236
|
+
}
|
|
237
|
+
.timeline--alternate .timeline__marker {
|
|
238
|
+
grid-column: 2;
|
|
239
|
+
}
|
|
240
|
+
.timeline--alternate .timeline__item:nth-child(odd) .timeline__body {
|
|
241
|
+
grid-column: 1;
|
|
242
|
+
text-align: end;
|
|
243
|
+
}
|
|
244
|
+
.timeline--alternate .timeline__item:nth-child(even) .timeline__body {
|
|
245
|
+
grid-column: 3;
|
|
246
|
+
text-align: start;
|
|
247
|
+
}
|
|
248
|
+
.timeline--alternate .timeline__item:not(:last-child)::before {
|
|
249
|
+
inset-inline-start: 50%;
|
|
250
|
+
transform: translateX(-50%);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@media (prefers-reduced-motion: reduce) {
|
|
255
|
+
.timeline__item[data-state="current"] .timeline__marker::after {
|
|
256
|
+
display: none;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes timeline-ping {
|
|
262
|
+
0% {
|
|
263
|
+
transform: scale(1);
|
|
264
|
+
opacity: 1;
|
|
265
|
+
}
|
|
266
|
+
75%,
|
|
267
|
+
100% {
|
|
268
|
+
transform: scale(1.85);
|
|
269
|
+
opacity: 0;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/* @stisla/style — Toast. Ported from src/scss/components/_toast.scss. A status message: a fixed
|
|
2
|
+
* .toast-region stack at one of six viewport corners holds .toast cards laid out as a 3-column grid
|
|
3
|
+
* [icon | content | close]. Shown via [data-state="open|closed"] on each toast (base renders open, so
|
|
4
|
+
* static markup-first toasts show without JS). References the @theme tokens: colors var(--color-*),
|
|
5
|
+
* sizes/spacing --spacing(n), type var(--text-*) / var(--leading-*) / var(--font-weight-*), radius
|
|
6
|
+
* var(--radius-*), shadow var(--shadow-*); only no-namespace customs use --st-* (border-width,
|
|
7
|
+
* duration, z-index). z-index routes through the z-index scale (--z-index-toast, above modal so a toast
|
|
8
|
+
* can confirm a dialog action). Knobs are --toast-*. Enqueue/dismiss/timers ship with the JS layer; the entrance
|
|
9
|
+
* fade uses @starting-style + allow-discrete so region-mounted toasts animate with no JS paint.
|
|
10
|
+
* @layer components. Authoring rules: ../../../../PORTING.md */
|
|
11
|
+
|
|
12
|
+
@layer components {
|
|
13
|
+
/* === Region === fixed stack at a viewport corner; pointer-events: none so the page stays
|
|
14
|
+
interactive in the gaps, each toast re-enabling events on itself. Default placement is top-end. */
|
|
15
|
+
.toast-region {
|
|
16
|
+
position: fixed;
|
|
17
|
+
z-index: var(--toast-region-z-index, var(--z-index-toast));
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: var(--toast-region-gap, --spacing(4));
|
|
21
|
+
max-width: var(--toast-region-max-width, --spacing(96));
|
|
22
|
+
width: calc(100% - 2 * var(--toast-region-inset, --spacing(4)));
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
|
|
25
|
+
inset-block-start: var(--toast-region-inset, --spacing(4));
|
|
26
|
+
inset-inline-end: var(--toast-region-inset, --spacing(4));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.toast-region--top-start {
|
|
30
|
+
inset-block-start: var(--toast-region-inset, --spacing(4));
|
|
31
|
+
inset-block-end: auto;
|
|
32
|
+
inset-inline-start: var(--toast-region-inset, --spacing(4));
|
|
33
|
+
inset-inline-end: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.toast-region--top-center {
|
|
37
|
+
inset-block-start: var(--toast-region-inset, --spacing(4));
|
|
38
|
+
inset-block-end: auto;
|
|
39
|
+
inset-inline: 0;
|
|
40
|
+
margin-inline: auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.toast-region--top-end {
|
|
44
|
+
inset-block-start: var(--toast-region-inset, --spacing(4));
|
|
45
|
+
inset-block-end: auto;
|
|
46
|
+
inset-inline-start: auto;
|
|
47
|
+
inset-inline-end: var(--toast-region-inset, --spacing(4));
|
|
48
|
+
align-items: end;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Bottom variants reverse so the newest toast sits at the corner and older ones push up. */
|
|
52
|
+
.toast-region--bottom-start {
|
|
53
|
+
inset-block-start: auto;
|
|
54
|
+
inset-block-end: var(--toast-region-inset, --spacing(4));
|
|
55
|
+
inset-inline-start: var(--toast-region-inset, --spacing(4));
|
|
56
|
+
inset-inline-end: auto;
|
|
57
|
+
flex-direction: column-reverse;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.toast-region--bottom-center {
|
|
61
|
+
inset-block-start: auto;
|
|
62
|
+
inset-block-end: var(--toast-region-inset, --spacing(4));
|
|
63
|
+
inset-inline: 0;
|
|
64
|
+
margin-inline: auto;
|
|
65
|
+
flex-direction: column-reverse;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.toast-region--bottom-end {
|
|
69
|
+
inset-block-start: auto;
|
|
70
|
+
inset-block-end: var(--toast-region-inset, --spacing(4));
|
|
71
|
+
inset-inline-start: auto;
|
|
72
|
+
inset-inline-end: var(--toast-region-inset, --spacing(4));
|
|
73
|
+
flex-direction: column-reverse;
|
|
74
|
+
align-items: end;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* === Toast === base = displayed (a bare .toast or one inline in docs renders without a fade). The
|
|
78
|
+
allow-discrete on display holds the grid value through the close fade so the transition plays
|
|
79
|
+
before layout collapses. */
|
|
80
|
+
.toast {
|
|
81
|
+
z-index: var(--toast-z-index, var(--z-index-toast));
|
|
82
|
+
pointer-events: auto;
|
|
83
|
+
display: grid;
|
|
84
|
+
grid-template-columns: auto 1fr auto;
|
|
85
|
+
align-items: start;
|
|
86
|
+
column-gap: var(--toast-column-gap, --spacing(3));
|
|
87
|
+
width: 100%;
|
|
88
|
+
min-width: var(--toast-min-width, --spacing(72));
|
|
89
|
+
max-width: var(--toast-max-width, --spacing(87.5));
|
|
90
|
+
padding: var(--toast-padding-block, --spacing(4))
|
|
91
|
+
var(--toast-padding-inline, --spacing(4));
|
|
92
|
+
background-color: var(--toast-bg, var(--color-surface));
|
|
93
|
+
color: var(--toast-color, var(--color-foreground));
|
|
94
|
+
line-height: var(--leading-none);
|
|
95
|
+
border: var(--toast-border-width, var(--st-border-width)) solid
|
|
96
|
+
var(--toast-border-color, var(--color-border));
|
|
97
|
+
border-radius: var(--toast-radius, var(--radius-lg));
|
|
98
|
+
box-shadow: var(--toast-shadow, var(--shadow-md));
|
|
99
|
+
opacity: 1;
|
|
100
|
+
transform: none;
|
|
101
|
+
transition:
|
|
102
|
+
opacity var(--toast-transition-duration, var(--transition-duration-normal)) ease,
|
|
103
|
+
transform var(--toast-transition-duration, var(--transition-duration-normal)) ease,
|
|
104
|
+
display var(--toast-transition-duration, var(--transition-duration-normal)) ease
|
|
105
|
+
allow-discrete;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Bottom-region toasts slide in from below so the motion matches the stack growth direction. */
|
|
109
|
+
.toast-region--bottom-start > .toast,
|
|
110
|
+
.toast-region--bottom-center > .toast,
|
|
111
|
+
.toast-region--bottom-end > .toast {
|
|
112
|
+
--toast-enter-transform: translateY(--spacing(2));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Single-line toasts (title only) center vertically — the start-aligned optical nudge that keeps a
|
|
116
|
+
multi-line title lined up with the icon reads off-balance with one line. */
|
|
117
|
+
.toast:not(:has(.toast__body)):not(:has(.toast__action)) {
|
|
118
|
+
align-items: center;
|
|
119
|
+
}
|
|
120
|
+
.toast:not(:has(.toast__body)):not(:has(.toast__action)) .toast__icon {
|
|
121
|
+
margin-block-start: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.toast[data-state="closed"] {
|
|
125
|
+
display: none;
|
|
126
|
+
opacity: 0;
|
|
127
|
+
transform: var(
|
|
128
|
+
--toast-enter-transform,
|
|
129
|
+
translateY(calc(--spacing(2) * -1))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Region-mounted open toasts get an entrance fade via @starting-style (the "from" values as the
|
|
134
|
+
element leaves display: none). Static inline toasts don't match, so they render flat. */
|
|
135
|
+
.toast-region > .toast[data-state="open"] {
|
|
136
|
+
@starting-style {
|
|
137
|
+
opacity: 0;
|
|
138
|
+
transform: var(
|
|
139
|
+
--toast-enter-transform,
|
|
140
|
+
translateY(calc(--spacing(2) * -1))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* === Parts === col 1, leading visual (required). The small optical nudge lands the icon's center
|
|
146
|
+
on the title's cap line; grid align-items: start anchors every column to the row top. */
|
|
147
|
+
.toast__icon {
|
|
148
|
+
grid-column: 1;
|
|
149
|
+
margin-block-start: --spacing(0.5);
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
width: var(--toast-icon-size, --spacing(4.5));
|
|
154
|
+
height: var(--toast-icon-size, --spacing(4.5));
|
|
155
|
+
color: var(--toast-icon-color, var(--color-muted-foreground));
|
|
156
|
+
line-height: var(--leading-none);
|
|
157
|
+
flex-shrink: 0;
|
|
158
|
+
}
|
|
159
|
+
.toast__icon > svg,
|
|
160
|
+
.toast__icon > i {
|
|
161
|
+
width: 100%;
|
|
162
|
+
height: 100%;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Col 2 — content stack; its own vertical flex gap, independent of the grid row gap. */
|
|
166
|
+
.toast__content {
|
|
167
|
+
grid-column: 2;
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-direction: column;
|
|
170
|
+
gap: var(--toast-content-gap, --spacing(1.5));
|
|
171
|
+
min-width: 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.toast__header {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: --spacing(2);
|
|
178
|
+
font-size: var(--toast-header-font-size, var(--text-sm));
|
|
179
|
+
font-weight: var(--toast-header-font-weight, var(--font-weight-semibold));
|
|
180
|
+
line-height: var(--leading-none);
|
|
181
|
+
color: var(--toast-color, var(--color-foreground));
|
|
182
|
+
min-width: 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Optional timestamp slot — "10 mins ago"; reads muted next to the title. */
|
|
186
|
+
.toast__timestamp {
|
|
187
|
+
font-weight: var(--font-weight-normal);
|
|
188
|
+
font-size: var(--text-xs);
|
|
189
|
+
color: var(--color-muted-foreground);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.toast__body {
|
|
193
|
+
font-size: var(--toast-body-font-size, var(--text-sm));
|
|
194
|
+
line-height: var(--leading-normal);
|
|
195
|
+
color: var(--toast-body-color, var(--color-muted-foreground));
|
|
196
|
+
min-width: 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.toast__action {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: var(--toast-action-gap, --spacing(1));
|
|
203
|
+
margin-block-start: --spacing(1.5);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Col 3 — close chip; anchored top-right, never inflates row height beyond the content stack. */
|
|
207
|
+
.toast__close {
|
|
208
|
+
grid-column: 3;
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
width: var(--toast-close-size, --spacing(6));
|
|
214
|
+
height: var(--toast-close-size, --spacing(6));
|
|
215
|
+
padding: 0;
|
|
216
|
+
color: var(--toast-close-color, var(--color-muted-foreground));
|
|
217
|
+
background-color: transparent;
|
|
218
|
+
border: 0;
|
|
219
|
+
border-radius: 50%;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition:
|
|
222
|
+
background-color var(--transition-duration-fast) ease,
|
|
223
|
+
color var(--transition-duration-fast) ease;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.toast__close:hover {
|
|
227
|
+
color: var(--toast-close-color-hover, var(--color-foreground));
|
|
228
|
+
background-color: var(--toast-close-bg-hover, var(--color-accent));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.toast__close:focus-visible {
|
|
232
|
+
outline: 2px solid var(--color-ring);
|
|
233
|
+
outline-offset: 2px;
|
|
234
|
+
color: var(--toast-close-color-hover, var(--color-foreground));
|
|
235
|
+
background-color: var(--toast-close-bg-hover, var(--color-accent));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.toast__close > svg,
|
|
239
|
+
.toast__close > i {
|
|
240
|
+
width: --spacing(3.5);
|
|
241
|
+
height: --spacing(3.5);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* === Intent modifiers === each shifts ONLY the icon color; the surface stays neutral so stacked
|
|
245
|
+
toasts of mixed intent still read as one family. */
|
|
246
|
+
.toast--primary {
|
|
247
|
+
--toast-icon-color: var(--color-primary);
|
|
248
|
+
}
|
|
249
|
+
.toast--success {
|
|
250
|
+
--toast-icon-color: var(--color-success);
|
|
251
|
+
}
|
|
252
|
+
.toast--warning {
|
|
253
|
+
--toast-icon-color: var(--color-warning);
|
|
254
|
+
}
|
|
255
|
+
.toast--danger {
|
|
256
|
+
--toast-icon-color: var(--color-danger);
|
|
257
|
+
}
|
|
258
|
+
.toast--info {
|
|
259
|
+
--toast-icon-color: var(--color-info);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@media (prefers-reduced-motion: reduce) {
|
|
263
|
+
.toast {
|
|
264
|
+
transition: none;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* @stisla/style — Toggle. Ported from src/scss/components/_toggle.scss. A two-state press button:
|
|
2
|
+
* outline-neutral rest, accent hover, highlight active. Active comes from THREE sources, all paint
|
|
3
|
+
* identically: [aria-pressed="true"] (JS press button), [data-state="active"] (JS single-select
|
|
4
|
+
* member), or .toggle-input:checked + .toggle (form-data, native input drives the label). References
|
|
5
|
+
* the @theme tokens (colors var(--color-*), spacing/sizes --spacing(n), type var(--text-*) /
|
|
6
|
+
* var(--font-weight-*) / var(--leading-*), radius var(--radius-*)); only no-namespace customs use
|
|
7
|
+
* --st-* (border-width, duration). Knobs are --toggle-*; sizes compact/roomy → sm/lg; pill radius
|
|
8
|
+
* literal 9999px. State via attributes / native, no is-*. @layer components. Authoring: ../../../../PORTING.md */
|
|
9
|
+
|
|
10
|
+
@layer components {
|
|
11
|
+
.toggle {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
gap: var(--toggle-gap, --spacing(2));
|
|
16
|
+
height: var(--toggle-height, --spacing(9));
|
|
17
|
+
padding-block: var(--toggle-padding-block, 0);
|
|
18
|
+
padding-inline: var(--toggle-padding-inline, --spacing(3));
|
|
19
|
+
font-family: inherit;
|
|
20
|
+
font-size: var(--toggle-font-size, var(--text-sm));
|
|
21
|
+
font-weight: var(--toggle-font-weight, var(--font-weight-medium));
|
|
22
|
+
line-height: var(--leading-none);
|
|
23
|
+
color: var(--toggle-color, var(--color-foreground));
|
|
24
|
+
background: var(--toggle-bg, transparent);
|
|
25
|
+
border: var(--toggle-border-width, var(--st-border-width)) solid
|
|
26
|
+
var(--toggle-border-color, var(--color-border));
|
|
27
|
+
border-radius: var(--toggle-radius, var(--radius-md));
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
user-select: none;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
text-overflow: ellipsis;
|
|
34
|
+
transition:
|
|
35
|
+
background-color var(--transition-duration-fast) ease,
|
|
36
|
+
border-color var(--transition-duration-fast) ease,
|
|
37
|
+
color var(--transition-duration-fast) ease;
|
|
38
|
+
|
|
39
|
+
&:hover {
|
|
40
|
+
background: var(--toggle-bg-hover, var(--color-accent));
|
|
41
|
+
color: var(--toggle-color-hover, var(--color-accent-foreground));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:focus-visible {
|
|
45
|
+
outline: 2px solid var(--toggle-ring, var(--color-ring));
|
|
46
|
+
outline-offset: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:disabled,
|
|
50
|
+
&[aria-disabled="true"] {
|
|
51
|
+
cursor: not-allowed;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
opacity: 0.55;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Active — three sources, one paint block (after the base so it wins specificity ties). */
|
|
58
|
+
.toggle[aria-pressed="true"],
|
|
59
|
+
.toggle[data-state="active"],
|
|
60
|
+
.toggle-input:checked + .toggle {
|
|
61
|
+
background: var(--toggle-bg-active, var(--color-highlight));
|
|
62
|
+
color: var(--toggle-color-active, var(--color-highlight-foreground));
|
|
63
|
+
border-color: var(--toggle-border-color-active, var(--toggle-bg-active, var(--color-highlight)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Icons scale to font-size — 1em so they shrink with --sm / grow with --lg automatically. */
|
|
67
|
+
.toggle :is(svg, i, .toggle__icon) {
|
|
68
|
+
width: 1em;
|
|
69
|
+
height: 1em;
|
|
70
|
+
flex-shrink: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
.toggle {
|
|
75
|
+
transition: none;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* === Sizes (base = md) === */
|
|
80
|
+
.toggle--sm {
|
|
81
|
+
--toggle-radius: var(--radius-sm);
|
|
82
|
+
--toggle-height: --spacing(7);
|
|
83
|
+
--toggle-padding-inline: --spacing(2.5);
|
|
84
|
+
--toggle-font-size: var(--text-xs);
|
|
85
|
+
}
|
|
86
|
+
.toggle--lg {
|
|
87
|
+
--toggle-radius: var(--radius-lg);
|
|
88
|
+
--toggle-height: --spacing(11);
|
|
89
|
+
--toggle-padding-inline: --spacing(4);
|
|
90
|
+
--toggle-font-size: var(--text-base);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* === Icon shapes === square at every size (mirrors .button--icon-only / --icon-round). */
|
|
94
|
+
.toggle--icon-only {
|
|
95
|
+
--toggle-padding-inline: 0;
|
|
96
|
+
width: var(--toggle-height, --spacing(9));
|
|
97
|
+
}
|
|
98
|
+
.toggle--icon-round {
|
|
99
|
+
border-radius: 9999px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* === Hidden input (form-data path) === visually hidden so SR + keyboard still reach it; the
|
|
103
|
+
adjacent <label class="toggle"> carries the visible paint via the :checked sibling rule above. */
|
|
104
|
+
.toggle-input {
|
|
105
|
+
position: absolute;
|
|
106
|
+
width: 1px;
|
|
107
|
+
height: 1px;
|
|
108
|
+
padding: 0;
|
|
109
|
+
margin: -1px;
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
clip: rect(0, 0, 0, 0);
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
border: 0;
|
|
114
|
+
|
|
115
|
+
&:focus-visible + .toggle {
|
|
116
|
+
outline: 2px solid var(--toggle-ring, var(--color-ring));
|
|
117
|
+
outline-offset: 2px;
|
|
118
|
+
}
|
|
119
|
+
&:disabled + .toggle {
|
|
120
|
+
cursor: not-allowed;
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
opacity: 0.55;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|