@stackoverflow/stacks 2.0.9 → 2.1.0

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.
Files changed (98) hide show
  1. package/LICENSE.MD +1 -1
  2. package/README.md +7 -9
  3. package/dist/css/stacks.css +193 -206
  4. package/dist/css/stacks.min.css +1 -1
  5. package/dist/js/stacks.js +1 -1
  6. package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +2 -3
  7. package/lib/components/activity-indicator/activity-indicator.visual.test.ts +2 -3
  8. package/lib/components/anchor/anchor.a11y.test.ts +2 -4
  9. package/lib/components/anchor/anchor.visual.test.ts +2 -4
  10. package/lib/components/avatar/avatar.a11y.test.ts +2 -3
  11. package/lib/components/avatar/avatar.visual.test.ts +2 -3
  12. package/lib/components/award-bling/award-bling.a11y.test.ts +2 -4
  13. package/lib/components/award-bling/award-bling.visual.test.ts +2 -4
  14. package/lib/components/badge/badge.a11y.test.ts +7 -16
  15. package/lib/components/badge/badge.visual.test.ts +8 -21
  16. package/lib/components/banner/banner.a11y.test.ts +2 -3
  17. package/lib/components/banner/banner.visual.test.ts +2 -3
  18. package/lib/components/block-link/block-link.a11y.test.ts +4 -9
  19. package/lib/components/block-link/block-link.less +7 -10
  20. package/lib/components/block-link/block-link.visual.test.ts +4 -9
  21. package/lib/components/breadcrumbs/breadcrumbs.a11y.test.ts +2 -3
  22. package/lib/components/breadcrumbs/breadcrumbs.visual.test.ts +2 -3
  23. package/lib/components/button/button.a11y.test.ts +2 -3
  24. package/lib/components/button/button.less +70 -35
  25. package/lib/components/button/button.visual.test.ts +2 -3
  26. package/lib/components/card/card.a11y.test.ts +2 -3
  27. package/lib/components/card/card.visual.test.ts +3 -6
  28. package/lib/components/check-control/check-control.a11y.test.ts +2 -4
  29. package/lib/components/check-control/check-control.visual.test.ts +2 -4
  30. package/lib/components/check-group/check-group.a11y.test.ts +2 -4
  31. package/lib/components/check-group/check-group.visual.test.ts +2 -4
  32. package/lib/components/checkbox_radio/checkbox_radio.a11y.test.ts +2 -4
  33. package/lib/components/checkbox_radio/checkbox_radio.less +1 -13
  34. package/lib/components/checkbox_radio/checkbox_radio.visual.test.ts +2 -4
  35. package/lib/components/code-block/code-block.a11y.test.ts +2 -4
  36. package/lib/components/code-block/code-block.visual.test.ts +2 -4
  37. package/lib/components/description/description.a11y.test.ts +2 -4
  38. package/lib/components/description/description.visual.test.ts +2 -4
  39. package/lib/components/empty-state/empty-state.a11y.test.ts +2 -3
  40. package/lib/components/empty-state/empty-state.visual.test.ts +2 -3
  41. package/lib/components/expandable/expandable.a11y.test.ts +2 -3
  42. package/lib/components/expandable/expandable.visual.test.ts +2 -3
  43. package/lib/components/input-fill/input-fill.a11y.test.ts +2 -3
  44. package/lib/components/input-fill/input-fill.visual.test.ts +2 -3
  45. package/lib/components/input-message/input-message.a11y.test.ts +2 -3
  46. package/lib/components/input-message/input-message.visual.test.ts +2 -3
  47. package/lib/components/input_textarea/input_textarea.a11y.test.ts +4 -7
  48. package/lib/components/input_textarea/input_textarea.less +2 -20
  49. package/lib/components/input_textarea/input_textarea.visual.test.ts +4 -7
  50. package/lib/components/label/label.a11y.test.ts +2 -3
  51. package/lib/components/label/label.visual.test.ts +2 -3
  52. package/lib/components/link/link.a11y.test.ts +2 -3
  53. package/lib/components/link/link.visual.test.ts +2 -3
  54. package/lib/components/link-preview/link-preview.a11y.test.ts +2 -3
  55. package/lib/components/link-preview/link-preview.visual.test.ts +3 -3
  56. package/lib/components/menu/menu.a11y.test.ts +2 -3
  57. package/lib/components/menu/menu.visual.test.ts +2 -3
  58. package/lib/components/modal/modal.a11y.test.ts +2 -3
  59. package/lib/components/modal/modal.visual.test.ts +2 -3
  60. package/lib/components/navigation/navigation.a11y.test.ts +2 -3
  61. package/lib/components/navigation/navigation.less +3 -1
  62. package/lib/components/navigation/navigation.visual.test.ts +3 -6
  63. package/lib/components/notice/notice.a11y.test.ts +2 -3
  64. package/lib/components/notice/notice.visual.test.ts +2 -3
  65. package/lib/components/page-title/page-title.a11y.test.ts +2 -3
  66. package/lib/components/page-title/page-title.visual.test.ts +2 -3
  67. package/lib/components/pagination/pagination.a11y.test.ts +2 -3
  68. package/lib/components/pagination/pagination.less +9 -0
  69. package/lib/components/pagination/pagination.visual.test.ts +2 -3
  70. package/lib/components/progress-bar/progress-bar.a11y.test.ts +7 -18
  71. package/lib/components/progress-bar/progress-bar.visual.test.ts +7 -18
  72. package/lib/components/select/select.less +1 -15
  73. package/lib/components/spinner/spinner.a11y.test.ts +2 -3
  74. package/lib/components/spinner/spinner.visual.test.ts +4 -7
  75. package/lib/components/table/table.a11y.test.ts +3 -4
  76. package/lib/components/table/table.visual.test.ts +2 -3
  77. package/lib/components/tag/tag.a11y.test.ts +2 -3
  78. package/lib/components/tag/tag.less +27 -21
  79. package/lib/components/tag/tag.visual.test.ts +3 -6
  80. package/lib/components/toast/toast.a11y.test.ts +2 -3
  81. package/lib/components/toast/toast.visual.test.ts +2 -3
  82. package/lib/components/toggle-switch/toggle-switch.a11y.test.ts +3 -6
  83. package/lib/components/toggle-switch/toggle-switch.less +5 -16
  84. package/lib/components/toggle-switch/toggle-switch.visual.test.ts +3 -7
  85. package/lib/components/topbar/topbar.less +61 -39
  86. package/lib/components/topbar/topbar.visual.test.ts +188 -0
  87. package/lib/components/uploader/uploader.less +1 -1
  88. package/lib/exports/__snapshots__/color-mixins.less.test.ts.snap +2 -0
  89. package/lib/exports/__snapshots__/color.less.test.ts.snap +12 -0
  90. package/lib/exports/color-sets.less +17 -7
  91. package/lib/exports/mixins.less +33 -0
  92. package/lib/input-utils.less +0 -3
  93. package/lib/test/a11y-test-utils.ts +94 -0
  94. package/lib/test/assertions.ts +10 -3
  95. package/lib/test/test-utils.ts +152 -300
  96. package/lib/test/visual-test-utils.ts +58 -0
  97. package/package.json +7 -8
  98. package/lib/components/popover/tooltip.visual.test.ts +0 -31
