@webority/theme 0.9.10 → 0.9.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +25 -0
- package/scss/_wui-components.scss +1 -0
- package/scss/wui/_data.scss +26 -36
- package/scss/wui/_display.scss +21 -2
- package/scss/wui/_feedback.scss +259 -0
- package/scss/wui/_navigation.scss +9 -2
- package/scss/wui/_utilities.scss +86 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.12",
|
|
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": {
|
package/scss/_components.scss
CHANGED
|
@@ -419,6 +419,31 @@
|
|
|
419
419
|
--bs-dropdown-link-active-color: var(--color-primary-hover);
|
|
420
420
|
--bs-dropdown-link-active-bg: var(--color-primary-soft);
|
|
421
421
|
box-shadow: var(--wui-shadow-overlay);
|
|
422
|
+
// A long menu (account / workspace lists) must never spill past the screen and clip its
|
|
423
|
+
// last rows: cap the panel to the viewport and scroll INSIDE it. The room subtracted
|
|
424
|
+
// leaves space for a topbar-height trigger + margin — the common anchor for these long
|
|
425
|
+
// lists — so a top-anchored menu fits; Popper flips/shifts the panel for other anchors.
|
|
426
|
+
// The thin thumb matches the shared rail (--wui-scrollbar*), like .wui-scroll.
|
|
427
|
+
max-height: calc(100vh - 5rem);
|
|
428
|
+
overflow-y: auto;
|
|
429
|
+
scrollbar-width: thin;
|
|
430
|
+
scrollbar-color: var(--wui-scrollbar) transparent;
|
|
431
|
+
}
|
|
432
|
+
.dropdown-menu::-webkit-scrollbar {
|
|
433
|
+
width: 8px;
|
|
434
|
+
}
|
|
435
|
+
.dropdown-menu::-webkit-scrollbar-track {
|
|
436
|
+
background: transparent;
|
|
437
|
+
}
|
|
438
|
+
.dropdown-menu::-webkit-scrollbar-thumb {
|
|
439
|
+
background-color: var(--wui-scrollbar);
|
|
440
|
+
border-radius: 9999px;
|
|
441
|
+
border: 2px solid transparent;
|
|
442
|
+
background-clip: padding-box;
|
|
443
|
+
}
|
|
444
|
+
.dropdown-menu::-webkit-scrollbar-thumb:hover {
|
|
445
|
+
background-color: var(--wui-scrollbar-hover);
|
|
446
|
+
background-clip: padding-box;
|
|
422
447
|
}
|
|
423
448
|
|
|
424
449
|
.dropdown-item {
|
package/scss/wui/_data.scss
CHANGED
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
color: var(--color-text-subtle);
|
|
14
14
|
}
|
|
15
15
|
.empty-state-title {
|
|
16
|
-
|
|
16
|
+
// Matches .tbl-empty-title (the DataTable empty state) so the two read as one family.
|
|
17
|
+
font-size: 18px;
|
|
17
18
|
font-weight: 600;
|
|
18
19
|
line-height: 1.35;
|
|
19
20
|
color: var(--color-text);
|
|
20
21
|
}
|
|
21
22
|
.empty-state-message {
|
|
22
23
|
margin: 6px 0 0;
|
|
23
|
-
|
|
24
|
+
// Matches .tbl-empty-text.
|
|
25
|
+
font-size: 16px;
|
|
24
26
|
line-height: 1.5;
|
|
25
27
|
color: var(--color-text-muted);
|
|
26
28
|
}
|
|
@@ -112,39 +114,26 @@
|
|
|
112
114
|
.table-ledger tbody tr:hover td.wui-table-empty:has(> .tbl-empty) {
|
|
113
115
|
background: transparent;
|
|
114
116
|
}
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// state
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
overflow-x: hidden;
|
|
134
|
-
}
|
|
135
|
-
.table:has(> tbody > tr > td.wui-table-empty) {
|
|
117
|
+
// The React AppDataTable renders its empty state as a SIBLING of the table, INSIDE `.table-wrap`
|
|
118
|
+
// (the horizontal scroller), in this `.tbl-empty-block`. Two things fall out of that placement:
|
|
119
|
+
// 1. the horizontal scrollbar sits at the BOTTOM of the wrap — below this message — instead of
|
|
120
|
+
// being wedged between the header band and the message (which is what an outside-the-wrap
|
|
121
|
+
// block produced), and
|
|
122
|
+
// 2. `position: sticky; left: 0` + `width: 100%` pins the message to the wrap's VISIBLE width and
|
|
123
|
+
// holds it there while a wide header scrolls behind it: `width: 100%` on a child of an
|
|
124
|
+
// overflow container resolves to the container's CONTENT (visible) width, not its scroll width,
|
|
125
|
+
// so the message never grows the scroll region and never slides off on a narrow screen.
|
|
126
|
+
//
|
|
127
|
+
// This deliberately does NOT use `container-type` + `100cqw` — the query container collapses
|
|
128
|
+
// `.table-wrap` to a sliver when it is a flex/grid item (see the `.table-wrap` note below). Sticky
|
|
129
|
+
// sidesteps that entirely. (The `.wui-table-empty` cell rules above remain for Razor, where the
|
|
130
|
+
// empty state is author-placed inside a `<td colSpan>`.)
|
|
131
|
+
.tbl-empty-block {
|
|
132
|
+
position: sticky;
|
|
133
|
+
left: 0;
|
|
134
|
+
// = the wrap's visible width (content box of the overflow container), never its scroll width.
|
|
136
135
|
width: 100%;
|
|
137
|
-
|
|
138
|
-
table-layout: fixed;
|
|
139
|
-
}
|
|
140
|
-
// Under the fixed layout above the columns are narrow, so a long header label would
|
|
141
|
-
// spill out of its cell and back into horizontal overflow — the very thing we just
|
|
142
|
-
// removed. Clip each header to its column (ellipsis) in the empty state; short labels
|
|
143
|
-
// are unaffected, and the data table keeps its natural header sizing.
|
|
144
|
-
.table:has(> tbody > tr > td.wui-table-empty) > thead th {
|
|
145
|
-
overflow: hidden;
|
|
146
|
-
white-space: nowrap;
|
|
147
|
-
text-overflow: ellipsis;
|
|
136
|
+
// `.tbl-empty` / `.tbl-empty-plain` inside own the centring and padding.
|
|
148
137
|
}
|
|
149
138
|
|
|
150
139
|
// ── Table (base) ──
|
|
@@ -300,8 +289,9 @@
|
|
|
300
289
|
// ITEM (a very common product layout), forcing the whole table into a tiny horizontal
|
|
301
290
|
// scroll. It was briefly added for a `100cqw` empty-state pin; the collapse is far worse
|
|
302
291
|
// than the empty message not being perfectly centred on a narrow scrolling table, so the
|
|
303
|
-
// pin was removed.
|
|
304
|
-
//
|
|
292
|
+
// pin was removed. The empty state now just sizes like a normal table (see the empty-state
|
|
293
|
+
// block above) — if it ever needs viewport-relative sizing again, do it without turning
|
|
294
|
+
// the scroll container into a query container.
|
|
305
295
|
}
|
|
306
296
|
.table-ledger {
|
|
307
297
|
border-spacing: 0;
|
package/scss/wui/_display.scss
CHANGED
|
@@ -700,7 +700,8 @@ a.info-row:hover .info-value {
|
|
|
700
700
|
font-size: 12.5px;
|
|
701
701
|
color: var(--color-text-muted);
|
|
702
702
|
}
|
|
703
|
-
.timeline-at
|
|
703
|
+
.timeline-at,
|
|
704
|
+
.timeline-time {
|
|
704
705
|
color: var(--color-text-subtle);
|
|
705
706
|
font-variant-numeric: tabular-nums;
|
|
706
707
|
}
|
|
@@ -820,12 +821,30 @@ a.info-row:hover .info-value {
|
|
|
820
821
|
justify-content: space-between;
|
|
821
822
|
gap: 12px;
|
|
822
823
|
}
|
|
823
|
-
|
|
824
|
+
// Date OVER time — the stamp stacks vertically so the time reads on its own line beneath the
|
|
825
|
+
// date. `.timeline-when` groups the two wherever a date+time stamp appears (both variants), so
|
|
826
|
+
// the pair moves and aligns as one unit.
|
|
827
|
+
.timeline-when {
|
|
828
|
+
display: inline-flex;
|
|
829
|
+
flex-direction: column;
|
|
830
|
+
gap: 1px;
|
|
831
|
+
}
|
|
832
|
+
// Progress: the stamp is pinned top-right, so the stack is right-aligned under the title row.
|
|
833
|
+
.timeline--progress .timeline-head .timeline-when {
|
|
824
834
|
flex: none;
|
|
835
|
+
align-items: flex-end;
|
|
836
|
+
text-align: right;
|
|
837
|
+
}
|
|
838
|
+
.timeline--progress .timeline-head .timeline-at,
|
|
839
|
+
.timeline--progress .timeline-head .timeline-time {
|
|
825
840
|
font-size: 12.5px;
|
|
826
841
|
color: var(--color-text-subtle);
|
|
827
842
|
white-space: nowrap;
|
|
828
843
|
}
|
|
844
|
+
// The time-of-day line is a touch smaller/lighter than the date it sits under.
|
|
845
|
+
.timeline-time {
|
|
846
|
+
font-size: 12px;
|
|
847
|
+
}
|
|
829
848
|
// An upcoming step greys down — it hasn't happened yet.
|
|
830
849
|
.timeline--progress .timeline-item.is-upcoming .timeline-title {
|
|
831
850
|
color: var(--color-text-muted);
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// ── In-product feedback ──
|
|
2
|
+
// AppFeedback / <app-feedback> / <wui-feedback> — a floating trigger pinned to a page
|
|
3
|
+
// corner that opens a small panel with a message box. The element authors the markup and
|
|
4
|
+
// owns the states (`open` / `busy` on the host, `hidden` on the panel, error and thank-you
|
|
5
|
+
// blocks); everything visual lives here.
|
|
6
|
+
//
|
|
7
|
+
// Part of the wui-components layer — see _wui-components.scss for load order.
|
|
8
|
+
|
|
9
|
+
wui-feedback {
|
|
10
|
+
position: fixed;
|
|
11
|
+
bottom: 20px;
|
|
12
|
+
z-index: var(--wui-z-fixed);
|
|
13
|
+
display: block;
|
|
14
|
+
}
|
|
15
|
+
wui-feedback,
|
|
16
|
+
wui-feedback[position="bottom-right"] {
|
|
17
|
+
right: 20px;
|
|
18
|
+
left: auto;
|
|
19
|
+
}
|
|
20
|
+
wui-feedback[position="bottom-left"] {
|
|
21
|
+
left: 20px;
|
|
22
|
+
right: auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Trigger ──
|
|
26
|
+
.feedback-trigger {
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 8px;
|
|
30
|
+
height: var(--wui-control-height-sm);
|
|
31
|
+
padding: 0 16px;
|
|
32
|
+
border: none;
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
background: var(--color-primary);
|
|
35
|
+
color: var(--color-primary-text);
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
line-height: 1;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
41
|
+
transition:
|
|
42
|
+
background-color 160ms var(--wui-ease-ui),
|
|
43
|
+
transform 160ms var(--wui-ease-ui);
|
|
44
|
+
}
|
|
45
|
+
.feedback-trigger:hover {
|
|
46
|
+
background: var(--color-primary-hover);
|
|
47
|
+
}
|
|
48
|
+
.feedback-trigger:focus-visible {
|
|
49
|
+
outline: 2px solid var(--color-primary);
|
|
50
|
+
outline-offset: 2px;
|
|
51
|
+
}
|
|
52
|
+
.feedback-trigger-icon {
|
|
53
|
+
display: grid;
|
|
54
|
+
place-items: center;
|
|
55
|
+
line-height: 0;
|
|
56
|
+
}
|
|
57
|
+
.feedback-trigger-label {
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Panel ──
|
|
62
|
+
// Anchored to the same corner as the trigger, opening upward — the trigger sits at the
|
|
63
|
+
// bottom of the viewport, so there is only ever room above it.
|
|
64
|
+
.feedback-panel {
|
|
65
|
+
position: absolute;
|
|
66
|
+
bottom: calc(100% + 12px);
|
|
67
|
+
width: 340px;
|
|
68
|
+
max-width: calc(100vw - 40px);
|
|
69
|
+
padding: 16px;
|
|
70
|
+
background: var(--color-surface);
|
|
71
|
+
border: 1px solid var(--color-border);
|
|
72
|
+
border-radius: var(--wui-radius-lg);
|
|
73
|
+
box-shadow: var(--wui-shadow-overlay-lg);
|
|
74
|
+
color: var(--color-text);
|
|
75
|
+
text-align: start;
|
|
76
|
+
}
|
|
77
|
+
wui-feedback[position="bottom-right"] .feedback-panel {
|
|
78
|
+
right: 0;
|
|
79
|
+
}
|
|
80
|
+
wui-feedback[position="bottom-left"] .feedback-panel {
|
|
81
|
+
left: 0;
|
|
82
|
+
}
|
|
83
|
+
// `hidden` on a custom element's child is honoured, but a later display rule would win —
|
|
84
|
+
// state it explicitly so the panel cannot be left visible by cascade order.
|
|
85
|
+
.feedback-panel[hidden] {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.feedback-head {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: flex-start;
|
|
92
|
+
justify-content: space-between;
|
|
93
|
+
gap: 12px;
|
|
94
|
+
margin-bottom: 12px;
|
|
95
|
+
}
|
|
96
|
+
.feedback-heading {
|
|
97
|
+
margin: 0;
|
|
98
|
+
font-size: 15px;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
line-height: 1.3;
|
|
101
|
+
color: var(--color-text);
|
|
102
|
+
}
|
|
103
|
+
.feedback-close {
|
|
104
|
+
display: grid;
|
|
105
|
+
place-items: center;
|
|
106
|
+
width: 28px;
|
|
107
|
+
height: 28px;
|
|
108
|
+
flex: 0 0 auto;
|
|
109
|
+
padding: 0;
|
|
110
|
+
border: none;
|
|
111
|
+
border-radius: var(--wui-radius-sm);
|
|
112
|
+
background: transparent;
|
|
113
|
+
color: var(--color-text-muted);
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
.feedback-close:hover {
|
|
117
|
+
background: var(--color-bg-muted);
|
|
118
|
+
color: var(--color-text);
|
|
119
|
+
}
|
|
120
|
+
.feedback-close:focus-visible {
|
|
121
|
+
outline: 2px solid var(--color-primary);
|
|
122
|
+
outline-offset: 1px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.feedback-body[hidden],
|
|
126
|
+
.feedback-actions[hidden],
|
|
127
|
+
.feedback-thanks[hidden] {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Fields ──
|
|
132
|
+
.feedback-field {
|
|
133
|
+
display: block;
|
|
134
|
+
margin-bottom: 12px;
|
|
135
|
+
}
|
|
136
|
+
.feedback-field-label {
|
|
137
|
+
display: block;
|
|
138
|
+
margin-bottom: 6px;
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
color: var(--color-text-muted);
|
|
142
|
+
}
|
|
143
|
+
.feedback-message,
|
|
144
|
+
.feedback-email {
|
|
145
|
+
display: block;
|
|
146
|
+
width: 100%;
|
|
147
|
+
padding: 10px 12px;
|
|
148
|
+
background: var(--color-surface);
|
|
149
|
+
border: 1px solid var(--color-border);
|
|
150
|
+
border-radius: var(--wui-radius-sm);
|
|
151
|
+
color: var(--color-text);
|
|
152
|
+
font-family: inherit;
|
|
153
|
+
font-size: 14px;
|
|
154
|
+
line-height: 1.45;
|
|
155
|
+
}
|
|
156
|
+
.feedback-message {
|
|
157
|
+
min-height: 96px;
|
|
158
|
+
resize: vertical;
|
|
159
|
+
}
|
|
160
|
+
.feedback-message:focus-visible,
|
|
161
|
+
.feedback-email:focus-visible {
|
|
162
|
+
outline: none;
|
|
163
|
+
border-color: var(--color-primary);
|
|
164
|
+
box-shadow: 0 0 0 3px var(--color-primary-soft);
|
|
165
|
+
}
|
|
166
|
+
.feedback-email {
|
|
167
|
+
height: var(--wui-control-height-sm);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.feedback-error {
|
|
171
|
+
margin: 0 0 4px;
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
line-height: 1.4;
|
|
174
|
+
color: var(--color-danger-text);
|
|
175
|
+
}
|
|
176
|
+
.feedback-error[hidden] {
|
|
177
|
+
display: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Actions ──
|
|
181
|
+
.feedback-actions {
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: flex-end;
|
|
184
|
+
gap: 8px;
|
|
185
|
+
}
|
|
186
|
+
.feedback-cancel,
|
|
187
|
+
.feedback-submit {
|
|
188
|
+
height: var(--wui-control-height-sm);
|
|
189
|
+
padding: 0 14px;
|
|
190
|
+
border-radius: var(--wui-radius-sm);
|
|
191
|
+
font-size: 14px;
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
line-height: 1;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
}
|
|
196
|
+
.feedback-cancel {
|
|
197
|
+
border: 1px solid var(--color-border);
|
|
198
|
+
background: transparent;
|
|
199
|
+
color: var(--color-text);
|
|
200
|
+
}
|
|
201
|
+
.feedback-cancel:hover {
|
|
202
|
+
background: var(--color-bg-muted);
|
|
203
|
+
}
|
|
204
|
+
.feedback-submit {
|
|
205
|
+
border: none;
|
|
206
|
+
background: var(--color-primary);
|
|
207
|
+
color: var(--color-primary-text);
|
|
208
|
+
}
|
|
209
|
+
.feedback-submit:hover:not(:disabled) {
|
|
210
|
+
background: var(--color-primary-hover);
|
|
211
|
+
}
|
|
212
|
+
.feedback-submit:disabled {
|
|
213
|
+
opacity: 0.65;
|
|
214
|
+
cursor: default;
|
|
215
|
+
}
|
|
216
|
+
.feedback-cancel:focus-visible,
|
|
217
|
+
.feedback-submit:focus-visible {
|
|
218
|
+
outline: 2px solid var(--color-primary);
|
|
219
|
+
outline-offset: 2px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Thank-you ──
|
|
223
|
+
.feedback-thanks {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: 10px;
|
|
227
|
+
padding: 8px 0 4px;
|
|
228
|
+
font-size: 14px;
|
|
229
|
+
color: var(--color-success-text);
|
|
230
|
+
}
|
|
231
|
+
.feedback-thanks-icon {
|
|
232
|
+
display: grid;
|
|
233
|
+
place-items: center;
|
|
234
|
+
line-height: 0;
|
|
235
|
+
}
|
|
236
|
+
.feedback-thanks-text {
|
|
237
|
+
font-weight: 500;
|
|
238
|
+
line-height: 1.4;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Small screens ──
|
|
242
|
+
// The panel is nearly the width of the phone, so pin it to both edges rather than to the
|
|
243
|
+
// trigger's corner — a 340px card anchored to one side overhangs on the narrowest devices.
|
|
244
|
+
@include media-breakpoint-down(sm) {
|
|
245
|
+
.feedback-panel {
|
|
246
|
+
position: fixed;
|
|
247
|
+
right: 12px;
|
|
248
|
+
left: 12px;
|
|
249
|
+
bottom: calc(var(--wui-control-height-sm) + 32px);
|
|
250
|
+
width: auto;
|
|
251
|
+
max-width: none;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@media (prefers-reduced-motion: reduce) {
|
|
256
|
+
.feedback-trigger {
|
|
257
|
+
transition: none;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -127,6 +127,11 @@
|
|
|
127
127
|
|
|
128
128
|
// Optional overflow wrapper: the scroll chevrons live HERE, outside the scrolling strip,
|
|
129
129
|
// so they stay pinned while the tabs scroll under them.
|
|
130
|
+
// A little horizontal padding insets the strip from its container's edges, so the first/last
|
|
131
|
+
// tab — and its full-width active underline — never lands on a rounded container's corner (a
|
|
132
|
+
// tab strip placed flush in a card read as the underline hanging outside it). Horizontal only:
|
|
133
|
+
// both surfaces render this wrapper for horizontal strips and skip it for vertical, so the two
|
|
134
|
+
// stay identical.
|
|
130
135
|
.app-tabs-wrap {
|
|
131
136
|
position: relative;
|
|
132
137
|
display: flex;
|
|
@@ -146,10 +151,12 @@
|
|
|
146
151
|
// Auto-scrolling the active tab into view (and the chevrons) glides rather than jumps.
|
|
147
152
|
scroll-behavior: smooth;
|
|
148
153
|
}
|
|
149
|
-
// Doubled to (0,2,0) so the strip keeps scrolling even if a consumer
|
|
150
|
-
// after the theme, which would otherwise let `.nav`'s flex-wrap:wrap
|
|
154
|
+
// Doubled to (0,2,0) so the strip keeps scrolling AND keeps its gutters even if a consumer
|
|
155
|
+
// loads Bootstrap after the theme, which would otherwise let `.nav`'s flex-wrap:wrap and
|
|
156
|
+
// padding-left:0 win on source order.
|
|
151
157
|
.app-tabs.app-tabs {
|
|
152
158
|
flex-wrap: nowrap;
|
|
159
|
+
padding: 0 12px;
|
|
153
160
|
}
|
|
154
161
|
.app-tabs::-webkit-scrollbar {
|
|
155
162
|
display: none;
|
package/scss/wui/_utilities.scss
CHANGED
|
@@ -164,12 +164,11 @@
|
|
|
164
164
|
align-items: center;
|
|
165
165
|
justify-content: space-between;
|
|
166
166
|
gap: 16px;
|
|
167
|
-
// Content-driven wrap
|
|
168
|
-
// actions pinned right; once a long title needs
|
|
169
|
-
// row, so the actions drop onto their own line —
|
|
170
|
-
// lone wrapped item at flex-start — instead of
|
|
171
|
-
// doubles as the row-gap to that dropped line.
|
|
172
|
-
// down; this is the any-width, content-driven case.)
|
|
167
|
+
// Content-driven wrap at EVERY width — there is no breakpoint stack. While the title block
|
|
168
|
+
// and actions both fit, space-between keeps the actions pinned right; once a long title needs
|
|
169
|
+
// the full width they can no longer share the row, so the actions drop onto their own line —
|
|
170
|
+
// left-aligned, since space-between places a lone wrapped item at flex-start — instead of
|
|
171
|
+
// being crushed against the title. The gap above doubles as the row-gap to that dropped line.
|
|
173
172
|
flex-wrap: wrap;
|
|
174
173
|
}
|
|
175
174
|
// Left block: an optional leading icon tile + the title stack, kept together.
|
|
@@ -188,8 +187,8 @@
|
|
|
188
187
|
flex: none;
|
|
189
188
|
display: inline-grid;
|
|
190
189
|
place-items: center;
|
|
191
|
-
width:
|
|
192
|
-
height:
|
|
190
|
+
width: 55px;
|
|
191
|
+
height: 55px;
|
|
193
192
|
border-radius: var(--radius);
|
|
194
193
|
corner-shape: squircle;
|
|
195
194
|
background: var(--color-primary-soft);
|
|
@@ -262,6 +261,14 @@
|
|
|
262
261
|
display: flex;
|
|
263
262
|
gap: 8px;
|
|
264
263
|
align-items: center;
|
|
264
|
+
// Several action buttons wrap among themselves if their own line still can't hold them all
|
|
265
|
+
// — but they never force the block full-width, so a single button stays its natural size.
|
|
266
|
+
flex-wrap: wrap;
|
|
267
|
+
// Don't let the actions be squeezed. The title block (`.page-header-lead`, min-width: 0)
|
|
268
|
+
// absorbs the shrink instead, so the actions keep their natural size and the row stays on
|
|
269
|
+
// one line while it fits — then `.page-header-bar`'s flex-wrap drops the WHOLE actions
|
|
270
|
+
// block onto its own line only once the content genuinely can't share the row.
|
|
271
|
+
flex: none;
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
// ── Size scale ── (`size`): the title and the block's rhythm scale together.
|
|
@@ -272,8 +279,8 @@
|
|
|
272
279
|
font-size: 20px;
|
|
273
280
|
}
|
|
274
281
|
.page-header-sm .page-header-icon {
|
|
275
|
-
width:
|
|
276
|
-
height:
|
|
282
|
+
width: 49px;
|
|
283
|
+
height: 49px;
|
|
277
284
|
}
|
|
278
285
|
.page-header-lg {
|
|
279
286
|
margin-bottom: 32px;
|
|
@@ -282,8 +289,8 @@
|
|
|
282
289
|
font-size: 30px;
|
|
283
290
|
}
|
|
284
291
|
.page-header-lg .page-header-icon {
|
|
285
|
-
width:
|
|
286
|
-
height:
|
|
292
|
+
width: 63px;
|
|
293
|
+
height: 63px;
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
// ── Centre alignment ── (`align="center"`): for empty-state and wizard mastheads.
|
|
@@ -318,21 +325,18 @@
|
|
|
318
325
|
// its actions stack, and the actions stretch to the full width rather than
|
|
319
326
|
// crowding the title. Bootstrap's lg tier (991.98px), not an invented breakpoint.
|
|
320
327
|
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
328
|
+
// The masthead bar is NOT stacked at a breakpoint. It wraps by CONTENT: `.page-header-bar`
|
|
329
|
+
// carries `flex-wrap: wrap` and its actions `flex: none`, so the title and actions share one
|
|
330
|
+
// row wherever they fit and the actions drop below only when the row genuinely can't hold both
|
|
331
|
+
// — a hard `max-width: 991.98px` column-stack pushed the actions under the title even on a
|
|
332
|
+
// 768px tablet that had ample room for them side by side.
|
|
333
|
+
//
|
|
334
|
+
// Selectors below are DOUBLED (`.field-row.field-row`) on purpose. A media query adds no
|
|
335
|
+
// specificity, so a consumer's own single-class rule — written later in the cascade, as a
|
|
336
|
+
// consuming app's stylesheet always is — silently beats the responsive rule and the layout
|
|
337
|
+
// never collapses. Doubling puts these at (0,2,0) so they hold, while still leaving a consumer
|
|
338
|
+
// free to override deliberately.
|
|
326
339
|
@media (max-width: 991.98px) {
|
|
327
|
-
.page-header-bar.page-header-bar {
|
|
328
|
-
flex-direction: column;
|
|
329
|
-
align-items: stretch;
|
|
330
|
-
gap: 12px;
|
|
331
|
-
}
|
|
332
|
-
.page-actions.page-actions {
|
|
333
|
-
flex-wrap: wrap;
|
|
334
|
-
width: 100%;
|
|
335
|
-
}
|
|
336
340
|
// A two-up field row is too tight for a tablet column.
|
|
337
341
|
.field-row.field-row,
|
|
338
342
|
.field-row-3.field-row-3 {
|
|
@@ -422,6 +426,62 @@
|
|
|
422
426
|
display: inline-block;
|
|
423
427
|
}
|
|
424
428
|
|
|
429
|
+
// Password-requirements checklist — the content of the popover AppInput (type="password" +
|
|
430
|
+
// passwordRequirements) opens on focus. Rendered identically by React (a JSX <ul>) and Razor
|
|
431
|
+
// (the same <ul> built in webority-ui.js), so both surfaces read the same. `.pw-popover` is the
|
|
432
|
+
// custom class both put on Bootstrap's `.popover` panel.
|
|
433
|
+
.pw-popover .popover-body {
|
|
434
|
+
padding: 12px 14px;
|
|
435
|
+
}
|
|
436
|
+
.pw-criteria {
|
|
437
|
+
list-style: none;
|
|
438
|
+
margin: 0;
|
|
439
|
+
padding: 0;
|
|
440
|
+
display: flex;
|
|
441
|
+
flex-direction: column;
|
|
442
|
+
gap: 8px;
|
|
443
|
+
}
|
|
444
|
+
.pw-criteria .pw-rule {
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
gap: 8px;
|
|
448
|
+
font-size: 13px;
|
|
449
|
+
line-height: 1.3;
|
|
450
|
+
}
|
|
451
|
+
// The state mark is a baked-colour circled ✓/✕ — matched to the AA status ramp. The list TEXT
|
|
452
|
+
// uses the -text token for contrast; the mark itself needs less and uses the vivid ramp value.
|
|
453
|
+
.pw-criteria .pw-mark {
|
|
454
|
+
flex: none;
|
|
455
|
+
width: 18px;
|
|
456
|
+
height: 18px;
|
|
457
|
+
background: center / contain no-repeat;
|
|
458
|
+
}
|
|
459
|
+
.pw-criteria .pw-rule-text {
|
|
460
|
+
min-width: 0;
|
|
461
|
+
}
|
|
462
|
+
.pw-criteria .pw-rule.met {
|
|
463
|
+
color: var(--color-success-text);
|
|
464
|
+
}
|
|
465
|
+
.pw-criteria .pw-rule.met .pw-mark {
|
|
466
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2317C653' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m8 12 2.5 2.5 5.5-6'/%3E%3C/svg%3E");
|
|
467
|
+
}
|
|
468
|
+
.pw-criteria .pw-rule.unmet {
|
|
469
|
+
color: var(--color-danger-text);
|
|
470
|
+
}
|
|
471
|
+
.pw-criteria .pw-rule.unmet .pw-mark {
|
|
472
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23F8285A' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m9 9 6 6m0-6-6 6'/%3E%3C/svg%3E");
|
|
473
|
+
}
|
|
474
|
+
// The checklist opens with a PLAIN FADE only — no drop / zoom / scale. It opts out of the shared
|
|
475
|
+
// menu-open motion (_menu-motion.scss animates every `.popover.show`) and lets Bootstrap's own
|
|
476
|
+
// `.fade` opacity transition carry the fade in and out. Both surfaces render `.popover.pw-popover`
|
|
477
|
+
// (React via react-bootstrap's Popover, Razor via Bootstrap's popover), so this makes the two
|
|
478
|
+
// identical. Higher specificity than `.popover.show` so it wins regardless of partial load order.
|
|
479
|
+
.popover.pw-popover.show {
|
|
480
|
+
animation: none;
|
|
481
|
+
scale: none;
|
|
482
|
+
translate: none;
|
|
483
|
+
}
|
|
484
|
+
|
|
425
485
|
// Page/system-level announcement bar (AppBanner / <app-banner>). Full-width and persistent —
|
|
426
486
|
// distinct from an inline .alert or a transient toast. Tones reuse the shared soft/text ramp.
|
|
427
487
|
.wui-banner {
|