@wildmason/aegis 1.5.4
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/fesm2022/wildmason-aegis.mjs +883 -0
- package/dist/fesm2022/wildmason-aegis.mjs.map +1 -0
- package/dist/types/wildmason-aegis.d.ts +545 -0
- package/inputs.css +1537 -0
- package/package.json +57 -0
- package/themes.css +778 -0
package/inputs.css
ADDED
|
@@ -0,0 +1,1537 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Aegis Design System — inputs.css
|
|
3
|
+
Shared form control primitives for Works, Helm, Slate, Mortar.
|
|
4
|
+
|
|
5
|
+
Usage
|
|
6
|
+
-----
|
|
7
|
+
Import after themes.css so all --wm-* tokens are defined:
|
|
8
|
+
|
|
9
|
+
@import '@aegis/themes.css';
|
|
10
|
+
@import '@aegis/inputs.css';
|
|
11
|
+
|
|
12
|
+
Class reference
|
|
13
|
+
---------------
|
|
14
|
+
Border-radius utilities (replaces Tailwind rounded-*):
|
|
15
|
+
.wm-r-xs border-radius: 4px — chips, badges, inline elements
|
|
16
|
+
.wm-r-sm border-radius: 6px — buttons, inputs, sidebar items
|
|
17
|
+
.wm-r-md border-radius: 8px — modals, dropdowns, popovers
|
|
18
|
+
.wm-r-lg border-radius: 12px — search overlays, large cards
|
|
19
|
+
.wm-r-full border-radius: 9999px — pills, avatars
|
|
20
|
+
.wm-r-l left corners only — left half of split button
|
|
21
|
+
.wm-r-r right corners only — right half of split button
|
|
22
|
+
.wm-r-t top corners only — top of stacked elements
|
|
23
|
+
|
|
24
|
+
Shadow utilities (replaces Tailwind shadow-*):
|
|
25
|
+
.wm-shadow box-shadow: --wm-shadow-sm — embedded widgets
|
|
26
|
+
.wm-shadow-md box-shadow: --wm-shadow-md — cards, dropdowns, popovers
|
|
27
|
+
.wm-shadow-lg box-shadow: --wm-shadow-lg — modals, overlays
|
|
28
|
+
|
|
29
|
+
Menu item:
|
|
30
|
+
.wm-menu-item Hover-background for buttons inside popovers/dropdowns
|
|
31
|
+
|
|
32
|
+
Form controls:
|
|
33
|
+
.wm-field Vertical field container (label + control + help)
|
|
34
|
+
.wm-label Field label
|
|
35
|
+
.wm-checkbox Custom-styled native checkbox (appearance:none)
|
|
36
|
+
.wm-check-label Label wrapper: flex row aligning .wm-checkbox + text
|
|
37
|
+
.wm-input Base input — text, textarea, native select
|
|
38
|
+
.wm-input-group Composite row: icon + .wm-input-bare (e.g. search)
|
|
39
|
+
.wm-input-bare Borderless inner input used inside .wm-input-group
|
|
40
|
+
.wm-input-icon Leading/trailing icon slot inside .wm-input-group
|
|
41
|
+
.wm-select-trigger Button-based custom select trigger (for app-select etc.)
|
|
42
|
+
.wm-number-input Number input variant (spinner chrome removed)
|
|
43
|
+
.wm-number-controls ± button strip appended to .wm-input-group
|
|
44
|
+
.wm-number-btn Individual +/− step button
|
|
45
|
+
.wm-ghost-field Ghost wrapper — chrome hidden at rest, shown on hover
|
|
46
|
+
.wm-error-msg Inline danger message block below a field
|
|
47
|
+
|
|
48
|
+
Keyboard:
|
|
49
|
+
.wm-kbd Single keyboard key badge (heavier bottom border = key depth)
|
|
50
|
+
.wm-kbd-combo Flex wrapper for key combinations — auto-injects '+' between keys
|
|
51
|
+
|
|
52
|
+
Spec
|
|
53
|
+
----
|
|
54
|
+
- Standard height: 36px (use padding for textarea)
|
|
55
|
+
- Focus ring: 3px diffuse box-shadow — no layout shift
|
|
56
|
+
- Labels: 12px / font-weight 500 / text-muted
|
|
57
|
+
- Hover: bg → wm-bg-sunken, border → wm-border-hover
|
|
58
|
+
- Focus: border → wm-color-accent, animated ring
|
|
59
|
+
- Disabled: 50% opacity, not-allowed cursor
|
|
60
|
+
============================================================ */
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/* ============================================================
|
|
64
|
+
Chevron — per colour-scheme
|
|
65
|
+
============================================================
|
|
66
|
+
SVG data URIs cannot reference CSS custom properties or
|
|
67
|
+
currentColor, so we bake a light and dark version and assign
|
|
68
|
+
via --wm-chevron. Used by select.wm-input and .wm-select-trigger.
|
|
69
|
+
============================================================ */
|
|
70
|
+
|
|
71
|
+
:root,
|
|
72
|
+
[data-theme='warm-light'],
|
|
73
|
+
[data-theme='neutral-light'],
|
|
74
|
+
[data-theme='solarized-precision'] {
|
|
75
|
+
--wm-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-opacity='0.6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[data-theme='arctic-night'],
|
|
79
|
+
[data-theme='vampires-kiss'],
|
|
80
|
+
[data-theme='twilight-peach'],
|
|
81
|
+
[data-theme='thistle-mocha'],
|
|
82
|
+
[data-theme='posh-sandalwood'],
|
|
83
|
+
[data-theme='crepuscular-sky'],
|
|
84
|
+
[data-theme='source-control-dark'] {
|
|
85
|
+
--wm-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-opacity='0.6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/* ============================================================
|
|
90
|
+
Focus ring animation
|
|
91
|
+
============================================================
|
|
92
|
+
Shared by .wm-input:focus, .wm-input-group:focus-within, and
|
|
93
|
+
.wm-select-trigger[aria-expanded='true']. Expands from 0 then
|
|
94
|
+
settles at 3px — communicates focus without adding layout space.
|
|
95
|
+
============================================================ */
|
|
96
|
+
|
|
97
|
+
@keyframes wm-focus-pulse {
|
|
98
|
+
0% { box-shadow: 0 0 0 0px color-mix(in srgb, var(--wm-color-accent) 60%, transparent); }
|
|
99
|
+
55% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--wm-color-accent) 6%, transparent); }
|
|
100
|
+
100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--wm-color-accent) 15%, transparent); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
/* ============================================================
|
|
105
|
+
Field wrapper — .wm-field
|
|
106
|
+
============================================================
|
|
107
|
+
Vertical flex column holding a label, control, and optional
|
|
108
|
+
help/error text. Gap of 4px keeps label visually coupled to
|
|
109
|
+
its control without merging into it.
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
<div class="wm-field">
|
|
113
|
+
<label class="wm-label">Name</label>
|
|
114
|
+
<input class="wm-input" type="text">
|
|
115
|
+
<span class="wm-field-help">Optional helper text</span>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
Spacing helpers (.space-y / .space-x):
|
|
119
|
+
Apply to consecutive siblings to auto-gap them.
|
|
120
|
+
============================================================ */
|
|
121
|
+
|
|
122
|
+
.wm-field {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 4px;
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Vertical and horizontal auto-spacing between consecutive fields */
|
|
130
|
+
.wm-field.space-y + .wm-field { margin-top: 0.75rem; }
|
|
131
|
+
.wm-field.space-x + .wm-field { margin-left: 0.75rem; }
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
/* ============================================================
|
|
135
|
+
Label — .wm-label
|
|
136
|
+
============================================================
|
|
137
|
+
12px / medium weight / text-muted. Rendered as a block so it
|
|
138
|
+
occupies its own row in the flex column. user-select:none
|
|
139
|
+
prevents accidental highlight when clicking adjacent controls.
|
|
140
|
+
============================================================ */
|
|
141
|
+
|
|
142
|
+
.wm-label {
|
|
143
|
+
display: block;
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
font-weight: 500;
|
|
146
|
+
color: var(--wm-text-muted);
|
|
147
|
+
line-height: 1;
|
|
148
|
+
user-select: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
/* ============================================================
|
|
153
|
+
Help and error text
|
|
154
|
+
============================================================
|
|
155
|
+
Sit below the control inside a .wm-field. Error text uses
|
|
156
|
+
danger color; help text uses muted.
|
|
157
|
+
============================================================ */
|
|
158
|
+
|
|
159
|
+
.wm-field-help {
|
|
160
|
+
font-size: 11px;
|
|
161
|
+
color: var(--wm-text-muted);
|
|
162
|
+
line-height: 1.4;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.wm-field-error {
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
color: var(--wm-color-danger);
|
|
168
|
+
line-height: 1.4;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
/* ============================================================
|
|
173
|
+
Checkbox — .wm-checkbox + .wm-check-label
|
|
174
|
+
============================================================
|
|
175
|
+
Custom-styled native checkbox. appearance:none hides the
|
|
176
|
+
native chrome; checked state renders via a baked-in white
|
|
177
|
+
SVG checkmark on the accent fill (works across all themes).
|
|
178
|
+
|
|
179
|
+
Usage:
|
|
180
|
+
<label class="wm-check-label">
|
|
181
|
+
<input type="checkbox" class="wm-checkbox">
|
|
182
|
+
<span>Enable feature</span>
|
|
183
|
+
</label>
|
|
184
|
+
|
|
185
|
+
Standalone (pair with aria-label):
|
|
186
|
+
<input type="checkbox" class="wm-checkbox" aria-label="…">
|
|
187
|
+
============================================================ */
|
|
188
|
+
|
|
189
|
+
@keyframes wm-check-draw {
|
|
190
|
+
from { stroke-dashoffset: 20; }
|
|
191
|
+
to { stroke-dashoffset: 0; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.wm-checkbox {
|
|
195
|
+
appearance: none;
|
|
196
|
+
-webkit-appearance: none;
|
|
197
|
+
flex-shrink: 0;
|
|
198
|
+
width: 16px;
|
|
199
|
+
height: 16px;
|
|
200
|
+
box-sizing: border-box;
|
|
201
|
+
border: 1.5px solid var(--wm-border-input);
|
|
202
|
+
border-radius: var(--wm-radius-xs);
|
|
203
|
+
background-color: var(--wm-bg-base);
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
position: relative;
|
|
206
|
+
vertical-align: middle;
|
|
207
|
+
transition:
|
|
208
|
+
border-color 150ms ease,
|
|
209
|
+
background-color 150ms ease,
|
|
210
|
+
box-shadow 150ms ease;
|
|
211
|
+
|
|
212
|
+
--tw-ring-shadow: none;
|
|
213
|
+
--tw-ring-offset-shadow: none;
|
|
214
|
+
--tw-ring-color: transparent;
|
|
215
|
+
--tw-shadow: none;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.wm-checkbox:hover:not(:checked):not(:disabled) {
|
|
219
|
+
background-color: var(--wm-bg-sunken);
|
|
220
|
+
border-color: var(--wm-border-hover);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.wm-checkbox:checked {
|
|
224
|
+
background-color: var(--wm-color-accent);
|
|
225
|
+
border-color: var(--wm-color-accent);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Animated checkmark via pseudo-element SVG */
|
|
229
|
+
.wm-checkbox:checked::after {
|
|
230
|
+
content: '';
|
|
231
|
+
display: block;
|
|
232
|
+
position: absolute;
|
|
233
|
+
inset: 0;
|
|
234
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 8'%3E%3Cpolyline points='1,4 3.5,6.5 9,1' fill='none' stroke='%23ffffff' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round' stroke-dasharray='20' stroke-dashoffset='0'/%3E%3C/svg%3E");
|
|
235
|
+
background-repeat: no-repeat;
|
|
236
|
+
background-position: center;
|
|
237
|
+
background-size: 10px 8px;
|
|
238
|
+
animation: wm-check-draw 150ms ease-out forwards;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.wm-checkbox:focus-visible {
|
|
242
|
+
outline: none;
|
|
243
|
+
border-color: var(--wm-color-accent);
|
|
244
|
+
animation: wm-focus-pulse 400ms cubic-bezier(0.2, 0, 0.4, 1) forwards;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.wm-checkbox:disabled {
|
|
248
|
+
opacity: 0.5;
|
|
249
|
+
cursor: not-allowed;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Label wrapper */
|
|
253
|
+
.wm-check-label {
|
|
254
|
+
display: inline-flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 0.5rem;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
user-select: none;
|
|
259
|
+
font-size: 0.875rem;
|
|
260
|
+
color: var(--wm-text-primary);
|
|
261
|
+
line-height: 1.4;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.wm-check-label:has(.wm-checkbox:disabled) {
|
|
265
|
+
opacity: 0.5;
|
|
266
|
+
cursor: not-allowed;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@media (prefers-reduced-motion: reduce) {
|
|
270
|
+
.wm-checkbox:checked::after { animation: none; }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
/* ============================================================
|
|
275
|
+
Menu item — .wm-menu-item
|
|
276
|
+
============================================================
|
|
277
|
+
Hover-background style for buttons inside popovers, dropdowns,
|
|
278
|
+
and custom menus. Matches the pattern used by .ctx-menu-item
|
|
279
|
+
in the context-menu component.
|
|
280
|
+
|
|
281
|
+
Usage:
|
|
282
|
+
<button class="wm-menu-item w-full text-left px-3 py-1.5 text-xs"
|
|
283
|
+
style="color: var(--wm-text-primary)">
|
|
284
|
+
Menu action
|
|
285
|
+
</button>
|
|
286
|
+
============================================================ */
|
|
287
|
+
|
|
288
|
+
.wm-menu-item {
|
|
289
|
+
transition: background 0.1s;
|
|
290
|
+
}
|
|
291
|
+
.wm-menu-item:hover {
|
|
292
|
+
background: var(--wm-bg-hover);
|
|
293
|
+
}
|
|
294
|
+
.wm-menu-item:focus-visible {
|
|
295
|
+
outline: 2px solid var(--wm-color-accent);
|
|
296
|
+
outline-offset: -2px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
/* ============================================================
|
|
301
|
+
Border-radius utilities — .wm-r-*
|
|
302
|
+
============================================================
|
|
303
|
+
Named after the radius token scale. Use instead of Tailwind's
|
|
304
|
+
rounded-* so radius values stay in sync with the design token
|
|
305
|
+
scale across all apps (with or without Tailwind present).
|
|
306
|
+
|
|
307
|
+
Full-corner:
|
|
308
|
+
.wm-r-xs → --wm-radius-xs (4px) chips, badges, inline elements
|
|
309
|
+
.wm-r-sm → --wm-radius-sm (6px) buttons, inputs, sidebar items
|
|
310
|
+
.wm-r-md → --wm-radius-md (8px) modals, dropdowns, popovers
|
|
311
|
+
.wm-r-lg → --wm-radius-lg (12px) search overlays, large cards
|
|
312
|
+
.wm-r-full → --wm-radius-full (9999px) pills, avatars
|
|
313
|
+
|
|
314
|
+
Partial-corner (split buttons, stacked panels):
|
|
315
|
+
.wm-r-l left corners only
|
|
316
|
+
.wm-r-r right corners only
|
|
317
|
+
.wm-r-t top corners only
|
|
318
|
+
|
|
319
|
+
Usage:
|
|
320
|
+
<div class="wm-r-md"> … </div>
|
|
321
|
+
<button class="wm-btn wm-r-l">Left</button>
|
|
322
|
+
<button class="wm-btn wm-r-r">Right</button>
|
|
323
|
+
============================================================ */
|
|
324
|
+
|
|
325
|
+
.wm-r-xs { border-radius: var(--wm-radius-xs); }
|
|
326
|
+
.wm-r-sm { border-radius: var(--wm-radius-sm); }
|
|
327
|
+
.wm-r-md { border-radius: var(--wm-radius-md); }
|
|
328
|
+
.wm-r-lg { border-radius: var(--wm-radius-lg); }
|
|
329
|
+
.wm-r-full { border-radius: var(--wm-radius-full); }
|
|
330
|
+
|
|
331
|
+
.wm-r-l { border-radius: var(--wm-radius-xs) 0 0 var(--wm-radius-xs); }
|
|
332
|
+
.wm-r-r { border-radius: 0 var(--wm-radius-xs) var(--wm-radius-xs) 0; }
|
|
333
|
+
.wm-r-t { border-radius: var(--wm-radius-xs) var(--wm-radius-xs) 0 0; }
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/* ============================================================
|
|
337
|
+
Shadow utilities — .wm-shadow / .wm-shadow-md / .wm-shadow-lg
|
|
338
|
+
============================================================
|
|
339
|
+
Named after the shadow token scale. Reserve for floating/elevated
|
|
340
|
+
elements only — borders (--wm-border) handle hierarchy; shadows
|
|
341
|
+
handle depth. Use instead of Tailwind's shadow-* utilities.
|
|
342
|
+
|
|
343
|
+
.wm-shadow → --wm-shadow-sm embedded widgets, subtle lift
|
|
344
|
+
.wm-shadow-md → --wm-shadow-md cards, dropdowns, popovers
|
|
345
|
+
.wm-shadow-lg → --wm-shadow-lg modals, search overlays, command palettes
|
|
346
|
+
|
|
347
|
+
Usage:
|
|
348
|
+
<div class="wm-r-md wm-shadow-md"> Dropdown </div>
|
|
349
|
+
<div class="wm-r-lg wm-shadow-lg"> Modal </div>
|
|
350
|
+
============================================================ */
|
|
351
|
+
|
|
352
|
+
.wm-shadow { box-shadow: var(--wm-shadow-sm); }
|
|
353
|
+
.wm-shadow-md { box-shadow: var(--wm-shadow-md); }
|
|
354
|
+
.wm-shadow-lg { box-shadow: var(--wm-shadow-lg); }
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
/* ============================================================
|
|
358
|
+
Base input — .wm-input
|
|
359
|
+
============================================================
|
|
360
|
+
Applies to: <input>, <textarea>, <select> (native).
|
|
361
|
+
36px height for single-line; textarea overrides height.
|
|
362
|
+
|
|
363
|
+
States:
|
|
364
|
+
Hover — bg-inset, border-hover
|
|
365
|
+
Focus — accent border + animated ring
|
|
366
|
+
Disabled — 50% opacity, not-allowed cursor
|
|
367
|
+
|
|
368
|
+
Usage:
|
|
369
|
+
<input class="wm-input" type="text" placeholder="…">
|
|
370
|
+
<select class="wm-input"> … </select>
|
|
371
|
+
<textarea class="wm-input"></textarea>
|
|
372
|
+
============================================================ */
|
|
373
|
+
|
|
374
|
+
.wm-input {
|
|
375
|
+
/* Dimensions */
|
|
376
|
+
display: block;
|
|
377
|
+
width: 100%;
|
|
378
|
+
height: 36px;
|
|
379
|
+
box-sizing: border-box;
|
|
380
|
+
padding: 0 12px;
|
|
381
|
+
|
|
382
|
+
/* Typography */
|
|
383
|
+
font-size: 14px;
|
|
384
|
+
font-family: inherit;
|
|
385
|
+
color: var(--wm-text-primary);
|
|
386
|
+
line-height: 36px;
|
|
387
|
+
|
|
388
|
+
/* Surface */
|
|
389
|
+
background-color: var(--wm-bg-base);
|
|
390
|
+
border: 1px solid var(--wm-border-input);
|
|
391
|
+
border-radius: var(--wm-radius-md);
|
|
392
|
+
|
|
393
|
+
/* Reset */
|
|
394
|
+
outline: none;
|
|
395
|
+
appearance: none;
|
|
396
|
+
-webkit-appearance: none;
|
|
397
|
+
|
|
398
|
+
/* Neutralise Tailwind ring variables so they can't bleed into box-shadow */
|
|
399
|
+
--tw-ring-shadow: none;
|
|
400
|
+
--tw-ring-offset-shadow: none;
|
|
401
|
+
--tw-ring-color: transparent;
|
|
402
|
+
--tw-shadow: none;
|
|
403
|
+
|
|
404
|
+
transition:
|
|
405
|
+
background-color 150ms ease,
|
|
406
|
+
border-color 150ms ease,
|
|
407
|
+
box-shadow 150ms ease;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* Placeholder: uses --wm-text-placeholder (intentionally lower contrast —
|
|
411
|
+
exempt from WCAG 1.4.3 per WCAG 2.2; inputs must have a visible label). */
|
|
412
|
+
.wm-input::placeholder {
|
|
413
|
+
color: var(--wm-text-placeholder);
|
|
414
|
+
opacity: 1;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* Hover: step to inset bg + secondary border */
|
|
418
|
+
.wm-input:hover:not(:focus):not(:disabled) {
|
|
419
|
+
background-color: var(--wm-bg-sunken);
|
|
420
|
+
border-color: var(--wm-border-hover);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/* Focus: accent border + animated diffuse ring */
|
|
424
|
+
.wm-input:focus {
|
|
425
|
+
outline: none;
|
|
426
|
+
border-color: var(--wm-color-accent);
|
|
427
|
+
animation: wm-focus-pulse 400ms cubic-bezier(0.2, 0, 0.4, 1) forwards;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Disabled */
|
|
431
|
+
.wm-input:disabled {
|
|
432
|
+
opacity: 0.5;
|
|
433
|
+
cursor: not-allowed;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Inherit color-scheme so date/time picker chrome (calendar icon, spinners)
|
|
437
|
+
matches the active theme's light/dark declaration. */
|
|
438
|
+
.wm-input[type='date'],
|
|
439
|
+
.wm-input[type='time'],
|
|
440
|
+
.wm-input[type='datetime-local'] {
|
|
441
|
+
color-scheme: inherit;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* Keep native date/time picker indicator in sync with surrounding icon opacity */
|
|
445
|
+
.wm-input[type='date']::-webkit-calendar-picker-indicator,
|
|
446
|
+
.wm-input[type='time']::-webkit-calendar-picker-indicator,
|
|
447
|
+
.wm-input[type='datetime-local']::-webkit-calendar-picker-indicator {
|
|
448
|
+
color: FieldText;
|
|
449
|
+
opacity: 0.6;
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
/* ============================================================
|
|
455
|
+
Textarea variant
|
|
456
|
+
============================================================
|
|
457
|
+
Overrides height and line-height from .wm-input. Minimum 80px;
|
|
458
|
+
user can resize vertically. Padding shifts to 8px top/bottom
|
|
459
|
+
to align the first line of text with the 36px input baseline.
|
|
460
|
+
============================================================ */
|
|
461
|
+
|
|
462
|
+
textarea.wm-input {
|
|
463
|
+
height: auto;
|
|
464
|
+
min-height: 80px;
|
|
465
|
+
padding: 8px 12px;
|
|
466
|
+
line-height: 1.5;
|
|
467
|
+
resize: vertical;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
/* ============================================================
|
|
472
|
+
Native select variant
|
|
473
|
+
============================================================
|
|
474
|
+
appearance:none strips the native chevron; we restore one via
|
|
475
|
+
--wm-chevron (light/dark SVG data URI defined at top of file).
|
|
476
|
+
============================================================ */
|
|
477
|
+
|
|
478
|
+
select.wm-input {
|
|
479
|
+
background-image: var(--wm-chevron);
|
|
480
|
+
background-repeat: no-repeat;
|
|
481
|
+
background-position: right 12px center;
|
|
482
|
+
padding-right: 32px;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
/* ============================================================
|
|
488
|
+
Composite input group — .wm-input-group
|
|
489
|
+
============================================================
|
|
490
|
+
Wraps a leading/trailing icon (.wm-input-icon) together with
|
|
491
|
+
a borderless inner input (.wm-input-bare). The border and
|
|
492
|
+
focus ring belong to the group, not the inner input.
|
|
493
|
+
|
|
494
|
+
Usage:
|
|
495
|
+
<div class="wm-input-group">
|
|
496
|
+
<span class="wm-input-icon">
|
|
497
|
+
<svg …/>
|
|
498
|
+
</span>
|
|
499
|
+
<input class="wm-input-bare" type="search" placeholder="Search…">
|
|
500
|
+
</div>
|
|
501
|
+
============================================================ */
|
|
502
|
+
|
|
503
|
+
.wm-input-group {
|
|
504
|
+
position: relative;
|
|
505
|
+
display: flex;
|
|
506
|
+
align-items: center;
|
|
507
|
+
height: 36px;
|
|
508
|
+
box-sizing: border-box;
|
|
509
|
+
background-color: var(--wm-bg-base);
|
|
510
|
+
border: 1px solid var(--wm-border-input);
|
|
511
|
+
border-radius: var(--wm-radius-md);
|
|
512
|
+
transition:
|
|
513
|
+
background-color 150ms ease,
|
|
514
|
+
border-color 150ms ease,
|
|
515
|
+
box-shadow 150ms ease;
|
|
516
|
+
|
|
517
|
+
--tw-ring-shadow: none;
|
|
518
|
+
--tw-ring-offset-shadow: none;
|
|
519
|
+
--tw-ring-color: transparent;
|
|
520
|
+
--tw-shadow: none;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* Hover: skip if the inner input is focused or the group is disabled */
|
|
524
|
+
.wm-input-group:hover:not(:has(.wm-input-bare:focus)):not(:has(:disabled)) {
|
|
525
|
+
background-color: var(--wm-bg-sunken);
|
|
526
|
+
border-color: var(--wm-border-hover);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Focus: border + animated ring on the group when inner input is focused */
|
|
530
|
+
.wm-input-group:focus-within {
|
|
531
|
+
outline: none;
|
|
532
|
+
border-color: var(--wm-color-accent);
|
|
533
|
+
animation: wm-focus-pulse 400ms cubic-bezier(0.2, 0, 0.4, 1) forwards;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
/* ============================================================
|
|
538
|
+
Bare inner input — .wm-input-bare
|
|
539
|
+
============================================================
|
|
540
|
+
Used exclusively inside .wm-input-group. No border or background
|
|
541
|
+
of its own — those belong to the group wrapper.
|
|
542
|
+
============================================================ */
|
|
543
|
+
|
|
544
|
+
.wm-input-bare {
|
|
545
|
+
flex: 1;
|
|
546
|
+
min-width: 0;
|
|
547
|
+
height: 100%;
|
|
548
|
+
padding: 0 12px;
|
|
549
|
+
font-size: 14px;
|
|
550
|
+
font-family: inherit;
|
|
551
|
+
color: var(--wm-text-primary);
|
|
552
|
+
background: transparent;
|
|
553
|
+
border: none;
|
|
554
|
+
outline: none;
|
|
555
|
+
box-sizing: border-box;
|
|
556
|
+
|
|
557
|
+
--tw-ring-shadow: none;
|
|
558
|
+
--tw-ring-offset-shadow: none;
|
|
559
|
+
--tw-ring-color: transparent;
|
|
560
|
+
--tw-shadow: none;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.wm-input-bare::placeholder {
|
|
564
|
+
color: var(--wm-text-placeholder);
|
|
565
|
+
opacity: 1;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
/* ============================================================
|
|
570
|
+
Icon slot — .wm-input-icon
|
|
571
|
+
============================================================
|
|
572
|
+
Leading or trailing icon inside a .wm-input-group. Uses the
|
|
573
|
+
CSS system color FieldText so it matches native picker chrome
|
|
574
|
+
in light and dark color-scheme contexts. pointer-events:none
|
|
575
|
+
keeps clicks on the icon from stealing focus from the input.
|
|
576
|
+
|
|
577
|
+
Usage:
|
|
578
|
+
<!-- leading icon -->
|
|
579
|
+
<span class="wm-input-icon"><svg …/></span>
|
|
580
|
+
<input class="wm-input-bare" …>
|
|
581
|
+
|
|
582
|
+
<!-- trailing icon (e.g. clear button — add pointer-events:auto) -->
|
|
583
|
+
<input class="wm-input-bare" …>
|
|
584
|
+
<button class="wm-input-icon" style="pointer-events:auto"> … </button>
|
|
585
|
+
============================================================ */
|
|
586
|
+
|
|
587
|
+
.wm-input-icon {
|
|
588
|
+
display: flex;
|
|
589
|
+
align-items: center;
|
|
590
|
+
justify-content: center;
|
|
591
|
+
flex-shrink: 0;
|
|
592
|
+
width: 36px;
|
|
593
|
+
color: FieldText;
|
|
594
|
+
opacity: 0.6;
|
|
595
|
+
pointer-events: none;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* Remove the left padding gap when an icon directly precedes the bare input */
|
|
599
|
+
.wm-input-icon + .wm-input-bare {
|
|
600
|
+
padding-left: 0;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
/* ============================================================
|
|
605
|
+
Custom select trigger — .wm-select-trigger
|
|
606
|
+
============================================================
|
|
607
|
+
A <button> styled identically to .wm-input for use with
|
|
608
|
+
component-level select pickers (app-select, popover select).
|
|
609
|
+
Matches the 36px height and all hover/focus states of .wm-input.
|
|
610
|
+
|
|
611
|
+
Opened state: set aria-expanded="true" on the button to trigger
|
|
612
|
+
the focus-ring style while the popover is open.
|
|
613
|
+
|
|
614
|
+
Usage:
|
|
615
|
+
<button class="wm-select-trigger" aria-expanded="false">
|
|
616
|
+
<span>Selected value</span>
|
|
617
|
+
<svg … /> <!-- chevron -->
|
|
618
|
+
</button>
|
|
619
|
+
|
|
620
|
+
Direct-child icon rule keeps the chevron opacity consistent
|
|
621
|
+
without overriding nested icon components inside the trigger.
|
|
622
|
+
============================================================ */
|
|
623
|
+
|
|
624
|
+
.wm-select-trigger {
|
|
625
|
+
display: flex;
|
|
626
|
+
align-items: center;
|
|
627
|
+
justify-content: space-between;
|
|
628
|
+
gap: 8px;
|
|
629
|
+
width: 100%;
|
|
630
|
+
height: 36px;
|
|
631
|
+
box-sizing: border-box;
|
|
632
|
+
padding: 0 12px;
|
|
633
|
+
font-size: 14px;
|
|
634
|
+
font-family: inherit;
|
|
635
|
+
color: var(--wm-text-primary);
|
|
636
|
+
background-color: var(--wm-bg-base);
|
|
637
|
+
border: 1px solid var(--wm-border-input);
|
|
638
|
+
border-radius: var(--wm-radius-md);
|
|
639
|
+
text-align: left;
|
|
640
|
+
cursor: pointer;
|
|
641
|
+
outline: none;
|
|
642
|
+
transition:
|
|
643
|
+
background-color 150ms ease,
|
|
644
|
+
border-color 150ms ease,
|
|
645
|
+
box-shadow 150ms ease;
|
|
646
|
+
|
|
647
|
+
--tw-ring-shadow: none;
|
|
648
|
+
--tw-ring-offset-shadow: none;
|
|
649
|
+
--tw-ring-color: transparent;
|
|
650
|
+
--tw-shadow: none;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* Trailing chevron / icon — direct child only to avoid clobbering nested icons */
|
|
654
|
+
.wm-select-trigger > svg,
|
|
655
|
+
.wm-select-trigger > i {
|
|
656
|
+
color: FieldText;
|
|
657
|
+
opacity: 0.6;
|
|
658
|
+
font-size: 12px;
|
|
659
|
+
flex-shrink: 0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* Hover: skip when open (aria-expanded) or disabled */
|
|
663
|
+
.wm-select-trigger:hover:not([aria-expanded='true']):not(:disabled) {
|
|
664
|
+
background-color: var(--wm-bg-sunken);
|
|
665
|
+
border-color: var(--wm-border-hover);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* Open state: same visual as focus */
|
|
669
|
+
.wm-select-trigger[aria-expanded='true'],
|
|
670
|
+
.wm-select-trigger:focus-visible {
|
|
671
|
+
outline: none;
|
|
672
|
+
border-color: var(--wm-color-accent);
|
|
673
|
+
animation: wm-focus-pulse 400ms cubic-bezier(0.2, 0, 0.4, 1) forwards;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.wm-select-trigger:disabled {
|
|
677
|
+
opacity: 0.5;
|
|
678
|
+
cursor: not-allowed;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
/* ============================================================
|
|
683
|
+
Number input — .wm-number-input / .wm-number-controls
|
|
684
|
+
============================================================
|
|
685
|
+
Removes native browser spinners and replaces them with a
|
|
686
|
+
custom ± button strip (.wm-number-controls) appended inside
|
|
687
|
+
a .wm-input-group.
|
|
688
|
+
|
|
689
|
+
Usage:
|
|
690
|
+
<div class="wm-input-group">
|
|
691
|
+
<input class="wm-input-bare wm-number-input" type="number" value="5">
|
|
692
|
+
<div class="wm-number-controls">
|
|
693
|
+
<button class="wm-number-btn" aria-label="Increase">+</button>
|
|
694
|
+
<button class="wm-number-btn" aria-label="Decrease">−</button>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
============================================================ */
|
|
698
|
+
|
|
699
|
+
/* Strip native spinners */
|
|
700
|
+
.wm-number-input::-webkit-inner-spin-button,
|
|
701
|
+
.wm-number-input::-webkit-outer-spin-button {
|
|
702
|
+
-webkit-appearance: none;
|
|
703
|
+
margin: 0;
|
|
704
|
+
}
|
|
705
|
+
.wm-number-input {
|
|
706
|
+
-moz-appearance: textfield;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/* ± strip: sits flush at the right edge of the group, stacked vertically */
|
|
710
|
+
.wm-number-controls {
|
|
711
|
+
display: flex;
|
|
712
|
+
flex-direction: column;
|
|
713
|
+
flex-shrink: 0;
|
|
714
|
+
gap: 1px;
|
|
715
|
+
padding: 3px 4px 3px 2px;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.wm-number-btn {
|
|
719
|
+
display: flex;
|
|
720
|
+
align-items: center;
|
|
721
|
+
justify-content: center;
|
|
722
|
+
width: 20px;
|
|
723
|
+
height: 14px;
|
|
724
|
+
padding: 0;
|
|
725
|
+
background: transparent;
|
|
726
|
+
border: none;
|
|
727
|
+
border-radius: var(--wm-radius-xs);
|
|
728
|
+
color: FieldText;
|
|
729
|
+
opacity: 0.6;
|
|
730
|
+
cursor: pointer;
|
|
731
|
+
transition: opacity 150ms ease, background-color 150ms ease;
|
|
732
|
+
outline: none;
|
|
733
|
+
font-family: inherit;
|
|
734
|
+
|
|
735
|
+
--tw-ring-shadow: none;
|
|
736
|
+
--tw-ring-offset-shadow: none;
|
|
737
|
+
--tw-ring-color: transparent;
|
|
738
|
+
--tw-shadow: none;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.wm-number-btn:hover:not(:disabled) {
|
|
742
|
+
opacity: 1;
|
|
743
|
+
background-color: color-mix(in srgb, FieldText 8%, transparent);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.wm-number-btn:disabled {
|
|
747
|
+
opacity: 0.25;
|
|
748
|
+
cursor: not-allowed;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
/* ============================================================
|
|
753
|
+
Ghost field — .wm-ghost-field
|
|
754
|
+
============================================================
|
|
755
|
+
Wraps any .wm-input, .wm-input-group, or .wm-select-trigger
|
|
756
|
+
to give it a "read-only until touched" appearance. At rest the
|
|
757
|
+
border and background are transparent so it looks like static
|
|
758
|
+
text. The 1px border box is preserved — zero layout shift when
|
|
759
|
+
chrome appears on hover.
|
|
760
|
+
|
|
761
|
+
Hover: same mid-tone bg + secondary border as a normal input hover.
|
|
762
|
+
Focus: standard .wm-input:focus / :focus-within takes over.
|
|
763
|
+
|
|
764
|
+
Usage:
|
|
765
|
+
<div class="wm-ghost-field">
|
|
766
|
+
<input class="wm-input" type="text" value="Editable value">
|
|
767
|
+
</div>
|
|
768
|
+
|
|
769
|
+
With number field (hides ± strip until hovered/focused):
|
|
770
|
+
<div class="wm-ghost-field">
|
|
771
|
+
<div class="wm-input-group">
|
|
772
|
+
<input class="wm-input-bare wm-number-input" type="number">
|
|
773
|
+
<div class="wm-number-controls"> … </div>
|
|
774
|
+
</div>
|
|
775
|
+
</div>
|
|
776
|
+
============================================================ */
|
|
777
|
+
|
|
778
|
+
/* Resting: transparent chrome */
|
|
779
|
+
.wm-ghost-field .wm-input:not(:focus),
|
|
780
|
+
.wm-ghost-field .wm-input-group:not(:focus-within),
|
|
781
|
+
.wm-ghost-field .wm-select-trigger:not([aria-expanded='true']):not(:focus-visible) {
|
|
782
|
+
border-color: transparent;
|
|
783
|
+
background-color: transparent;
|
|
784
|
+
box-shadow: none;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/* Hover: reveal standard input hover chrome */
|
|
788
|
+
.wm-ghost-field:hover .wm-input:not(:focus),
|
|
789
|
+
.wm-ghost-field:hover .wm-input-group:not(:focus-within),
|
|
790
|
+
.wm-ghost-field:hover .wm-select-trigger:not([aria-expanded='true']):not(:focus-visible) {
|
|
791
|
+
background-color: var(--wm-bg-sunken);
|
|
792
|
+
border-color: var(--wm-border-hover);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* Hide the ± strip on a ghost number field until hover or focus */
|
|
796
|
+
.wm-ghost-field .wm-input-group:not(:focus-within) .wm-number-controls {
|
|
797
|
+
visibility: hidden;
|
|
798
|
+
}
|
|
799
|
+
.wm-ghost-field:hover .wm-input-group:not(:focus-within) .wm-number-controls,
|
|
800
|
+
.wm-ghost-field .wm-input-group:focus-within .wm-number-controls {
|
|
801
|
+
visibility: visible;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
/* ============================================================
|
|
806
|
+
Error message block — .wm-error-msg
|
|
807
|
+
============================================================
|
|
808
|
+
Inline danger block displayed below a field (or at the top of
|
|
809
|
+
a form) to communicate validation or server errors. Uses the
|
|
810
|
+
per-theme danger tokens for background and border, keeping the
|
|
811
|
+
block readable across all 12 themes.
|
|
812
|
+
|
|
813
|
+
Usage:
|
|
814
|
+
<div class="wm-error-msg">Incorrect username or password.</div>
|
|
815
|
+
============================================================ */
|
|
816
|
+
|
|
817
|
+
.wm-error-msg {
|
|
818
|
+
background-color: var(--wm-color-danger-bg);
|
|
819
|
+
color: var(--wm-color-danger);
|
|
820
|
+
border: 1px solid var(--wm-color-danger-border);
|
|
821
|
+
border-radius: var(--wm-radius-sm);
|
|
822
|
+
padding: 10px 12px;
|
|
823
|
+
font-size: 13px;
|
|
824
|
+
line-height: 1.4;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.wm-success-msg {
|
|
828
|
+
background-color: var(--wm-color-success-bg);
|
|
829
|
+
color: var(--wm-color-success);
|
|
830
|
+
border: 1px solid var(--wm-color-success-border);
|
|
831
|
+
border-radius: var(--wm-radius-sm);
|
|
832
|
+
padding: 10px 12px;
|
|
833
|
+
font-size: 13px;
|
|
834
|
+
line-height: 1.4;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.wm-warning-msg {
|
|
838
|
+
background-color: var(--wm-color-warning-bg);
|
|
839
|
+
color: var(--wm-color-warning);
|
|
840
|
+
border: 1px solid var(--wm-color-warning-border);
|
|
841
|
+
border-radius: var(--wm-radius-sm);
|
|
842
|
+
padding: 10px 12px;
|
|
843
|
+
font-size: 13px;
|
|
844
|
+
line-height: 1.4;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.wm-info-msg {
|
|
848
|
+
background-color: color-mix(in srgb, var(--wm-color-accent) 8%, transparent);
|
|
849
|
+
color: var(--wm-text-secondary);
|
|
850
|
+
border: 1px solid color-mix(in srgb, var(--wm-color-accent) 25%, transparent);
|
|
851
|
+
border-radius: var(--wm-radius-sm);
|
|
852
|
+
padding: 10px 12px;
|
|
853
|
+
font-size: 13px;
|
|
854
|
+
line-height: 1.4;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
/* ============================================================
|
|
859
|
+
Loading spinner — .wm-spinner
|
|
860
|
+
============================================================
|
|
861
|
+
A CSS-only spinner that uses the accent color. Must be paired
|
|
862
|
+
with aria-label or aria-busy on its container.
|
|
863
|
+
|
|
864
|
+
Usage:
|
|
865
|
+
<div class="wm-spinner" aria-label="Loading…" role="status"></div>
|
|
866
|
+
|
|
867
|
+
Prefers-reduced-motion: hides the spin, shows a static ring so
|
|
868
|
+
the presence of the loading state is still communicated.
|
|
869
|
+
============================================================ */
|
|
870
|
+
|
|
871
|
+
@keyframes wm-spin {
|
|
872
|
+
to { transform: rotate(360deg); }
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.wm-spinner {
|
|
876
|
+
width: 20px;
|
|
877
|
+
height: 20px;
|
|
878
|
+
border: 2px solid var(--wm-border);
|
|
879
|
+
border-top-color: var(--wm-color-accent);
|
|
880
|
+
border-radius: var(--wm-radius-full);
|
|
881
|
+
animation: wm-spin 600ms linear infinite;
|
|
882
|
+
flex-shrink: 0;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.wm-spinner--sm {
|
|
886
|
+
width: 14px;
|
|
887
|
+
height: 14px;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.wm-spinner--lg {
|
|
891
|
+
width: 28px;
|
|
892
|
+
height: 28px;
|
|
893
|
+
border-width: 3px;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
/* ============================================================
|
|
898
|
+
Linear progress bar — .wm-progress
|
|
899
|
+
============================================================
|
|
900
|
+
Two variants:
|
|
901
|
+
Indeterminate — use when duration is unknown (loading, fetching)
|
|
902
|
+
Determinate — use when progress 0–100 is known (upload, install)
|
|
903
|
+
|
|
904
|
+
Indeterminate usage:
|
|
905
|
+
<div class="wm-progress" role="progressbar" aria-label="Loading…">
|
|
906
|
+
<div class="wm-progress__bar wm-progress__bar--indeterminate"></div>
|
|
907
|
+
</div>
|
|
908
|
+
|
|
909
|
+
Determinate usage (CSS custom property drives width):
|
|
910
|
+
<div class="wm-progress" role="progressbar"
|
|
911
|
+
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"
|
|
912
|
+
aria-label="Uploading…">
|
|
913
|
+
<div class="wm-progress__bar" style="--wm-progress-value: 40%"></div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
Sizes: default 2px, --sm 1px, --lg 4px
|
|
917
|
+
|
|
918
|
+
Prefers-reduced-motion: indeterminate bar freezes at 30% width.
|
|
919
|
+
============================================================ */
|
|
920
|
+
|
|
921
|
+
@keyframes wm-progress-slide {
|
|
922
|
+
0% { transform: translateX(-100%) scaleX(0.25); }
|
|
923
|
+
50% { transform: translateX(60%) scaleX(0.5); }
|
|
924
|
+
100% { transform: translateX(400%) scaleX(0.25); }
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.wm-progress {
|
|
928
|
+
position: relative;
|
|
929
|
+
width: 100%;
|
|
930
|
+
height: 2px;
|
|
931
|
+
background: var(--wm-border);
|
|
932
|
+
border-radius: var(--wm-radius-full);
|
|
933
|
+
overflow: hidden;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.wm-progress--sm { height: 1px; }
|
|
937
|
+
.wm-progress--lg { height: 4px; }
|
|
938
|
+
|
|
939
|
+
.wm-progress__bar {
|
|
940
|
+
position: absolute;
|
|
941
|
+
inset: 0;
|
|
942
|
+
background: var(--wm-color-accent);
|
|
943
|
+
border-radius: var(--wm-radius-full);
|
|
944
|
+
/* Determinate: width driven by --wm-progress-value (default 0%) */
|
|
945
|
+
width: var(--wm-progress-value, 0%);
|
|
946
|
+
transition: width 200ms ease;
|
|
947
|
+
transform-origin: left center;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.wm-progress__bar--indeterminate {
|
|
951
|
+
width: 40%;
|
|
952
|
+
animation: wm-progress-slide 1.4s ease-in-out infinite;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
@media (prefers-reduced-motion: reduce) {
|
|
956
|
+
.wm-progress__bar--indeterminate {
|
|
957
|
+
animation: none;
|
|
958
|
+
width: 30%;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
/* ============================================================
|
|
964
|
+
Circular progress — .wm-circle-progress
|
|
965
|
+
============================================================
|
|
966
|
+
SVG-based ring. Both determinate and indeterminate modes.
|
|
967
|
+
|
|
968
|
+
The component is a <svg> element — use the mixin via a helper
|
|
969
|
+
element or build it inline.
|
|
970
|
+
|
|
971
|
+
Indeterminate:
|
|
972
|
+
<svg class="wm-circle-progress wm-circle-progress--indeterminate"
|
|
973
|
+
viewBox="0 0 36 36" role="progressbar" aria-label="Loading…">
|
|
974
|
+
<circle class="wm-circle-progress__track" cx="18" cy="18" r="15"/>
|
|
975
|
+
<circle class="wm-circle-progress__fill" cx="18" cy="18" r="15"/>
|
|
976
|
+
</svg>
|
|
977
|
+
|
|
978
|
+
Determinate (set --wm-circle-pct, 0–94.2 = 0–100%):
|
|
979
|
+
<svg class="wm-circle-progress"
|
|
980
|
+
viewBox="0 0 36 36" role="progressbar"
|
|
981
|
+
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
|
|
982
|
+
aria-label="60% complete">
|
|
983
|
+
<circle class="wm-circle-progress__track" cx="18" cy="18" r="15"/>
|
|
984
|
+
<circle class="wm-circle-progress__fill" cx="18" cy="18" r="15"
|
|
985
|
+
style="--wm-circle-pct: 56.5"/>
|
|
986
|
+
</svg>
|
|
987
|
+
|
|
988
|
+
Sizes: default 36×36px, --sm 20px, --lg 56px
|
|
989
|
+
r=15 → circumference ≈ 94.25; --wm-circle-pct maps 0–94.25 to stroke progress.
|
|
990
|
+
============================================================ */
|
|
991
|
+
|
|
992
|
+
@keyframes wm-circle-spin {
|
|
993
|
+
to { transform: rotate(360deg); }
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
@keyframes wm-circle-dash {
|
|
997
|
+
0% { stroke-dashoffset: 94; }
|
|
998
|
+
50% { stroke-dashoffset: 24; stroke-dasharray: 65 30; }
|
|
999
|
+
100% { stroke-dashoffset: 94; }
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.wm-circle-progress {
|
|
1003
|
+
width: 36px;
|
|
1004
|
+
height: 36px;
|
|
1005
|
+
flex-shrink: 0;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.wm-circle-progress--sm { width: 20px; height: 20px; }
|
|
1009
|
+
.wm-circle-progress--lg { width: 56px; height: 56px; }
|
|
1010
|
+
|
|
1011
|
+
.wm-circle-progress__track {
|
|
1012
|
+
fill: none;
|
|
1013
|
+
stroke: var(--wm-border);
|
|
1014
|
+
stroke-width: 3;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.wm-circle-progress__fill {
|
|
1018
|
+
fill: none;
|
|
1019
|
+
stroke: var(--wm-color-accent);
|
|
1020
|
+
stroke-width: 3;
|
|
1021
|
+
stroke-linecap: round;
|
|
1022
|
+
/* Determinate: circumference=94.25; pct drives dashoffset */
|
|
1023
|
+
stroke-dasharray: 94.25;
|
|
1024
|
+
stroke-dashoffset: calc(94.25 - var(--wm-circle-pct, 0));
|
|
1025
|
+
transform: rotate(-90deg);
|
|
1026
|
+
transform-origin: 50% 50%;
|
|
1027
|
+
transition: stroke-dashoffset 300ms ease;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
.wm-circle-progress--indeterminate .wm-circle-progress__fill {
|
|
1031
|
+
stroke-dasharray: 60 34;
|
|
1032
|
+
stroke-dashoffset: 94;
|
|
1033
|
+
animation:
|
|
1034
|
+
wm-circle-spin 1.4s linear infinite,
|
|
1035
|
+
wm-circle-dash 1.4s ease-in-out infinite;
|
|
1036
|
+
transition: none;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1040
|
+
.wm-circle-progress--indeterminate .wm-circle-progress__fill {
|
|
1041
|
+
animation: none;
|
|
1042
|
+
stroke-dasharray: 40 54;
|
|
1043
|
+
opacity: 0.5;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
/* ============================================================
|
|
1049
|
+
Button — .wm-btn
|
|
1050
|
+
============================================================
|
|
1051
|
+
Base interactive button. Always pair with a variant modifier.
|
|
1052
|
+
|
|
1053
|
+
Variants: --primary (default), --secondary, --ghost, --destructive
|
|
1054
|
+
Sizes: --sm (28px), --md (36px, default), --lg (40px)
|
|
1055
|
+
|
|
1056
|
+
The focus ring on primary and destructive buttons uses a
|
|
1057
|
+
double-ring (canvas gap + colour ring) rather than the diffuse
|
|
1058
|
+
wm-focus-pulse, because those buttons have a solid fill that
|
|
1059
|
+
would visually merge with the pulse ring.
|
|
1060
|
+
|
|
1061
|
+
Usage:
|
|
1062
|
+
<button class="wm-btn wm-btn--primary">Save</button>
|
|
1063
|
+
<button class="wm-btn wm-btn--secondary wm-btn--sm">Cancel</button>
|
|
1064
|
+
<a class="wm-btn wm-btn--ghost" href="…">Back</a>
|
|
1065
|
+
============================================================ */
|
|
1066
|
+
|
|
1067
|
+
.wm-btn {
|
|
1068
|
+
display: inline-flex;
|
|
1069
|
+
align-items: center;
|
|
1070
|
+
justify-content: center;
|
|
1071
|
+
gap: 6px;
|
|
1072
|
+
height: 36px;
|
|
1073
|
+
padding: 0 14px;
|
|
1074
|
+
font-size: 0.875rem;
|
|
1075
|
+
font-family: inherit;
|
|
1076
|
+
font-weight: 600;
|
|
1077
|
+
white-space: nowrap;
|
|
1078
|
+
line-height: 1;
|
|
1079
|
+
border: 1px solid transparent;
|
|
1080
|
+
border-radius: var(--wm-radius-md);
|
|
1081
|
+
cursor: pointer;
|
|
1082
|
+
outline: none;
|
|
1083
|
+
text-decoration: none;
|
|
1084
|
+
box-sizing: border-box;
|
|
1085
|
+
transition:
|
|
1086
|
+
background-color var(--wm-transition-fast),
|
|
1087
|
+
border-color var(--wm-transition-fast),
|
|
1088
|
+
color var(--wm-transition-fast),
|
|
1089
|
+
box-shadow var(--wm-transition-fast);
|
|
1090
|
+
|
|
1091
|
+
--tw-ring-shadow: none;
|
|
1092
|
+
--tw-ring-offset-shadow: none;
|
|
1093
|
+
--tw-ring-color: transparent;
|
|
1094
|
+
--tw-shadow: none;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* ── Variants ──────────────────────────────────────────────── */
|
|
1098
|
+
|
|
1099
|
+
.wm-btn--primary {
|
|
1100
|
+
background-color: var(--wm-color-accent);
|
|
1101
|
+
border-color: var(--wm-color-accent);
|
|
1102
|
+
color: var(--wm-color-accent-text);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.wm-btn--primary:hover:not(:disabled) {
|
|
1106
|
+
background-color: var(--wm-color-accent-hover);
|
|
1107
|
+
border-color: var(--wm-color-accent-hover);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.wm-btn--secondary {
|
|
1111
|
+
background-color: transparent;
|
|
1112
|
+
border-color: var(--wm-border-input);
|
|
1113
|
+
color: var(--wm-text-primary);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
.wm-btn--secondary:hover:not(:disabled) {
|
|
1117
|
+
background-color: var(--wm-bg-sunken);
|
|
1118
|
+
border-color: var(--wm-border-hover);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.wm-btn--ghost {
|
|
1122
|
+
background-color: transparent;
|
|
1123
|
+
border-color: transparent;
|
|
1124
|
+
color: var(--wm-text-secondary);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.wm-btn--ghost:hover:not(:disabled) {
|
|
1128
|
+
background-color: var(--wm-bg-hover);
|
|
1129
|
+
color: var(--wm-text-primary);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.wm-btn--destructive {
|
|
1133
|
+
background-color: var(--wm-color-danger);
|
|
1134
|
+
border-color: var(--wm-color-danger);
|
|
1135
|
+
color: #ffffff;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
.wm-btn--destructive:hover:not(:disabled) {
|
|
1139
|
+
background-color: color-mix(in srgb, var(--wm-color-danger) 85%, black);
|
|
1140
|
+
border-color: color-mix(in srgb, var(--wm-color-danger) 85%, black);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.wm-btn--flat {
|
|
1144
|
+
background-color: var(--wm-bg-foreground);
|
|
1145
|
+
border-color: transparent;
|
|
1146
|
+
color: var(--wm-text-primary);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.wm-btn--flat:hover:not(:disabled) {
|
|
1150
|
+
background-color: var(--wm-bg-hover);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/* ── Sizes ─────────────────────────────────────────────────── */
|
|
1154
|
+
|
|
1155
|
+
.wm-btn--xs {
|
|
1156
|
+
height: 22px;
|
|
1157
|
+
padding: 0 6px;
|
|
1158
|
+
font-size: 0.6875rem;
|
|
1159
|
+
border-radius: var(--wm-radius-xs);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.wm-btn--sm {
|
|
1163
|
+
height: 28px;
|
|
1164
|
+
padding: 0 10px;
|
|
1165
|
+
font-size: 0.8125rem;
|
|
1166
|
+
border-radius: var(--wm-radius-sm);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.wm-btn--lg {
|
|
1170
|
+
height: 40px;
|
|
1171
|
+
padding: 0 18px;
|
|
1172
|
+
font-size: 0.9375rem;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* ── Focus ─────────────────────────────────────────────────── */
|
|
1176
|
+
|
|
1177
|
+
/* Secondary and ghost: diffuse pulse (border changes from neutral → accent) */
|
|
1178
|
+
.wm-btn--secondary:focus-visible,
|
|
1179
|
+
.wm-btn--ghost:focus-visible,
|
|
1180
|
+
.wm-btn--flat:focus-visible {
|
|
1181
|
+
outline: none;
|
|
1182
|
+
border-color: var(--wm-color-accent);
|
|
1183
|
+
animation: wm-focus-pulse 400ms cubic-bezier(0.2, 0, 0.4, 1) forwards;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/* Primary and destructive: double-ring so the ring contrasts against the fill */
|
|
1187
|
+
.wm-btn--primary:focus-visible {
|
|
1188
|
+
outline: none;
|
|
1189
|
+
box-shadow:
|
|
1190
|
+
0 0 0 2px var(--wm-bg-base),
|
|
1191
|
+
0 0 0 4px var(--wm-color-accent);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.wm-btn--destructive:focus-visible {
|
|
1195
|
+
outline: none;
|
|
1196
|
+
box-shadow:
|
|
1197
|
+
0 0 0 2px var(--wm-bg-base),
|
|
1198
|
+
0 0 0 4px var(--wm-color-danger);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/* ── States ────────────────────────────────────────────────── */
|
|
1202
|
+
|
|
1203
|
+
.wm-btn:disabled {
|
|
1204
|
+
opacity: 0.5;
|
|
1205
|
+
cursor: not-allowed;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.wm-btn--loading {
|
|
1209
|
+
cursor: wait;
|
|
1210
|
+
pointer-events: none;
|
|
1211
|
+
opacity: 0.75;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/* SVG icons inside buttons: inherit colour, fixed size */
|
|
1215
|
+
.wm-btn svg {
|
|
1216
|
+
flex-shrink: 0;
|
|
1217
|
+
width: 16px;
|
|
1218
|
+
height: 16px;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.wm-btn--xs svg { width: 10px; height: 10px; }
|
|
1222
|
+
.wm-btn--sm svg { width: 14px; height: 14px; }
|
|
1223
|
+
.wm-btn--lg svg { width: 18px; height: 18px; }
|
|
1224
|
+
|
|
1225
|
+
/* Icon-only: square button, no padding, equal width/height */
|
|
1226
|
+
.btn-icon-only {
|
|
1227
|
+
padding: 0;
|
|
1228
|
+
width: 36px;
|
|
1229
|
+
}
|
|
1230
|
+
.wm-btn--xs.btn-icon-only { width: 22px; }
|
|
1231
|
+
.wm-btn--sm.btn-icon-only { width: 28px; }
|
|
1232
|
+
.wm-btn--lg.btn-icon-only { width: 40px; }
|
|
1233
|
+
|
|
1234
|
+
/* ── CSS Tooltip ───────────────────────────────────────────── */
|
|
1235
|
+
/* Apply data-tooltip="Label" to any element. Shows below by */
|
|
1236
|
+
/* default; flip with data-tooltip-pos="top" (see below). */
|
|
1237
|
+
|
|
1238
|
+
[data-tooltip] {
|
|
1239
|
+
position: relative;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
[data-tooltip]::after {
|
|
1243
|
+
content: attr(data-tooltip);
|
|
1244
|
+
position: absolute;
|
|
1245
|
+
top: calc(100% + 6px);
|
|
1246
|
+
left: 50%;
|
|
1247
|
+
transform: translateX(-50%) translateY(-2px);
|
|
1248
|
+
white-space: nowrap;
|
|
1249
|
+
padding: 3px 8px;
|
|
1250
|
+
background: var(--wm-text-primary);
|
|
1251
|
+
color: var(--wm-bg-base);
|
|
1252
|
+
font-family: var(--wm-font-sans);
|
|
1253
|
+
font-size: 11.5px;
|
|
1254
|
+
font-weight: 500;
|
|
1255
|
+
line-height: 1.4;
|
|
1256
|
+
border-radius: var(--wm-radius-sm);
|
|
1257
|
+
pointer-events: none;
|
|
1258
|
+
opacity: 0;
|
|
1259
|
+
transition:
|
|
1260
|
+
opacity var(--wm-transition-fast),
|
|
1261
|
+
transform var(--wm-transition-fast);
|
|
1262
|
+
z-index: 9000;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
[data-tooltip]:hover::after,
|
|
1266
|
+
[data-tooltip]:focus-visible::after {
|
|
1267
|
+
opacity: 1;
|
|
1268
|
+
transform: translateX(-50%) translateY(0);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
[data-tooltip-pos='top']::after {
|
|
1272
|
+
top: auto;
|
|
1273
|
+
bottom: calc(100% + 6px);
|
|
1274
|
+
transform: translateX(-50%) translateY(2px);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
[data-tooltip-pos='top']:hover::after,
|
|
1278
|
+
[data-tooltip-pos='top']:focus-visible::after {
|
|
1279
|
+
transform: translateX(-50%) translateY(0);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
/* ============================================================
|
|
1284
|
+
Reduced motion
|
|
1285
|
+
============================================================
|
|
1286
|
+
For users who have opted-in to reduced motion in their OS,
|
|
1287
|
+
all transitions and animations are replaced with instant
|
|
1288
|
+
equivalents. Focus states still show a visible ring — just
|
|
1289
|
+
without the expand-then-settle pulse.
|
|
1290
|
+
============================================================ */
|
|
1291
|
+
|
|
1292
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1293
|
+
.wm-input,
|
|
1294
|
+
.wm-input-group,
|
|
1295
|
+
.wm-select-trigger,
|
|
1296
|
+
.wm-number-btn,
|
|
1297
|
+
.wm-btn {
|
|
1298
|
+
transition: none;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/* Replace the animated pulse with a static diffuse ring */
|
|
1302
|
+
.wm-input:focus,
|
|
1303
|
+
.wm-input-group:focus-within,
|
|
1304
|
+
.wm-select-trigger[aria-expanded='true'],
|
|
1305
|
+
.wm-select-trigger:focus-visible,
|
|
1306
|
+
.wm-btn--secondary:focus-visible,
|
|
1307
|
+
.wm-btn--ghost:focus-visible {
|
|
1308
|
+
animation: none;
|
|
1309
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wm-color-accent) 25%, transparent);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.wm-spinner {
|
|
1313
|
+
animation: none;
|
|
1314
|
+
opacity: 0.5;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
/* ============================================================
|
|
1320
|
+
Badge — item type labels
|
|
1321
|
+
Usage: <span class="wm-badge wm-badge--task">Task</span>
|
|
1322
|
+
============================================================ */
|
|
1323
|
+
|
|
1324
|
+
.wm-badge {
|
|
1325
|
+
display: inline-flex;
|
|
1326
|
+
align-items: center;
|
|
1327
|
+
gap: 0.25rem;
|
|
1328
|
+
padding: 0.1875rem 0.5rem;
|
|
1329
|
+
border-radius: var(--wm-radius-sm);
|
|
1330
|
+
font-size: 0.6875rem;
|
|
1331
|
+
font-weight: 600;
|
|
1332
|
+
line-height: 1.4;
|
|
1333
|
+
white-space: nowrap;
|
|
1334
|
+
letter-spacing: 0.01em;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.wm-badge--task { color: var(--wm-color-task); background: color-mix(in srgb, var(--wm-color-task) 12%, transparent); }
|
|
1338
|
+
.wm-badge--bug { color: var(--wm-color-bug); background: color-mix(in srgb, var(--wm-color-bug) 12%, transparent); }
|
|
1339
|
+
.wm-badge--story { color: var(--wm-color-story); background: color-mix(in srgb, var(--wm-color-story) 12%, transparent); }
|
|
1340
|
+
.wm-badge--epic { color: var(--wm-color-epic-text); background: var(--wm-color-epic); }
|
|
1341
|
+
.wm-badge--initiative { color: var(--wm-color-initiative-text); background: var(--wm-color-initiative); }
|
|
1342
|
+
.wm-badge--completed { color: var(--wm-color-completed); background: color-mix(in srgb, var(--wm-color-completed) 12%, transparent); }
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
/* ============================================================
|
|
1346
|
+
Priority chip — urgency indicators
|
|
1347
|
+
Usage: <span class="wm-chip wm-chip--high">High</span>
|
|
1348
|
+
============================================================ */
|
|
1349
|
+
|
|
1350
|
+
.wm-chip {
|
|
1351
|
+
display: inline-flex;
|
|
1352
|
+
align-items: center;
|
|
1353
|
+
gap: 0.25rem;
|
|
1354
|
+
padding: 0.1875rem 0.5rem;
|
|
1355
|
+
border-radius: var(--wm-radius-full);
|
|
1356
|
+
font-size: 0.6875rem;
|
|
1357
|
+
font-weight: 600;
|
|
1358
|
+
line-height: 1.4;
|
|
1359
|
+
white-space: nowrap;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.wm-chip--critical { color: var(--wm-priority-critical-text); background: var(--wm-priority-critical-bg); }
|
|
1363
|
+
.wm-chip--high { color: var(--wm-priority-high-text); background: var(--wm-priority-high-bg); }
|
|
1364
|
+
.wm-chip--medium { color: var(--wm-priority-medium-text); background: var(--wm-priority-medium-bg); }
|
|
1365
|
+
.wm-chip--low { color: var(--wm-priority-low-text); background: var(--wm-priority-low-bg); }
|
|
1366
|
+
.wm-chip--none { color: var(--wm-text-muted); background: var(--wm-bg-sunken); }
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
/* ============================================================
|
|
1370
|
+
Dialog footer utility
|
|
1371
|
+
Place inside [body] slot of <wm-dialog>:
|
|
1372
|
+
<div body>
|
|
1373
|
+
<p>Message</p>
|
|
1374
|
+
<div class="wm-dialog-footer">…buttons…</div>
|
|
1375
|
+
</div>
|
|
1376
|
+
============================================================ */
|
|
1377
|
+
|
|
1378
|
+
.wm-dialog-footer {
|
|
1379
|
+
display: flex;
|
|
1380
|
+
justify-content: flex-end;
|
|
1381
|
+
gap: 0.5rem;
|
|
1382
|
+
padding-top: 1rem;
|
|
1383
|
+
margin-top: 1rem;
|
|
1384
|
+
border-top: 1px solid var(--wm-border);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
/* ============================================================
|
|
1389
|
+
Action list — vertical list of full-width button actions
|
|
1390
|
+
Use for 4+ actions in narrow containers (splash, welcome, settings).
|
|
1391
|
+
Pair with a single primary CTA above the list.
|
|
1392
|
+
|
|
1393
|
+
Usage:
|
|
1394
|
+
<button class="wm-btn wm-btn--primary" style="width:100%">Primary action</button>
|
|
1395
|
+
<ul class="wm-action-list">
|
|
1396
|
+
<li><button class="wm-action-list-item">Action label</button></li>
|
|
1397
|
+
</ul>
|
|
1398
|
+
============================================================ */
|
|
1399
|
+
|
|
1400
|
+
.wm-action-list {
|
|
1401
|
+
list-style: none;
|
|
1402
|
+
margin: 0.5rem 0 0;
|
|
1403
|
+
padding: 0;
|
|
1404
|
+
display: flex;
|
|
1405
|
+
flex-direction: column;
|
|
1406
|
+
gap: 2px;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
.wm-action-list-item {
|
|
1410
|
+
display: flex;
|
|
1411
|
+
align-items: center;
|
|
1412
|
+
gap: 0.5rem;
|
|
1413
|
+
width: 100%;
|
|
1414
|
+
padding: 0.5rem 0.75rem;
|
|
1415
|
+
background: transparent;
|
|
1416
|
+
border: none;
|
|
1417
|
+
border-radius: var(--wm-radius-md);
|
|
1418
|
+
font-size: 0.875rem;
|
|
1419
|
+
font-weight: 500;
|
|
1420
|
+
color: var(--wm-text-secondary);
|
|
1421
|
+
cursor: pointer;
|
|
1422
|
+
text-align: left;
|
|
1423
|
+
transition: background var(--wm-duration-fast) var(--wm-ease-standard),
|
|
1424
|
+
color var(--wm-duration-fast) var(--wm-ease-standard);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.wm-action-list-item:hover {
|
|
1428
|
+
background: var(--wm-bg-hover);
|
|
1429
|
+
color: var(--wm-text-primary);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
.wm-action-list-item:focus-visible {
|
|
1433
|
+
outline: 2px solid var(--wm-color-accent);
|
|
1434
|
+
outline-offset: -1px;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
.wm-action-list-item svg {
|
|
1438
|
+
flex-shrink: 0;
|
|
1439
|
+
width: 16px;
|
|
1440
|
+
height: 16px;
|
|
1441
|
+
stroke: currentColor;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1445
|
+
.wm-action-list-item { transition: none; }
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
/* ============================================================
|
|
1450
|
+
Keyboard key — .wm-kbd / .wm-kbd-combo
|
|
1451
|
+
============================================================
|
|
1452
|
+
.wm-kbd renders a single key as a small badge resembling a
|
|
1453
|
+
physical keyboard key. The heavier bottom border simulates
|
|
1454
|
+
the key's lower edge depth.
|
|
1455
|
+
|
|
1456
|
+
.wm-kbd-combo wraps two or more adjacent .wm-kbd elements
|
|
1457
|
+
and injects a '+' connector between them via CSS so no extra
|
|
1458
|
+
markup is needed.
|
|
1459
|
+
|
|
1460
|
+
Usage — single key:
|
|
1461
|
+
<kbd class="wm-kbd">Esc</kbd>
|
|
1462
|
+
|
|
1463
|
+
Usage — key combination:
|
|
1464
|
+
<span class="wm-kbd-combo">
|
|
1465
|
+
<kbd class="wm-kbd">Ctrl</kbd>
|
|
1466
|
+
<kbd class="wm-kbd">K</kbd>
|
|
1467
|
+
</span>
|
|
1468
|
+
|
|
1469
|
+
Usage — longer combo:
|
|
1470
|
+
<span class="wm-kbd-combo">
|
|
1471
|
+
<kbd class="wm-kbd">Ctrl</kbd>
|
|
1472
|
+
<kbd class="wm-kbd">Shift</kbd>
|
|
1473
|
+
<kbd class="wm-kbd">P</kbd>
|
|
1474
|
+
</span>
|
|
1475
|
+
============================================================ */
|
|
1476
|
+
|
|
1477
|
+
.wm-kbd {
|
|
1478
|
+
display: inline-flex;
|
|
1479
|
+
align-items: center;
|
|
1480
|
+
justify-content: center;
|
|
1481
|
+
padding: 1px 5px;
|
|
1482
|
+
min-width: 20px;
|
|
1483
|
+
height: 20px;
|
|
1484
|
+
box-sizing: border-box;
|
|
1485
|
+
|
|
1486
|
+
font-family: var(--wm-font-sans);
|
|
1487
|
+
font-size: 11px;
|
|
1488
|
+
font-weight: 500;
|
|
1489
|
+
font-style: normal;
|
|
1490
|
+
line-height: 1;
|
|
1491
|
+
white-space: nowrap;
|
|
1492
|
+
|
|
1493
|
+
color: var(--wm-text-secondary);
|
|
1494
|
+
background: var(--wm-bg-sunken);
|
|
1495
|
+
|
|
1496
|
+
/* Key face: uniform border on top/sides, heavier on bottom for depth */
|
|
1497
|
+
border: 1px solid var(--wm-border-input);
|
|
1498
|
+
border-bottom-width: 2px;
|
|
1499
|
+
border-radius: var(--wm-radius-xs);
|
|
1500
|
+
|
|
1501
|
+
user-select: none;
|
|
1502
|
+
pointer-events: none;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
/* ----------------------------------------------------------------
|
|
1507
|
+
Combo wrapper — auto-injects '+' between adjacent .wm-kbd
|
|
1508
|
+
----------------------------------------------------------------
|
|
1509
|
+
Each .wm-kbd after the first gets a left margin to create room
|
|
1510
|
+
for the generated '+' connector, which is positioned absolutely
|
|
1511
|
+
in the gap between the two keys.
|
|
1512
|
+
---------------------------------------------------------------- */
|
|
1513
|
+
|
|
1514
|
+
.wm-kbd-combo {
|
|
1515
|
+
display: inline-flex;
|
|
1516
|
+
align-items: center;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.wm-kbd-combo > .wm-kbd + .wm-kbd {
|
|
1520
|
+
position: relative;
|
|
1521
|
+
margin-left: 14px;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
.wm-kbd-combo > .wm-kbd + .wm-kbd::before {
|
|
1525
|
+
content: '+';
|
|
1526
|
+
position: absolute;
|
|
1527
|
+
/* sit in the 14px gap: 4px from the right edge of this key's left edge */
|
|
1528
|
+
right: calc(100% + 4px);
|
|
1529
|
+
top: 50%;
|
|
1530
|
+
transform: translateY(-50%);
|
|
1531
|
+
font-size: 9px;
|
|
1532
|
+
font-weight: 400;
|
|
1533
|
+
line-height: 1;
|
|
1534
|
+
font-family: var(--wm-font-sans);
|
|
1535
|
+
color: var(--wm-text-muted);
|
|
1536
|
+
pointer-events: none;
|
|
1537
|
+
}
|