@webority/theme 0.34.0 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -19
- package/dist/purge-safelist.json +16 -0
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_wui-components.scss +1 -0
- package/scss/wui/_avatar-picker.scss +116 -0
- package/scss/wui/_image-crop.scss +70 -1
- package/scss/wui/_kanban.scss +81 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "Webority shared design system — Bootstrap 5.3 SCSS overrides, --wui-* tokens, and shared component CSS. The single source of brand chrome for React portals and Razor sites.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ── Avatar picker ──
|
|
2
|
+
// AppAvatarPicker / <app-avatar-picker>: a profile photo (or its placeholder) beside the actions
|
|
3
|
+
// that change it. The crop dialog it opens is <wui-image-crop> (_image-crop.scss). The diameter
|
|
4
|
+
// comes from --wui-avatar-size on .wui-avatar-picker, which both wrappers set from `size`.
|
|
5
|
+
// Every colour is a theme token, so dark mode follows the token flip.
|
|
6
|
+
//
|
|
7
|
+
// Part of the wui-components layer, see _wui-components.scss for load order.
|
|
8
|
+
|
|
9
|
+
.wui-avatar-picker {
|
|
10
|
+
// The md diameter; both wrappers set their own size inline, which wins.
|
|
11
|
+
--wui-avatar-size: 96px;
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 16px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.wui-avatar-picker-face {
|
|
19
|
+
position: relative;
|
|
20
|
+
display: grid;
|
|
21
|
+
flex: none;
|
|
22
|
+
place-items: center;
|
|
23
|
+
width: var(--wui-avatar-size);
|
|
24
|
+
height: var(--wui-avatar-size);
|
|
25
|
+
padding: 0;
|
|
26
|
+
border: 1px solid var(--color-border);
|
|
27
|
+
border-radius: 50%;
|
|
28
|
+
background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface));
|
|
29
|
+
color: var(--color-primary);
|
|
30
|
+
font: inherit;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
.wui-avatar-picker-face:disabled {
|
|
34
|
+
cursor: default;
|
|
35
|
+
}
|
|
36
|
+
.wui-avatar-picker-face[aria-busy="true"] {
|
|
37
|
+
cursor: progress;
|
|
38
|
+
}
|
|
39
|
+
.wui-avatar-picker-face:focus-visible {
|
|
40
|
+
outline: 2px solid var(--color-primary);
|
|
41
|
+
outline-offset: 3px;
|
|
42
|
+
}
|
|
43
|
+
// A file held over the photo: the ring says it can be dropped there.
|
|
44
|
+
.wui-avatar-picker.is-dragging .wui-avatar-picker-face {
|
|
45
|
+
outline: 2px dashed var(--color-primary);
|
|
46
|
+
outline-offset: 3px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.wui-avatar-picker-img {
|
|
50
|
+
width: 100%;
|
|
51
|
+
height: 100%;
|
|
52
|
+
border-radius: 50%;
|
|
53
|
+
object-fit: cover;
|
|
54
|
+
}
|
|
55
|
+
.wui-avatar-picker-placeholder {
|
|
56
|
+
display: grid;
|
|
57
|
+
place-items: center;
|
|
58
|
+
font-size: calc(var(--wui-avatar-size) * 0.36);
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
letter-spacing: -0.02em;
|
|
61
|
+
line-height: 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The camera mark on the ring's edge, so the photo reads as something you can change.
|
|
65
|
+
.wui-avatar-picker-badge {
|
|
66
|
+
position: absolute;
|
|
67
|
+
right: 0;
|
|
68
|
+
bottom: 0;
|
|
69
|
+
display: grid;
|
|
70
|
+
place-items: center;
|
|
71
|
+
width: calc(max(28px, var(--wui-avatar-size) * 0.3));
|
|
72
|
+
height: calc(max(28px, var(--wui-avatar-size) * 0.3));
|
|
73
|
+
border: 2px solid var(--color-surface);
|
|
74
|
+
border-radius: 50%;
|
|
75
|
+
background: var(--color-primary);
|
|
76
|
+
color: var(--color-primary-text);
|
|
77
|
+
}
|
|
78
|
+
.wui-avatar-picker-face:disabled:not([aria-busy="true"]) .wui-avatar-picker-badge {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.wui-avatar-picker-actions {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-wrap: wrap;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
}
|
|
88
|
+
.wui-avatar-picker-actions .btn {
|
|
89
|
+
display: inline-flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 6px;
|
|
92
|
+
}
|
|
93
|
+
.wui-avatar-picker-actions [hidden] {
|
|
94
|
+
display: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// "Take photo" opens the front camera, which only a phone or tablet has: it shows on touch screens.
|
|
98
|
+
.wui-avatar-picker-actions .wui-avatar-picker-camera {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
@media (pointer: coarse) {
|
|
102
|
+
.wui-avatar-picker-actions .wui-avatar-picker-camera:not([hidden]) {
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
}
|
|
105
|
+
.wui-avatar-picker-actions .btn {
|
|
106
|
+
min-height: 44px;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// On a phone the actions sit under the photo, full width, so each is an easy target.
|
|
111
|
+
@media (max-width: 575.98px) {
|
|
112
|
+
.wui-avatar-picker {
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
align-items: flex-start;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ── Image crop ──
|
|
2
|
-
// <wui-image-crop>: the crop surface AppLogoPicker /
|
|
2
|
+
// <wui-image-crop>: the crop surface AppLogoPicker / AppAvatarPicker (and their Razor tags) show in a dialog before
|
|
3
3
|
// upload. The image is fitted into the stage; the frame is positioned by the element (inline
|
|
4
4
|
// left/top/width/height) and dims everything outside it with a very large box-shadow, which the
|
|
5
5
|
// stage clips. Every colour is a theme token, so dark mode follows the token flip.
|
|
@@ -33,6 +33,11 @@ wui-image-crop {
|
|
|
33
33
|
position: absolute;
|
|
34
34
|
max-width: none;
|
|
35
35
|
pointer-events: none;
|
|
36
|
+
// A phone photo carries its orientation in EXIF; pinned here so a page rule cannot turn it off,
|
|
37
|
+
// because the element and the canvas both rely on the browser drawing it the right way up.
|
|
38
|
+
image-orientation: from-image;
|
|
39
|
+
// The element turns the image about its centre for rotate().
|
|
40
|
+
transform-origin: 50% 50%;
|
|
36
41
|
}
|
|
37
42
|
.wui-crop-img[hidden],
|
|
38
43
|
.wui-crop-frame[hidden] {
|
|
@@ -144,3 +149,67 @@ wui-image-crop {
|
|
|
144
149
|
outline: 2px solid var(--color-primary);
|
|
145
150
|
outline-offset: 2px;
|
|
146
151
|
}
|
|
152
|
+
|
|
153
|
+
// Rotate and Reset (the rotatable attribute), on their own row so the zoom slider keeps its width
|
|
154
|
+
// on a phone.
|
|
155
|
+
.wui-crop-tools {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
gap: 8px;
|
|
160
|
+
margin-top: 12px;
|
|
161
|
+
}
|
|
162
|
+
.wui-crop-tools[hidden],
|
|
163
|
+
.wui-crop-previews[hidden] {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
166
|
+
.wui-crop-toolbtn {
|
|
167
|
+
display: inline-flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
min-height: 36px;
|
|
171
|
+
padding: 0 14px;
|
|
172
|
+
border: 1px solid var(--color-border);
|
|
173
|
+
border-radius: 999px;
|
|
174
|
+
background: var(--color-surface);
|
|
175
|
+
color: var(--color-text);
|
|
176
|
+
font: inherit;
|
|
177
|
+
font-size: 0.875rem;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
.wui-crop-toolbtn:hover {
|
|
181
|
+
border-color: var(--color-border-strong);
|
|
182
|
+
}
|
|
183
|
+
.wui-crop-toolbtn:focus-visible {
|
|
184
|
+
outline: 2px solid var(--color-primary);
|
|
185
|
+
outline-offset: 2px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Live copies of the crop (the previews attribute), drawn by the element on a canvas each.
|
|
189
|
+
.wui-crop-previews {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
gap: 16px;
|
|
194
|
+
margin-top: 16px;
|
|
195
|
+
}
|
|
196
|
+
.wui-crop-preview {
|
|
197
|
+
flex: none;
|
|
198
|
+
border-radius: var(--radius);
|
|
199
|
+
background: var(--color-bg-muted);
|
|
200
|
+
box-shadow: 0 0 0 1px var(--color-border);
|
|
201
|
+
}
|
|
202
|
+
.wui-crop-previews--round .wui-crop-preview {
|
|
203
|
+
border-radius: 50%;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// A finger needs a 44px target; a mouse keeps the compact controls.
|
|
207
|
+
@media (pointer: coarse) {
|
|
208
|
+
.wui-crop-zoombtn {
|
|
209
|
+
width: 44px;
|
|
210
|
+
height: 44px;
|
|
211
|
+
}
|
|
212
|
+
.wui-crop-toolbtn {
|
|
213
|
+
min-height: 44px;
|
|
214
|
+
}
|
|
215
|
+
}
|
package/scss/wui/_kanban.scss
CHANGED
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
min-width: 0;
|
|
54
54
|
max-width: 100%;
|
|
55
55
|
min-height: var(--wui-kanban-column-min-height);
|
|
56
|
-
|
|
56
|
+
// A lane is the neutral grey-blue in every brand: a brand layer retints --color-bg-muted (Signal
|
|
57
|
+
// makes it violet), but leaves --bs-tertiary-bg alone. Dark mode maps it to --wui-dark-bg-muted.
|
|
58
|
+
background: var(--bs-tertiary-bg);
|
|
57
59
|
border: 1px solid var(--color-border);
|
|
58
60
|
border-radius: var(--wui-radius-lg);
|
|
59
61
|
transition:
|
|
@@ -132,36 +134,63 @@
|
|
|
132
134
|
display: none;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
.
|
|
137
|
+
// The page's own controls at the right of the column head (a "+" that adds a card, say). They sit
|
|
138
|
+
// before the collapse button, which then gives up its auto margin.
|
|
139
|
+
.wui-kanban-actions {
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 4px;
|
|
143
|
+
flex: none;
|
|
144
|
+
margin-inline-start: auto;
|
|
145
|
+
}
|
|
146
|
+
.wui-kanban-actions + .wui-kanban-collapse {
|
|
147
|
+
margin-inline-start: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Shown only on a collapsed column that refuses the carried card (see the collapsed rail below).
|
|
151
|
+
.wui-kanban-lock {
|
|
152
|
+
display: none;
|
|
153
|
+
flex: none;
|
|
154
|
+
color: var(--color-text-muted);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// .wui-kanban-action is the icon button a page puts in the actions slot; it matches the toggle.
|
|
158
|
+
.wui-kanban-collapse,
|
|
159
|
+
.wui-kanban-action {
|
|
136
160
|
display: inline-grid;
|
|
137
161
|
place-items: center;
|
|
138
162
|
flex: none;
|
|
139
163
|
width: 32px;
|
|
140
164
|
height: 32px;
|
|
141
|
-
margin-inline-start: auto;
|
|
142
165
|
padding: 0;
|
|
143
166
|
border: 0;
|
|
144
167
|
border-radius: var(--wui-radius);
|
|
145
168
|
background: transparent;
|
|
146
169
|
color: var(--color-text-muted);
|
|
147
170
|
cursor: pointer;
|
|
171
|
+
}
|
|
172
|
+
.wui-kanban-collapse {
|
|
173
|
+
margin-inline-start: auto;
|
|
148
174
|
|
|
149
175
|
svg {
|
|
150
176
|
transition: transform 160ms var(--wui-ease-ui);
|
|
151
177
|
}
|
|
152
178
|
}
|
|
153
179
|
// The column itself is the muted fill, so hover lifts to the surface.
|
|
154
|
-
.wui-kanban-collapse:hover
|
|
180
|
+
.wui-kanban-collapse:hover,
|
|
181
|
+
.wui-kanban-action:hover {
|
|
155
182
|
background: var(--color-surface);
|
|
156
183
|
color: var(--color-text);
|
|
157
184
|
}
|
|
158
185
|
.wui-kanban-collapse:focus-visible,
|
|
186
|
+
.wui-kanban-action:focus-visible,
|
|
159
187
|
.wui-kanban-switch:focus-visible {
|
|
160
188
|
outline: 2px solid var(--color-primary);
|
|
161
189
|
outline-offset: 2px;
|
|
162
190
|
}
|
|
163
191
|
@media (pointer: coarse) {
|
|
164
|
-
.wui-kanban-collapse
|
|
192
|
+
.wui-kanban-collapse,
|
|
193
|
+
.wui-kanban-action {
|
|
165
194
|
width: 44px;
|
|
166
195
|
height: 44px;
|
|
167
196
|
}
|
|
@@ -224,6 +253,19 @@
|
|
|
224
253
|
.wui-kanban-card[data-wui-overflow] {
|
|
225
254
|
display: none;
|
|
226
255
|
}
|
|
256
|
+
// A card's title, for the product's card content: two lines at every width, phones included, so
|
|
257
|
+
// one long title never stretches a card past its neighbours.
|
|
258
|
+
.wui-kanban-card-title {
|
|
259
|
+
display: -webkit-box;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
-webkit-line-clamp: 2;
|
|
262
|
+
line-clamp: 2;
|
|
263
|
+
-webkit-box-orient: vertical;
|
|
264
|
+
overflow-wrap: anywhere;
|
|
265
|
+
font-size: 0.875rem;
|
|
266
|
+
font-weight: 600;
|
|
267
|
+
line-height: 1.35;
|
|
268
|
+
}
|
|
227
269
|
|
|
228
270
|
// "Show N more" under a capped column's cards; "Show fewer" once it is open.
|
|
229
271
|
.wui-kanban-more {
|
|
@@ -373,7 +415,9 @@
|
|
|
373
415
|
border: 1px dashed var(--color-border-strong);
|
|
374
416
|
}
|
|
375
417
|
|
|
376
|
-
// ── Collapsed column: a narrow
|
|
418
|
+
// ── Collapsed column: a narrow rail that still takes drops ──
|
|
419
|
+
// Top to bottom: the count, a lock while the carried card cannot land here, the title on its side,
|
|
420
|
+
// then the expand button.
|
|
377
421
|
.wui-kanban-column[data-collapsed] {
|
|
378
422
|
flex: 0 0 var(--wui-kanban-collapsed-width);
|
|
379
423
|
align-self: stretch;
|
|
@@ -381,20 +425,33 @@
|
|
|
381
425
|
.wui-kanban-head {
|
|
382
426
|
flex-direction: column;
|
|
383
427
|
flex-wrap: nowrap;
|
|
384
|
-
|
|
428
|
+
gap: 12px;
|
|
429
|
+
padding: 14px 6px;
|
|
430
|
+
}
|
|
431
|
+
.wui-kanban-count {
|
|
432
|
+
order: 1;
|
|
433
|
+
}
|
|
434
|
+
.wui-kanban-lock {
|
|
435
|
+
order: 2;
|
|
436
|
+
}
|
|
437
|
+
&[data-wui-drop="refused"] .wui-kanban-lock {
|
|
438
|
+
display: inline-flex;
|
|
385
439
|
}
|
|
386
440
|
.wui-kanban-title {
|
|
441
|
+
order: 3;
|
|
387
442
|
writing-mode: vertical-rl;
|
|
388
443
|
flex: none;
|
|
389
444
|
white-space: nowrap;
|
|
390
445
|
}
|
|
391
446
|
.wui-kanban-collapse {
|
|
392
|
-
order:
|
|
447
|
+
order: 4;
|
|
393
448
|
margin-inline-start: 0;
|
|
394
449
|
}
|
|
395
450
|
.wui-kanban-collapse svg {
|
|
396
451
|
transform: rotate(180deg);
|
|
397
452
|
}
|
|
453
|
+
.wui-kanban-dot,
|
|
454
|
+
.wui-kanban-actions,
|
|
398
455
|
.wui-kanban-summary,
|
|
399
456
|
.wui-kanban-body,
|
|
400
457
|
.wui-kanban-more,
|
|
@@ -487,13 +544,25 @@
|
|
|
487
544
|
.wui-kanban-head {
|
|
488
545
|
flex-direction: row;
|
|
489
546
|
flex-wrap: wrap;
|
|
547
|
+
gap: 6px 8px;
|
|
490
548
|
padding: 12px 12px 8px;
|
|
491
549
|
}
|
|
550
|
+
.wui-kanban-count,
|
|
551
|
+
.wui-kanban-lock,
|
|
552
|
+
.wui-kanban-title {
|
|
553
|
+
order: 0;
|
|
554
|
+
}
|
|
492
555
|
.wui-kanban-title {
|
|
493
556
|
writing-mode: horizontal-tb;
|
|
494
557
|
flex: 0 1 auto;
|
|
495
558
|
white-space: normal;
|
|
496
559
|
}
|
|
560
|
+
.wui-kanban-dot {
|
|
561
|
+
display: block;
|
|
562
|
+
}
|
|
563
|
+
.wui-kanban-actions {
|
|
564
|
+
display: inline-flex;
|
|
565
|
+
}
|
|
497
566
|
.wui-kanban-collapse {
|
|
498
567
|
display: none;
|
|
499
568
|
}
|
|
@@ -516,6 +585,10 @@
|
|
|
516
585
|
.wui-kanban-summary:empty {
|
|
517
586
|
display: none;
|
|
518
587
|
}
|
|
588
|
+
// The drop note already says why; the rail's lock is a wide-screen cue.
|
|
589
|
+
.wui-kanban-column[data-collapsed][data-wui-drop="refused"] .wui-kanban-lock {
|
|
590
|
+
display: none;
|
|
591
|
+
}
|
|
519
592
|
}
|
|
520
593
|
|
|
521
594
|
@media (prefers-reduced-motion: reduce) {
|