@webority/theme 0.52.1 → 0.53.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.52.1",
3
+ "version": "0.53.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": {
@@ -106,7 +106,8 @@
106
106
  .btn-outline-primary {
107
107
  --bs-btn-color: var(--color-primary-ink);
108
108
  --bs-btn-border-color: var(--color-primary);
109
- --bs-btn-hover-color: var(--wui-primary-text);
109
+ // The hover fill is the theme's primary (the lifted one in dark mode), so its label is too.
110
+ --bs-btn-hover-color: var(--color-primary-text);
110
111
  --bs-btn-hover-bg: var(--color-primary);
111
112
  --bs-btn-hover-border-color: var(--color-primary);
112
113
  --bs-btn-active-color: var(--wui-primary-text);
@@ -131,6 +132,22 @@
131
132
  --bs-btn-disabled-border-color: var(--wui-danger-strong);
132
133
  }
133
134
 
135
+ // Neutral outline on the theme's text tokens. Bootstrap's compiled default draws it in
136
+ // $secondary, which drops to about 2.4:1 on a dark surface.
137
+ .btn-outline-secondary {
138
+ --bs-btn-color: var(--color-text-muted);
139
+ --bs-btn-border-color: var(--color-border-strong);
140
+ --bs-btn-hover-color: var(--color-text);
141
+ --bs-btn-hover-bg: var(--color-bg-muted);
142
+ --bs-btn-hover-border-color: var(--color-border-strong);
143
+ --bs-btn-active-color: var(--color-text);
144
+ --bs-btn-active-bg: var(--color-border);
145
+ --bs-btn-active-border-color: var(--color-border-strong);
146
+ --bs-btn-disabled-color: var(--color-text-muted);
147
+ --bs-btn-disabled-border-color: var(--color-border);
148
+ --bs-btn-focus-box-shadow: 0 0 0 0.25rem var(--bs-focus-ring-color);
149
+ }
150
+
134
151
  // Secondary-danger — the destructive counterpart of .btn-secondary: a soft red wash
135
152
  // instead of a solid red fill, for a quieter destructive action (e.g. a "Remove" that
136
153
  // shouldn't shout like a solid .btn-danger). Mirrors .btn-secondary exactly — low-alpha
@@ -148,6 +165,9 @@
148
165
  --bs-btn-active-color: var(--color-danger-text);
149
166
  --bs-btn-active-bg: color-mix(in srgb, var(--color-danger) 26%, transparent);
150
167
  --bs-btn-active-border-color: transparent;
168
+ // Bootstrap builds the focus ring from --bs-btn-focus-shadow-rgb, which only its own
169
+ // variants set; a custom variant needs the ring itself or keyboard focus shows nothing.
170
+ --bs-btn-focus-box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--color-danger) 50%, transparent);
151
171
  }
152
172
 
153
173
  // Soft neutral tinted button — a quieter alternative to .btn-light for a secondary
@@ -162,6 +182,7 @@
162
182
  --bs-btn-active-bg: var(--color-border-strong);
163
183
  --bs-btn-active-color: var(--color-text);
164
184
  --bs-btn-active-border-color: transparent;
185
+ --bs-btn-focus-box-shadow: 0 0 0 0.25rem var(--bs-focus-ring-color);
165
186
  }
166
187
 
167
188
  // Light outline for a dark banner/hero CTA, where .btn-outline-primary's brand-coloured
package/scss/_tokens.scss CHANGED
@@ -272,11 +272,11 @@
272
272
  --wui-dark-text: #e6e8ee;
273
273
  --wui-dark-text-muted: #b4b9c6;
274
274
  --wui-dark-text-subtle: #8a90a0;
275
- // Primary as TEXT and as the fill under near-black ink. Lifted from #6B83EC, which measured
276
- // 3.75:1 as text on its own soft tint over the muted fill (avatar initials, selected rows, primary
277
- // pills, the dropzone's Browse link); this clears 4.5:1 on every dark surface and tint.
278
- --wui-dark-primary: #8097F0;
279
- --wui-dark-primary-hover:#93A6F2;
275
+ // Primary as TEXT, rings and focus borders, and as the fill under near-black ink: the brand
276
+ // primary lifted until it clears 4.5:1 on every dark surface and tint (_variables.scss), so a
277
+ // brand set with wui-brand-primary reaches dark mode. A runtime override must clear the same.
278
+ --wui-dark-primary: #{wui-hex($wui-dark-primary)};
279
+ --wui-dark-primary-hover: #{wui-hex($wui-dark-primary-hover)};
280
280
 
281
281
  // Dark mode shadow remaps — lighter shadows (white ink) for visibility on dark surfaces.
282
282
  --wui-dark-shadow-thumb: 0 2px 6px rgba(255, 255, 255, 0.1);
@@ -123,13 +123,42 @@ $wui-ink-surfaces: (#FFFFFF, #F4F6F8, #F5F6FA, #F9FBFC);
123
123
  @return #0F0F0F;
124
124
  }
125
125
 
