@webority/theme 0.6.3 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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": {
@@ -92,24 +92,86 @@
92
92
  }
93
93
 
94
94
  .btn-danger {
95
- // Pin hover/active to the pressed-danger token so Bootstrap cannot auto-shade the fill
96
- // off-spec — the same treatment .btn-primary gets above. That is what stops the label
97
- // flipping between states: once the fill stops moving, color-contrast() resolves the
98
- // same way on every state.
99
- //
100
- // The label is deliberately NOT pinned to white. --wui-danger (#F8285A) carries white
101
- // at 3.86:1 and --wui-danger-active (#E82646) at 4.38:1, both under the 4.5:1 AA floor,
102
- // so forcing white is a serious color-contrast violation — axe flags it on BOTH
103
- // surfaces. Bootstrap's automatic choice (the dark label, 5.44:1) is the accessible one
104
- // and is what has always shipped.
105
- //
106
- // If white-on-red is wanted — it is the conventional destructive look — the fix is to
107
- // darken --wui-danger to about #C81340 (white 5.80:1), which is a brand decision, not a
108
- // per-component override.
109
- --bs-btn-hover-bg: var(--wui-danger-active);
110
- --bs-btn-hover-border-color: var(--wui-danger-active);
111
- --bs-btn-active-bg: var(--wui-danger-active);
112
- --bs-btn-active-border-color: var(--wui-danger-active);
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
@@ -152,6 +152,11 @@
152
152
  --wui-warning-5: #FEFBF2;
153
153
 
154
154
  --wui-danger-text: #b61d42;
155
+ // Deepened danger fill for surfaces that carry WHITE text (btn-danger). The brighter
156
+ // --wui-danger/-active below are too light for white (3.86:1 / 4.38:1, under AA); these
157
+ // clear it — #C81340 → 5.80:1, hover → higher. Keep white-on-danger off the other tokens.
158
+ --wui-danger-strong: #C81340;
159
+ --wui-danger-strong-hover: #AB1036;
155
160
  --wui-danger-active: #E82646;
156
161
  --wui-danger: #F8285A;
157
162
  --wui-danger-50: #FC94AD;
@@ -284,10 +284,6 @@ textarea.textarea:focus {
284
284
  border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
285
285
  box-shadow: none;
286
286
  }
287
- .input.is-invalid,
288
- .input[aria-invalid="true"] {
289
- padding-right: 41px;
290
- }
291
287
  .field-error {
292
288
  font-size: 12px;
293
289
  line-height: normal;
@@ -393,7 +389,7 @@ textarea.textarea:focus {
393
389
  pointer-events: none;
394
390
  }
395
391
 
396
- // Error state — danger-tinted border + room for a trailing affix.
392
+ // Error state — danger-tinted border, plus a danger glyph at the trailing edge.
397
393
  .input.error,
398
394
  .input.error:focus,
399
395
  .textarea.error,
@@ -402,8 +398,19 @@ textarea.textarea:focus {
402
398
  border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
403
399
  box-shadow: none;
404
400
  }
405
- .input.error {
401
+
402
+ // The trailing error glyph. Scoped with the child combinator to a PLAIN field only:
403
+ // a shell-wrapped input (leading icon / unit / password / trailing action) is nested
404
+ // one level deeper, so it keeps its own right edge and never collides with the glyph.
405
+ // Covers both error paths — the `.error` class and `[aria-invalid]` / `.is-invalid`.
406
+ .field > .input.error,
407
+ .field > .input.is-invalid,
408
+ .field > .input[aria-invalid="true"] {
406
409
  padding-right: 41px;
410
+ 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");
411
+ background-repeat: no-repeat;
412
+ background-position: right 14px center;
413
+ background-size: 18px;
407
414
  }
408
415
  // Dark mode: keep the value in an errored field legible.
409
416
  :root[data-theme="dark"] .input.error,
@@ -526,7 +533,7 @@ textarea.textarea:focus {
526
533
  display: flex;
527
534
  flex-direction: column;
528
535
  align-items: flex-start;
529
- gap: 6px;
536
+ gap: 4px;
530
537
  margin-bottom: 16px;
531
538
  }
532
539
  .field > .label,
@@ -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;