@stackoverflow/stacks 2.2.0 → 2.3.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.
Files changed (31) hide show
  1. package/dist/css/stacks.css +2719 -1174
  2. package/dist/css/stacks.min.css +1 -1
  3. package/lib/atomic/__snapshots__/spacing.less.test.ts.snap +1928 -0
  4. package/lib/atomic/spacing.less +59 -303
  5. package/lib/atomic/spacing.less.test.ts +12 -0
  6. package/lib/components/block-link/block-link.less +6 -2
  7. package/lib/components/button/button.a11y.test.ts +14 -18
  8. package/lib/components/button/button.less +10 -17
  9. package/lib/components/button/button.test.setup.ts +36 -0
  10. package/lib/components/button/button.visual.test.ts +3 -33
  11. package/lib/components/button-group/button-group.a11y.test.ts +12 -0
  12. package/lib/components/button-group/button-group.less +49 -50
  13. package/lib/components/button-group/button-group.test.setup.ts +77 -0
  14. package/lib/components/button-group/button-group.visual.test.ts +7 -0
  15. package/lib/components/input_textarea/input_textarea.less +3 -1
  16. package/lib/components/notice/notice.a11y.test.ts +1 -1
  17. package/lib/components/notice/notice.less +105 -76
  18. package/lib/components/notice/notice.visual.test.ts +2 -2
  19. package/lib/components/pagination/pagination.less +5 -1
  20. package/lib/components/post-summary/post-summary.a11y.test.ts +25 -0
  21. package/lib/components/post-summary/post-summary.less +2 -1
  22. package/lib/components/post-summary/post-summary.test.setup.ts +435 -0
  23. package/lib/components/post-summary/post-summary.visual.test.ts +17 -0
  24. package/lib/components/select/select.less +5 -1
  25. package/lib/components/topbar/topbar.less +366 -335
  26. package/lib/components/topbar/topbar.visual.test.ts +28 -0
  27. package/lib/components/uploader/uploader.less +5 -1
  28. package/lib/exports/spacing-mixins.less +67 -0
  29. package/lib/tsconfig.build.json +1 -1
  30. package/lib/tsconfig.json +3 -3
  31. package/package.json +15 -15
@@ -2,82 +2,81 @@
2
2
  // CONTEXTUAL STYLES
3
3
  #stacks-internals #screen-sm({
4
4
  .s-btn {
5
+ --_bu-px: 0.4em;
6
+
5
7
  &.s-btn__dropdown {
6
8
  padding-right: 1.2em;
7
9
 
8
10
  &:after {
9
- right: 0.4em;
11
+ right: var(--_bu-px);
10
12
  }
11
13
  }
12
-
13
- padding-left: 0.4em;
14
- padding-right: 0.4em;
15
14
  }
16
15
  }, @force-selector: true);
17
16
 