126
+ // ---- The primary on a dark surface -----------------------------------
127
+ // Dark mode paints text, rings, focus borders and the fill under the near-black label with one
128
+ // colour, --wui-dark-primary. A brand primary is chosen as a light-mode fill and sinks into a dark
129
+ // page (an indigo or royal blue measures under 3:1 there), so the dark primary is $primary mixed
130
+ // with white, in 2% steps, until it clears 4.5:1 on every dark surface (the --wui-dark-bg-*
131
+ // hexes), on its own 15% avatar tint, on the brand's 20% soft tint and under the near-black label.
132
+ $wui-dark-ink-surfaces: (#0F1117, #0D0F15, #232733, #181B23);
133
+
134
+ @function wui-dark-primary-worst($ink, $primary) {
135
+ $tints: (color.mix($ink, #181B23, 15%), color.mix($primary, #232733, 20%), #0F0F0F);
136
+ @return wui-worst-contrast($ink, list.join($wui-dark-ink-surfaces, $tints));
137
+ }
138
+
139
+ @function wui-dark-primary($primary) {
140
+ $ink: $primary;
141
+ $step: 0;
142
+ @while wui-dark-primary-worst($ink, $primary) < $wui-ink-floor and $step < 50 {
143
+ $step: $step + 1;
144
+ $ink: color.mix(#FFFFFF, $primary, $step * 2%);
145
+ }
146
+ @return rgb(
147
+ math.round(color.channel($ink, "red", $space: rgb)),
148
+ math.round(color.channel($ink, "green", $space: rgb)),
149
+ math.round(color.channel($ink, "blue", $space: rgb))
150
+ );
151
+ }
152
+
126
153
  $wui-primary-ink: wui-primary-ink($primary) !default;
127
154
  $wui-primary-text: wui-on-primary($primary) !default;
155
+ $wui-dark-primary: wui-dark-primary($primary) !default;
156
+ $wui-dark-primary-hover: color.mix(#FFFFFF, $wui-dark-primary, 12%) !default;
128
157
  $link-color: $wui-primary-ink !default;
129
158
 
130
159
  // Brand helper — set a product's primary ONCE and have the theme-colour map
131
160
  // (so every .btn-primary / .bg-primary / .text-primary utility), $link-color,
132
- // $link-hover-color, the primary ink and the on-primary label follow, instead of
161
+ // $link-hover-color, the primary ink, the on-primary label and the dark primary follow, instead of
133
162
  // overriding each in turn. Call it in the product's pre-bootstrap variables,
134
163
  // BEFORE `bootstrap/scss/bootstrap`, e.g.:
135
164
  //
@@ -144,6 +173,8 @@ $link-color: $wui-primary-ink !default;
144
173
  $theme-colors: map-merge($theme-colors, ("primary": $primary)) !global;
145
174
  $wui-primary-ink: wui-primary-ink($primary) !global;
146
175
  $wui-primary-text: wui-on-primary($primary) !global;
176
+ $wui-dark-primary: wui-dark-primary($primary) !global;
177
+ $wui-dark-primary-hover: color.mix(#FFFFFF, $wui-dark-primary, 12%) !global;
147
178
  $link-color: $wui-primary-ink !global;
148
179
  $link-hover-color: mix(black, $wui-primary-ink, 12%) !global;
149
180
  }
@@ -214,8 +214,8 @@
214
214
  --color-primary: var(--wui-dark-primary);
215
215
  --color-primary-hover: var(--wui-dark-primary-hover);
216
216
  --color-primary-soft: color-mix(in srgb, var(--wui-primary) 20%, transparent);
217
- // Dark mode primary text: #fff on #6B83EC measures 3.5:1, below WCAG AA (4.5:1).
218
- // Map to a near-black for safe contrast (6.9:1 on --wui-dark-primary) when text sits on primary fill.
217
+ // White fails on the lifted dark primary; the derivation in _variables.scss keeps this near-black
218
+ // at 4.5:1 or better on it.
219
219
  --color-primary-text: #0F0F0F;
220
220
  // The dark primary is already lifted to clear 4.5:1 on every dark surface and tint.
221
221
  --color-primary-ink: var(--wui-dark-primary);
@@ -238,6 +238,9 @@
238
238
  .wui-modal-footer {
239
239
  flex: 0 0 auto;
240
240
  display: flex;
241
+ // On a narrow phone, or with enlarged text spacing, a button that no longer fits moves to its
242
+ // own line instead of squeezing its label onto two.
243
+ flex-wrap: wrap;
241
244
  align-items: center;
242
245
  justify-content: flex-end;
243
246
  gap: 10px;
@@ -645,7 +648,6 @@
645
648
  .wui-toast--warning {
646
649
  --wui-toast-accent: var(--color-warning);
647
650
  }
648
- .wui-toast--error,
649
651
  .wui-toast--danger {
650
652
  --wui-toast-accent: var(--color-danger);
651
653
  }
@@ -769,11 +769,19 @@
769
769
  width: var(--app-sidebar-w);
770
770
  padding: 20px 16px;
771
771
  transform: translateX(-100%);
772
- transition: transform 0.24s var(--wui-ease-out-quart);
772
+ // Hidden once the slide-out ends so a closed drawer's links leave the Tab order.
773
+ visibility: hidden;
774
+ transition:
775
+ transform 0.24s var(--wui-ease-out-quart),
776
+ visibility 0s linear 0.24s;
773
777
  box-shadow: var(--shadow-overlay-lg);
774
778
  }
775
779
  .app-shell.app-shell.mobile-open .app-sidebar {
776
780
  transform: translateX(0);
781
+ visibility: visible;
782
+ transition:
783
+ transform 0.24s var(--wui-ease-out-quart),
784
+ visibility 0s;
777
785
  }
778
786
  // The rail is a desktop affordance; on a drawer it makes no sense, so the
779
787
  // drawer always shows full labels even when the desktop state is collapsed.