@@ -23,7 +23,6 @@
23
23
  --theme-topbar-search-background: var(--theme-topbar-background-color, var(--white));
24
24
  --theme-topbar-search-placeholder: var(--theme-topbar-item-color, var(--black-400));
25
25
  --theme-topbar-search-border: var(--theme-topbar-item-color, var(--black-400));
26
- --theme-topbar-search-border-focus: var(--theme-topbar-item-color, var(--black-400));
27
26
 
28
27
  // Search switcher
29
28
  --theme-topbar-select-color: var(--theme-topbar-item-color, var(--black-400));
@@ -38,6 +37,44 @@
38
37
 
39
38
  .highcontrast-mode({ border-bottom: 1px solid currentColor; });
40
39
 
40
+ // Overrides for focus style colors in forced light variant
41
+ &&__light {
42
+ --focus-neutral: .set-white()[default]; // forces white for inner focus ring color
43
+ }
44
+
45
+ .dark-mode({
46
+ &.s-topbar__light {
47
+ --focus-theme: var(--theme-dark-secondary-custom-200, .set-theme-secondary-default()[200]);
48
+ }
49
+ });
50
+
51
+ .highcontrast-dark-mode({
52
+ &.s-topbar__light {
53
+ --focus-theme: .set-theme-secondary-default()[200];
54
+ }
55
+ });
56
+
57
+ // Overrides for focus style colors in forced dark variant
58
+ .highcontrast-mode({
59
+ &.s-topbar__dark {
60
+ --focus-theme: .theme-dark-default()[secondary];
61
+ }
62
+ });
63
+
64
+ &&__dark {
65
+ --focus-neutral: .set-black()[600]; // set to match .s-topbar__dark --theme-topbar-background-color;
66
+ --focus-theme: var(--theme-dark-secondary-custom-400, .theme-dark-default()[secondary]);
67
+ }
68
+
69
+ // focus styles
70
+ a&--logo,
71
+ & &--content &--item:not(&--item__unset),
72
+ &--notice {
73
+ &:focus-visible {
74
+ .focus-styles(true);
75
+ }
76
+ }
77
+
41
78
  // Wraps the content so the topbar stretches 100% w/ content at some value below that
42
79
  .s-topbar--container {
43
80
  width: var(--s-full); // wmx12; Consumers should use atomic classes to override this
@@ -54,14 +91,17 @@
54
91
  display: flex;
55
92
  align-items: center;
56
93
  background-color: transparent;
94
+ border-radius: var(--br-sm);
57
95
  }
58
96
 