18
- // VARIANTS
19
- &:not(&--radio) .s-btn:not(:first-child):not(:last-child),
20
- &&--radio .s-btn:not(:first-of-type):not(:last-of-type) {
21
- border-radius: 0;
22
- }
23
-
24
- &:not(&--radio) .s-btn:first-child:not(:only-child),
25
- &&--radio .s-btn:first-of-type:not(:last-of-type) {
26
- border-top-right-radius: 0;
27
- border-bottom-right-radius: 0;
28
- }
29
-
30
- &:not(&--radio) .s-btn:last-child:not(:only-child),
31
- &&--radio .s-btn:last-of-type:not(:first-of-type) {
32
- border-top-left-radius: 0;
33
- border-bottom-left-radius: 0;
17
+ // CHILD ELEMENTS
18
+ form {
19
+ display: flex;
20
+ margin-right: calc(var(--su-static1) * -1); // -1px
34
21
  }
35
22
 
36
- &:not(&--radio) .s-btn:not(:last-child),
37
- &&--radio .s-btn:not(:last-of-type) {
38
- margin-right: calc(var(--su-static1) * -1);
39
- }
23
+ // --_bu-py values set below to ensure btn-group height matches same-sized button height
24
+ // See https://github.com/StackEng/StackOverflow/pull/18992#pullrequestreview-1947490680
25
+ .s-btn {
26
+ --_bu-br: var(--br-sm);
27
+ --_bu-bc-hover: transparent;
28
+ --_bu-px: calc(var(--su12) - var(--su1)); // 11px
29
+ --_bu-py: calc(var(--su6) + 0.65px); // 6.65px
40
30
 
41
- // CHILD ELEMENTS
42
- form {
43
- &:not(:first-child):not(:last-child) {
44
- .s-btn {
45
- border-radius: 0;
46
- }
31
+ &.s-btn__xs {
32
+ --_bu-px: calc(var(--su8) - var(--su1)); // 7px
33
+ --_bu-py: calc(var(--su2) + 0.9px); // 2.9px
47
34
  }
48
35
 
49
- &:last-child:not(:only-child) {
50
- .s-btn:not(:last-child) {
51
- border-radius: 0;
52
- }
36
+ &.s-btn__sm {
37
+ --_bu-px: calc(var(--su8) + var(--su1)); // 9px
38
+ --_bu-py: calc(var(--su4) + (var(--su2) - 0.15px)); // 5.85px
53
39
  }
54
40
 
55
- &:first-child:not(:only-child) {
56
- .s-btn:not(:first-child) {
57
- border-radius: 0;
58
- }
41
+ &.s-btn__md {
42
+ --_bu-px: var(--su12);
43
+ --_bu-py: calc(var(--su8) + 0.15px); // 8.15px
59
44
  }
60
45
 
61
- display: flex;
62
- margin-right: calc(var(--su-static1) * -1); // -1px
63
- }
46
+ &.is-selected,
47
+ &--radio:checked + .s-btn {
48
+ font-weight: bold;
49
+ }
64
50
 
65
- .s-btn {
66
- &:active {
67
- z-index: var(--zi-active);
51
+ .s-btn--badge {
52
+ // set negative margins so button height isn't affect by badge
53
+ margin-bottom: -100%;
54
+ margin-top: -100%;
55
+ font-weight: normal;
68
56
  }
69
57
 
70
- &.is-selected,
71
- &--radio:checked + .s-btn {
72
- z-index: var(--zi-selected); // When the button is active or selected, it should pop above its siblings
58
+ & .s-btn--text {
59
+ &:before {
60
+ content: attr(data-text);
61
+ content: attr(data-text) / "";
62
+ font-weight: bold;
63
+ height: 0;
64
+ pointer-events: none;
65
+ user-select: none;
66
+ visibility: hidden;
67
+ }
68
+
69
+ display: inline-flex;
70
+ flex-direction: column;
73
71
  }
74
72
 
75
- margin-bottom: calc(var(--su-static1) * -1); // When wrapping we need to account for the border
76
73
  white-space: nowrap; // When the buttons wrap, they get super tall and mess up the whole layout
77
74
  }
78
75
 
79
76
  // STATIC COMPONENT STYLES
80
- display: flex;
77
+ border: var(--su-static1) solid var(--black-300);
78
+ border-radius: var(--br-md);
79
+ display: inline-flex;
81
80
  flex-wrap: wrap;
82
- margin-bottom: var(--su-static1); // Compensate for buttons having a margin bottom of -1px to account for row wrapping
81
+ padding: calc(var(--su-static4) - var(--su-static1));
83
82
  }
@@ -0,0 +1,77 @@
1
+ import { html } from "@open-wc/testing";
2
+ import type { TestVariationArgs } from "../../test/test-utils";
3
+
4
+ const btns = [
5
+ { name: "Newest", isSelected: true },
6
+ { name: "Frequent" },
7
+ { name: "Active" },
8
+ ];
9
+
10
+ const getBtn = ({
11
+ name = "",
12
+ isRadio,
13
+ isSelected,
14
+ hasBadge,
15
+ }: {
16
+ name: string;
17
+ isRadio?: boolean;
18
+ isSelected?: boolean;
19
+ hasBadge?: boolean;
20
+ }): string => {
21
+ const baseClasses = "s-btn s-btn__muted";
22
+ const btnChildren = `
23
+ <span class="s-btn--text" data-text="${name}">${name}</span>
24
+ ${
25
+ hasBadge
26
+ ? `<span class="s-btn--badge"><span class="s-btn--number">123</span></span>`
27
+ : ""
28
+ }
29
+ `;
30
+
31
+ return isRadio
32
+ ? `<input
33
+ class="s-btn--radio"
34
+ type="radio"
35
+ name="test-btn-radio-group"
36
+ id="btn-${name}"
37
+ ${isSelected ? "checked" : ""}/>
38
+ <label class="${baseClasses}" for="btn-${name}">
39
+ ${btnChildren}
40
+ </label>`
41
+ : `<button
42
+ class="${baseClasses}${isSelected ? " is-selected" : ""}"
43
+ ${isSelected ? `aria-current="true"` : ""}
44
+ type="button">
45
+ ${btnChildren}
46
+ </button>`;
47
+ };
48
+
49
+ const getBtns = (ids: number[]): string => {
50
+ return ids.map((id) => getBtn(btns[id])).join("");
51
+ };
52
+
53
+ const testArgs: TestVariationArgs = {
54
+ baseClass: "s-btn-group",
55
+ children: {
56
+ default: getBtns([0, 1, 2]),
57
+ single: getBtns([0]),
58
+ form: `
59
+ ${getBtns([0])}
60
+ <form class="mb0 p0">
61
+ ${getBtn(btns[1])}
62
+ </form>
63
+ ${getBtns([2])}
64
+ `,
65
+ badge: btns.map((btn) => getBtn({ ...btn, hasBadge: true })).join(""),
66
+ radio: btns.map((btn) => getBtn({ ...btn, isRadio: true })).join(""),
67
+ },
68
+ template: ({ component, testid }) =>
69
+ html`<div
70
+ class="d-inline-flex ai-center jc-center p8"
71
+ data-testid="${testid}"
72
+ >
73
+ ${component}
74
+ </div>`,
75
+ };
76
+
77
+ export { testArgs };
@@ -0,0 +1,7 @@
1
+ import { testArgs } from "./button-group.test.setup";
2
+ import { runVisualTests } from "../../test/visual-test-utils";
3
+ import "../../index";
4
+
5
+ describe("button group", () => {
6
+ runVisualTests(testArgs);
7
+ });
@@ -102,7 +102,9 @@
102
102
  }
103
103
 
104
104
  // INTERACTION
105
- &:focus {
105
+ // Note: We're applying the focus styles both on `:focus` and `:focus-within` since this component can sometimes be used on the parent of an input such as in our tag selector in Core.
106
+ &:focus,
107
+ &:focus-within {
106
108
  .focus-styles();
107
109
  }
108
110
 
@@ -9,7 +9,7 @@ describe("notice", () => {
9
9
  primary: ["important"],
10
10
  },
11
11
  children: {
12
- default: `Test notice`,
12
+ default: `Test notice <code>some code</code> <a class="s-link s-link__inherit s-link__underlined" href="#">Link</a>`,
13
13
  },
14
14
  tag: "aside",
15
15
  });
@@ -1,107 +1,129 @@
1
+ /**
2
+ * Generate color variables with a given color name
3
+ *
4
+ * Usage example:
5
+ * .generate-variant-variables(purple, important);
6
+ *
7
+ * @colorName - The name of the color to use to construct variables values
8
+ * @modifier - Modifier name to determine variable values for that modifier
9
+ */
10
+ .generate-variant-variables(@colorName: "", @modifier: "") {
11
+ & when (@modifier = "") {
12
+ --_no-bc: ~"var(--@{colorName}-300)";
13
+ --_no-bg: ~"var(--@{colorName}-100)";
14
+ --_no-btn-bg-active: ~"var(--@{colorName}-200)";
15
+ --_no-btn-bg-focus: ~"var(--@{colorName}-200)";
16
+ --_no-btn-fc: ~"var(--@{colorName}-500)";
17
+ --_no-code-bc: ~"var(--@{colorName}-300)";
18
+ --_no-code-bg: ~"var(--@{colorName}-200)";
19
+ }
20
+
21
+ & when (@modifier = important) {
22
+ --_no-bc: var(--_no-bg);
23
+ --_no-bg: ~"var(--@{colorName}-400)";
24
+ --_no-fc: var(--white);
25
+ --_no-btn-bg-active: ~"var(--@{colorName}-500)";
26
+ --_no-btn-bg-focus: ~"var(--@{colorName}-500)";
27
+ --_no-btn-fc: ~"var(--@{colorName}-100)";
28
+ --_no-code-bc: ~"var(--@{colorName}-300)";
29
+ --_no-code-bg: ~"var(--@{colorName}-500)";
30
+
31
+ .highcontrast-mode({
32
+ --_no-bg: ~"var(--@{colorName}-500)";
33
+ });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Generate styles for a notice-based component
39
+ *
40
+ * Usage example:
41
+ * .construct-notice-component(s-banner);
42
+ *
43
+ * @baseClass - The base class name for the notice component
44
+ */
1
45
  .construct-notice-component(@baseClass) {
2
46
  --_no-bc: var(--black-225);
3
47
  --_no-bg: var(--black-100);
4
48
  --_no-fc: var(--black-500);
5
- --_no-btn-bg-focus: var(--black-225);
6
49
  --_no-btn-bg-active: var(--black-250);
50
+ --_no-btn-bg-focus: var(--black-225);
7
51
  --_no-btn-fc: var(--_no-fc);
52
+ --_no-code-bc: var(--black-300);
53
+ --_no-code-bg: var(--black-200);
54
+ --_no-code-fc: var(--_no-fc);
8
55
 
9
- // MODIFIERS
10
- &:not(&__important) {
11
- .dark-mode({
12
- --_no-bc: var(--_no-bg);
13
- });
14
-
15
- .highcontrast-mode({
16
- --_no-bc: currentColor;
17
- });
18
-
19
- .highcontrast-dark-mode({
20
- --_no-bc: currentColor;
21
- });
22
- }
56
+ // CONTEXTUAL STYLES
57
+ .dark-mode({
58
+ --_no-bc: var(--_no-bg);
59
+ });
60
+
61
+ .highcontrast-mode({
62
+ &,
63
+ &.@{baseClass}__danger,
64
+ &.@{baseClass}__info,
65
+ &.@{baseClass}__success,
66
+ &.@{baseClass}__warning {
67
+ --_no-code-bc: var(--black-400);
68
+ --_no-code-bg: var(--white);
69
+ --_no-code-fc: var(--black);
70
+
71
+ &.@{baseClass}__important {
72
+ --_no-code-bc: var(--black-200);
73
+ --_no-code-bg: var(--black);
74
+ --_no-code-fc: var(--white);
75
+ }
76
+ }
77
+ });
23
78
 
24
- &__important {
79
+ // MODIFIERS
80
+ &__important:not(.@{baseClass}__danger):not(.@{baseClass}__info):not(.@{baseClass}__success):not(.@{baseClass}__warning) {
25
81
  --_no-bc: var(--_no-bg);
26
82
  --_no-bg: var(--black-500);
27
83
  --_no-fc: var(--white);
28
84
  --_no-btn-bg-focus: var(--black-600);
29
85
  --_no-btn-bg-active: var(--black-600);
30
86
  --_no-btn-fc: var(--_no-fc);
31
- --_no-code-bg: var(--black-300);
87
+ --_no-code-bc: var(--black-300);
88
+ --_no-code-bg: var(--black-600);
32
89
  }
33
90
 
34
91
  // VARIANTS
35
92
  &__danger {
36
- --_no-bc: var(--red-300);
37
- --_no-bg: var(--red-100);
38
- --_no-btn-bg-active: var(--red-200);
39
- --_no-btn-bg-focus: var(--red-200);
40
- --_no-btn-fc: var(--red-500);
41
- --_no-code-bg: var(--red-300);
93
+ &:not(.@{baseClass}__important) {
94
+ .generate-variant-variables(red);
95
+ }
42
96
 
43
97
  &.@{baseClass}__important {
44
- --_no-bc: var(--_no-bg);
45
- --_no-bg: var(--red-400);
46
- --_no-btn-bg-active: var(--red-500);
47
- --_no-btn-bg-focus: var(--red-500);
48
- --_no-btn-fc: var(--red-100);
49
-
50
- .highcontrast-mode({
51
- --_no-bg: var(--red-500);
52
- });
98
+ .generate-variant-variables(red, important);
53
99
  }
54
100
  }
55
101
 
56
102
  &__info {
57
- --_no-bc: var(--theme-secondary-300);
58
- --_no-bg: var(--theme-secondary-100);
59
- --_no-btn-bg-focus: var(--theme-secondary-200);
60
- --_no-btn-bg-active: var(--theme-secondary-200);
61
- --_no-btn-fc: var(--theme-secondary-500);
62
- --_no-code-bg: var(--theme-secondary-300);
103
+ &:not(.@{baseClass}__important) {
104
+ .generate-variant-variables(theme-secondary);
105
+ }
63
106
 
64
107
  &.@{baseClass}__important {
65
- --_no-bc: var(--_no-bg);
66
- --_no-bg: var(--theme-secondary-400);
67
- --_no-btn-bg-active: var(--theme-secondary-500);
68
- --_no-btn-bg-focus: var(--theme-secondary-500);
69
- --_no-btn-fc: var(--theme-secondary-100);
70
-
71
- .highcontrast-mode({
72
- --_no-bg: var(--theme-secondary-500);
73
- });
108
+ .generate-variant-variables(theme-secondary, important);
74
109
  }
75
110
  }
76
111
 
77
112
  &__success {
78
- --_no-bc: var(--green-300);
79
- --_no-bg: var(--green-100);
80
- --_no-btn-bg-active: var(--green-200);
81
- --_no-btn-bg-focus: var(--green-200);
82
- --_no-btn-fc: var(--green-500);
83
- --_no-code-bg: var(--green-300);
113
+ &:not(.@{baseClass}__important) {
114
+ .generate-variant-variables(green);
115
+ }
84
116
 
85
117
  &.@{baseClass}__important {
86
- --_no-bc: var(--_no-bg);
87
- --_no-bg: var(--green-400);
88
- --_no-btn-bg-active: var(--green-500);
89
- --_no-btn-bg-focus: var(--green-500);
90
- --_no-btn-fc: var(--green-100);
91
-
92
- .highcontrast-mode({
93
- --_no-bg: var(--green-500);
94
- });
118
+ .generate-variant-variables(green, important);
95
119
  }
96
120
  }
97
121
 
98
122
  &__warning {
99
- --_no-bc: var(--yellow-300);
100
- --_no-bg: var(--yellow-100);
101
- --_no-btn-bg-active: var(--yellow-200);
102
- --_no-btn-bg-focus: var(--yellow-200);
103
- --_no-btn-fc: var(--yellow-600);
104
- --_no-code-bg: var(--yellow-300);
123
+ &:not(.@{baseClass}__important) {
124
+ .generate-variant-variables(yellow);
125
+ --_no-btn-fc: var(--yellow-600);
126
+ }
105
127
 
106
128
  &.@{baseClass}__important {
107
129
  --_no-bc: var(--_no-bg);
@@ -110,9 +132,13 @@
110
132
  --_no-btn-fc: var(--_no-fc);
111
133
  --_no-btn-bg-active: var(--yellow-300);
112
134
  --_no-btn-bg-focus: var(--yellow-300);
135
+ --_no-code-bc: var(--yellow-500);
136
+ --_no-code-bg: var(--yellow-300);
113
137
 
114
138
  .dark-mode({
115
- --_no-fc: var(--yellow-200);
139
+ --_no-fc: var(--white);
140
+ --_no-code-bc: var(--yellow-300);
141
+ --_no-code-bg: var(--yellow-500);
116
142
  });
117
143
 
118
144
  .highcontrast-mode({
@@ -126,21 +152,24 @@
126
152
 
127
153
  // CHILD ELEMENTS
128
154
  code {
129
- background: var(--_no-code-bg, transparent);
155
+ background-color: var(--_no-code-bg);
156
+ color: var(--_no-code-fc);
157
+ outline: var(--su-static1) solid var(--_no-code-bc);
158
+
159
+ border-radius: var(--br-sm);
160
+ padding-left: var(--su2);
161
+ padding-right: var(--su2);
130
162
  }
131
163
 
132
164
  & &--btn {
133
165
  // TODO: decouple .s-notice--btn from .s-btn
134
- &:not(:focus) {
135
- box-shadow: none; // This will prevent default .s-btn box-shadow from showing
136
- }
137
-
138
166
  &:active {
139
167
  background-color: var(--_no-btn-bg-active, inherit) !important;
140
168
  }
141
169
 
142
- &:focus,
143
- &:hover {
170
+ &:focus-visible,
171
+ &:hover,
172
+ &.focus-inset-bordered {
144
173
  background-color: var(--_no-btn-bg-focus, inherit) !important;
145
174
  }
146
175
 
@@ -13,11 +13,11 @@ describe("notice", () => {
13
13
  ariaHidden: "false",
14
14
  },
15
15
  children: {
16
- default: `Test notice`,
16
+ default: `Test notice <code>some code</code> <a class="s-link s-link__inherit s-link__underlined" href="#">Link</a>`,
17
17
  },
18
18
  tag: "aside",
19
19
  template: ({ component, testid }) => html`
20
- <div class="d-inline-block p8" data-testid="${testid}">
20
+ <div class="d-inline-block p8 wmx5" data-testid="${testid}">
21
21
  ${component}
22
22
  </div>
23
23
  `,
@@ -40,9 +40,13 @@
40
40
  }
41
41
 
42
42
  &:focus-visible {
43
+ .focus-styles(true, true);
44
+ }
45
+
46
+ &:focus-visible,
47
+ &.focus-inset-bordered {
43
48
  background-color: var(--_pa-item-bg-focus);
44
49
  color: var(--_pa-item-fc-focus);
45
- .focus-styles(true, true);
46
50
  }
47
51
 
48
52
  background-color: var(--_pa-item-bg);
@@ -0,0 +1,25 @@
1
+ import { runA11yTests } from "../../test/a11y-test-utils";
2
+ import testArgs from "./post-summary.test.setup";
3
+ import "../../index";
4
+
5
+ describe("post-summary", () => {
6
+ // Base, sparce
7
+ runA11yTests({
8
+ ...testArgs.base,
9
+ // TODO resolve test failures
10
+ skippedTestids: [
11
+ /-deleted/,
12
+ /-ignored/,
13
+ /-highcontrast-(light|dark)-watched/,
14
+ ],
15
+ });
16
+
17
+ // Truncated description sizes
18
+ runA11yTests(testArgs.sizes);
19
+
20
+ // Stats - answers, view hotness
21
+ runA11yTests(testArgs.stats);
22
+
23
+ // Badges
24
+ runA11yTests(testArgs.badges);
25
+ });
@@ -67,7 +67,8 @@
67
67
  &,
68
68
  &:active,
69
69
  &:hover,
70
- &:focus {
70
+ &:focus,
71
+ .focus-bordered {
71
72
  .highcontrast-mode({
72
73
  border-color: currentColor;
73
74
  });