@dorsk/tsumikit 0.79.0 → 0.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/atoms/Artwork.svelte +14 -10
- package/dist/components/atoms/Artwork.svelte.d.ts +1 -1
- package/dist/components/atoms/Badge.svelte +1 -4
- package/dist/components/atoms/Button.svelte +303 -9
- package/dist/components/atoms/Button.svelte.d.ts +4 -1
- package/dist/components/atoms/Card.svelte +142 -0
- package/dist/components/atoms/Checkbox.svelte +177 -1
- package/dist/components/atoms/Input.svelte +35 -0
- package/dist/components/atoms/Link.svelte +61 -0
- package/dist/components/atoms/Progress.svelte +74 -1
- package/dist/components/atoms/Progress.svelte.d.ts +1 -0
- package/dist/components/atoms/Select.svelte +30 -0
- package/dist/components/atoms/Skeleton.svelte +54 -3
- package/dist/components/atoms/Skeleton.svelte.d.ts +1 -0
- package/dist/components/atoms/Spinner.svelte +152 -3
- package/dist/components/atoms/Spinner.svelte.d.ts +1 -1
- package/dist/components/atoms/Switch.svelte +135 -0
- package/dist/components/atoms/Textarea.svelte +36 -0
- package/dist/components/atoms/Thinking.svelte +130 -0
- package/dist/components/atoms/Thinking.svelte.d.ts +15 -0
- package/dist/components/molecules/CopyButton.svelte +146 -5
- package/dist/components/molecules/Field.svelte +55 -1
- package/dist/components/molecules/FileButton.svelte +4 -2
- package/dist/components/molecules/InputGroup.svelte +1 -1
- package/dist/components/molecules/LookPicker.svelte +100 -0
- package/dist/components/molecules/LookPicker.svelte.d.ts +17 -0
- package/dist/components/molecules/MotionSettings.svelte +166 -0
- package/dist/components/molecules/MotionSettings.svelte.d.ts +18 -0
- package/dist/components/molecules/RadioGroup.svelte +61 -0
- package/dist/components/molecules/SectionHeader.svelte +2 -0
- package/dist/components/molecules/Tabs.svelte +157 -4
- package/dist/components/molecules/ThemePicker.svelte +8 -3
- package/dist/fx.d.ts +149 -0
- package/dist/fx.js +111 -0
- package/dist/fx.md +58 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +8 -2
- package/dist/looks/README.md +101 -0
- package/dist/looks/depth.css +939 -0
- package/dist/looks/editorial.css +545 -0
- package/dist/looks/tactile.css +871 -0
- package/dist/markdown.js +47 -21
- package/dist/pointer.d.ts +3 -0
- package/dist/pointer.js +40 -0
- package/dist/stores/fx.svelte.d.ts +17 -0
- package/dist/stores/fx.svelte.js +64 -0
- package/dist/stores/theme.svelte.d.ts +8 -2
- package/dist/stores/theme.svelte.js +33 -7
- package/dist/styles/themes.css +249 -249
- package/dist/styles/tokens.css +4 -3
- package/dist/theme-mode.d.ts +31 -0
- package/dist/theme-mode.js +19 -0
- package/package.json +3 -2
|
@@ -44,8 +44,10 @@
|
|
|
44
44
|
|
|
45
45
|
const field = getFieldContext();
|
|
46
46
|
const isInvalid = $derived(invalid || !!field?.invalid);
|
|
47
|
+
let armed = $state(false);
|
|
47
48
|
|
|
48
49
|
function change(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
50
|
+
armed = true;
|
|
49
51
|
onchange?.(e);
|
|
50
52
|
playSound(e.currentTarget.checked ? 'toggle-on' : 'toggle-off', sound);
|
|
51
53
|
}
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
class="checkbox {klass}"
|
|
61
63
|
class:checkbox-sm={size === 'sm'}
|
|
62
64
|
class:checkbox-lg={size === 'lg'}
|
|
65
|
+
class:armed
|
|
63
66
|
data-tsu="Checkbox"
|
|
64
67
|
>
|
|
65
68
|
<input
|
|
@@ -72,7 +75,10 @@
|
|
|
72
75
|
aria-describedby={ariaDescribedby ?? field?.describedBy}
|
|
73
76
|
aria-invalid={ariaInvalid ?? (isInvalid ? 'true' : undefined)}
|
|
74
77
|
/>
|
|
75
|
-
<span class="box" aria-hidden="true"
|
|
78
|
+
<span class="box" aria-hidden="true">
|
|
79
|
+
<svg viewBox="0 0 20 20"><path class="tick" pathLength="1" d="M5.5 10.5l3 3 6-7" /></svg>
|
|
80
|
+
<span class="fx"></span>
|
|
81
|
+
</span>
|
|
76
82
|
<span class="label-text" class:sr-only={labelHidden}>{label}</span>
|
|
77
83
|
</label>
|
|
78
84
|
|
|
@@ -110,6 +116,7 @@
|
|
|
110
116
|
border-radius: var(--r-sm);
|
|
111
117
|
background: var(--control-fill);
|
|
112
118
|
box-shadow: var(--control-shadow);
|
|
119
|
+
isolation: isolate;
|
|
113
120
|
transition:
|
|
114
121
|
background var(--dur-fast) var(--ease),
|
|
115
122
|
border-color var(--dur-fast) var(--ease),
|
|
@@ -156,6 +163,175 @@
|
|
|
156
163
|
opacity var(--dur-spring) var(--ease-spring),
|
|
157
164
|
scale var(--dur-spring) var(--ease-spring);
|
|
158
165
|
}
|
|
166
|
+
svg {
|
|
167
|
+
position: absolute;
|
|
168
|
+
inset: -1px;
|
|
169
|
+
width: calc(100% + 2px);
|
|
170
|
+
height: calc(100% + 2px);
|
|
171
|
+
overflow: visible;
|
|
172
|
+
fill: none;
|
|
173
|
+
stroke-linecap: round;
|
|
174
|
+
stroke-linejoin: round;
|
|
175
|
+
}
|
|
176
|
+
.tick {
|
|
177
|
+
stroke: var(--text-on-accent);
|
|
178
|
+
stroke-width: 2.25;
|
|
179
|
+
stroke-dasharray: 1 2;
|
|
180
|
+
stroke-dashoffset: 1.05;
|
|
181
|
+
transition: stroke-dashoffset var(--dur-exit-fast) var(--ease-out);
|
|
182
|
+
}
|
|
183
|
+
.fx {
|
|
184
|
+
position: absolute;
|
|
185
|
+
inset: 0;
|
|
186
|
+
z-index: -1;
|
|
187
|
+
font-size: var(--cb-box, var(--chip-sm));
|
|
188
|
+
pointer-events: none;
|
|
189
|
+
}
|
|
190
|
+
.fx::before,
|
|
191
|
+
.fx::after {
|
|
192
|
+
content: '';
|
|
193
|
+
position: absolute;
|
|
194
|
+
opacity: 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Effects follow the closest data-fx-check ancestor; draw is the default. */
|
|
198
|
+
.checkbox:not(:global([data-fx-check]:not([data-fx-check='draw'])) *),
|
|
199
|
+
:global([data-fx-check='draw']) .checkbox:not(:global([data-fx-check='draw'] [data-fx-check]:not([data-fx-check='draw'])) *),
|
|
200
|
+
:global([data-fx-check='pop']) .checkbox:not(:global([data-fx-check='pop'] [data-fx-check]:not([data-fx-check='pop'])) *),
|
|
201
|
+
:global([data-fx-check='confetti']) .checkbox:not(:global([data-fx-check='confetti'] [data-fx-check]:not([data-fx-check='confetti'])) *) {
|
|
202
|
+
& input:checked:not(:indeterminate) + .box::after {
|
|
203
|
+
opacity: 0;
|
|
204
|
+
transition: none;
|
|
205
|
+
}
|
|
206
|
+
& input:checked:not(:indeterminate) + .box .tick {
|
|
207
|
+
stroke-dashoffset: 0;
|
|
208
|
+
transition: stroke-dashoffset var(--dur-exit-slow) var(--ease-out) calc(var(--dur-fast) / 3);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
:global([data-fx-check='pop']) .checkbox:not(:global([data-fx-check='pop'] [data-fx-check]:not([data-fx-check='pop'])) *) {
|
|
213
|
+
&.armed input:checked + .box {
|
|
214
|
+
animation: cb-pop var(--dur-spring) var(--ease-out);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
@keyframes cb-pop {
|
|
218
|
+
30% {
|
|
219
|
+
scale: 0.8;
|
|
220
|
+
}
|
|
221
|
+
65% {
|
|
222
|
+
scale: 1.14;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
:global([data-fx-check='confetti']) .checkbox:not(:global([data-fx-check='confetti'] [data-fx-check]:not([data-fx-check='confetti'])) *) {
|
|
227
|
+
& .fx::before,
|
|
228
|
+
& .fx::after {
|
|
229
|
+
inset: -0.85em;
|
|
230
|
+
border-radius: var(--r-pill);
|
|
231
|
+
}
|
|
232
|
+
& .fx::before {
|
|
233
|
+
background:
|
|
234
|
+
radial-gradient(circle at 50% 6%, var(--accent) 0 0.06em, transparent 0.08em),
|
|
235
|
+
radial-gradient(circle at 94% 50%, var(--info) 0 0.06em, transparent 0.08em),
|
|
236
|
+
radial-gradient(circle at 50% 94%, var(--accent) 0 0.06em, transparent 0.08em),
|
|
237
|
+
radial-gradient(circle at 6% 50%, var(--ok) 0 0.06em, transparent 0.08em);
|
|
238
|
+
}
|
|
239
|
+
& .fx::after {
|
|
240
|
+
background:
|
|
241
|
+
radial-gradient(circle at 81% 19%, var(--warn) 0 0.045em, transparent 0.065em),
|
|
242
|
+
radial-gradient(circle at 81% 81%, var(--accent) 0 0.045em, transparent 0.065em),
|
|
243
|
+
radial-gradient(circle at 19% 81%, var(--info) 0 0.045em, transparent 0.065em),
|
|
244
|
+
radial-gradient(circle at 19% 19%, var(--accent) 0 0.045em, transparent 0.065em);
|
|
245
|
+
}
|
|
246
|
+
&.armed input:checked:not(:indeterminate) + .box .fx::before {
|
|
247
|
+
animation: cb-burst var(--dur-spring) var(--ease);
|
|
248
|
+
}
|
|
249
|
+
&.armed input:checked:not(:indeterminate) + .box .fx::after {
|
|
250
|
+
animation: cb-burst-late var(--dur-spring) var(--ease);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
@keyframes cb-burst {
|
|
254
|
+
0% {
|
|
255
|
+
opacity: 0;
|
|
256
|
+
scale: 0.35;
|
|
257
|
+
}
|
|
258
|
+
20%,
|
|
259
|
+
60% {
|
|
260
|
+
opacity: 1;
|
|
261
|
+
}
|
|
262
|
+
100% {
|
|
263
|
+
opacity: 0;
|
|
264
|
+
scale: 1.05;
|
|
265
|
+
rotate: 18deg;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
@keyframes cb-burst-late {
|
|
269
|
+
0% {
|
|
270
|
+
opacity: 0;
|
|
271
|
+
scale: 0.3;
|
|
272
|
+
}
|
|
273
|
+
30%,
|
|
274
|
+
70% {
|
|
275
|
+
opacity: 1;
|
|
276
|
+
}
|
|
277
|
+
100% {
|
|
278
|
+
opacity: 0;
|
|
279
|
+
scale: 0.9;
|
|
280
|
+
rotate: -14deg;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Two bars, the short and long legs of the tick, fold flat into the dash. */
|
|
285
|
+
:global([data-fx-check='morph']) .checkbox:not(:global([data-fx-check='morph'] [data-fx-check]:not([data-fx-check='morph'])) *) {
|
|
286
|
+
& .box::after {
|
|
287
|
+
display: none;
|
|
288
|
+
}
|
|
289
|
+
& .fx {
|
|
290
|
+
z-index: 0;
|
|
291
|
+
}
|
|
292
|
+
& .fx::before,
|
|
293
|
+
& .fx::after {
|
|
294
|
+
left: 0;
|
|
295
|
+
top: 0;
|
|
296
|
+
height: 0.11em;
|
|
297
|
+
border-radius: var(--r-pill);
|
|
298
|
+
background: var(--text-on-accent);
|
|
299
|
+
scale: 0;
|
|
300
|
+
transition:
|
|
301
|
+
opacity var(--dur-fast) var(--ease),
|
|
302
|
+
translate var(--dur-spring) var(--ease-spring),
|
|
303
|
+
rotate var(--dur-spring) var(--ease-spring),
|
|
304
|
+
scale var(--dur-spring) var(--ease-spring);
|
|
305
|
+
}
|
|
306
|
+
& .fx::before {
|
|
307
|
+
width: 0.26em;
|
|
308
|
+
translate: 0.22em 0.545em;
|
|
309
|
+
rotate: 45deg;
|
|
310
|
+
}
|
|
311
|
+
& .fx::after {
|
|
312
|
+
width: 0.5em;
|
|
313
|
+
translate: 0.325em 0.445em;
|
|
314
|
+
rotate: -49deg;
|
|
315
|
+
}
|
|
316
|
+
& input:checked + .box .fx::before,
|
|
317
|
+
& input:checked + .box .fx::after {
|
|
318
|
+
opacity: 1;
|
|
319
|
+
scale: 1;
|
|
320
|
+
}
|
|
321
|
+
& input:indeterminate + .box .fx::before {
|
|
322
|
+
opacity: 1;
|
|
323
|
+
translate: 0.37em 0.445em;
|
|
324
|
+
rotate: 0deg;
|
|
325
|
+
scale: 1;
|
|
326
|
+
}
|
|
327
|
+
& input:indeterminate + .box .fx::after {
|
|
328
|
+
opacity: 1;
|
|
329
|
+
translate: 0.25em 0.445em;
|
|
330
|
+
rotate: 0deg;
|
|
331
|
+
scale: 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
159
335
|
input:focus-visible + .box {
|
|
160
336
|
outline: var(--focus-ring);
|
|
161
337
|
outline-offset: var(--focus-ring-offset);
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
style:width={wrapped ? undefined : width}
|
|
107
107
|
bind:value
|
|
108
108
|
{...rest}
|
|
109
|
+
placeholder={rest.placeholder ?? (field ? ' ' : undefined)}
|
|
109
110
|
disabled={isDisabled}
|
|
110
111
|
onkeydown={onenter || onsubmit || onkeydown ? handleKeydown : undefined}
|
|
111
112
|
id={id ?? field?.id}
|
|
@@ -158,6 +159,10 @@
|
|
|
158
159
|
outline-offset: var(--focus-ring-offset);
|
|
159
160
|
box-shadow: var(--control-shadow), var(--focus-glow);
|
|
160
161
|
}
|
|
162
|
+
.input::placeholder {
|
|
163
|
+
color: var(--text-faint);
|
|
164
|
+
opacity: 1;
|
|
165
|
+
}
|
|
161
166
|
.input:disabled {
|
|
162
167
|
opacity: var(--disabled-opacity);
|
|
163
168
|
cursor: not-allowed;
|
|
@@ -208,6 +213,36 @@
|
|
|
208
213
|
.input[aria-invalid='true']:focus {
|
|
209
214
|
border-color: var(--danger);
|
|
210
215
|
}
|
|
216
|
+
.input {
|
|
217
|
+
--focus-tint: var(--accent);
|
|
218
|
+
}
|
|
219
|
+
.input[aria-invalid='true'] {
|
|
220
|
+
--focus-tint: var(--danger);
|
|
221
|
+
}
|
|
222
|
+
/* Focus bloom: a solid ring whose soft halo swells, then settles to a quieter glow. */
|
|
223
|
+
.input:not(.grouped):focus:not(:global([data-fx-focus='none']) *),
|
|
224
|
+
:global(:is([data-fx-focus='bloom'], [data-fx-focus='label'])) .input:not(.grouped):focus:not(:global(:is([data-fx-focus='bloom'], [data-fx-focus='label']) [data-fx-focus='none']) *) {
|
|
225
|
+
outline: none;
|
|
226
|
+
box-shadow:
|
|
227
|
+
var(--control-shadow),
|
|
228
|
+
0 0 0 var(--focus-ring-width) var(--focus-tint),
|
|
229
|
+
0 0 16px 2px color-mix(in srgb, var(--focus-tint) 24%, transparent);
|
|
230
|
+
animation: focus-bloom var(--dur-fast) var(--ease-out);
|
|
231
|
+
}
|
|
232
|
+
@keyframes focus-bloom {
|
|
233
|
+
0% {
|
|
234
|
+
box-shadow:
|
|
235
|
+
var(--control-shadow),
|
|
236
|
+
0 0 0 0 transparent,
|
|
237
|
+
0 0 0 0 transparent;
|
|
238
|
+
}
|
|
239
|
+
45% {
|
|
240
|
+
box-shadow:
|
|
241
|
+
var(--control-shadow),
|
|
242
|
+
0 0 0 calc(var(--focus-ring-width) + 1px) var(--focus-tint),
|
|
243
|
+
0 0 20px 4px color-mix(in srgb, var(--focus-tint) 32%, transparent);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
211
246
|
/* Inside an InputGroup the group draws the focus ring, dims the whole group
|
|
212
247
|
and the overlaid adornments report their widths; the text stays clear of them. */
|
|
213
248
|
.input.grouped {
|
|
@@ -77,10 +77,71 @@
|
|
|
77
77
|
.underline-hover:hover {
|
|
78
78
|
text-decoration: underline;
|
|
79
79
|
}
|
|
80
|
+
.link:hover {
|
|
81
|
+
--link-on: 0%;
|
|
82
|
+
--link-draw: 100%;
|
|
83
|
+
--link-mark: color-mix(in srgb, currentColor 30%, transparent);
|
|
84
|
+
--link-deco: transparent;
|
|
85
|
+
--link-in: var(--dur-press);
|
|
86
|
+
--link-grow: var(--dur-fast);
|
|
87
|
+
--link-hold: var(--dur-instant);
|
|
88
|
+
}
|
|
80
89
|
}
|
|
81
90
|
.align-center {
|
|
82
91
|
text-align: center;
|
|
83
92
|
}
|
|
93
|
+
|
|
94
|
+
/* Hover, focus and press only raise these; each variant decides what they paint. */
|
|
95
|
+
.link:is(:focus-visible, :active) {
|
|
96
|
+
--link-on: 0%;
|
|
97
|
+
--link-draw: 100%;
|
|
98
|
+
--link-mark: color-mix(in srgb, currentColor 30%, transparent);
|
|
99
|
+
--link-deco: transparent;
|
|
100
|
+
--link-in: var(--dur-press);
|
|
101
|
+
--link-grow: var(--dur-fast);
|
|
102
|
+
--link-hold: var(--dur-instant);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Sweep line: the underline fills in from the left and leaves to the right. */
|
|
106
|
+
.link:not(.underline-none):not(:global([data-fx-link]:not([data-fx-link='sweep'])) *),
|
|
107
|
+
:global([data-fx-link='sweep']) .link:not(.underline-none):not(:global([data-fx-link='sweep'] [data-fx-link]:not([data-fx-link='sweep'])) *) {
|
|
108
|
+
--link-base: 0%;
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
background-image:
|
|
111
|
+
linear-gradient(currentColor, currentColor),
|
|
112
|
+
linear-gradient(color-mix(in srgb, currentColor 30%, transparent), color-mix(in srgb, currentColor 30%, transparent));
|
|
113
|
+
background-repeat: no-repeat;
|
|
114
|
+
background-position: var(--link-on, 100%) 100%, 0 100%;
|
|
115
|
+
background-size: var(--link-draw, 0%) 1.5px, var(--link-base) 1px;
|
|
116
|
+
transition:
|
|
117
|
+
color var(--dur-fast) ease,
|
|
118
|
+
background-size var(--dur-fast) var(--ease-out);
|
|
119
|
+
&.underline-always {
|
|
120
|
+
--link-base: 100%;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Highlighter: a marker swipes in behind the words and fades on leave. */
|
|
125
|
+
:global([data-fx-link='highlight']) .link:not(.underline-none):not(:global([data-fx-link='highlight'] [data-fx-link]:not([data-fx-link='highlight'])) *) {
|
|
126
|
+
margin-inline: -0.18em;
|
|
127
|
+
padding-inline: 0.18em;
|
|
128
|
+
border-radius: 0.2em 0.35em 0.25em 0.4em;
|
|
129
|
+
background-image: linear-gradient(100deg, var(--link-mark), color-mix(in srgb, var(--link-mark) 70%, transparent));
|
|
130
|
+
background-repeat: no-repeat;
|
|
131
|
+
background-size: var(--link-draw, 0%) 100%;
|
|
132
|
+
text-decoration-color: var(--link-deco, color-mix(in srgb, currentColor 45%, transparent));
|
|
133
|
+
text-underline-offset: 0.2em;
|
|
134
|
+
transition:
|
|
135
|
+
color var(--dur-fast) ease,
|
|
136
|
+
text-decoration-color var(--dur-fast) ease,
|
|
137
|
+
--link-mark var(--link-in, var(--dur-exit)) ease,
|
|
138
|
+
background-size var(--link-grow, var(--dur-instant)) var(--ease-out) var(--link-hold, var(--dur-exit));
|
|
139
|
+
}
|
|
140
|
+
@property --link-mark {
|
|
141
|
+
syntax: '<color>';
|
|
142
|
+
inherits: false;
|
|
143
|
+
initial-value: transparent;
|
|
144
|
+
}
|
|
84
145
|
@media (pointer: coarse) {
|
|
85
146
|
.link {
|
|
86
147
|
position: relative;
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// gradient — accent→teal fill (storage meter / library-health bars).
|
|
11
11
|
// striped — diagonal stripes; animated while in-flight (unpacking).
|
|
12
12
|
// indeterminate — explicit "scanning"/"importing" mode without a percentage.
|
|
13
|
+
// variant — `liquid`: two soft pulses flow through the track while
|
|
14
|
+
// indeterminate; `stripes`: a diagonal shimmer drifts over
|
|
15
|
+
// the fill.
|
|
13
16
|
let {
|
|
14
17
|
value,
|
|
15
18
|
max = 100,
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
shrink = true,
|
|
23
26
|
block = false,
|
|
24
27
|
indeterminate: indeterminateProp = false,
|
|
28
|
+
variant = 'default',
|
|
25
29
|
markers = [],
|
|
26
30
|
toneAt,
|
|
27
31
|
color,
|
|
@@ -51,6 +55,7 @@
|
|
|
51
55
|
striped?: boolean;
|
|
52
56
|
// Force indeterminate mode even when a `value` is supplied.
|
|
53
57
|
indeterminate?: boolean;
|
|
58
|
+
variant?: 'default' | 'liquid' | 'stripes';
|
|
54
59
|
/** Thin vertical lines at `at / max` (caps, thresholds). */
|
|
55
60
|
markers?: { at: number; label?: string; tone?: Tone }[];
|
|
56
61
|
/** Percent thresholds that auto-pick warn/danger; an explicit `tone` other
|
|
@@ -80,7 +85,7 @@
|
|
|
80
85
|
{#snippet track()}
|
|
81
86
|
<div
|
|
82
87
|
data-tsu="Progress"
|
|
83
|
-
class="progress tone-{toneClass} size-{size} {klass}"
|
|
88
|
+
class="progress tone-{toneClass} size-{size} v-{variant} {klass}"
|
|
84
89
|
class:grow={grow}
|
|
85
90
|
class:no-shrink={!shrink}
|
|
86
91
|
class:block={block}
|
|
@@ -272,4 +277,72 @@
|
|
|
272
277
|
opacity: 0.35;
|
|
273
278
|
}
|
|
274
279
|
}
|
|
280
|
+
.progress.v-liquid.indeterminate .bar {
|
|
281
|
+
animation: none;
|
|
282
|
+
opacity: 0;
|
|
283
|
+
}
|
|
284
|
+
.progress.v-liquid.indeterminate::before,
|
|
285
|
+
.progress.v-liquid.indeterminate::after {
|
|
286
|
+
content: '';
|
|
287
|
+
position: absolute;
|
|
288
|
+
inset: 0;
|
|
289
|
+
width: 55%;
|
|
290
|
+
border-radius: inherit;
|
|
291
|
+
background: linear-gradient(90deg, transparent, var(--fill, var(--accent)) 35% 65%, transparent);
|
|
292
|
+
transform: translateX(-100%);
|
|
293
|
+
animation: tsu-progress-liquid 1.7s var(--ease-drawer) infinite;
|
|
294
|
+
}
|
|
295
|
+
.progress.v-liquid.indeterminate::after {
|
|
296
|
+
width: 35%;
|
|
297
|
+
opacity: 0.55;
|
|
298
|
+
animation: tsu-progress-liquid-2 1.7s var(--ease-in-out) -0.7s infinite;
|
|
299
|
+
}
|
|
300
|
+
@keyframes tsu-progress-liquid {
|
|
301
|
+
to {
|
|
302
|
+
transform: translateX(282%);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
@keyframes tsu-progress-liquid-2 {
|
|
306
|
+
to {
|
|
307
|
+
transform: translateX(386%);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
.progress.v-stripes .bar {
|
|
311
|
+
position: relative;
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
}
|
|
314
|
+
.progress.v-stripes .bar::after {
|
|
315
|
+
content: '';
|
|
316
|
+
position: absolute;
|
|
317
|
+
inset: 0 -1.5rem 0 0;
|
|
318
|
+
background: repeating-linear-gradient(
|
|
319
|
+
135deg,
|
|
320
|
+
transparent 0 0.5rem,
|
|
321
|
+
color-mix(in srgb, var(--text-on-accent) 28%, transparent) 0.5rem 1rem
|
|
322
|
+
);
|
|
323
|
+
animation: tsu-progress-stripes 0.7s linear infinite;
|
|
324
|
+
}
|
|
325
|
+
@keyframes tsu-progress-stripes {
|
|
326
|
+
from {
|
|
327
|
+
transform: translateX(-1.4142rem);
|
|
328
|
+
}
|
|
329
|
+
to {
|
|
330
|
+
transform: translateX(0);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
@media (prefers-reduced-motion: reduce) {
|
|
334
|
+
.progress.v-liquid.indeterminate {
|
|
335
|
+
background: color-mix(in srgb, var(--fill, var(--accent)) 35%, transparent);
|
|
336
|
+
}
|
|
337
|
+
.progress.v-liquid.indeterminate::before,
|
|
338
|
+
.progress.v-liquid.indeterminate::after {
|
|
339
|
+
display: none;
|
|
340
|
+
}
|
|
341
|
+
.progress.v-liquid.indeterminate .bar {
|
|
342
|
+
display: none;
|
|
343
|
+
}
|
|
344
|
+
.progress.v-stripes .bar::after {
|
|
345
|
+
animation: none;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
275
348
|
</style>
|
|
@@ -279,6 +279,36 @@
|
|
|
279
279
|
.select[aria-invalid='true']:focus {
|
|
280
280
|
border-color: var(--danger);
|
|
281
281
|
}
|
|
282
|
+
.select {
|
|
283
|
+
--focus-tint: var(--accent);
|
|
284
|
+
}
|
|
285
|
+
.select[aria-invalid='true'] {
|
|
286
|
+
--focus-tint: var(--danger);
|
|
287
|
+
}
|
|
288
|
+
/* Focus bloom: a solid ring whose soft halo swells, then settles to a quieter glow. */
|
|
289
|
+
.select:not(.ghost, .embedded):focus:not(:global([data-fx-focus='none']) *),
|
|
290
|
+
:global(:is([data-fx-focus='bloom'], [data-fx-focus='label'])) .select:not(.ghost, .embedded):focus:not(:global(:is([data-fx-focus='bloom'], [data-fx-focus='label']) [data-fx-focus='none']) *) {
|
|
291
|
+
outline: none;
|
|
292
|
+
box-shadow:
|
|
293
|
+
var(--control-shadow),
|
|
294
|
+
0 0 0 var(--focus-ring-width) var(--focus-tint),
|
|
295
|
+
0 0 16px 2px color-mix(in srgb, var(--focus-tint) 24%, transparent);
|
|
296
|
+
animation: focus-bloom var(--dur-fast) var(--ease-out);
|
|
297
|
+
}
|
|
298
|
+
@keyframes focus-bloom {
|
|
299
|
+
0% {
|
|
300
|
+
box-shadow:
|
|
301
|
+
var(--control-shadow),
|
|
302
|
+
0 0 0 0 transparent,
|
|
303
|
+
0 0 0 0 transparent;
|
|
304
|
+
}
|
|
305
|
+
45% {
|
|
306
|
+
box-shadow:
|
|
307
|
+
var(--control-shadow),
|
|
308
|
+
0 0 0 calc(var(--focus-ring-width) + 1px) var(--focus-tint),
|
|
309
|
+
0 0 20px 4px color-mix(in srgb, var(--focus-tint) 32%, transparent);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
282
312
|
.select:disabled {
|
|
283
313
|
cursor: not-allowed;
|
|
284
314
|
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
// Shimmering placeholder bar(s) standing in for content that is still
|
|
4
4
|
// loading. Decorative only — the surrounding region should carry aria-busy.
|
|
5
|
-
// Bars are tinted from --text so they read on any surface
|
|
6
|
-
// viewport-anchored so every bar on screen shimmers in sync
|
|
5
|
+
// Bars are tinted from --text so they read on any surface. `shimmer` is a
|
|
6
|
+
// viewport-anchored sweep so every bar on screen shimmers in sync; `sheen`
|
|
7
|
+
// runs one light across each bar; `pulse` breathes in opacity, each bar a
|
|
8
|
+
// beat behind the previous one (`--skeleton-phase` offsets the whole set).
|
|
7
9
|
type Own = {
|
|
8
10
|
width?: string;
|
|
9
11
|
height?: string;
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
/** Round avatar placeholder — width follows height. */
|
|
13
15
|
circle?: boolean;
|
|
14
16
|
radius?: string;
|
|
17
|
+
variant?: 'shimmer' | 'sheen' | 'pulse';
|
|
15
18
|
class?: string;
|
|
16
19
|
};
|
|
17
20
|
let {
|
|
@@ -20,6 +23,7 @@
|
|
|
20
23
|
lines = 1,
|
|
21
24
|
circle = false,
|
|
22
25
|
radius = 'var(--r-sm)',
|
|
26
|
+
variant = 'shimmer',
|
|
23
27
|
class: klass = '',
|
|
24
28
|
...rest
|
|
25
29
|
}: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
|
|
@@ -29,13 +33,14 @@
|
|
|
29
33
|
const count = $derived(Math.max(1, Math.floor(lines)));
|
|
30
34
|
</script>
|
|
31
35
|
|
|
32
|
-
<span class="skeleton {klass}" data-tsu="Skeleton" aria-hidden="true" style:width={w} {...rest}>
|
|
36
|
+
<span class="skeleton v-{variant} {klass}" data-tsu="Skeleton" aria-hidden="true" style:width={w} {...rest}>
|
|
33
37
|
{#each { length: count } as _, i (i)}
|
|
34
38
|
<span
|
|
35
39
|
class="skeleton-bar"
|
|
36
40
|
style:height
|
|
37
41
|
style:border-radius={r}
|
|
38
42
|
style:width={count > 1 && i === count - 1 ? '60%' : undefined}
|
|
43
|
+
style:--i={i}
|
|
39
44
|
></span>
|
|
40
45
|
{/each}
|
|
41
46
|
</span>
|
|
@@ -72,4 +77,50 @@
|
|
|
72
77
|
background: var(--sk-base);
|
|
73
78
|
}
|
|
74
79
|
}
|
|
80
|
+
.v-sheen .skeleton-bar,
|
|
81
|
+
.v-pulse .skeleton-bar {
|
|
82
|
+
background: var(--sk-base);
|
|
83
|
+
}
|
|
84
|
+
.v-sheen .skeleton-bar {
|
|
85
|
+
position: relative;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
animation: none;
|
|
88
|
+
}
|
|
89
|
+
.v-sheen .skeleton-bar::after {
|
|
90
|
+
content: '';
|
|
91
|
+
position: absolute;
|
|
92
|
+
inset: 0;
|
|
93
|
+
width: 45%;
|
|
94
|
+
background: linear-gradient(100deg, transparent 20%, var(--sk-hi) 50%, transparent 80%);
|
|
95
|
+
transform: translateX(-100%);
|
|
96
|
+
animation: tsu-skeleton-sheen 2s var(--ease-in-out) infinite;
|
|
97
|
+
}
|
|
98
|
+
@keyframes tsu-skeleton-sheen {
|
|
99
|
+
70%,
|
|
100
|
+
100% {
|
|
101
|
+
transform: translateX(260%);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
.v-pulse .skeleton-bar {
|
|
105
|
+
animation: tsu-skeleton-pulse 1.6s var(--ease-in-out) infinite;
|
|
106
|
+
animation-delay: calc(0.13s * (var(--i, 0) + var(--skeleton-phase, 0)));
|
|
107
|
+
}
|
|
108
|
+
@keyframes tsu-skeleton-pulse {
|
|
109
|
+
0%,
|
|
110
|
+
100% {
|
|
111
|
+
opacity: 1;
|
|
112
|
+
}
|
|
113
|
+
50% {
|
|
114
|
+
opacity: 0.45;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
@media (prefers-reduced-motion: reduce) {
|
|
118
|
+
.v-pulse .skeleton-bar {
|
|
119
|
+
animation: none;
|
|
120
|
+
}
|
|
121
|
+
.v-sheen .skeleton-bar::after {
|
|
122
|
+
animation: none;
|
|
123
|
+
transform: translateX(60%);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
75
126
|
</style>
|