@stackoverflow/stacks 2.1.1 → 2.3.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 (31) hide show
  1. package/dist/css/stacks.css +2242 -774
  2. package/dist/css/stacks.min.css +1 -1
  3. package/lib/atomic/__snapshots__/misc.less.test.ts.snap +893 -0
  4. package/lib/atomic/__snapshots__/spacing.less.test.ts.snap +1928 -0
  5. package/lib/atomic/misc.less +30 -0
  6. package/lib/atomic/misc.less.test.ts +12 -0
  7. package/lib/atomic/spacing.less +59 -303
  8. package/lib/atomic/spacing.less.test.ts +12 -0
  9. package/lib/components/badge/badge.a11y.test.ts +59 -18
  10. package/lib/components/badge/badge.less +16 -1
  11. package/lib/components/badge/badge.visual.test.ts +44 -16
  12. package/lib/components/button/button.a11y.test.ts +14 -18
  13. package/lib/components/button/button.less +16 -22
  14. package/lib/components/button/button.test.setup.ts +36 -0
  15. package/lib/components/button/button.visual.test.ts +3 -33
  16. package/lib/components/button-group/button-group.a11y.test.ts +12 -0
  17. package/lib/components/button-group/button-group.less +43 -49
  18. package/lib/components/button-group/button-group.test.setup.ts +77 -0
  19. package/lib/components/button-group/button-group.visual.test.ts +7 -0
  20. package/lib/components/input_textarea/input_textarea.less +3 -1
  21. package/lib/components/post-summary/post-summary.a11y.test.ts +25 -0
  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/topbar/topbar.less +365 -335
  25. package/lib/components/topbar/topbar.visual.test.ts +28 -0
  26. package/lib/exports/__snapshots__/color.less.test.ts.snap +24 -24
  27. package/lib/exports/color-sets.less +12 -12
  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 +13 -13
@@ -1,15 +1,13 @@
1
1
  import { runVisualTests } from "../../test/visual-test-utils";
2
- // import { Icons } from "@stackoverflow/stacks-icons";
2
+ import { IconEyeSm } from "@stackoverflow/stacks-icons/icons";
3
3
  import "../../index";
4
4
  import { html } from "@open-wc/testing";
5
5
 
6
6
  const variants = {
7
7
  blings: ["gold", "silver", "bronze"],
8
8
  numbers: ["answered", "bounty", "important", "rep", "rep-down", "votes"],
9
- states: {
10
- filled: ["danger", "muted"],
11
- other: ["info", "warning"],
12
- },
9
+ filled: ["danger", "muted"],
10
+ states: ["danger", "muted", "info", "new", "warning"],
13
11
  users: ["admin", "moderator", "staff"],
14
12
  };
15
13
 
@@ -46,7 +44,6 @@ describe("badge", () => {
46
44
  },
47
45
  options: {
48
46
  includeNullVariant: false,
49
- includeNullModifier: false,
50
47
  },
51
48
  tag: "span",
52
49
  template,
@@ -62,22 +59,52 @@ describe("badge", () => {
62
59
  },
63
60
  options: {
64
61
  includeNullVariant: false,
62
+ },
63
+ tag: "span",
64
+ template,
65
+ });
66
+
67
+ // State badges
68
+ runVisualTests({
69
+ baseClass: "s-badge",
70
+ variants: variants.states,
71
+ children: {
72
+ default: `state badge`,
73
+ },
74
+ tag: "span",
75
+ options: {
76
+ includeNullVariant: false,
77
+ },
78
+ template,
79
+ });
80
+
81
+ // State badges w/ filled modifier
82
+ runVisualTests({
83
+ baseClass: "s-badge",
84
+ variants: variants.filled,
85
+ modifiers: {
86
+ primary: ["filled"],
87
+ },
88
+ children: {
89
+ default: `filled badge`,
90
+ },
91
+ options: {
65
92
  includeNullModifier: false,
66
93
  },
67
94
  tag: "span",
68
95
  template,
69
96
  });
70
97
 
