@webority/theme 0.6.3 → 0.6.5
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 +80 -18
- package/scss/_tokens.scss +9 -0
- package/scss/wui/_controls.scss +78 -7
- package/scss/wui/_data.scss +17 -4
- package/scss/wui/_typography.scss +22 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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
|
@@ -92,24 +92,86 @@
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
.btn-danger {
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
--bs-btn-
|
|
110
|
-
--bs-btn-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
// White-on-red destructive look via the --wui-danger-strong token: white fails AA on the
|
|
96
|
+
// brighter danger fills (#F8285A → 3.86:1, #E82646 → 4.38:1), so the button uses the
|
|
97
|
+
// deepened fill (5.80:1) that carries white cleanly. Token-driven so a rebrand tracks it;
|
|
98
|
+
// the brighter --wui-danger stays for badges/dots that never carry white text.
|
|
99
|
+
--bs-btn-color: #fff;
|
|
100
|
+
--bs-btn-bg: var(--wui-danger-strong);
|
|
101
|
+
--bs-btn-border-color: var(--wui-danger-strong);
|
|
102
|
+
--bs-btn-hover-color: #fff;
|
|
103
|
+
--bs-btn-hover-bg: var(--wui-danger-strong-hover);
|
|
104
|
+
--bs-btn-hover-border-color: var(--wui-danger-strong-hover);
|
|
105
|
+
--bs-btn-active-color: #fff;
|
|
106
|
+
--bs-btn-active-bg: var(--wui-danger-strong-hover);
|
|
107
|
+
--bs-btn-active-border-color: var(--wui-danger-strong-hover);
|
|
108
|
+
--bs-btn-disabled-color: #fff;
|
|
109
|
+
--bs-btn-disabled-bg: var(--wui-danger-strong);
|
|
110
|
+
--bs-btn-disabled-border-color: var(--wui-danger-strong);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
// ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
|
|
115
|
+
// Bootstrap ships a bare .dropdown-item; the design system gives the menu real
|
|
116
|
+
// padding, an icon–label gap, a rounded hover highlight tinted with the brand,
|
|
117
|
+
// and a danger variant so a row-action menu reads as one considered control.
|
|
118
|
+
.dropdown-menu {
|
|
119
|
+
--bs-dropdown-min-width: 12rem;
|
|
120
|
+
--bs-dropdown-padding-x: 0.375rem;
|
|
121
|
+
--bs-dropdown-padding-y: 0.375rem;
|
|
122
|
+
--bs-dropdown-item-padding-y: 0.5rem;
|
|
123
|
+
--bs-dropdown-item-padding-x: 0.625rem;
|
|
124
|
+
--bs-dropdown-border-color: var(--wui-border-light);
|
|
125
|
+
--bs-dropdown-border-radius: var(--wui-radius-lg);
|
|
126
|
+
--bs-dropdown-link-color: var(--wui-text-1);
|
|
127
|
+
--bs-dropdown-link-hover-color: var(--wui-primary-pressed);
|
|
128
|
+
--bs-dropdown-link-hover-bg: var(--wui-primary-5);
|
|
129
|
+
--bs-dropdown-link-active-color: var(--wui-primary-pressed);
|
|
130
|
+
--bs-dropdown-link-active-bg: var(--wui-primary-10);
|
|
131
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.dropdown-item {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 0.625rem;
|
|
138
|
+
border-radius: var(--wui-radius-sm);
|
|
139
|
+
font-size: 0.875rem;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
transition: background-color 0.12s ease, color 0.12s ease;
|
|
142
|
+
|
|
143
|
+
// Leading glyph: quiet by default, brand-tinted on hover — a font-size so both
|
|
144
|
+
// the React <Icon> (a sized <i>) and the Razor <i class="bi"> land the same.
|
|
145
|
+
.bi, i, svg {
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
font-size: 1rem;
|
|
148
|
+
color: var(--wui-text-3);
|
|
149
|
+
transition: color 0.12s ease;
|
|
150
|
+
}
|
|
151
|
+
&:hover .bi, &:hover i, &:hover svg,
|
|
152
|
+
&:focus .bi, &:focus i, &:focus svg {
|
|
153
|
+
color: var(--wui-primary);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// A leading picture instead of a glyph — an external logo/avatar, a brand mark.
|
|
157
|
+
// Sized to sit optically with the 1rem glyph so mixed menus still line up.
|
|
158
|
+
img {
|
|
159
|
+
flex-shrink: 0;
|
|
160
|
+
width: 1.125rem;
|
|
161
|
+
height: 1.125rem;
|
|
162
|
+
border-radius: var(--wui-radius-sm);
|
|
163
|
+
object-fit: cover;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Destructive row action — red label, red glyph, red-tinted hover.
|
|
167
|
+
&.danger {
|
|
168
|
+
--bs-dropdown-link-color: var(--wui-danger-text);
|
|
169
|
+
--bs-dropdown-link-hover-color: var(--wui-danger-text);
|
|
170
|
+
--bs-dropdown-link-hover-bg: var(--wui-danger-10);
|
|
171
|
+
.bi, i, svg { color: var(--wui-danger); }
|
|
172
|
+
&:hover .bi, &:hover i, &:hover svg,
|
|
173
|
+
&:focus .bi, &:focus i, &:focus svg { color: var(--wui-danger); }
|
|
174
|
+
}
|
|
113
175
|
}
|
|
114
176
|
|
|
115
177
|
|
package/scss/_tokens.scss
CHANGED
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
// rows, so they must line up — they were 46.8px and 45px, which reads as a
|
|
71
71
|
// misalignment on every filter bar. 44px is also the WCAG touch-target floor.
|
|
72
72
|
--wui-control-height: 44px;
|
|
73
|
+
// Optional size steps (sm/lg) for dense toolbars / marketing-scale fields. md is the
|
|
74
|
+
// default above; a control opts into a step with .input-sm / .input-lg.
|
|
75
|
+
--wui-control-height-sm: 36px;
|
|
76
|
+
--wui-control-height-lg: 52px;
|
|
73
77
|
|
|
74
78
|
// Scrollbar thumb — one quiet grey-blue at three interaction strengths.
|
|
75
79
|
--wui-scrollbar: rgba(145, 158, 171, 0.45);
|
|
@@ -152,6 +156,11 @@
|
|
|
152
156
|
--wui-warning-5: #FEFBF2;
|
|
153
157
|
|
|
154
158
|
--wui-danger-text: #b61d42;
|
|
159
|
+
// Deepened danger fill for surfaces that carry WHITE text (btn-danger). The brighter
|
|
160
|
+
// --wui-danger/-active below are too light for white (3.86:1 / 4.38:1, under AA); these
|
|
161
|
+
// clear it — #C81340 → 5.80:1, hover → higher. Keep white-on-danger off the other tokens.
|
|
162
|
+
--wui-danger-strong: #C81340;
|
|
163
|
+
--wui-danger-strong-hover: #AB1036;
|
|
155
164
|
--wui-danger-active: #E82646;
|
|
156
165
|
--wui-danger: #F8285A;
|
|
157
166
|
--wui-danger-50: #FC94AD;
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -193,6 +193,18 @@
|
|
|
193
193
|
box-shadow 0.12s;
|
|
194
194
|
font-family: inherit;
|
|
195
195
|
}
|
|
196
|
+
// Size steps — sm/lg opt out of the shared 44px control height for dense toolbars or
|
|
197
|
+
// marketing-scale fields. md is the base rule above; AppInput adds .input-sm / .input-lg.
|
|
198
|
+
.input-sm {
|
|
199
|
+
height: var(--wui-control-height-sm);
|
|
200
|
+
padding: 6px 10px;
|
|
201
|
+
font-size: 13px;
|
|
202
|
+
}
|
|
203
|
+
.input-lg {
|
|
204
|
+
height: var(--wui-control-height-lg);
|
|
205
|
+
padding: 14px 16px;
|
|
206
|
+
font-size: 16px;
|
|
207
|
+
}
|
|
196
208
|
.textarea {
|
|
197
209
|
height: auto;
|
|
198
210
|
min-height: 120px;
|
|
@@ -200,6 +212,13 @@
|
|
|
200
212
|
line-height: 1.5;
|
|
201
213
|
resize: vertical;
|
|
202
214
|
}
|
|
215
|
+
// Auto-grow — JS drives the height to fit the content (Razor: data-app-autosize wired in
|
|
216
|
+
// webority-ui.js; React: the autoGrow prop). The manual grabber and the scrollbar are off
|
|
217
|
+
// so they can't fight the measured height.
|
|
218
|
+
.textarea.autosize {
|
|
219
|
+
resize: none;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
}
|
|
203
222
|
.input:hover:not(:focus):not(:disabled),
|
|
204
223
|
.select:hover:not(:focus):not(:disabled),
|
|
205
224
|
.textarea:hover:not(:focus):not(:disabled) {
|
|
@@ -284,10 +303,6 @@ textarea.textarea:focus {
|
|
|
284
303
|
border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
|
|
285
304
|
box-shadow: none;
|
|
286
305
|
}
|
|
287
|
-
.input.is-invalid,
|
|
288
|
-
.input[aria-invalid="true"] {
|
|
289
|
-
padding-right: 41px;
|
|
290
|
-
}
|
|
291
306
|
.field-error {
|
|
292
307
|
font-size: 12px;
|
|
293
308
|
line-height: normal;
|
|
@@ -393,7 +408,7 @@ textarea.textarea:focus {
|
|
|
393
408
|
pointer-events: none;
|
|
394
409
|
}
|
|
395
410
|
|
|
396
|
-
// Error state — danger-tinted border
|
|
411
|
+
// Error state — danger-tinted border, plus a danger glyph at the trailing edge.
|
|
397
412
|
.input.error,
|
|
398
413
|
.input.error:focus,
|
|
399
414
|
.textarea.error,
|
|
@@ -402,8 +417,19 @@ textarea.textarea:focus {
|
|
|
402
417
|
border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
|
|
403
418
|
box-shadow: none;
|
|
404
419
|
}
|
|
405
|
-
|
|
420
|
+
|
|
421
|
+
// The trailing error glyph. Scoped with the child combinator to a PLAIN field only:
|
|
422
|
+
// a shell-wrapped input (leading icon / unit / password / trailing action) is nested
|
|
423
|
+
// one level deeper, so it keeps its own right edge and never collides with the glyph.
|
|
424
|
+
// Covers both error paths — the `.error` class and `[aria-invalid]` / `.is-invalid`.
|
|
425
|
+
.field > .input.error,
|
|
426
|
+
.field > .input.is-invalid,
|
|
427
|
+
.field > .input[aria-invalid="true"] {
|
|
406
428
|
padding-right: 41px;
|
|
429
|
+
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='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cline x1='12' y1='8' x2='12' y2='12'/%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'/%3E%3C/svg%3E");
|
|
430
|
+
background-repeat: no-repeat;
|
|
431
|
+
background-position: right 14px center;
|
|
432
|
+
background-size: 18px;
|
|
407
433
|
}
|
|
408
434
|
// Dark mode: keep the value in an errored field legible.
|
|
409
435
|
:root[data-theme="dark"] .input.error,
|
|
@@ -489,12 +515,57 @@ textarea.textarea:focus {
|
|
|
489
515
|
background: transparent;
|
|
490
516
|
}
|
|
491
517
|
|
|
518
|
+
// Clearable ✕ — a trailing reset button, placed inside .input-wrap / .input-icon. It only
|
|
519
|
+
// renders while the field has a value; .has-clear reserves the room so text never sits under it.
|
|
520
|
+
.input.has-clear {
|
|
521
|
+
padding-right: 40px;
|
|
522
|
+
}
|
|
523
|
+
.input-clear {
|
|
524
|
+
position: absolute;
|
|
525
|
+
right: 8px;
|
|
526
|
+
top: 50%;
|
|
527
|
+
transform: translateY(-50%);
|
|
528
|
+
display: grid;
|
|
529
|
+
place-items: center;
|
|
530
|
+
width: 28px;
|
|
531
|
+
height: 28px;
|
|
532
|
+
padding: 0;
|
|
533
|
+
border: none;
|
|
534
|
+
border-radius: 50%;
|
|
535
|
+
background: transparent;
|
|
536
|
+
color: var(--color-text-subtle);
|
|
537
|
+
cursor: pointer;
|
|
538
|
+
transition:
|
|
539
|
+
color 0.12s ease,
|
|
540
|
+
background 0.12s ease;
|
|
541
|
+
animation: wui-clear-in 0.12s var(--wui-ease-ui);
|
|
542
|
+
}
|
|
543
|
+
.input-clear:hover {
|
|
544
|
+
color: var(--color-text);
|
|
545
|
+
background: var(--wui-bg-muted);
|
|
546
|
+
}
|
|
547
|
+
@keyframes wui-clear-in {
|
|
548
|
+
from { opacity: 0; transform: translateY(-50%) scale(0.72); }
|
|
549
|
+
to { opacity: 1; transform: translateY(-50%) scale(1); }
|
|
550
|
+
}
|
|
551
|
+
|
|
492
552
|
.hint,
|
|
493
553
|
.field-hint {
|
|
494
554
|
margin-top: 2px;
|
|
495
555
|
font-size: 12px;
|
|
496
556
|
color: var(--color-text-subtle);
|
|
497
557
|
}
|
|
558
|
+
// Character counter — a right-aligned count under the field; turns danger at the limit.
|
|
559
|
+
.field-count {
|
|
560
|
+
margin-top: 2px;
|
|
561
|
+
font-size: 12px;
|
|
562
|
+
line-height: normal;
|
|
563
|
+
text-align: right;
|
|
564
|
+
color: var(--color-text-subtle);
|
|
565
|
+
}
|
|
566
|
+
.field-count.at-limit {
|
|
567
|
+
color: var(--color-danger-text);
|
|
568
|
+
}
|
|
498
569
|
.label {
|
|
499
570
|
display: block;
|
|
500
571
|
font-size: 14px;
|
|
@@ -526,7 +597,7 @@ textarea.textarea:focus {
|
|
|
526
597
|
display: flex;
|
|
527
598
|
flex-direction: column;
|
|
528
599
|
align-items: flex-start;
|
|
529
|
-
gap:
|
|
600
|
+
gap: 4px;
|
|
530
601
|
margin-bottom: 16px;
|
|
531
602
|
}
|
|
532
603
|
.field > .label,
|
package/scss/wui/_data.scss
CHANGED
|
@@ -572,17 +572,30 @@
|
|
|
572
572
|
// Table extras — a sortable header and numeric cells. These sit AFTER the
|
|
573
573
|
// .table-ledger rules above on purpose: they are opt-in additions on top of
|
|
574
574
|
// whichever table variant is in use, not part of the base a variant overrides.
|
|
575
|
+
// NEVER give a <th> a display that is not a table display.
|
|
576
|
+
//
|
|
577
|
+
// This carried `display: flex`, which takes the cell OUT of the table's layout entirely: it
|
|
578
|
+
// stops being a column, so every sortable header left the header row and stacked at its own
|
|
579
|
+
// y-position. Measured on a consumer's page, a sortable table's headers sat at three
|
|
580
|
+
// different y-values against the neighbouring non-sortable table's one, and the header box
|
|
581
|
+
// grew from 42px to 126px. It shipped in 0.6.4.
|
|
582
|
+
//
|
|
583
|
+
// Nothing else was wrong — background, colour, padding, borders, font, tracking, radii all
|
|
584
|
+
// resolved correctly. One declaration disintegrated the row, and only for tables that
|
|
585
|
+
// actually have a sortable column, which is why half a product's screens looked fine.
|
|
586
|
+
//
|
|
587
|
+
// The alignment it was buying is done on the icon instead, which is an inline box inside a
|
|
588
|
+
// normal cell and cannot disturb the table.
|
|
575
589
|
.table th.sortable {
|
|
576
|
-
display: flex;
|
|
577
|
-
align-items: center;
|
|
578
|
-
gap: 6px;
|
|
579
590
|
cursor: pointer;
|
|
580
591
|
user-select: none;
|
|
581
592
|
}
|
|
582
593
|
.table th.sortable .sort-icon {
|
|
583
594
|
// Reserved space so the header does not jump width when the arrow appears.
|
|
595
|
+
display: inline-block;
|
|
596
|
+
vertical-align: middle;
|
|
584
597
|
width: 12px;
|
|
585
|
-
|
|
598
|
+
margin-left: 6px;
|
|
586
599
|
color: var(--color-text-subtle);
|
|
587
600
|
opacity: 0;
|
|
588
601
|
transition: opacity 0.12s ease;
|
|
@@ -117,10 +117,6 @@
|
|
|
117
117
|
background: color-mix(in srgb, var(--color-text) 8%, transparent);
|
|
118
118
|
color: var(--color-text);
|
|
119
119
|
}
|
|
120
|
-
.chip.is-disabled {
|
|
121
|
-
opacity: 0.55;
|
|
122
|
-
pointer-events: none;
|
|
123
|
-
}
|
|
124
120
|
|
|
125
121
|
/* Tones — tinted surface + on-tint text; icon/remove inherit the tone colour. */
|
|
126
122
|
.chip.primary {
|
|
@@ -169,6 +165,28 @@
|
|
|
169
165
|
color: currentColor;
|
|
170
166
|
}
|
|
171
167
|
|
|
168
|
+
// Disabled — deliberately NOT opacity, and deliberately after the tones.
|
|
169
|
+
//
|
|
170
|
+
// `opacity: .55` fades the whole chip, which scales the LABEL's contrast down with it:
|
|
171
|
+
// #687088 on #F5F6FA at .55 alpha resolves to 2.08:1, less than half the 4.5:1 AA floor,
|
|
172
|
+
// and axe reports it as a serious violation on both surfaces. Explicit muted tokens read
|
|
173
|
+
// as the same state and stay legible at 4.56:1.
|
|
174
|
+
//
|
|
175
|
+
// It sits after the tone block on purpose. `.chip.is-disabled` and `.chip.primary` have
|
|
176
|
+
// equal specificity, so source order decides — declared before them, a disabled primary
|
|
177
|
+
// chip kept its primary colours. That is very likely why opacity was reached for in the
|
|
178
|
+
// first place: it composes over any tone without having to win the cascade.
|
|
179
|
+
.chip.is-disabled {
|
|
180
|
+
border-color: var(--color-border);
|
|
181
|
+
background: var(--color-bg-muted);
|
|
182
|
+
color: var(--color-text-subtle);
|
|
183
|
+
pointer-events: none;
|
|
184
|
+
}
|
|
185
|
+
.chip.is-disabled .chip-ic,
|
|
186
|
+
.chip.is-disabled .chip-remove {
|
|
187
|
+
color: var(--color-text-subtle);
|
|
188
|
+
}
|
|
189
|
+
|
|
172
190
|
/* ── Autocomplete (input-first combobox; extends .search-select) ── */
|
|
173
191
|
.search-select-ac {
|
|
174
192
|
justify-content: flex-start;
|