@linxin666/dsh-usage 0.4.10 → 0.4.12
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/README.i18n.yaml +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/lib/client.js +1773 -366
- package/lib/client.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +255 -0
- package/lib/types/client/RangeBubble.d.ts +46 -0
- package/lib/types/client/SegmentGroup.d.ts +28 -0
- package/lib/types/client/UsagePage.d.ts +19 -0
- package/lib/types/client/UsageSectionCard.d.ts +11 -18
- package/lib/types/client/locales.d.ts +13 -2
- package/lib/types/client/section-integration.d.ts +47 -0
- package/lib/types/client/settings-form.d.ts +235 -0
- package/lib/types/client/usage-settings-card.d.ts +48 -0
- package/lib/types/client/web-extension-page.d.ts +50 -0
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +838 -0
- package/src/client/RangeBubble.tsx +164 -0
- package/src/client/SegmentGroup.tsx +93 -0
- package/src/client/UsagePage.tsx +108 -0
- package/src/client/UsageSectionCard.tsx +173 -270
- package/src/client/index.ts +19 -30
- package/src/client/locales.ts +46 -24
- package/src/client/section-integration.ts +88 -0
- package/src/client/settings-card.module.css +821 -0
- package/src/client/settings-form.ts +510 -0
- package/src/client/usage-settings-card.ts +72 -0
- package/src/client/usage.module.css +74 -82
- package/src/client/web-extension-page.ts +151 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
/* Generated by scripts/sync-shared.mjs from shared/client/settings/settings-card.module.css. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". */
|
|
2
|
+
/* Plugin settings card chrome + staged form fields.
|
|
3
|
+
* Aligned with the official ui-settings-plugins PluginCard / fields CSS:
|
|
4
|
+
* same semantic tokens, radius, typography and states so family cards read
|
|
5
|
+
* as siblings of the built-in Shell / Agent loop / Web search cards. */
|
|
6
|
+
|
|
7
|
+
.card {
|
|
8
|
+
/* The card's local scale: every literal below reads one of these, and the
|
|
9
|
+
nested density overrides the same seven on the same element, so one rule
|
|
10
|
+
set serves both densities. */
|
|
11
|
+
--settings-card-radius: 12px;
|
|
12
|
+
--settings-card-header-pad: 16px;
|
|
13
|
+
--settings-card-header-gap: 13px;
|
|
14
|
+
--settings-card-title-size: 15px;
|
|
15
|
+
--settings-card-description-size: 13px;
|
|
16
|
+
--settings-card-body-inset: 16px;
|
|
17
|
+
--settings-card-motion: 0.18s;
|
|
18
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
19
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
20
|
+
border-radius: var(--settings-card-radius);
|
|
21
|
+
list-style: none;
|
|
22
|
+
transition: border-color 0.16s, background 0.16s;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.card:hover {
|
|
26
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.cardOpen {
|
|
30
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
31
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Nested density: a topic card inside another card's body. It overrides the
|
|
35
|
+
local scale only, so the first-level card and this one stay one rule set. */
|
|
36
|
+
.cardNested {
|
|
37
|
+
--settings-card-radius: 10px;
|
|
38
|
+
--settings-card-header-pad: 10px 12px;
|
|
39
|
+
--settings-card-header-gap: 10px;
|
|
40
|
+
--settings-card-title-size: 13px;
|
|
41
|
+
--settings-card-description-size: 12px;
|
|
42
|
+
--settings-card-body-inset: 12px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* One line: the header already carries the full sentence on its title. */
|
|
46
|
+
.cardNested .description {
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
text-overflow: ellipsis;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.header {
|
|
53
|
+
appearance: none;
|
|
54
|
+
width: 100%;
|
|
55
|
+
/* The suite does not force a global border-box, so width:100% + padding
|
|
56
|
+
would stretch the row 32px past the card and push the trailing chevron
|
|
57
|
+
(last flex item) outside the card border. */
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
font: inherit;
|
|
60
|
+
color: inherit;
|
|
61
|
+
text-align: left;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
background: transparent;
|
|
64
|
+
border: 0;
|
|
65
|
+
border-radius: var(--settings-card-radius);
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: var(--settings-card-header-gap);
|
|
68
|
+
padding: var(--settings-card-header-pad);
|
|
69
|
+
display: flex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.header:focus-visible {
|
|
73
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
74
|
+
outline-offset: -2px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.headerStatic {
|
|
78
|
+
width: 100%;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
border-radius: var(--settings-card-radius);
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: var(--settings-card-header-gap);
|
|
83
|
+
padding: var(--settings-card-header-pad);
|
|
84
|
+
display: flex;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.headText {
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
flex: 1;
|
|
90
|
+
gap: 4px;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
display: flex;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Optional leading plugin mark: a quiet tile that takes the ink color on
|
|
96
|
+
hover and while the card is open, so the whole row reads as one control. */
|
|
97
|
+
.mark {
|
|
98
|
+
flex: none;
|
|
99
|
+
display: grid;
|
|
100
|
+
place-items: center;
|
|
101
|
+
width: 34px;
|
|
102
|
+
height: 34px;
|
|
103
|
+
border-radius: 10px;
|
|
104
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
105
|
+
color: var(--dsw-alias-label-secondary);
|
|
106
|
+
transition: color 0.16s;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.header:hover .mark,
|
|
110
|
+
.cardOpen .mark {
|
|
111
|
+
color: var(--dsw-alias-label-primary);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.name {
|
|
115
|
+
color: var(--dsw-alias-label-primary);
|
|
116
|
+
font-size: var(--settings-card-title-size);
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
line-height: 1.4;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.description {
|
|
122
|
+
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
|
|
123
|
+
print leave 12-13px hint text below readable contrast (orca-link #778399
|
|
124
|
+
on cream measures ~3.4:1). The 18px leading matches the reference
|
|
125
|
+
settings-page description rhythm. */
|
|
126
|
+
color: var(--dsw-alias-label-secondary);
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
line-height: 18px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Header trailing control (an immediate toggle): a flex item that keeps its
|
|
132
|
+
natural width while the title block takes the remaining space. */
|
|
133
|
+
.headerAction {
|
|
134
|
+
flex: none;
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.chevron {
|
|
140
|
+
color: var(--dsw-alias-label-tertiary);
|
|
141
|
+
flex: none;
|
|
142
|
+
width: 16px;
|
|
143
|
+
height: 16px;
|
|
144
|
+
transition: transform 0.16s, color 0.16s;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.header:hover .chevron {
|
|
148
|
+
color: var(--dsw-alias-label-secondary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.chevronOpen {
|
|
152
|
+
transform: rotate(180deg);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Body disclosure. One transition pair drives both legs: the wrapper
|
|
156
|
+
animates 0fr -> 1fr and back, the body is its only item and shrinks into the
|
|
157
|
+
collapsed track. Only the moving states clip, so a settled card never cuts
|
|
158
|
+
off a field's popup. */
|
|
159
|
+
.disclosure {
|
|
160
|
+
display: grid;
|
|
161
|
+
grid-template-rows: 0fr;
|
|
162
|
+
opacity: 0;
|
|
163
|
+
transition: grid-template-rows var(--settings-card-motion) ease, opacity var(--settings-card-motion) ease;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.disclosure[data-state='collapsed'],
|
|
167
|
+
.disclosure[data-state='entering'],
|
|
168
|
+
.disclosure[data-state='closing'] {
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.disclosure[data-state='entering'],
|
|
173
|
+
.disclosure[data-state='shown'] {
|
|
174
|
+
grid-template-rows: 1fr;
|
|
175
|
+
opacity: 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.disclosure > .body {
|
|
179
|
+
min-height: 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* The body is the query container its rows answer to: the card's own width,
|
|
183
|
+
not the viewport's, decides whether a control still fits beside its text. */
|
|
184
|
+
.body {
|
|
185
|
+
container: settings-card / inline-size;
|
|
186
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
187
|
+
margin: 0 var(--settings-card-body-inset);
|
|
188
|
+
padding-bottom: 8px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.readOnly {
|
|
192
|
+
color: var(--dsw-alias-label-secondary);
|
|
193
|
+
margin: 12px 0 0;
|
|
194
|
+
font-size: 12px;
|
|
195
|
+
line-height: 1.5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.notExposed {
|
|
199
|
+
color: var(--dsw-alias-state-warn-primary);
|
|
200
|
+
margin: 12px 0 0;
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
line-height: 1.5;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* The refused write, under the rows it belongs to; the control itself has
|
|
206
|
+
already gone back to the stored value. */
|
|
207
|
+
.failed {
|
|
208
|
+
/* The suite's error alias is state-error-primary; a label-error token does
|
|
209
|
+
not exist, so the var() would resolve to nothing and the message would
|
|
210
|
+
inherit the ink color, losing the failure signal. */
|
|
211
|
+
color: var(--dsw-alias-state-error-primary, #b42318);
|
|
212
|
+
margin: 8px 0 4px;
|
|
213
|
+
font-size: 12px;
|
|
214
|
+
line-height: 18px;
|
|
215
|
+
overflow-wrap: anywhere;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* One settings row: the label and its hint on the left, the control on the
|
|
219
|
+
right across both lines, 16px above and below, a hairline between two rows.
|
|
220
|
+
The markup keeps label, control, hint in reading order and the grid places
|
|
221
|
+
the control, so a field built outside this file (a plugin's own preset
|
|
222
|
+
picker) lands in the same row by using the same classes. */
|
|
223
|
+
.field {
|
|
224
|
+
display: grid;
|
|
225
|
+
/* The control track is capped at half the row: an auto track sizes to its
|
|
226
|
+
widest content, and a three-option segment carrying a long label (a
|
|
227
|
+
localized "inherit from the deployment") then leaves the text column a few
|
|
228
|
+
characters wide. Capped, the segment wraps its options instead. */
|
|
229
|
+
grid-template-columns: minmax(0, 1fr) fit-content(50%);
|
|
230
|
+
grid-template-areas:
|
|
231
|
+
"head control"
|
|
232
|
+
"hint control";
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 4px 24px;
|
|
235
|
+
padding: 16px 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.field > .head {
|
|
239
|
+
grid-area: head;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.field > :is(.hint, .invalid) {
|
|
243
|
+
grid-area: hint;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.field > :not(.head, .hint, .invalid) {
|
|
247
|
+
grid-area: control;
|
|
248
|
+
justify-self: end;
|
|
249
|
+
max-width: 100%;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.field + .field {
|
|
253
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.head {
|
|
257
|
+
align-items: center;
|
|
258
|
+
gap: 8px;
|
|
259
|
+
display: flex;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.label {
|
|
263
|
+
min-width: 0;
|
|
264
|
+
color: var(--dsw-alias-label-primary);
|
|
265
|
+
flex: 1;
|
|
266
|
+
font-size: 14px;
|
|
267
|
+
font-weight: 400;
|
|
268
|
+
line-height: 22px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.badges {
|
|
272
|
+
align-items: center;
|
|
273
|
+
gap: 8px;
|
|
274
|
+
display: inline-flex;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.badge {
|
|
278
|
+
white-space: nowrap;
|
|
279
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
280
|
+
color: var(--dsw-alias-label-secondary);
|
|
281
|
+
border-radius: 999px;
|
|
282
|
+
padding: 1px 8px;
|
|
283
|
+
font-size: 11px;
|
|
284
|
+
font-weight: 500;
|
|
285
|
+
line-height: 17px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.reset {
|
|
289
|
+
font: inherit;
|
|
290
|
+
color: var(--dsw-alias-label-secondary);
|
|
291
|
+
cursor: pointer;
|
|
292
|
+
background: transparent;
|
|
293
|
+
border: none;
|
|
294
|
+
padding: 0;
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
line-height: 1.5;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.reset:hover:not(:disabled) {
|
|
300
|
+
color: var(--dsw-alias-label-primary);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.reset:disabled {
|
|
304
|
+
cursor: default;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.reset:focus-visible {
|
|
308
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
309
|
+
outline-offset: 2px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Segmented controls (SegmentGroup.tsx): a 2px-inset track the options sit in,
|
|
313
|
+
and one raised box that marks the pressed option. The track is the ink at low
|
|
314
|
+
alpha over whatever panel holds it: a layer token resolves to the panel's own
|
|
315
|
+
fill under the dark theme and under skins, which hides the groove. The track
|
|
316
|
+
wraps instead of clipping, so localized labels stay complete in a narrow card. */
|
|
317
|
+
.segmentGroup {
|
|
318
|
+
position: relative;
|
|
319
|
+
display: inline-flex;
|
|
320
|
+
flex-wrap: wrap;
|
|
321
|
+
align-items: center;
|
|
322
|
+
gap: 2px;
|
|
323
|
+
padding: 2px;
|
|
324
|
+
border-radius: 8px;
|
|
325
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary) 7%, transparent);
|
|
326
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dsw-alias-label-primary) 5%, transparent);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.segment {
|
|
330
|
+
appearance: none;
|
|
331
|
+
margin: 0;
|
|
332
|
+
padding: 3px 12px;
|
|
333
|
+
border: 0;
|
|
334
|
+
border-radius: 7px;
|
|
335
|
+
background: transparent;
|
|
336
|
+
color: var(--dsw-alias-label-secondary);
|
|
337
|
+
font: inherit;
|
|
338
|
+
font-size: 13px;
|
|
339
|
+
font-weight: 500;
|
|
340
|
+
line-height: 18px;
|
|
341
|
+
white-space: nowrap;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
transition: background-color 120ms ease, color 120ms ease;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.segment:hover:not(:disabled):not([data-active]) {
|
|
347
|
+
color: var(--dsw-alias-label-primary);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* The raised box marking the pressed option: the highlight once the group has
|
|
351
|
+
measured it, the option's own background for the frame before. Dark lifts
|
|
352
|
+
by white alpha: a layer fill there resolves to the track's own grey. */
|
|
353
|
+
.segmentGroup:not([data-pill]) > .segment[data-active],
|
|
354
|
+
.segmentGroup[data-pill]::before {
|
|
355
|
+
background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-layer-3));
|
|
356
|
+
box-shadow: 0 1px 2px rgb(20 20 19 / 8%);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
:global(body[data-ds-dark-theme]) .segmentGroup:not([data-pill]) > .segment[data-active],
|
|
360
|
+
:global(body[data-ds-dark-theme]) .segmentGroup[data-pill]::before {
|
|
361
|
+
background: rgb(255 255 255 / 14%);
|
|
362
|
+
box-shadow: none;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.segment[data-active] {
|
|
366
|
+
color: var(--dsw-alias-label-primary);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* The sliding highlight: one box at the pressed option's measured place that
|
|
370
|
+
glides to the next one. The glide keeps playing under reduced motion: a
|
|
371
|
+
quarter-second move of one small box is how the control reads. */
|
|
372
|
+
.segmentGroup[data-pill]::before {
|
|
373
|
+
content: "";
|
|
374
|
+
position: absolute;
|
|
375
|
+
left: 0;
|
|
376
|
+
top: 0;
|
|
377
|
+
width: var(--segment-pill-w, 0px);
|
|
378
|
+
height: var(--segment-pill-h, 0px);
|
|
379
|
+
border-radius: 7px;
|
|
380
|
+
transform: translate(var(--segment-pill-x, 0px), var(--segment-pill-y, 0px));
|
|
381
|
+
pointer-events: none;
|
|
382
|
+
transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
|
383
|
+
width 240ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
|
384
|
+
height 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* The options paint above the highlight, the group's first painted box. */
|
|
388
|
+
.segmentGroup > * {
|
|
389
|
+
position: relative;
|
|
390
|
+
z-index: 1;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.segment:focus-visible {
|
|
394
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
395
|
+
outline-offset: 1px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.segment:disabled {
|
|
399
|
+
cursor: not-allowed;
|
|
400
|
+
opacity: 0.45;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.segmentGroup[data-pill]:has(> .segment:disabled)::before {
|
|
404
|
+
opacity: 0.45;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.input,
|
|
408
|
+
.select {
|
|
409
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
410
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
411
|
+
height: 34px;
|
|
412
|
+
font: inherit;
|
|
413
|
+
color: var(--dsw-alias-label-primary);
|
|
414
|
+
border-radius: 8px;
|
|
415
|
+
padding: 0 12px;
|
|
416
|
+
font-size: 13px;
|
|
417
|
+
line-height: 1.5;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.input:focus-visible,
|
|
421
|
+
.select:focus-visible {
|
|
422
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
423
|
+
outline: none;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.input:disabled,
|
|
427
|
+
.select:disabled {
|
|
428
|
+
color: var(--dsw-alias-label-tertiary);
|
|
429
|
+
cursor: default;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.inputInvalid {
|
|
433
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
|
|
434
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
435
|
+
height: 34px;
|
|
436
|
+
font: inherit;
|
|
437
|
+
color: var(--dsw-alias-label-primary);
|
|
438
|
+
border-radius: 8px;
|
|
439
|
+
padding: 0 12px;
|
|
440
|
+
font-size: 13px;
|
|
441
|
+
line-height: 1.5;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.inputInvalid:focus-visible {
|
|
445
|
+
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
|
|
446
|
+
outline-offset: 1px;
|
|
447
|
+
border-color: var(--dsw-alias-state-error-primary, #b42318);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Beside its text a field keeps a width that reads a value at a glance and
|
|
451
|
+
leaves the hint its room; text fields and pickers share it, so a column of
|
|
452
|
+
rows lines their left edges up. */
|
|
453
|
+
.field > :is(.input, .inputInvalid, .select, .selectWrap, .rangeAnchor) {
|
|
454
|
+
box-sizing: border-box;
|
|
455
|
+
width: 220px;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.rangeAnchor > :is(.input, .inputInvalid) {
|
|
459
|
+
box-sizing: border-box;
|
|
460
|
+
width: 100%;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* A narrow card hands the control the line under the text, where it starts at
|
|
464
|
+
the row's left edge, rather than crushing the hint into a few characters a
|
|
465
|
+
line; a text field or a picker then takes the row's whole width. This block
|
|
466
|
+
follows the widths above so it outranks them. */
|
|
467
|
+
@container settings-card (max-width: 440px) {
|
|
468
|
+
.field {
|
|
469
|
+
grid-template-columns: minmax(0, 1fr);
|
|
470
|
+
grid-template-areas:
|
|
471
|
+
"head"
|
|
472
|
+
"hint"
|
|
473
|
+
"control";
|
|
474
|
+
row-gap: 8px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.field > :not(.head, .hint, .invalid) {
|
|
478
|
+
justify-self: start;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.field > :is(.input, .inputInvalid, .select, .selectWrap, .rangeAnchor) {
|
|
482
|
+
justify-self: stretch;
|
|
483
|
+
width: auto;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.selectWrap {
|
|
488
|
+
position: relative;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.selectButton {
|
|
492
|
+
appearance: none;
|
|
493
|
+
width: 100%;
|
|
494
|
+
text-align: left;
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
align-items: center;
|
|
497
|
+
justify-content: space-between;
|
|
498
|
+
gap: 8px;
|
|
499
|
+
display: flex;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.selectLabel {
|
|
503
|
+
min-width: 0;
|
|
504
|
+
overflow: hidden;
|
|
505
|
+
text-overflow: ellipsis;
|
|
506
|
+
white-space: nowrap;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.selectChevron {
|
|
510
|
+
color: var(--dsw-alias-label-tertiary);
|
|
511
|
+
flex: none;
|
|
512
|
+
transition: transform 0.16s;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.selectChevronOpen {
|
|
516
|
+
transform: rotate(180deg);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.selectPopup {
|
|
520
|
+
position: absolute;
|
|
521
|
+
top: calc(100% + 4px);
|
|
522
|
+
left: 0;
|
|
523
|
+
right: 0;
|
|
524
|
+
z-index: 40;
|
|
525
|
+
flex-direction: column;
|
|
526
|
+
max-height: 240px;
|
|
527
|
+
overflow-y: auto;
|
|
528
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
529
|
+
border-radius: 8px;
|
|
530
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
531
|
+
box-shadow: 0 8px 24px var(--dsw-alias-bg-mask-2);
|
|
532
|
+
padding: 4px;
|
|
533
|
+
opacity: 0;
|
|
534
|
+
transform: translateY(-4px);
|
|
535
|
+
transition: opacity 0.1s ease, transform 0.1s ease;
|
|
536
|
+
display: flex;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.selectPopupOpen {
|
|
540
|
+
opacity: 1;
|
|
541
|
+
transform: none;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.selectPopupClose {
|
|
545
|
+
opacity: 0;
|
|
546
|
+
transform: translateY(-4px);
|
|
547
|
+
pointer-events: none;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* Coarse adjustment (RangeBubble.tsx): a popover with one slider over the
|
|
551
|
+
field's whole range, hung off the precise control. Hidden through
|
|
552
|
+
visibility, so a closed bubble takes no pointer and no tab stop; the delay on
|
|
553
|
+
visibility lets the fade finish before it goes. */
|
|
554
|
+
.rangeAnchor {
|
|
555
|
+
position: relative;
|
|
556
|
+
display: inline-flex;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.rangeBubble {
|
|
560
|
+
position: absolute;
|
|
561
|
+
right: 0;
|
|
562
|
+
z-index: 40;
|
|
563
|
+
box-sizing: border-box;
|
|
564
|
+
width: 220px;
|
|
565
|
+
padding: 10px 12px 6px;
|
|
566
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
567
|
+
border-radius: 10px;
|
|
568
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
569
|
+
box-shadow: 0 8px 24px var(--dsw-alias-bg-mask-2);
|
|
570
|
+
opacity: 0;
|
|
571
|
+
visibility: hidden;
|
|
572
|
+
transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s linear 0.12s;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.rangeBubble[data-side="above"] {
|
|
576
|
+
bottom: calc(100% + 6px);
|
|
577
|
+
transform: translateY(4px);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.rangeBubble[data-side="below"] {
|
|
581
|
+
top: calc(100% + 6px);
|
|
582
|
+
transform: translateY(-4px);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.rangeBubble[data-open] {
|
|
586
|
+
opacity: 1;
|
|
587
|
+
visibility: visible;
|
|
588
|
+
transform: none;
|
|
589
|
+
transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* The slider: a thin groove filled up to the thumb in the brand color, and a
|
|
593
|
+
round thumb that grows a little while it is held. */
|
|
594
|
+
.rangeSlider {
|
|
595
|
+
appearance: none;
|
|
596
|
+
display: block;
|
|
597
|
+
width: 100%;
|
|
598
|
+
height: 18px;
|
|
599
|
+
margin: 0;
|
|
600
|
+
background: transparent;
|
|
601
|
+
cursor: pointer;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.rangeSlider::-webkit-slider-runnable-track {
|
|
605
|
+
height: 4px;
|
|
606
|
+
border-radius: 999px;
|
|
607
|
+
background: linear-gradient(to right,
|
|
608
|
+
var(--dsw-alias-brand-primary) var(--range-fill, 0%),
|
|
609
|
+
color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent) var(--range-fill, 0%));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.rangeSlider::-moz-range-track {
|
|
613
|
+
height: 4px;
|
|
614
|
+
border-radius: 999px;
|
|
615
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.rangeSlider::-moz-range-progress {
|
|
619
|
+
height: 4px;
|
|
620
|
+
border-radius: 999px;
|
|
621
|
+
background: var(--dsw-alias-brand-primary);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.rangeSlider::-webkit-slider-thumb {
|
|
625
|
+
appearance: none;
|
|
626
|
+
width: 14px;
|
|
627
|
+
height: 14px;
|
|
628
|
+
margin-top: -5px;
|
|
629
|
+
border: 0;
|
|
630
|
+
border-radius: 50%;
|
|
631
|
+
background: #fff;
|
|
632
|
+
box-shadow: 0 0 0 1px rgb(0 0 0 / 12%), 0 1px 3px rgb(0 0 0 / 25%);
|
|
633
|
+
transition: transform 0.12s ease;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.rangeSlider::-moz-range-thumb {
|
|
637
|
+
width: 14px;
|
|
638
|
+
height: 14px;
|
|
639
|
+
border: 0;
|
|
640
|
+
border-radius: 50%;
|
|
641
|
+
background: #fff;
|
|
642
|
+
box-shadow: 0 0 0 1px rgb(0 0 0 / 12%), 0 1px 3px rgb(0 0 0 / 25%);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.rangeSlider:active::-webkit-slider-thumb {
|
|
646
|
+
transform: scale(1.15);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.rangeSlider:focus-visible {
|
|
650
|
+
outline: none;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.rangeSlider:focus-visible::-webkit-slider-thumb {
|
|
654
|
+
box-shadow: 0 0 0 2px var(--dsw-alias-brand-primary);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.rangeSlider:disabled {
|
|
658
|
+
cursor: not-allowed;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.rangeEnds {
|
|
662
|
+
display: flex;
|
|
663
|
+
justify-content: space-between;
|
|
664
|
+
margin-top: 2px;
|
|
665
|
+
color: var(--dsw-alias-label-secondary);
|
|
666
|
+
font-size: 11px;
|
|
667
|
+
line-height: 16px;
|
|
668
|
+
font-variant-numeric: tabular-nums;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.selectOption {
|
|
672
|
+
/* 弹层是 max-height 受限的 flex 列:选项必须保持自然行高,超出交给弹层滚动 */
|
|
673
|
+
flex-shrink: 0;
|
|
674
|
+
border-radius: 6px;
|
|
675
|
+
padding: 6px 10px;
|
|
676
|
+
font-size: 13px;
|
|
677
|
+
line-height: 1.5;
|
|
678
|
+
color: var(--dsw-alias-label-primary);
|
|
679
|
+
cursor: pointer;
|
|
680
|
+
white-space: nowrap;
|
|
681
|
+
overflow: hidden;
|
|
682
|
+
text-overflow: ellipsis;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.selectOption:hover,
|
|
686
|
+
.selectOptionActive {
|
|
687
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.selectOptionSelected {
|
|
691
|
+
color: var(--dsw-alias-brand-primary);
|
|
692
|
+
font-weight: 500;
|
|
693
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.invalid {
|
|
697
|
+
color: var(--dsw-alias-state-error-primary, #b42318);
|
|
698
|
+
margin: 0;
|
|
699
|
+
font-size: 12px;
|
|
700
|
+
line-height: 18px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.hint {
|
|
704
|
+
color: var(--dsw-alias-label-secondary);
|
|
705
|
+
margin: 0;
|
|
706
|
+
font-size: 12px;
|
|
707
|
+
line-height: 18px;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/* Immediate boolean toggle: a 36x20 capsule with a 16px thumb. The on state
|
|
711
|
+
fills with the primary-action pair rather than a fixed state color, so it
|
|
712
|
+
stays legible in every theme; disabled dims through opacity instead of a
|
|
713
|
+
second palette. */
|
|
714
|
+
.toggle {
|
|
715
|
+
appearance: none;
|
|
716
|
+
display: inline-flex;
|
|
717
|
+
align-items: center;
|
|
718
|
+
background: transparent;
|
|
719
|
+
border: 0;
|
|
720
|
+
border-radius: 999px;
|
|
721
|
+
margin: 0;
|
|
722
|
+
padding: 0;
|
|
723
|
+
cursor: pointer;
|
|
724
|
+
font: inherit;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.toggleTrack {
|
|
728
|
+
position: relative;
|
|
729
|
+
flex: none;
|
|
730
|
+
width: 36px;
|
|
731
|
+
height: 20px;
|
|
732
|
+
border-radius: 999px;
|
|
733
|
+
/* Off must still read as a rail: border-l2 is an alpha hairline color and
|
|
734
|
+
leaves the track nearly invisible on a card surface. */
|
|
735
|
+
background: var(--dsw-alias-label-dimmed);
|
|
736
|
+
transition: background-color 0.18s ease;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.toggleThumb {
|
|
740
|
+
--toggle-travel: 16px;
|
|
741
|
+
--toggle-overshoot: 1px;
|
|
742
|
+
position: absolute;
|
|
743
|
+
top: 2px;
|
|
744
|
+
left: 2px;
|
|
745
|
+
width: 16px;
|
|
746
|
+
height: 16px;
|
|
747
|
+
border-radius: 50%;
|
|
748
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
749
|
+
box-shadow: 0 1px 2px rgb(20 20 19 / 18%);
|
|
750
|
+
translate: 0 0;
|
|
751
|
+
transition: translate 0.18s ease;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.toggle[aria-checked='true'] .toggleTrack {
|
|
755
|
+
background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.toggle[aria-checked='true'] .toggleThumb {
|
|
759
|
+
translate: var(--toggle-travel) 0;
|
|
760
|
+
background: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-3));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/* Pressed, the thumb travels across with a double bounce — past its end, back,
|
|
764
|
+
settled — while the track's colour changes on its own clock. Only a switch
|
|
765
|
+
the reader has pressed is armed (data-moved): a thumb drawn by a page
|
|
766
|
+
opening stands where it is. */
|
|
767
|
+
.toggle[data-moved][aria-checked='true'] .toggleThumb {
|
|
768
|
+
animation: toggle-on 0.35s cubic-bezier(0.34, 1.45, 0.64, 1) both;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.toggle[data-moved][aria-checked='false'] .toggleThumb {
|
|
772
|
+
animation: toggle-off 0.35s cubic-bezier(0.34, 1.45, 0.64, 1) both;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
@keyframes toggle-on {
|
|
776
|
+
0% { translate: 0 0; }
|
|
777
|
+
55% { translate: calc(var(--toggle-travel) + var(--toggle-overshoot)) 0; }
|
|
778
|
+
80%, 100% { translate: var(--toggle-travel) 0; }
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
@keyframes toggle-off {
|
|
782
|
+
0% { translate: var(--toggle-travel) 0; }
|
|
783
|
+
55% { translate: calc(0px - var(--toggle-overshoot)) 0; }
|
|
784
|
+
80%, 100% { translate: 0 0; }
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.toggle:focus-visible {
|
|
788
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
789
|
+
outline-offset: 2px;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.toggle:disabled {
|
|
793
|
+
opacity: 0.45;
|
|
794
|
+
cursor: not-allowed;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
@media (prefers-reduced-motion: reduce) {
|
|
798
|
+
.card,
|
|
799
|
+
.header,
|
|
800
|
+
.mark,
|
|
801
|
+
.chevron,
|
|
802
|
+
.chevronOpen,
|
|
803
|
+
.rangeBubble,
|
|
804
|
+
.selectChevron,
|
|
805
|
+
.selectChevronOpen,
|
|
806
|
+
.selectPopup,
|
|
807
|
+
.toggleTrack,
|
|
808
|
+
.toggleThumb {
|
|
809
|
+
transition: none;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.toggle[data-moved] .toggleThumb {
|
|
813
|
+
animation: none;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/* Reduced motion drops the height travel but keeps the fade, so a closing
|
|
817
|
+
body still leaves through the leg its own state started. */
|
|
818
|
+
.disclosure {
|
|
819
|
+
transition-property: opacity;
|
|
820
|
+
}
|
|
821
|
+
}
|