71
- // Icon badges
98
+ // State badges w/ filled modifier and icon
72
99
  runVisualTests({
73
100
  baseClass: "s-badge",
74
- variants: [...variants.states.filled, ...variants.states.other],
101
+ variants: variants.filled,
75
102
  modifiers: {
76
- primary: ["icon"],
103
+ primary: ["filled"],
104
+ secondary: ["icon"],
77
105
  },
78
106
  children: {
79
- default: "with icon",
80
- // icon: Icons.IconEyeSm, // TODO fix the icon imports
107
+ default: `${IconEyeSm} icon badge`,
81
108
  },
82
109
  options: {
83
110
  includeNullModifier: false,
@@ -86,17 +113,18 @@ describe("badge", () => {
86
113
  template,
87
114
  });
88
115
 
89
- // Filled badges
116
+ // State badges w/ icon
90
117
  runVisualTests({
91
118
  baseClass: "s-badge",
92
- variants: variants.states.filled,
119
+ variants: variants.states.filter((state) => state !== "new"),
120
+ modifiers: {
121
+ primary: ["icon"],
122
+ },
93
123
  children: {
94
- default: "filled",
95
- // icon: Icons.IconEyeOffSm, // TODO fix the icon imports
124
+ default: `${IconEyeSm} icon badge`,
96
125
  },
97
126
  options: {
98
127
  includeNullModifier: false,
99
- includeNullVariant: false,
100
128
  },
101
129
  tag: "span",
102
130
  template,
@@ -1,25 +1,21 @@
1
+ import testArgs from "./button.test.setup";
1
2
  import { runA11yTests } from "../../test/a11y-test-utils";
2
3
  import "../../index";
3
4
 
4
5
  describe("button", () => {
5
6
  runA11yTests({
6
- baseClass: "s-btn",
7
- variants: ["danger", "muted"],
8
- modifiers: {
9
- primary: ["filled", "outlined"],
10
- secondary: [...["xs", "sm", "md"], ...["dropdown", "icon"]],
11
- global: ["is-loading"],
12
- standalone: [
13
- ...["link", "unset"],
14
- ...["facebook", "github", "google"],
15
- ],
16
- },
17
- attributes: {
18
- type: "button",
19
- },
20
- children: {
21
- default: "Ask question",
22
- },
23
- tag: "button",
7
+ ...testArgs,
8
+ excludedTestids: [
9
+ /^s-btn-(?=.*unset).*badge$/, // s-btn with badge and unset variant not supported
10
+ ],
11
+ skippedTestids: [
12
+ // TODO resolve btn badge contrast issues
13
+ // matches tests with a badge in light and dark modes
14
+ /s-btn-(light|dark).*?badge/,
15
+ // matches tests with a badge in highcontrast-light modes, excluding filled, danger, github, facebook, sm, or xs
16
+ /s-btn-highcontrast-light-(?!.*(filled|danger|github|facebook|sm|xs)).*?badge/,
17
+ // matches tests with a badge in highcontrast-light modes, are muted and/or outlined, and are sm or xs
18
+ /s-btn-highcontrast-light-(?:muted-outlined-|muted-|outlined-)?(?:sm|xs).*?badge/,
19
+ ],
24
20
  });
25
21
  });
@@ -144,6 +144,10 @@
144
144
  // Resets
145
145
  &&__link,
146
146
  &&__unset {
147
+ --_bu-baw: 0;
148
+ --_bu-br: 0;
149
+ --_bu-p: 0;
150
+
147
151
  &:focus,
148
152
  &:focus-visible {
149
153
  outline-style: auto;
@@ -151,10 +155,6 @@
151
155
  }
152
156
 
153
157
  &&__link {
154
- --_bu-baw: 0;
155
- --_bu-br: 0;
156
- --_bu-p: 0;
157
-
158
158
  &,
159
159
  &:hover,
160
160
  &:active,
@@ -180,11 +180,8 @@
180
180
  &:hover,
181
181
  &:active,
182
182
  &:focus {
183
- --_bu-baw: 0;
184
183
  --_bu-bg: none;
185
- --_bu-br: 0;
186
184
  --_bu-fc: unset;
187
- --_bu-p: 0;
188
185
 
189
186
  cursor: default;
190
187
  font: unset;
@@ -204,12 +201,12 @@
204
201
  content: "";
205
202
  pointer-events: none;
206
203
  position: absolute;
207
- right: var(--_bu-p);
204
+ right: var(--_bu-px, var(--_bu-p));
208
205
  top: calc(50% - var(--su-static2));
209
206
  z-index: var(--zi-active);
210
207
  }
211
208
 
212
- padding-right: calc(var(--_bu-p) * 2.5);
209
+ padding-right: calc(var(--_bu-px, var(--_bu-p)) * 2.5);
213
210
  }
214
211
 
215
212
  &&__icon {
@@ -408,24 +405,21 @@
408
405
  // INTERACTION
409
406
  &:not(&__link):not(&__unset):focus-visible,
410
407
  &--radio:focus-visible + & {
411
- border-color: var(--theme-secondary-400) !important;
412
408
  .focus-styles(true, true);
413
409
  }
414
410
 
415
- &:not(&__link):not(&__unset):not(&__facebook):not(&__github):not(&__google):not(.is-selected):focus-visible,
416
- &--radio:focus-visible + & {
417
- &,
418
- &.s-btn__filled {
419
- &:not(:hover) .s-btn--number {
420
- color: var(--_bu-number-fc-focus, var(--_bu-number-fc-filled));
421
- }
411
+ &:not(&--radio:checked + label):not(&__link):not(&__unset):not(&__facebook):not(&__github):not(&__google):not(.is-selected) {
412
+ &:focus-visible {
413
+ &.s-btn__filled {
414
+ &:not(:hover) .s-btn--number {
415
+ color: var(--_bu-number-fc-focus, var(--_bu-number-fc-filled));
416
+ }
422
417
 
423
- background-color: var(--_bu-bg-focus, var(--_bu-filled-bg));
424
- color: var(--_bu-fc-focus, var(--_bu-filled-fc));
418
+ background-color: var(--_bu-bg-focus, var(--_bu-filled-bg));
419
+ color: var(--_bu-fc-focus, var(--_bu-filled-fc));
420
+ }
425
421
  }
426
- }
427
422
 
428
- &:not(&__link):not(&__unset):not(&__facebook):not(&__github):not(&__google):not(.is-selected) {
429
423
  &:hover {
430
424
  &.s-btn__filled {
431
425
  background-color: var(--_bu-filled-bg-hover);
@@ -472,7 +466,7 @@
472
466
  box-shadow: none;
473
467
  color: var(--_bu-fc);
474
468
  font-size: var(--_bu-fs);
475
- padding: var(--_bu-p);
469
+ padding: var(--_bu-py, var(--_bu-p)) var(--_bu-px, var(--_bu-p));
476
470
 
477
471
  cursor: pointer;
478
472
  display: inline-block;
@@ -0,0 +1,36 @@
1
+ import type { TestVariationArgs } from "../../test/test-utils";
2
+ import "../../index";
3
+
4
+ const getChild = (child?: string): string => {
5
+ switch (child) {
6
+ case "badge":
7
+ return `Ask question
8
+ <span class="s-btn--badge">
9
+ <span class="s-btn--number">198</span>
10
+ </span>`;
11
+ default:
12
+ return "Ask question";
13
+ }
14
+ };
15
+
16
+ // TODO test disabled states, interaction pseudo-classes
17
+ const testArgs: TestVariationArgs = {
18
+ baseClass: "s-btn",
19
+ variants: ["danger", "muted"],
20
+ modifiers: {
21
+ primary: ["filled", "outlined"],
22
+ secondary: [...["xs", "sm", "md"], ...["dropdown", "icon"]],
23
+ global: ["is-loading"],
24
+ standalone: [...["link", "unset"], ...["facebook", "github", "google"]],
25
+ },
26
+ attributes: {
27
+ type: "button",
28
+ },
29
+ children: {
30
+ default: getChild(),
31
+ badge: getChild("badge"),
32
+ },
33
+ tag: "button",
34
+ };
35
+
36
+ export default testArgs;
@@ -1,41 +1,11 @@
1
- import { html } from "@open-wc/testing";
1
+ import testArgs from "./button.test.setup";
2
2
  import { runVisualTests } from "../../test/visual-test-utils";
3
3
  import "../../index";
4
-
5
- const getChild = (child?: string): string => {
6
- switch (child) {
7
- case "badge":
8
- return `Ask question
9
- <span class="s-btn--badge">
10
- <span class="s-btn--number">198</span>
11
- </span>`;
12
- default:
13
- return "Ask question";
14
- }
15
- };
4
+ import { html } from "@open-wc/testing";
16
5
 
17
6
  describe("button", () => {
18
- // TODO test disabled states, interaction pseudo-classes
19
7
  runVisualTests({
20
- baseClass: "s-btn",
21
- variants: ["danger", "muted"],
22
- modifiers: {
23
- primary: ["filled", "outlined"],
24
- secondary: [...["xs", "sm", "md"], ...["dropdown", "icon"]],
25
- global: ["is-loading"],
26
- standalone: [
27
- ...["link", "unset"],
28
- ...["facebook", "github", "google"],
29
- ],
30
- },
31
- attributes: {
32
- type: "button",
33
- },
34
- children: {
35
- default: getChild(),
36
- badge: getChild("badge"),
37
- },
38
- tag: "button",
8
+ ...testArgs,
39
9
  template: ({ component, testid }) => html`
40
10
  <div
41
11
  class="bg-black-225 d-inline-flex ai-center jc-center hs1 ws2 p8"
@@ -0,0 +1,12 @@
1
+ import { testArgs } from "./button-group.test.setup";
2
+ import { runA11yTests } from "../../test/a11y-test-utils";
3
+ import "../../index";
4
+
5
+ describe("button group", () => {
6
+ runA11yTests({
7
+ ...testArgs,
8
+ // TODO remove skipped tests once btn badge contrast issues are resolved
9
+ // see also https://github.com/StackExchange/Stacks/pull/1663
10
+ skippedTestids: [/s-btn-group-(light|dark|highcontrast-light)-badge/],
11
+ });
12
+ });
@@ -2,82 +2,76 @@
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
+ .s-btn {
24
+ --_bu-br: var(--br-sm);
25
+ --_bu-bc-hover: transparent;
40
26
 
41
- // CHILD ELEMENTS
42
- form {
43
- &:not(:first-child):not(:last-child) {
44
- .s-btn {
45
- border-radius: 0;
46
- }
27
+ &,
28
+ &.s-btn__md {
29
+ --_bu-px: var(--su12);
30
+ --_bu-py: var(--su8);
47
31
  }
48
32
 
49
- &:last-child:not(:only-child) {
50
- .s-btn:not(:last-child) {
51
- border-radius: 0;
52
- }
33
+ &.s-btn___xs {
34
+ --_bu-px: var(--su8);
35
+ --_bu-py: var(--su4);
53
36
  }
54
37
 
55
- &:first-child:not(:only-child) {
56
- .s-btn:not(:first-child) {
57
- border-radius: 0;
58
- }
38
+ &.s-btn___sm {
39
+ --_bu-px: calc(var(--su8) + var(--su2));
40
+ --_bu-py: var(--su6);
59
41
  }
60
42
 
61
- display: flex;
62
- margin-right: calc(var(--su-static1) * -1); // -1px
63
- }
43
+ &.is-selected,
44
+ &--radio:checked + .s-btn {
45
+ font-weight: bold;
46
+ }
64
47
 
65
- .s-btn {
66
- &:active {
67
- z-index: var(--zi-active);
48
+ .s-btn--badge {
49
+ font-weight: normal;
68
50
  }
69
51
 
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
52
+ & .s-btn--text {
53
+ &:before {
54
+ content: attr(data-text);
55
+ content: attr(data-text) / "";
56
+ font-weight: bold;
57
+ height: 0;
58
+ pointer-events: none;
59
+ user-select: none;
60
+ visibility: hidden;
61
+ }
62
+
63
+ display: inline-flex;
64
+ flex-direction: column;
73
65
  }
74
66
 
75
- margin-bottom: calc(var(--su-static1) * -1); // When wrapping we need to account for the border
76
67
  white-space: nowrap; // When the buttons wrap, they get super tall and mess up the whole layout
77
68
  }
78
69
 
79
70
  // STATIC COMPONENT STYLES
80
- display: flex;
71
+ border: var(--su-static1) solid var(--black-300);
72
+ border-radius: var(--br-md);
73
+ display: inline-flex; // TODO investigate if changing from flex to inline-flex will be an issue
81
74
  flex-wrap: wrap;
82
75
  margin-bottom: var(--su-static1); // Compensate for buttons having a margin bottom of -1px to account for row wrapping
76
+ padding: calc(var(--su-static4) - var(--su-static1));
83
77
  }
@@ -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
 
@@ -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
+ });