59
- a.s-topbar--logo:hover {
60
- background-color: var(--theme-topbar-item-background-hover, var(--black-200));
61
- }
97
+ a.s-topbar--logo {
98
+ &:hover {
99
+ background-color: var(--theme-topbar-item-background-hover, var(--black-200));
100
+ }
62
101
 
63
- a.s-topbar--logo.is-selected {
64
- background-color: var(--theme-topbar-item-background-hover, var(--black-200));
102
+ &.is-selected {
103
+ background-color: var(--theme-topbar-item-background-hover, var(--black-200));
104
+ }
65
105
  }
66
106
 
67
107
  .s-topbar--menu-btn {
@@ -125,9 +165,6 @@
125
165
  }
126
166
 
127
167
  .s-navigation {
128
- .s-navigation--item:focus-visible {
129
- box-shadow: var(--theme-topbar-search-shadow-focus, 0 0 0 var(--su-static4) var(--focus-ring));
130
- }
131
168
  .s-navigation--item:not(.is-selected) {
132
169
  color: var(--theme-topbar-item-color, var(--black-400));
133
170
  }
@@ -139,10 +176,6 @@
139
176
  }
140
177
  .s-popover .s-navigation {
141
178
  .s-navigation--item {
142
- &:focus-visible {
143
- box-shadow: var(0 0 0 var(--su-static4) var(--focus-ring));
144
- }
145
-
146
179
  &:not(.is-selected) {
147
180
  &:hover {
148
181
  color: var(--black-600);
@@ -170,8 +203,6 @@
170
203
  --theme-topbar-search-background: var(--_white-static);
171
204
  --theme-topbar-search-placeholder: .set-black()[400];
172
205
  --theme-topbar-search-border: .set-black()[300];
173
- --theme-topbar-search-border-focus: .set-blue()[400];
174
- --theme-topbar-search-shadow-focus: 0 0 0 var(--su-static4) var(--focus-ring);
175
206
 
176
207
  // Search switcher
177
208
  --theme-topbar-select-color: .set-black()[500];
@@ -222,8 +253,6 @@
222
253
  --theme-topbar-search-background: lighten(@topbar-actual-background, @topbar-search-lightness-increase);
223
254
  --theme-topbar-search-placeholder: lighten(@topbar-actual-background, 60% + @topbar-search-lightness-increase);
224
255
  --theme-topbar-search-border: lighten(@topbar-actual-background, 20% + @topbar-search-lightness-increase);
225
- --theme-topbar-search-border-focus: lighten(@topbar-actual-background, 45% + @topbar-search-lightness-increase);
226
- --theme-topbar-search-shadow-focus: 0 0 0 var(--su-static4) fade(.set-white()[default], 30%);
227
256
 
228
257
  // Search switcher
229
258
  --theme-topbar-select-color: lighten(@topbar-actual-background, 60% + @topbar-search-lightness-increase);
@@ -290,16 +319,14 @@
290
319
  text-decoration: none;
291
320
  white-space: nowrap;
292
321
  position: relative;
322
+ border-radius: var(--br-sm);
293
323
 
294
324
  &:hover,
295
- &:focus,
296
325
  &.is-selected,
297
- &.is-selected:hover,
298
- &.is-selected:focus {
326
+ &.is-selected:hover {
299
327
  color: var(--theme-topbar-item-color-hover, var(--black-600));
300
328
  background-color: var(--theme-topbar-item-background-hover, var(--black-200));
301
329
  text-decoration: none;
302
- outline: none;
303
330
 
304
331
  .s-activity-indicator {
305
332
  top: calc(50% - calc(var(--su16) + var(--su2))); // 50% - 18px
@@ -343,16 +370,14 @@
343
370
 
344
371
  .topbar-notice-styles(transparent, transparent, var(--theme-topbar-item-color, var(--black-400)));
345
372
 
346
- &:hover,
347
- &:focus {
373
+ &:hover {
348
374
  .topbar-notice-styles(var(--theme-topbar-item-background-hover, var(--black-200)), var(--theme-topbar-item-background-hover, var(--black-200)), var(--theme-topbar-item-color-hover, var(--black-600)));
349
375
  }
350
376
 
351
377
  &.is-unread {
352
378
  .topbar-notice-styles(var(--theme-primary), var(--theme-primary), var(--white));
353
379
 
354
- &:hover,
355
- &:focus {
380
+ &:hover {
356
381
  .topbar-notice-styles(var(--theme-primary-500), var(--theme-primary-500), var(--white));
357
382
  }
358
383
  }
@@ -374,18 +399,16 @@
374
399
  flex-grow: 1;
375
400
 
376
401
  .s-input {
377
- border-color: var(--theme-topbar-search-border, var(--black-300));
402
+ &:not(:focus-visible) {
403
+ box-shadow: var(--theme-topbar-search-shadow);
404
+ }
405
+
378
406
  background-color: var(--theme-topbar-search-background, var(--white));
379
- box-shadow: var(--theme-topbar-search-shadow);
407
+ border-color: var(--theme-topbar-search-border, var(--black-300));
380
408
  color: var(--theme-topbar-search-color, var(--black-500));
381
409
  display: block;
382
410
  line-height: @inputLineHeights;
383
411
 
384
- &:focus {
385
- border-color: var(--theme-topbar-search-border-focus, var(--blue-400));
386
- box-shadow: var(--theme-topbar-search-shadow-focus, 0 0 0 var(--su-static4) var(--focus-ring));
387
- }
388
-
389
412
  &::placeholder {
390
413
  color: var(--theme-topbar-search-placeholder, var(--black-400));
391
414
  font-style: normal;
@@ -411,19 +434,18 @@
411
434
  }
412
435
 
413
436
  .s-select > select {
437
+ border-color: var(--theme-topbar-search-border, var(--black-300));
438
+
439
+ &:focus-visible {
440
+ z-index: var(--zi-selected);
441
+ }
442
+
414
443
  .brr0;
415
444
  height: 100%;
416
445
  line-height: @inputLineHeights;
417
-
418
- border-color: var(--theme-topbar-search-border, var(--black-300));
419
446
  background-color: var(--theme-topbar-select-background, var(--black-200));
420
447
  color: var(--theme-topbar-select-color, var(--black-500));
421
448
 
422
- &:focus {
423
- border-color: var(--theme-topbar-search-border-focus, var(--blue-400));
424
- box-shadow: var(--theme-topbar-search-shadow-focus, 0 0 0 var(--su-static4) var(--focus-ring));
425
- z-index: var(--zi-selected);
426
- }
427
449
  }
428
450
 
429
451
  // Drop the left border of the search input when it is next to the select
@@ -0,0 +1,188 @@
1
+ import { runVisualTests } from "../../test/visual-test-utils";
2
+ import "../../index";
3
+ import {
4
+ IconAchievements,
5
+ IconAlert,
6
+ IconHelp,
7
+ IconInbox,
8
+ IconModerator,
9
+ IconReviewQueue,
10
+ IconSearch,
11
+ IconStackExchange,
12
+ } from "@stackoverflow/stacks-icons/icons";
13
+
14
+ const base64Image =
15
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATwAAAE8CAMAAABq2/00AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhQTFRF/3IA/////44y/8OS/+fU//Xs/6tn/9e2EhvbMQAABH9JREFUeNrs3e1y2ygAhWELJHH/d7zuJpN1XAnxoe408Ly/6xnnHXEOIEwfDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gbAGEhrZl2VloY20PIk8tAzZbfkXA7eeuHyysVfdFMsXiY66ptiWF8Re05BdxF7HkBV7PUNW7FVx4E7sVSwrDjBw2+pC7NWwHtkTe3ULM7En9v6O2ONF7Ik9sSf2IPbEntgTexB7t8feTsynniD2Oh6tXex1hFoUe02kaxVi72JMZs+Rib0LLdkXFGLvaECWjkOxdxVmUex1rB6S2Kss2lcytSH2LnVkakPsXQ7E8xwTe5fuMrUh9vKj8CP4xN7lNOXc3mnwib1M2X7Z28VeS93mg0/sFZTG6WCMVSE51fqsYL4s9jpqQ+yV1caxErHXUxtir6c2xF5HbYRF7LXXhtjrqQ2x11MbYu9tLG4VtSH2empD7BUNxpPaSGKvPPh2sdcxX45i78baEHs18+X32hB7NfPl99oQex21MVHsxViaR8W1MU3s/XpMSvWF0tqYJfY+BmMq01dcG+scP6/6enTK9BW+1J0j9l5aYN0rP5BzM0XsbSUnAlpqY4LYezexFeRSbpslH3uDXdF64OG6est258PohXE8BK+7o6g29rJTQsPt1F3qK9qdTwMP2Wz2X1Vv0UvdbeQ53nl2Xesr2Z0Pww7Zs5lsafWW1MY+6JDNxv5/+kLb5/fXfzLirRdhKSGnr6A2tkGvJoxLGZnqva6NkAbdgA+pUN9a+QuWYYPu+98ee/W1/PJgoMG7lek7nW3UHiEdbKpcqu+kO2L9b4aG0rcWjt5jfWf2Z7kCeE891XtSG/O86A5d+tbZj1h0VW+c/ja90FG9uxMCFdWb/+SkZ1Oaq/d1vjzvXfvF+tLp+aiZT5S1Vm90lrFj02B3bW1d9X7T92u+7Aq9qk2D+Fob/v/Myu7YostC76xe9K966evfr5+7ekuXHVy1V6+R275pEMd3kPbGB+Syeod/8D7Sf41NBvPdMf7a4uXpaTGY0zd+4r0vDlKtwdPqHf/BOzzf8zQYqvRtcz54mXfUNUVyUL0T7AhcrBbKY/C36p1geVEy1y00+F3fBA9eKFxnlRXJa/XOcKxnqeF6Pv1VvTPc7RuXai6K5LN6Z9hQSUsb2Rh86pviUultaScTg/sMD15YeqmcT8/bFx1Foi/umk9P3hcMfqTenrabBT5j8DGTwHivwelOCTwNrnfJm/Ol2U0GZ74WdE+dBn9+Z/z2J9UZ7IjB7dZv8gPl9RRJIq89BiN57QZ38trn04G8k62E6xjcHuQ1D+JEXrvBnbz2+XQgr30+/SCveT69ktceg4m8doOBvDG/CXnkkTezvOXPQB555JFHHnnkkUceeeSRRx555JFHHnnkkWcnmTzyyCOPPPLII4888sgjjzzfhDzyyCOPPPL+9Ff+eyCPPPLII4888sgjjzzyyCOPPPLII4888sgDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPy//CPAAPiZOI3BPDFQAAAAAElFTkSuQmCC";
16
+
17
+ // Children
18
+ const children = {
19
+ hamburger: {
20
+ unselected: `<a href="#" class="s-topbar--menu-btn" aria-label="menu"><span></span></a>`,
21
+ selected: `<a href="#" class="s-topbar--menu-btn is-selected" aria-label="menu"><span></span></a>`,
22
+ },
23
+ logo: `<a href="#" class="s-topbar--logo" aria-label="logo">${IconStackExchange}</a>`,
24
+ notice: {
25
+ default: `<a href="#" class="s-topbar--notice">old</a>`,
26
+ unread: `<a href="#" class="s-topbar--notice is-unread">new</a>`,
27
+ },
28
+ navigation: {
29
+ minimal: `<ol class="s-navigation fw-nowrap"><li><a href="#" class="s-navigation--item">Products</a></li></ol>`,
30
+ full: `<ol class="s-navigation fw-nowrap sm:d-none">
31
+ <li><a href="#" class="s-navigation--item">About</a></li>
32
+ <li><a href="#" class="s-navigation--item is-selected">Products</a></li>
33
+ <li><a href="#" class="s-navigation--item">For Teams</a></li>
34
+ </ol>`,
35
+ },
36
+ searchbar: {
37
+ input: `<div class="s-topbar--searchbar--input-group">
38
+ <input type="text" placeholder="Search…" value="" autocomplete="off" class="s-input s-input__search">
39
+ ${IconSearch.replace("svg-icon iconSearch", "s-input-icon s-input-icon__search svg-icon iconSearch")}
40
+ </div>`,
41
+ select: `<div class="s-select">
42
+ <select aria-label="Search scope">
43
+ <option selected="selected">All</option>
44
+ <option>Public</option>
45
+ <option>Private Team 1</option>
46
+ </select>
47
+ </div>`,
48
+ },
49
+ content: {
50
+ items: `
51
+ <li>
52
+ <a href="#" class="s-topbar--item" aria-label="Inbox">
53
+ ${IconInbox} <span class="s-activity-indicator s-activity-indicator__danger">3</span>
54
+ </a>
55
+ </li>
56
+ <li>
57
+ <a href="#" class="s-topbar--item" aria-label="Achievements">
58
+ ${IconAchievements} <span class="s-activity-indicator s-activity-indicator__success">+10</span>
59
+ </a>
60
+ </li>
61
+ <li>
62
+ <a href="#" class="s-topbar--item" aria-label="Review queues">
63
+ ${IconReviewQueue}
64
+ <div class="s-activity-indicator s-activity-indicator__danger">
65
+ <div class="v-visible-sr">New activity</div>
66
+ </div>
67
+ </a>
68
+ </li>
69
+ <li><a href="#" class="s-topbar--item" aria-label="Help center">${IconHelp}</a></li>
70
+ <li><a href="#" class="s-topbar--item" aria-label="Site switcher">${IconStackExchange}</a></li>
71
+ <li><a href="#" class="s-topbar--item" title="Moderator inbox">${IconModerator}</a></li>
72
+ <li>
73
+ <a href="#" class="s-topbar--item" title="8 posts flagged for moderator attention">
74
+ <span class="s-badge s-badge__bounty">8</span>
75
+ </a>
76
+ </li>
77
+ `,
78
+ unset: `<li><a href="#" class="s-topbar--item" aria-label="Unset item">${IconAlert}</a></li>`,
79
+ loggedOut: `
80
+ <li>
81
+ <a href="#" class="s-topbar--item s-topbar--item__unset s-btn s-btn__outlined ws-nowrap">Log in</a>
82
+ </li>
83
+ <li>
84
+ <a href="#" class="s-topbar--item s-topbar--item__unset s-btn s-btn__filled ws-nowrap">Sign up</a>
85
+ </li>
86
+ `,
87
+ loggedIn: `<li>
88
+ <a href="#" class="s-topbar--item s-user-card s-user-card__small">
89
+ <span class="s-avatar s-avatar__24 s-user-card--avatar">
90
+ <img class="s-avatar--image" alt="demo avatar" src="${base64Image}">
91
+ <span class="v-visible-sr">John Doe</span>
92
+ </span>
93
+ <div class="s-user-card--info">
94
+ <ul class="s-user-card--awards">
95
+ <li class="s-user-card--rep">3,145</li>
96
+ <li class="s-award-bling s-award-bling__gold">3</li>
97
+ <li class="s-award-bling s-award-bling__silver">9</li>
98
+ <li class="s-award-bling s-award-bling__bronze">20</li>
99
+ </ul>
100
+ </div>
101
+ </a>
102
+ </li>`,
103
+ },
104
+ };
105
+
106
+ const topbarChildren = ({
107
+ hamburger = "",
108
+ loggedIn = true,
109
+ navigation = "",
110
+ notice = "",
111
+ searchInput = false,
112
+ searchSelect = false,
113
+ unsetItem = false,
114
+ }: {
115
+ hamburger?: "" | "selected" | "unselected";
116
+ loggedIn?: boolean;
117
+ navigation?: "" | "minimal" | "full";
118
+ notice?: "" | "default" | "unread";
119
+ searchInput?: boolean;
120
+ searchSelect?: boolean;
121
+ unsetItem?: boolean;
122
+ }) => {
123
+ return `
124
+ <div class="s-topbar--container">
125
+ ${hamburger ? (hamburger === "unselected" ? children.hamburger.unselected : children.hamburger.selected) : ""}
126
+ ${children.logo}
127
+ ${notice ? (notice === "default" ? children.notice.default : children.notice.unread) : ""}
128
+ ${
129
+ navigation
130
+ ? `
131
+ <nav aria-label="Demo primary navigation" role="presentation">
132
+ <ol class="s-navigation fw-nowrap sm:d-none">
133
+ ${navigation === "minimal" ? children.navigation.minimal : children.navigation.full}
134
+ </ol>
135
+ </nav>
136
+ `
137
+ : ""
138
+ }
139
+ ${
140
+ searchInput
141
+ ? `
142
+ <form class="s-topbar--searchbar" autocomplete="off">
143
+ ${searchSelect ? children.searchbar.select : ""}
144
+ ${children.searchbar.input}
145
+ </form>
146
+ `
147
+ : ""
148
+ }
149
+ <nav class="s-topbar--navigation" aria-label="Demo secondary navigation" role="presentation">
150
+ <ol class="s-topbar--content">
151
+ ${unsetItem ? children.content.unset : ""}
152
+ ${children.content.items}
153
+ ${loggedIn ? children.content.loggedIn : children.content.loggedOut}
154
+ </ol>
155
+ </nav>
156
+ </div>
157
+ `;
158
+ };
159
+
160
+ // Configurations
161
+ const topbars = {
162
+ default: topbarChildren({ hamburger: "unselected" }),
163
+ advanced: topbarChildren({
164
+ navigation: "minimal",
165
+ notice: "default",
166
+ searchInput: true,
167
+ searchSelect: true,
168
+ unsetItem: true,
169
+ }),
170
+ input: topbarChildren({ navigation: "minimal", searchInput: true }),
171
+ alt: topbarChildren({
172
+ hamburger: "selected",
173
+ navigation: "full",
174
+ notice: "unread",
175
+ searchInput: true,
176
+ searchSelect: true,
177
+ unsetItem: true,
178
+ }),
179
+ };
180
+
181
+ describe("topbar", () => {
182
+ runVisualTests({
183
+ baseClass: "s-topbar",
184
+ tag: "header",
185
+ children: topbars,
186
+ variants: ["dark variant", "light variant"], // `variant` added to make file labeling clearer
187
+ });
188
+ });
@@ -91,7 +91,7 @@
91
91
  & &--input {
92
92
  &:focus:focus-visible + .s-uploader--container {
93
93
  background-color: var(--_up-bg-focus);
94
- box-shadow: 0 0 0 var(--su-static4) var(--_up-focus-ring-color);
94
+ .focus-styles();
95
95
  }
96
96
 
97
97
  cursor: pointer;
@@ -359,6 +359,8 @@ body .themed {
359
359
  --translucent-warning: hsla(47, 79%, 58%, 0.4);
360
360
  --translucent-error: hsla(358, 62%, 47%, 0.15);
361
361
  --translucent-muted: hsla(210, 8%, 15%, 0.1);
362
+ --focus-neutral: var(--white);
363
+ --focus-theme: var(--theme-secondary-400);
362
364
  --focus-ring: var(--theme-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.15));
363
365
  --focus-ring-success: hsla(140, 40%, 75%, 0.4);
364
366
  --focus-ring-warning: hsla(47, 79%, 58%, 0.4);
@@ -147,6 +147,8 @@ body:not(.theme-highcontrast).theme-system .theme-light__forced .themed {
147
147
  --translucent-warning: hsla(47, 79%, 58%, 0.4);
148
148
  --translucent-error: hsla(358, 62%, 47%, 0.15);
149
149
  --translucent-muted: hsla(210, 8%, 15%, 0.1);
150
+ --focus-neutral: var(--white);
151
+ --focus-theme: var(--theme-secondary-400);
150
152
  --focus-ring: var(--theme-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.15));
151
153
  --focus-ring-success: hsla(140, 40%, 75%, 0.4);
152
154
  --focus-ring-warning: hsla(47, 79%, 58%, 0.4);
@@ -280,6 +282,8 @@ body:not(.theme-highcontrast):not(.theme-dark) .theme-dark__forced .themed {
280
282
  --translucent-warning: hsla(47, 79%, 58%, 0.4);
281
283
  --translucent-error: hsla(358, 62%, 47%, 0.15);
282
284
  --translucent-muted: hsla(210, 8%, 15%, 0.1);
285
+ --focus-neutral: var(--white);
286
+ --focus-theme: var(--theme-secondary-400);
283
287
  --focus-ring: var(--theme-dark-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.25));
284
288
  --focus-ring-success: hsla(140, 40%, 75%, 0.4);
285
289
  --focus-ring-warning: hsla(47, 79%, 58%, 0.4);
@@ -411,6 +415,8 @@ body:not(.theme-highcontrast):not(.theme-dark) .theme-dark__forced .themed {
411
415
  --translucent-warning: hsla(47, 79%, 58%, 0.4);
412
416
  --translucent-error: hsla(358, 62%, 47%, 0.15);
413
417
  --translucent-muted: hsla(210, 8%, 15%, 0.1);
418
+ --focus-neutral: var(--white);
419
+ --focus-theme: var(--theme-secondary-400);
414
420
  --focus-ring: var(--theme-dark-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.25));
415
421
  --focus-ring-success: hsla(140, 40%, 75%, 0.4);
416
422
  --focus-ring-warning: hsla(47, 79%, 58%, 0.4);
@@ -542,6 +548,8 @@ body.theme-highcontrast.theme-system .theme-light__forced {
542
548
  --translucent-warning: hsla(47, 76%, 46%, 0.9);
543
549
  --translucent-error: hsla(358, 62%, 47%, 0.9);
544
550
  --translucent-muted: hsla(210, 8%, 55%, 0.95);
551
+ --focus-neutral: var(--white);
552
+ --focus-theme: var(--theme-secondary-400);
545
553
  --focus-ring: hsla(206, 100%, 40%, 0.9);
546
554
  --focus-ring-success: hsla(140, 40%, 40%, 0.9);
547
555
  --focus-ring-warning: hsla(47, 76%, 46%, 0.9);
@@ -655,6 +663,8 @@ body.theme-highcontrast:not(.theme-dark) .theme-dark__forced {
655
663
  --translucent-warning: hsla(47, 76%, 46%, 0.9);
656
664
  --translucent-error: hsla(358, 62%, 47%, 0.9);
657
665
  --translucent-muted: hsla(210, 8%, 55%, 0.95);
666
+ --focus-neutral: var(--white);
667
+ --focus-theme: var(--theme-secondary-400);
658
668
  --focus-ring: hsla(206, 100%, 40%, 0.9);
659
669
  --focus-ring-success: hsla(140, 40%, 40%, 0.9);
660
670
  --focus-ring-warning: hsla(47, 76%, 46%, 0.9);
@@ -768,6 +778,8 @@ body.theme-highcontrast:not(.theme-dark) .theme-dark__forced {
768
778
  --translucent-warning: hsla(47, 76%, 46%, 0.9);
769
779
  --translucent-error: hsla(358, 62%, 47%, 0.9);
770
780
  --translucent-muted: hsla(210, 8%, 55%, 0.95);
781
+ --focus-neutral: var(--white);
782
+ --focus-theme: var(--theme-secondary-400);
771
783
  --focus-ring: hsla(206, 100%, 40%, 0.9);
772
784
  --focus-ring-success: hsla(140, 40%, 40%, 0.9);
773
785
  --focus-ring-warning: hsla(47, 76%, 46%, 0.9);
@@ -453,22 +453,28 @@
453
453
  muted: hsla(210, 8%, 55%, 0.95);
454
454
  }
455
455
 
456
- // focus (sets represents both light and dark mode)
456
+ // focus colors used for accessible focus styles
457
457
  .set-focus() {
458
+ neutral: var(--white);
459
+ theme: var(--theme-secondary-400);
460
+ }
461
+
462
+ // focus ring (sets represents both light and dark mode)
463
+ .set-focus-ring() {
458
464
  default: var(--theme-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.15));
459
465
  success: hsla(140, 40%, 75%, 0.4);
460
466
  warning: hsla(47, 79%, 58%, 0.4);
461
467
  error: hsla(358, 62%, 47%, 0.15);
462
468
  muted: hsla(210, 8%, 15%, 0.1);
463
469
  }
464
- .set-focus-dark() {
470
+ .set-focus-ring-dark() {
465
471
  default: var(--theme-dark-secondary-custom-focus-ring, hsla(206, 100%, 40%, 0.25));
466
472
  success: hsla(140, 40%, 75%, 0.4);
467
473
  warning: hsla(47, 79%, 58%, 0.4);
468
474
  error: hsla(358, 62%, 47%, 0.15);
469
475
  muted: hsla(210, 8%, 15%, 0.1);
470
476
  }
471
- .set-focus-hc() {
477
+ .set-focus-ring-hc() {
472
478
  default: hsla(206, 100%, 40%, 0.9);
473
479
  success: hsla(140, 40%, 40%, 0.9);
474
480
  warning: hsla(47, 76%, 46%, 0.9);
@@ -631,7 +637,8 @@
631
637
  bc: .set-bc();
632
638
  bs: .set-bs();
633
639
  translucent: .set-translucent();
634
- focus-ring: .set-focus();
640
+ focus: .set-focus();
641
+ focus-ring: .set-focus-ring();
635
642
  highlight: .set-highlight();
636
643
  scrollbar: .set-scrollbar();
637
644
  }
@@ -640,7 +647,8 @@
640
647
  bc: .set-bc();
641
648
  bs: .set-bs-dark();
642
649
  translucent: .set-translucent-dark();
643
- focus-ring: .set-focus-dark();
650
+ focus: .set-focus();
651
+ focus-ring: .set-focus-ring-dark();
644
652
  highlight: .set-highlight-dark();
645
653
  scrollbar: .set-scrollbar-dark();
646
654
  }
@@ -649,7 +657,8 @@
649
657
  bc: .set-bc-hc();
650
658
  bs: .set-bs-hc();
651
659
  translucent: .set-translucent-hc();
652
- focus-ring: .set-focus-hc();
660
+ focus: .set-focus();
661
+ focus-ring: .set-focus-ring-hc();
653
662
  highlight: .set-highlight-hc();
654
663
  scrollbar: .set-scrollbar-hc();
655
664
  }
@@ -658,7 +667,8 @@
658
667
  bc: .set-bc-hc();
659
668
  bs: .set-bs-hc-dark();
660
669
  translucent: .set-translucent-hc();
661
- focus-ring: .set-focus-hc();
670
+ focus: .set-focus();
671
+ focus-ring: .set-focus-ring-hc();
662
672
  highlight: .set-highlight-hc-dark();
663
673
  scrollbar: .set-scrollbar-hc-dark();
664
674
  }
@@ -110,6 +110,39 @@
110
110
  }
111
111
 
112
112
 
113
+ /**
114
+ * Focus styles for the given context.
115
+ *
116
+ * Usage example:
117
+ * .focus-styles(true, true);
118
+ *
119
+ * @inset: boolean - whether the focus style be placed inside the element.
120
+ * @border: boolean - whether the element's border color change to match the focus style.
121
+ */
122
+ .focus-styles(@inset: false, @border: false) {
123
+ & when not (@inset) and not (@border) {
124
+ box-shadow: 0 0 0 var(--su-static2) var(--focus-neutral), 0 0 0 var(--su-static4) var(--focus-theme);
125
+ }
126
+
127
+ & when not (@inset) and (@border) {
128
+ border-color: var(--focus-neutral) !important;
129
+ box-shadow: 0 0 0 var(--su-static1) var(--focus-neutral), 0 0 0 calc(var(--su-static4) - var(--su-static1)) var(--focus-theme);
130
+ }
131
+
132
+ & when (@inset) and not (@border) {
133
+ box-shadow: inset 0 0 0 var(--su-static2) var(--focus-theme), inset 0 0 0 var(--su-static4) var(--focus-neutral);
134
+ }
135
+
136
+ & when (@inset) and (@border) {
137
+ border-color: var(--focus-theme) !important;
138
+ box-shadow: inset 0 0 0 var(--su-static1) var(--focus-theme), inset 0 0 0 calc(var(--su-static4) - var(--su-static1)) var(--focus-neutral);
139
+ }
140
+
141
+ // We include a 2px transparent outline to ensure Windows High Contrast Forced Color Mode
142
+ // includes outlines as expected See https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/
143
+ outline: var(--su-static2) solid transparent !important;
144
+ }
145
+
113
146
  // =============================================================================
114
147
  // -- COLORS
115
148
  // The following mixins let us do color math on the browser. They take a
@@ -17,19 +17,16 @@
17
17
 
18
18
  .has-error & {
19
19
  --_@{prefix}-bc: var(--red-400);
20
- --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-error);
21
20
  @error();
22
21
  }
23
22
 
24
23
  .has-success & {
25
24
  --_@{prefix}-bc: var(--green-400);
26
- --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-success);
27
25
  @success();
28
26
  }
29
27
 
30
28
  .has-warning & {
31
29
  --_@{prefix}-bc: var(--yellow-500);
32
- --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-warning);
33
30
  @warning();
34
31
  }
35
32
  }