@webority/theme 0.24.0 → 0.26.0
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.md +16 -2
- package/dist/purge-safelist.json +199 -2
- package/dist/webority-brand-signal.css +1 -0
- package/dist/webority-theme.css +2 -2
- package/fonts/sans/OFL.txt +92 -0
- package/fonts/sans/WeboritySans-Bold.woff2 +0 -0
- package/fonts/sans/WeboritySans-BoldItalic.woff2 +0 -0
- package/fonts/sans/WeboritySans-Medium.woff2 +0 -0
- package/fonts/sans/WeboritySans-Regular.woff2 +0 -0
- package/fonts/sans/WeboritySans-RegularItalic.woff2 +0 -0
- package/fonts/sans/WeboritySans-SemiBold.woff2 +0 -0
- package/fonts/serif/WeboritySerif-Bold.woff2 +0 -0
- package/fonts/serif/WeboritySerif-BoldItalic.woff2 +0 -0
- package/fonts/serif/WeboritySerif-DemiBold.woff2 +0 -0
- package/fonts/serif/WeboritySerif-ExtraBold.woff2 +0 -0
- package/fonts/serif/WeboritySerif-Medium.woff2 +0 -0
- package/fonts/serif/WeboritySerif-Regular.woff2 +0 -0
- package/fonts/serif/WeboritySerif-RegularItalic.woff2 +0 -0
- package/index.scss +2 -1
- package/package.json +4 -2
- package/scss/_brand-signal.scss +202 -0
- package/scss/_fonts.scss +96 -0
- package/scss/_tokens.scss +55 -2
- package/scss/_variables.scss +2 -1
- package/scss/_wui-aliases.scss +21 -0
- package/scss/_wui-components.scss +12 -0
- package/scss/wui/_calendar.scss +256 -0
- package/scss/wui/_chart-card.scss +86 -0
- package/scss/wui/_chart.scss +524 -0
- package/scss/wui/_context-menu.scss +53 -0
- package/scss/wui/_data.scss +8 -6
- package/scss/wui/_display.scss +1 -1
- package/scss/wui/_image-crop.scss +146 -0
- package/scss/wui/_kanban.scss +434 -0
- package/scss/wui/_modal-remote.scss +55 -0
- package/scss/wui/_overlays.scss +39 -0
- package/scss/wui/_rich-text.scss +281 -0
- package/scss/wui/_shell-parts.scss +503 -0
- package/scss/wui/_shell.scss +11 -0
- package/scss/wui/_sortable.scss +135 -0
- package/scss/wui/_table-server.scss +40 -0
- package/scss/wui/_tree.scss +124 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
// Shell parts: notification bell, topbar search, phone tab bar, user menu.
|
|
2
|
+
//
|
|
3
|
+
// Part of the wui-components layer; imported after its base partial (see _wui-components.scss).
|
|
4
|
+
// The same markup comes from React AppShell (userMenu / notifications / search / tab items) and
|
|
5
|
+
// the Razor <app-shell-*> helpers. Every class here is wui-*; the frame classes it hangs off
|
|
6
|
+
// (.app-shell, .app-topbar-start, .app-content, .app-footer) are the shell's own.
|
|
7
|
+
//
|
|
8
|
+
// Phone floor: every control here is at least 44px tall below 768px, and nothing that opens is
|
|
9
|
+
// wider than the viewport less 24px.
|
|
10
|
+
|
|
11
|
+
.app-shell {
|
|
12
|
+
// One height the tab bar, the content's bottom padding and the footer's offset agree on.
|
|
13
|
+
--wui-tabbar-h: 60px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── Phone tab bar ───────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// Four destinations plus More (or five), fixed to the bottom below md. Above md the sidebar or
|
|
18
|
+
// drawer carries the nav, so the bar is not rendered visible at all.
|
|
19
|
+
.wui-tabbar {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
@include media-breakpoint-down(md) {
|
|
23
|
+
.wui-tabbar {
|
|
24
|
+
position: fixed;
|
|
25
|
+
right: 0;
|
|
26
|
+
bottom: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
z-index: var(--wui-z-fixed);
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-auto-columns: 1fr;
|
|
31
|
+
grid-auto-flow: column;
|
|
32
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
33
|
+
border-top: 1px solid var(--color-border);
|
|
34
|
+
background: var(--color-surface);
|
|
35
|
+
}
|
|
36
|
+
// The last content must scroll clear of the bar, and a footer sits on top of it.
|
|
37
|
+
.app-shell:has(> .wui-tabbar) .app-content {
|
|
38
|
+
padding-bottom: calc(var(--wui-tabbar-h) + 28px + env(safe-area-inset-bottom));
|
|
39
|
+
}
|
|
40
|
+
.app-shell:has(> .wui-tabbar) .app-footer {
|
|
41
|
+
bottom: calc(var(--wui-tabbar-h) + env(safe-area-inset-bottom));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
.wui-tabbar-item {
|
|
45
|
+
position: relative;
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
gap: 2px;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
min-height: var(--wui-tabbar-h);
|
|
53
|
+
padding: 6px 4px;
|
|
54
|
+
border: 0;
|
|
55
|
+
background: transparent;
|
|
56
|
+
font: inherit;
|
|
57
|
+
font-size: 12px;
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
color: var(--color-text-muted);
|
|
60
|
+
text-decoration: none;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
.wui-tabbar-item:hover {
|
|
64
|
+
color: var(--color-text);
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
}
|
|
67
|
+
.wui-tabbar-item.active {
|
|
68
|
+
color: var(--color-primary);
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
}
|
|
71
|
+
.wui-tabbar-item:focus-visible {
|
|
72
|
+
outline: 2px solid var(--color-primary);
|
|
73
|
+
outline-offset: -4px;
|
|
74
|
+
border-radius: var(--radius-lg);
|
|
75
|
+
}
|
|
76
|
+
.wui-tabbar-icon {
|
|
77
|
+
display: inline-grid;
|
|
78
|
+
place-items: center;
|
|
79
|
+
}
|
|
80
|
+
.wui-tabbar-label {
|
|
81
|
+
max-width: 100%;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
}
|
|
86
|
+
// A count over the icon. Neutral by default, toned like the nav badge it mirrors (-text on -soft,
|
|
87
|
+
// the 4.5:1 pairing the nav badge tones use).
|
|
88
|
+
.wui-tabbar-badge {
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: 6px;
|
|
91
|
+
left: calc(50% + 6px);
|
|
92
|
+
min-width: 18px;
|
|
93
|
+
height: 18px;
|
|
94
|
+
padding: 0 5px;
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
background: var(--color-bg-muted);
|
|
97
|
+
color: var(--color-text-muted);
|
|
98
|
+
font-size: 11px;
|
|
99
|
+
font-weight: 700;
|
|
100
|
+
line-height: 18px;
|
|
101
|
+
text-align: center;
|
|
102
|
+
}
|
|
103
|
+
.wui-tabbar-badge.tone-danger {
|
|
104
|
+
background: var(--color-danger-soft);
|
|
105
|
+
color: var(--color-danger-text);
|
|
106
|
+
}
|
|
107
|
+
.wui-tabbar-badge.tone-success {
|
|
108
|
+
background: var(--color-success-soft);
|
|
109
|
+
color: var(--color-success-text);
|
|
110
|
+
}
|
|
111
|
+
.wui-tabbar-badge.tone-warning {
|
|
112
|
+
background: var(--color-warning-soft);
|
|
113
|
+
color: var(--color-warning-text);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Topbar menus: shared card ───────────────────────────────────────────────────────────────
|
|
117
|
+
// Both menus open with Bootstrap's static display (Razor data-bs-display="static"; React sets
|
|
118
|
+
// data-bs-popper="static" while open), so placement is plain CSS: under the trigger, end-aligned.
|
|
119
|
+
.wui-usermenu-menu,
|
|
120
|
+
.wui-bell-menu {
|
|
121
|
+
top: calc(100% + 8px);
|
|
122
|
+
right: 0;
|
|
123
|
+
left: auto;
|
|
124
|
+
max-width: calc(100vw - 24px);
|
|
125
|
+
border: 1px solid var(--color-border);
|
|
126
|
+
border-radius: var(--radius-lg);
|
|
127
|
+
background: var(--color-surface);
|
|
128
|
+
box-shadow: var(--shadow-overlay);
|
|
129
|
+
}
|
|
130
|
+
.wui-usermenu-menu .dropdown-item {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: 10px;
|
|
134
|
+
min-height: 44px;
|
|
135
|
+
border-radius: var(--radius-sm);
|
|
136
|
+
color: var(--color-text-body);
|
|
137
|
+
white-space: normal;
|
|
138
|
+
}
|
|
139
|
+
.wui-usermenu-menu .dropdown-item:hover {
|
|
140
|
+
background: var(--color-bg-muted);
|
|
141
|
+
color: var(--color-text);
|
|
142
|
+
}
|
|
143
|
+
.wui-usermenu-menu .dropdown-item.danger {
|
|
144
|
+
color: var(--color-danger-text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── User menu ───────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
.wui-usermenu-trigger {
|
|
149
|
+
display: inline-flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
gap: 10px;
|
|
152
|
+
min-height: 44px;
|
|
153
|
+
padding: 4px 8px 4px 4px;
|
|
154
|
+
border: 0;
|
|
155
|
+
border-radius: 999px;
|
|
156
|
+
background: transparent;
|
|
157
|
+
color: var(--color-text);
|
|
158
|
+
font: inherit;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
}
|
|
161
|
+
.wui-usermenu-trigger:hover {
|
|
162
|
+
background: var(--color-bg-muted);
|
|
163
|
+
}
|
|
164
|
+
.wui-usermenu-trigger:focus-visible {
|
|
165
|
+
outline: 2px solid var(--color-primary);
|
|
166
|
+
outline-offset: 2px;
|
|
167
|
+
}
|
|
168
|
+
.wui-usermenu-text {
|
|
169
|
+
display: flex;
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
line-height: 1.2;
|
|
172
|
+
text-align: left;
|
|
173
|
+
}
|
|
174
|
+
.wui-usermenu-name {
|
|
175
|
+
font-size: 14px;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
color: var(--color-text);
|
|
178
|
+
}
|
|
179
|
+
.wui-usermenu-role {
|
|
180
|
+
font-size: 12px;
|
|
181
|
+
color: var(--color-text-muted);
|
|
182
|
+
}
|
|
183
|
+
.wui-usermenu-caret {
|
|
184
|
+
color: var(--color-text-muted);
|
|
185
|
+
}
|
|
186
|
+
// Below xl the name and role give way to the avatar alone.
|
|
187
|
+
@include media-breakpoint-down(xl) {
|
|
188
|
+
.wui-usermenu-text,
|
|
189
|
+
.wui-usermenu-caret {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
// The avatar alone is narrower than a touch target.
|
|
193
|
+
.wui-usermenu-trigger {
|
|
194
|
+
min-width: 44px;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
padding: 4px;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
.wui-usermenu-menu {
|
|
200
|
+
min-width: 260px;
|
|
201
|
+
padding: 8px;
|
|
202
|
+
}
|
|
203
|
+
.wui-usermenu-head {
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 12px;
|
|
207
|
+
margin-bottom: 6px;
|
|
208
|
+
padding: 8px 8px 12px;
|
|
209
|
+
border-bottom: 1px solid var(--color-border);
|
|
210
|
+
}
|
|
211
|
+
.wui-usermenu-who {
|
|
212
|
+
min-width: 0;
|
|
213
|
+
overflow-wrap: anywhere;
|
|
214
|
+
}
|
|
215
|
+
.wui-usermenu-logout {
|
|
216
|
+
margin: 0;
|
|
217
|
+
}
|
|
218
|
+
.wui-usermenu-logout .dropdown-item {
|
|
219
|
+
width: 100%;
|
|
220
|
+
border: 0;
|
|
221
|
+
background: transparent;
|
|
222
|
+
text-align: left;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Notification bell ───────────────────────────────────────────────────────────────────────
|
|
226
|
+
.wui-bell-trigger {
|
|
227
|
+
position: relative;
|
|
228
|
+
}
|
|
229
|
+
// White on the strong danger fill, the avatar badge's pairing. Ringed in the surface colour so it
|
|
230
|
+
// reads as sitting on the bell rather than touching it.
|
|
231
|
+
.wui-bell-count {
|
|
232
|
+
position: absolute;
|
|
233
|
+
top: 2px;
|
|
234
|
+
right: 0;
|
|
235
|
+
box-sizing: content-box;
|
|
236
|
+
min-width: 18px;
|
|
237
|
+
height: 18px;
|
|
238
|
+
padding: 0 5px;
|
|
239
|
+
border: 2px solid var(--color-surface);
|
|
240
|
+
border-radius: 999px;
|
|
241
|
+
background: var(--color-danger-strong);
|
|
242
|
+
color: var(--wui-bg-light);
|
|
243
|
+
font-size: 11px;
|
|
244
|
+
font-weight: 700;
|
|
245
|
+
line-height: 18px;
|
|
246
|
+
text-align: center;
|
|
247
|
+
}
|
|
248
|
+
.wui-bell-count[hidden] {
|
|
249
|
+
display: none;
|
|
250
|
+
}
|
|
251
|
+
.wui-bell-menu {
|
|
252
|
+
width: 360px;
|
|
253
|
+
padding: 0;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
}
|
|
256
|
+
.wui-bell-head {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
justify-content: space-between;
|
|
260
|
+
gap: 12px;
|
|
261
|
+
padding: 10px 16px;
|
|
262
|
+
border-bottom: 1px solid var(--color-border);
|
|
263
|
+
}
|
|
264
|
+
.wui-bell-title {
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
color: var(--color-text);
|
|
267
|
+
}
|
|
268
|
+
.wui-bell-markall {
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
min-height: 44px;
|
|
272
|
+
padding: 0 4px;
|
|
273
|
+
border: 0;
|
|
274
|
+
background: none;
|
|
275
|
+
color: var(--color-primary);
|
|
276
|
+
font: inherit;
|
|
277
|
+
font-size: 14px;
|
|
278
|
+
font-weight: 500;
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
}
|
|
281
|
+
.wui-bell-markall:hover {
|
|
282
|
+
text-decoration: underline;
|
|
283
|
+
}
|
|
284
|
+
.wui-bell-markall:disabled {
|
|
285
|
+
opacity: 0.6;
|
|
286
|
+
cursor: default;
|
|
287
|
+
}
|
|
288
|
+
.wui-bell-list {
|
|
289
|
+
max-height: min(420px, 60vh);
|
|
290
|
+
overflow-y: auto;
|
|
291
|
+
}
|
|
292
|
+
.wui-bell-list[aria-busy="true"] {
|
|
293
|
+
opacity: 0.7;
|
|
294
|
+
}
|
|
295
|
+
.wui-bell-item {
|
|
296
|
+
display: flex;
|
|
297
|
+
gap: 12px;
|
|
298
|
+
min-height: 44px;
|
|
299
|
+
padding: 12px 16px;
|
|
300
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
301
|
+
color: var(--color-text-body);
|
|
302
|
+
text-decoration: none;
|
|
303
|
+
}
|
|
304
|
+
a.wui-bell-item:hover {
|
|
305
|
+
background: var(--color-bg-muted);
|
|
306
|
+
text-decoration: none;
|
|
307
|
+
}
|
|
308
|
+
.wui-bell-item:last-child {
|
|
309
|
+
border-bottom: 0;
|
|
310
|
+
}
|
|
311
|
+
.wui-bell-dot {
|
|
312
|
+
flex: none;
|
|
313
|
+
width: 8px;
|
|
314
|
+
height: 8px;
|
|
315
|
+
margin-top: 6px;
|
|
316
|
+
border-radius: 50%;
|
|
317
|
+
background: var(--color-primary);
|
|
318
|
+
}
|
|
319
|
+
.wui-bell-item-read .wui-bell-dot {
|
|
320
|
+
visibility: hidden;
|
|
321
|
+
}
|
|
322
|
+
.wui-bell-icon {
|
|
323
|
+
flex: none;
|
|
324
|
+
color: var(--color-text-subtle);
|
|
325
|
+
}
|
|
326
|
+
.wui-bell-body {
|
|
327
|
+
display: flex;
|
|
328
|
+
flex-direction: column;
|
|
329
|
+
min-width: 0;
|
|
330
|
+
}
|
|
331
|
+
.wui-bell-item-title {
|
|
332
|
+
font-size: 14px;
|
|
333
|
+
font-weight: 500;
|
|
334
|
+
color: var(--color-text);
|
|
335
|
+
overflow-wrap: anywhere;
|
|
336
|
+
}
|
|
337
|
+
.wui-bell-item-read .wui-bell-item-title {
|
|
338
|
+
font-weight: 400;
|
|
339
|
+
}
|
|
340
|
+
.wui-bell-when {
|
|
341
|
+
font-size: 12px;
|
|
342
|
+
color: var(--color-text-muted);
|
|
343
|
+
}
|
|
344
|
+
.wui-bell-state {
|
|
345
|
+
padding: 20px 16px;
|
|
346
|
+
font-size: 14px;
|
|
347
|
+
color: var(--color-text-muted);
|
|
348
|
+
text-align: center;
|
|
349
|
+
}
|
|
350
|
+
.wui-bell-foot {
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
min-height: 44px;
|
|
355
|
+
border-top: 1px solid var(--color-border);
|
|
356
|
+
color: var(--color-primary);
|
|
357
|
+
font-size: 14px;
|
|
358
|
+
font-weight: 500;
|
|
359
|
+
text-decoration: none;
|
|
360
|
+
}
|
|
361
|
+
.wui-bell-foot:hover {
|
|
362
|
+
background: var(--color-bg-muted);
|
|
363
|
+
}
|
|
364
|
+
// A 360px card would overflow a 375px phone: below md the panel is a sheet inset 12px, under the
|
|
365
|
+
// 72px topbar.
|
|
366
|
+
@include media-breakpoint-down(md) {
|
|
367
|
+
.wui-bell-menu {
|
|
368
|
+
position: fixed;
|
|
369
|
+
top: 72px;
|
|
370
|
+
right: 12px;
|
|
371
|
+
left: 12px;
|
|
372
|
+
width: auto;
|
|
373
|
+
max-width: none;
|
|
374
|
+
}
|
|
375
|
+
.wui-bell-list {
|
|
376
|
+
max-height: calc(100dvh - 72px - 12px - 120px);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ── Topbar search ───────────────────────────────────────────────────────────────────────────
|
|
381
|
+
// A field after the topbar-start content above md; an icon button that opens the field as a
|
|
382
|
+
// full-width panel under the topbar below it.
|
|
383
|
+
.app-topbar-start:has(> .wui-topsearch) {
|
|
384
|
+
flex: 1 1 auto;
|
|
385
|
+
}
|
|
386
|
+
.wui-topsearch {
|
|
387
|
+
position: relative;
|
|
388
|
+
width: min(380px, 38vw);
|
|
389
|
+
}
|
|
390
|
+
.wui-topsearch-open {
|
|
391
|
+
display: none;
|
|
392
|
+
}
|
|
393
|
+
.wui-topsearch-results {
|
|
394
|
+
position: absolute;
|
|
395
|
+
top: calc(100% + 6px);
|
|
396
|
+
right: 0;
|
|
397
|
+
left: 0;
|
|
398
|
+
z-index: var(--wui-z-dropdown);
|
|
399
|
+
max-height: min(440px, 70vh);
|
|
400
|
+
padding: 6px;
|
|
401
|
+
overflow-y: auto;
|
|
402
|
+
border: 1px solid var(--color-border);
|
|
403
|
+
border-radius: var(--radius-lg);
|
|
404
|
+
background: var(--color-surface);
|
|
405
|
+
box-shadow: var(--shadow-overlay);
|
|
406
|
+
}
|
|
407
|
+
.wui-topsearch-results[hidden] {
|
|
408
|
+
display: none;
|
|
409
|
+
}
|
|
410
|
+
.wui-topsearch-group + .wui-topsearch-group {
|
|
411
|
+
margin-top: 4px;
|
|
412
|
+
padding-top: 4px;
|
|
413
|
+
border-top: 1px solid var(--color-border-light);
|
|
414
|
+
}
|
|
415
|
+
.wui-topsearch-group-label {
|
|
416
|
+
padding: 6px 10px 4px;
|
|
417
|
+
font-size: 12px;
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
letter-spacing: 0.06em;
|
|
420
|
+
text-transform: uppercase;
|
|
421
|
+
color: var(--color-text-subtle);
|
|
422
|
+
}
|
|
423
|
+
.wui-topsearch-hit {
|
|
424
|
+
display: flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
gap: 10px;
|
|
427
|
+
min-height: 44px;
|
|
428
|
+
padding: 6px 10px;
|
|
429
|
+
border-radius: var(--radius-sm);
|
|
430
|
+
color: var(--color-text-body);
|
|
431
|
+
text-decoration: none;
|
|
432
|
+
}
|
|
433
|
+
.wui-topsearch-hit:hover,
|
|
434
|
+
.wui-topsearch-hit.is-active {
|
|
435
|
+
background: var(--color-bg-muted);
|
|
436
|
+
color: var(--color-text);
|
|
437
|
+
text-decoration: none;
|
|
438
|
+
}
|
|
439
|
+
.wui-topsearch-hit-icon {
|
|
440
|
+
flex: none;
|
|
441
|
+
display: inline-grid;
|
|
442
|
+
place-items: center;
|
|
443
|
+
color: var(--color-text-subtle);
|
|
444
|
+
}
|
|
445
|
+
.wui-topsearch-hit-text {
|
|
446
|
+
display: flex;
|
|
447
|
+
flex-direction: column;
|
|
448
|
+
min-width: 0;
|
|
449
|
+
}
|
|
450
|
+
.wui-topsearch-hit-title {
|
|
451
|
+
font-size: 14px;
|
|
452
|
+
font-weight: 500;
|
|
453
|
+
color: var(--color-text);
|
|
454
|
+
overflow-wrap: anywhere;
|
|
455
|
+
}
|
|
456
|
+
.wui-topsearch-hit-sub {
|
|
457
|
+
font-size: 12px;
|
|
458
|
+
color: var(--color-text-muted);
|
|
459
|
+
}
|
|
460
|
+
.wui-topsearch-all {
|
|
461
|
+
margin-top: 4px;
|
|
462
|
+
border-top: 1px solid var(--color-border-light);
|
|
463
|
+
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
|
|
464
|
+
}
|
|
465
|
+
.wui-topsearch-empty {
|
|
466
|
+
padding: 14px 10px;
|
|
467
|
+
font-size: 14px;
|
|
468
|
+
color: var(--color-text-muted);
|
|
469
|
+
}
|
|
470
|
+
// While suggestions load, the leading glyph dims; the field stays usable.
|
|
471
|
+
.wui-topsearch.is-loading .search-icon {
|
|
472
|
+
opacity: 0.5;
|
|
473
|
+
}
|
|
474
|
+
@include media-breakpoint-down(md) {
|
|
475
|
+
.wui-topsearch {
|
|
476
|
+
width: auto;
|
|
477
|
+
margin-left: auto;
|
|
478
|
+
}
|
|
479
|
+
.wui-topsearch-open {
|
|
480
|
+
display: inline-flex;
|
|
481
|
+
}
|
|
482
|
+
.wui-topsearch-panel {
|
|
483
|
+
display: none;
|
|
484
|
+
}
|
|
485
|
+
.wui-topsearch.is-open .wui-topsearch-panel {
|
|
486
|
+
position: fixed;
|
|
487
|
+
top: 72px;
|
|
488
|
+
right: 0;
|
|
489
|
+
left: 0;
|
|
490
|
+
z-index: var(--wui-z-dropdown);
|
|
491
|
+
display: block;
|
|
492
|
+
padding: 12px;
|
|
493
|
+
border-bottom: 1px solid var(--color-border);
|
|
494
|
+
background: var(--color-surface);
|
|
495
|
+
box-shadow: var(--shadow-overlay);
|
|
496
|
+
}
|
|
497
|
+
.wui-topsearch-results {
|
|
498
|
+
position: static;
|
|
499
|
+
max-height: calc(100dvh - 72px - 24px - 52px);
|
|
500
|
+
margin-top: 8px;
|
|
501
|
+
box-shadow: none;
|
|
502
|
+
}
|
|
503
|
+
}
|
package/scss/wui/_shell.scss
CHANGED
|
@@ -736,6 +736,17 @@
|
|
|
736
736
|
.app-shell.app-shell .app-topbar {
|
|
737
737
|
padding-inline: 16px;
|
|
738
738
|
}
|
|
739
|
+
.app-shell.app-shell .app-content {
|
|
740
|
+
padding-block: 16px 32px;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Phones: the topbar's icon controls meet the 44px touch floor (36px is a pointer size).
|
|
744
|
+
@include media-breakpoint-down(md) {
|
|
745
|
+
.app-shell.app-shell .app-topbar-menu,
|
|
746
|
+
.app-topbar .topbar-icon-btn {
|
|
747
|
+
width: 44px;
|
|
748
|
+
height: 44px;
|
|
749
|
+
}
|
|
739
750
|
}
|
|
740
751
|
// The scrim exists only while the drawer is open, and only below lg — it is
|
|
741
752
|
// rendered unconditionally and made visible here so there is no JS breakpoint check.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// ── Sortable list ──
|
|
2
|
+
// AppSortableList / <app-sortable-list> / <wui-sortable>. The element moves the real items
|
|
3
|
+
// while the person drags and shows a `.wui-sortable-ghost` copy under the pointer; everything
|
|
4
|
+
// visual lives here.
|
|
5
|
+
//
|
|
6
|
+
// Part of the wui-components layer — see _wui-components.scss for load order.
|
|
7
|
+
|
|
8
|
+
wui-sortable {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
// An empty list is still a drop target.
|
|
13
|
+
min-height: 44px;
|
|
14
|
+
}
|
|
15
|
+
wui-sortable[orientation="horizontal"] {
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wui-sortable-item {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 8px;
|
|
24
|
+
padding: 6px 12px 6px 6px;
|
|
25
|
+
background: var(--color-surface);
|
|
26
|
+
border: 1px solid var(--color-border);
|
|
27
|
+
border-radius: var(--wui-radius);
|
|
28
|
+
corner-shape: squircle;
|
|
29
|
+
color: var(--color-text);
|
|
30
|
+
// Dragging a body text selection is never what a drag on an item means.
|
|
31
|
+
-webkit-user-select: none;
|
|
32
|
+
user-select: none;
|
|
33
|
+
}
|
|
34
|
+
// A pointer drag from the body starts on the item itself; touch scrolling stays on until the
|
|
35
|
+
// press-and-hold promotes it, so only the handle opts out of the browser's panning.
|
|
36
|
+
wui-sortable:not([handle]):not([disabled]) .wui-sortable-item:not([data-disabled]) {
|
|
37
|
+
cursor: grab;
|
|
38
|
+
}
|
|
39
|
+
wui-sortable[disabled] .wui-sortable-item,
|
|
40
|
+
.wui-sortable-item[data-disabled] {
|
|
41
|
+
color: var(--color-text-muted);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.wui-sortable-handle {
|
|
45
|
+
display: grid;
|
|
46
|
+
place-items: center;
|
|
47
|
+
flex: none;
|
|
48
|
+
// 44px is the touch target; the glyph stays small inside it.
|
|
49
|
+
width: 44px;
|
|
50
|
+
height: 44px;
|
|
51
|
+
padding: 0;
|
|
52
|
+
border: none;
|
|
53
|
+
border-radius: var(--wui-radius-sm);
|
|
54
|
+
background: transparent;
|
|
55
|
+
color: var(--color-text-subtle);
|
|
56
|
+
cursor: grab;
|
|
57
|
+
touch-action: none;
|
|
58
|
+
transition:
|
|
59
|
+
background-color 160ms var(--wui-ease-ui),
|
|
60
|
+
color 160ms var(--wui-ease-ui);
|
|
61
|
+
}
|
|
62
|
+
.wui-sortable-handle:hover {
|
|
63
|
+
background: var(--color-bg-muted);
|
|
64
|
+
color: var(--color-text);
|
|
65
|
+
}
|
|
66
|
+
.wui-sortable-handle:focus-visible {
|
|
67
|
+
outline: 2px solid var(--color-primary);
|
|
68
|
+
outline-offset: 2px;
|
|
69
|
+
}
|
|
70
|
+
// Lifted by the keyboard.
|
|
71
|
+
.wui-sortable-handle[aria-pressed="true"] {
|
|
72
|
+
background: var(--color-primary-soft);
|
|
73
|
+
color: var(--color-primary);
|
|
74
|
+
}
|
|
75
|
+
.wui-sortable-handle:disabled {
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
opacity: 0.45;
|
|
78
|
+
background: transparent;
|
|
79
|
+
color: var(--color-text-subtle);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.wui-sortable-body {
|
|
83
|
+
flex: 1 1 auto;
|
|
84
|
+
// Lets long content wrap instead of widening the list past a 375px phone.
|
|
85
|
+
min-width: 0;
|
|
86
|
+
overflow-wrap: anywhere;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The real item, while its ghost is under the pointer: it marks where the drop would land.
|
|
90
|
+
.wui-sortable-item.wui-sortable-dragging {
|
|
91
|
+
background: var(--color-primary-soft);
|
|
92
|
+
border-style: dashed;
|
|
93
|
+
border-color: var(--color-primary);
|
|
94
|
+
> * {
|
|
95
|
+
opacity: 0.35;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.wui-sortable-ghost {
|
|
100
|
+
position: fixed;
|
|
101
|
+
top: 0;
|
|
102
|
+
left: 0;
|
|
103
|
+
z-index: var(--wui-z-tooltip, 1650);
|
|
104
|
+
margin: 0;
|
|
105
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
106
|
+
pointer-events: none;
|
|
107
|
+
cursor: grabbing;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// While any drag runs, the whole page shows the grabbing cursor and does not select text.
|
|
111
|
+
html.wui-sortable-active,
|
|
112
|
+
html.wui-sortable-active * {
|
|
113
|
+
cursor: grabbing !important;
|
|
114
|
+
-webkit-user-select: none;
|
|
115
|
+
user-select: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The aria-live announcement region: read by screen readers, never seen.
|
|
119
|
+
.wui-sortable-live {
|
|
120
|
+
position: absolute;
|
|
121
|
+
width: 1px;
|
|
122
|
+
height: 1px;
|
|
123
|
+
margin: -1px;
|
|
124
|
+
padding: 0;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
clip-path: inset(50%);
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
border: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
.wui-sortable-handle {
|
|
133
|
+
transition: none;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Server-paged data table: loading, empty and error states, the busy dim and the server sort affordance.
|
|
2
|
+
//
|
|
3
|
+
// Part of the wui-components layer; imported after its base partial (see _wui-components.scss).
|
|
4
|
+
//
|
|
5
|
+
// `<app-data-table src>` wraps the usual `.table-wrap` in `.wui-table-server`; the rows, pager and
|
|
6
|
+
// states are swapped in by the behaviour layer (ui-elements serverTable.js). The sortable header
|
|
7
|
+
// (`th[data-sort-key]` gets `.sortable` and the `.sort-icon`) and the empty state
|
|
8
|
+
// (`.tbl-empty-block`) reuse the client-side table rules in _data.scss unchanged.
|
|
9
|
+
|
|
10
|
+
// While a page is in flight the current rows stay in place, dimmed, so the table does not jump;
|
|
11
|
+
// they are not interactive until the new page lands.
|
|
12
|
+
.wui-table-server > .table-wrap[aria-busy="true"] {
|
|
13
|
+
cursor: progress;
|
|
14
|
+
}
|
|
15
|
+
.wui-table-server > .table-wrap[aria-busy="true"] > .table > tbody {
|
|
16
|
+
opacity: 0.55;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
transition: opacity 0.15s ease;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A failed fetch shows its message and Retry in the state block; the stale rows of the previous
|
|
22
|
+
// page are hidden rather than left looking current.
|
|
23
|
+
.wui-table-server > .table-wrap[data-wui-table-error] > .table > tbody {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
26
|
+
.wui-table-error .tbl-empty-icon {
|
|
27
|
+
color: var(--color-danger-text);
|
|
28
|
+
background: var(--color-danger-soft);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The pager region exists from the first paint; an empty list has no pager to show.
|
|
32
|
+
.wui-table-pager:empty {
|
|
33
|
+
display: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@media (prefers-reduced-motion: reduce) {
|
|
37
|
+
.wui-table-server > .table-wrap[aria-busy="true"] > .table > tbody {
|
|
38
|
+
transition: none;
|
|
39
|
+
}
|
|
40
|
+
}
|