@stackoverflow/stacks 1.7.0 → 1.8.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.
@@ -23,19 +23,19 @@
23
23
  // MODIFIERS
24
24
  // Sizes
25
25
  &&__sm {
26
- --_la-fs: var(--fs-caption);
26
+ .size-styles(sm; la; @styles: fs);
27
27
  }
28
28
 
29
29
  &&__md {
30
- --_la-fs: var(--fs-body3);
30
+ .size-styles(md; la; @styles: fs);
31
31
  }
32
32
 
33
33
  &&__lg {
34
- --_la-fs: var(--fs-title);
34
+ .size-styles(lg; la; @styles: fs);
35
35
  }
36
36
 
37
37
  &&__xl {
38
- --_la-fs: var(--fs-headline1);
38
+ .size-styles(xl; la; @styles: fs);
39
39
  }
40
40
 
41
41
  // CHILD ELEMENTS
@@ -13,6 +13,21 @@ a,
13
13
 
14
14
  // STATES
15
15
  &.s-link {
16
+ &__danger,
17
+ &__grayscale,
18
+ &__inherit,
19
+ &__muted,
20
+ &__visited {
21
+ &:visited {
22
+ &:active,
23
+ &:hover {
24
+ color: var(--_li-fc-hover);
25
+ }
26
+
27
+ color: var(--_li-fc-visited);
28
+ }
29
+ }
30
+
16
31
  // MODIFIERS
17
32
  &__dropdown {
18
33
  &:after {
@@ -68,17 +83,17 @@ a,
68
83
  }
69
84
  }
70
85
 
86
+ // MODIFIERS
87
+ fieldset[disabled] & {
88
+ box-shadow: none !important;
89
+ opacity: var(--_o-disabled-static);
90
+ pointer-events: none;
91
+ }
92
+
71
93
  // INTERACTION
72
94
  &:active,
73
95
  &:hover {
74
- &,
75
- &:visited {
76
- color: var(--_li-fc-hover);
77
- }
78
- }
79
-
80
- &:visited {
81
- color: var(--_li-fc-visited);
96
+ color: var(--_li-fc-hover);
82
97
  }
83
98
 
84
99
  color: var(--_li-fc);
@@ -206,8 +206,12 @@
206
206
  margin: 0 !important;
207
207
  }
208
208
 
209
- padding: var(--su8) !important; // To override .s-btn class attributes
210
- position: absolute !important; // To override .s-btn class attributes
209
+ &,
210
+ &.s-btn { // To override .s-btn class attributes
211
+ padding: var(--su8);
212
+ position: absolute;
213
+ }
214
+
211
215
  right: var(--su8);
212
216
  top: var(--su8);
213
217
  }
@@ -295,6 +299,10 @@
295
299
 
296
300
  &--stats-item {
297
301
  &:not(.s-badge) {
302
+ &.is-deleted {
303
+ color: var(--white);
304
+ }
305
+
298
306
  align-items: center;
299
307
  border: var(--su1) solid transparent;
300
308
  display: inline-flex;
@@ -0,0 +1,148 @@
1
+ .s-select {
2
+ --_se-arrow-bc: currentColor transparent;
3
+ --_se-arrow-size: var(--su-static4); // Constant
4
+ --_se-select-bc: var(--bc-darker);
5
+ --_se-select-bc-focus: var(--theme-secondary-300);
6
+ --_se-select-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring);
7
+ --_se-select-bg: var(--white);
8
+ --_se-select-br: var(--br-sm);
9
+ --_se-select-fc: var(--black);
10
+ --_se-select-px: 0.7em;
11
+ --_se-select-py: 0.6em;
12
+ --_se-select-fs: var(--fs-body1);
13
+
14
+ // CONTEXTUAL STYLES
15
+ @supports (-webkit-overflow-scrolling: touch) {
16
+ --_se-select-fs: 16px; // Increase font size for mobile safari. This keeps our inputs from zooming the page while focused
17
+ --_se-select-px: 0.55em; // Compensate for the larger font size so we generally keep the input the same size
18
+ --_se-select-py: 0.4em; // Compensate for the larger font size so we generally keep the input the same size
19
+ }
20
+
21
+ // MODIFIERS
22
+ .input-states({
23
+ position: relative;
24
+ });
25
+
26
+ .validation-states(se-select);
27
+
28
+ .is-disabled & {
29
+ --_se-arrow-bc: var(--bc-darker) transparent;
30
+ }
31
+
32
+ &&__sm {
33
+ .size-styles(sm; se-select; @styles: fs);
34
+ // --_se-select-fs: var(--fs-caption);
35
+ }
36
+
37
+ &&__md {
38
+ .size-styles(md; se-select; @styles: br, fs);
39
+ --_se-select-py: 0.5em;
40
+ }
41
+
42
+ &&__lg {
43
+ .size-styles(lg; se-select; @styles: br, fs);
44
+ --_se-select-px: 0.6em;
45
+ --_se-select-py: 0.45em;
46
+ }
47
+
48
+ &&__xl {
49
+ .size-styles(xl; se-select; @styles: br, fs);
50
+ --_se-select-px: 0.5em;
51
+ --_se-select-py: 0.4em;
52
+ }
53
+
54
+ // CHILD ELEMENTS
55
+ select&,
56
+ & > select {
57
+ .webkit-autofill();
58
+ }
59
+
60
+ &:before,
61
+ &:after { // menu arrows
62
+ border-color: var(--_se-arrow-bc);
63
+ border-style: solid;
64
+ border-width: var(--_se-arrow-size);
65
+ content: "";
66
+ pointer-events: none;
67
+ position: absolute;
68
+ right: calc(var(--su-static12) + var(--su-static1));
69
+ z-index: var(--zi-selected);
70
+ }
71
+
72
+ &:after {
73
+ border-bottom-width: 0;
74
+ top: calc(50% + var(--su-static1));
75
+ }
76
+
77
+ &:before {
78
+ border-top-width: 0;
79
+ top: calc(50% - calc(var(--_se-arrow-size) + var(--su-static1)));
80
+ }
81
+
82
+ > select { // Menu
83
+ // CONTEXTUAL STYLES
84
+ fieldset[disabled] &,
85
+ &[disabled] {
86
+ cursor: not-allowed;
87
+ opacity: var(--_o-disabled-static);
88
+ }
89
+
90
+ &[readonly],
91
+ .is-readonly & { // [1]
92
+ --_se-select-bc: var(--bc-light);
93
+ --_se-select-bg: var(--black-050);
94
+ --_se-select-fc: var(--black-200);
95
+
96
+ .highcontrast-mode({
97
+ --_se-select-fc: var(--fc-light);
98
+ });
99
+
100
+ cursor: not-allowed;
101
+ }
102
+
103
+ // CHILD ELEMENTS
104
+ &::-moz-focus-inner {
105
+ outline: none !important;
106
+ }
107
+
108
+ // INTERACTION
109
+ &:focus {
110
+ .highcontrast-mode({
111
+ --_se-select-bc-focus: var(--black);
112
+ });
113
+
114
+ border-color: var(--_se-select-bc-focus);
115
+ box-shadow: var(--_se-select-bs-focus);
116
+
117
+ color: var(--black);
118
+ outline: 0;
119
+ }
120
+
121
+ background-color: var(--_se-select-bg);
122
+ border: var(--su-static1) solid var(--_se-select-bc);
123
+ border-radius: var(--_se-select-br);
124
+ color: var(--_se-select-fc);
125
+ font-size: var(--_se-select-fs);
126
+ padding: var(--_se-select-py) var(--_se-select-px);
127
+
128
+ appearance: none;
129
+ font-family: inherit;
130
+ height: 100%; // Fill the height of its parent
131
+ line-height: var(--lh-sm);
132
+ outline: 0;
133
+ padding-right: var(--su32);
134
+ position: relative; // This prevents Firefox from requiring a second click to select options
135
+ width: 100%;
136
+ }
137
+
138
+ .s-input-icon {
139
+ right: var(--su32);
140
+ }
141
+
142
+ color: var(--fc-dark);
143
+ position: relative;
144
+ }
145
+
146
+ // [1] The readonly attribute is not supported on select elements
147
+ // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly
148
+ // and https://github.com/StackExchange/Stacks/pull/1040#discussion_r931144086
@@ -42,12 +42,12 @@
42
42
  // MODIFIERS
43
43
  // Sizes
44
44
  &&__xs {
45
- --_ta-fs: var(--fs-fine);
45
+ .size-styles(xs; ta; @styles: fs);
46
46
  --_ta-lh: 1.4;
47
47
  --_ta-px: var(--su2);
48
48
  }
49
49
  &&__sm {
50
- --_ta-fs: var(--fs-caption);
50
+ .size-styles(sm; ta; @styles: fs);
51
51
  --_ta-lh: 1.5;
52
52
  }
53
53
  &&__md {
@@ -56,7 +56,7 @@
56
56
  --_ta-lh: 1.733333333;
57
57
  }
58
58
  &&__lg {
59
- --_ta-br: calc(var(--br-sm) + 1px);
59
+ --_ta-br: calc(var(--br-sm) + var(--su-static1));
60
60
  --_ta-fs: var(--fs-subheading);
61
61
  --_ta-lh: 1.684210526;
62
62
  --_ta-px: var(--su6);
@@ -5,6 +5,14 @@
5
5
  --_ts-multiple-bg: unset;
6
6
  --_ts-multiple-fc: var(--black-500);
7
7
 
8
+ fieldset[disabled] & {
9
+ &,
10
+ & label {
11
+ cursor: not-allowed;
12
+ opacity: var(--_o-disabled-static);
13
+ }
14
+ }
15
+
8
16
  &&__multiple { // TODO split single and multiple toggle into two seperate components
9
17
  input[type="radio"] {
10
18
  &:checked {
@@ -88,17 +88,6 @@
88
88
  }
89
89
  }
90
90
 
91
- // ===========================================================================
92
- // -- APPEARANCE
93
- // Use this to overwrite the default appearance properties
94
- // ---------------------------------------------------------------------------
95
- .appearance(@all) {
96
- -webkit-appearance: @all;
97
- -moz-appearance: @all;
98
- appearance: @all;
99
- }
100
-
101
-
102
91
  // ===========================================================================
103
92
  // -- FONT FACE
104
93
  // Used to load hosted, custom webfonts. You must provide the font's
@@ -194,6 +183,79 @@
194
183
  @amountDecimal);
195
184
  }
196
185
 
186
+ // =============================================================================
187
+ // -- SIZE STYLES
188
+ // The following mixins let us generate pseudo-private custom properties
189
+ // for common sizes. They expect a size (@size), a prefix for the custom
190
+ // property (@prefix), and an array of comma-separates abbreviated styles
191
+ // (@styles).
192
+ // -----------------------------------------------------------------------------
193
+
194
+ .size-styles(@size, @prefix, @styles, @index: length(@styles)) {
195
+ & when (@index > 0) {
196
+ @style: extract(@styles, @index);
197
+
198
+ // xs
199
+ & when (@size = xs) and (@style = fs) {
200
+ --_@{prefix}-fs: var(--fs-fine);
201
+ }
202
+
203
+ // sm
204
+ & when (@size = sm) and (@style = fs) {
205
+ --_@{prefix}-fs: var(--fs-caption);
206
+ }
207
+
208
+ // md
209
+ & when (@size = md) and (@style = br) {
210
+ --_@{prefix}-br: calc(var(--br-sm) + var(--su-static1));
211
+ }
212
+ & when (@size = md) and (@style = fs) {
213
+ --_@{prefix}-fs: var(--fs-body3);
214
+ }
215
+
216
+ // lg
217
+ & when (@size = lg) and (@style = br) {
218
+ --_@{prefix}-br: calc(var(--br-sm) + var(--su-static1));
219
+ }
220
+ & when (@size = lg) and (@style = fs) {
221
+ --_@{prefix}-fs: var(--fs-title);
222
+ }
223
+
224
+ // xl
225
+ & when (@size = xl) and (@style = br) {
226
+ --_@{prefix}-br: var(--br-md);
227
+ }
228
+ & when (@size = xl) and (@style = fs) {
229
+ --_@{prefix}-fs: var(--fs-headline1);
230
+ }
231
+
232
+ .size-styles(@size, @prefix, @styles, @index: @index - 1);
233
+ }
234
+ }
235
+
236
+ // =============================================================================
237
+ // -- WEBKIT AUTOFILL
238
+ // -----------------------------------------------------------------------------
239
+ .webkit-autofill() {
240
+ &:-webkit-autofill {
241
+ &:focus {
242
+ border-color: var(--blue-300);
243
+ // Since the box shadow is overwritten to show a background, we have to re-add the focus outline
244
+ -webkit-box-shadow: 0 0 0 1000px var(--blue-050) inset, 0 0 0 var(--su-static4) var(--focus-ring);
245
+ }
246
+
247
+ -webkit-box-shadow: 0 0 0 1000px var(--theme-secondary-050) inset; // This acts as a background color by stretching an inset box shadow across the input
248
+ -webkit-text-fill-color: var(--black);
249
+ border-color: var(--blue-300);
250
+ transition: background-color 0s 50000s; // A hack to infinitely delay background styles that come from the browser.
251
+ }
252
+
253
+ &::-webkit-contacts-auto-fill-button {
254
+ background-color: var(--black); // In Safari, make the autocomplete button darkmode-aware
255
+ }
256
+
257
+ }
258
+
197
259
  // ===========================================================================
198
260
  // Internals only beyond this point -- helpers for .font-face()
199
261
  // ---------------------------------------------------------------------------
@@ -0,0 +1,44 @@
1
+ .input-states(@rules) {
2
+ .is-disabled &,
3
+ .is-readonly &,
4
+ .has-success &,
5
+ .has-error &,
6
+ .has-warning & {
7
+ @rules();
8
+ }
9
+ };
10
+
11
+ .validation-states(@prefix, @error: {}, @success: {}, @warning: {}) {
12
+ .has-error &,
13
+ .has-success &,
14
+ .has-warning & {
15
+ --_@{prefix}-bc-focus: ~"var(--_@{prefix}-bc)";
16
+ }
17
+
18
+ .has-error & {
19
+ --_@{prefix}-bc: var(--red-400);
20
+ --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-error);
21
+ @error();
22
+ }
23
+
24
+ .has-success & {
25
+ --_@{prefix}-bc: var(--green-400);
26
+ --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-success);
27
+ @success();
28
+ }
29
+
30
+ .has-warning & {
31
+ --_@{prefix}-bc: var(--yellow-600);
32
+ --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-warning);
33
+ @warning();
34
+ }
35
+ }
36
+
37
+ .is-disabled,
38
+ .is-readonly,
39
+ .has-success,
40
+ .has-error,
41
+ .has-warning {
42
+ position: relative;
43
+ }
44
+
@@ -1,22 +1,11 @@
1
- //
2
- // STACK OVERFLOW
3
- // STATIC STACK ELEMENTS
4
- //
5
- // This CSS comes from Stacks, our CSS & Pattern library for rapidly building
6
- // Stack Overflow. For documentation of all these classes and how to contribute,
7
- // visit https://stackoverflow.design/
8
- //
9
- // This is where all the magic happens.
10
- //
11
- // ============================================================================
12
- // $ STATIC ELEMENTS
13
- // The following items are elements which we DO NOT allow communities
14
- // to modify via variables.
15
- // ----------------------------------------------------------------------------
1
+ // stacks-static.less contains styles which we DO NOT allow communities to modify via variables
2
+ // BASE
16
3
  @import "base/reset.less";
4
+ @import "base/fieldset.less";
17
5
  @import "base/icons.less";
6
+ @import "input-utils.less";
18
7
 
19
- // -- COMPONENTS
8
+ // COMPONENTS
20
9
  @import "components/activity-indicator.less";
21
10
  @import "components/anchors.less";
22
11
  @import "components/award-bling.less";
@@ -28,7 +17,9 @@
28
17
  @import "components/breadcrumbs.less";
29
18
  @import "components/button-groups.less";
30
19
  @import "components/cards.less";
20
+ @import "components/checkboxes-radios.less";
31
21
  @import "components/code-blocks.less";
22
+ @import "components/description.less";
32
23
  @import "components/expandable.less";
33
24
  @import "components/inputs.less";
34
25
  @import "components/labels.less";
@@ -44,6 +35,7 @@
44
35
  @import "components/post-summary.less";
45
36
  @import "components/progress-bars.less";
46
37
  @import "components/prose.less";
38
+ @import "components/select.less";
47
39
  @import "components/sidebar-widgets.less";
48
40
  @import "components/spinner.less";
49
41
  @import "components/table.less";
@@ -53,10 +45,10 @@
53
45
  @import "components/uploader.less";
54
46
  @import "components/user-cards.less";
55
47
 
56
- // -- LESS CONSTANTS AND MIXINS
48
+ // LESS CONSTANTS AND MIXINS
57
49
  @import "exports/exports.less";
58
50
 
59
- // -- ATOMIC CLASSES
51
+ // ATOMIC CLASSES
60
52
  @import "atomic/borders.less";
61
53
  @import "atomic/colors.less";
62
54
  @import "atomic/flex.less";
@@ -78,20 +70,18 @@
78
70
  #stacks-internals-collect-small();
79
71
  });
80
72
 
81
- // We need printing styles to be last so they can override all other styles.
82
- .print\:d-block {
83
- @media print {
73
+ @media print { // We need printing styles to be last so they can override all other styles.
74
+ .print\:d-block & {
84
75
  display: block !important;
85
76
  }
86
- }
87
-
88
- .print\:d-none {
89
- @media print {
77
+ .print\:d-none & {
90
78
  display: none !important;
91
79
  }
92
80
  }
93
81
  /* stylelint-enable */
94
82
 
95
- // -- CONFIG
83
+ // CONFIG
96
84
  @import "base/configuration-static.less";
97
85
  @import "base/internals.less";
86
+
87
+ // [1] The following items are elements which we DO NOT allow communities to modify via variables.
@@ -0,0 +1,77 @@
1
+ import { html, fixture, expect } from "@open-wc/testing";
2
+ import { screen } from "@testing-library/dom";
3
+ import "../ts/index";
4
+
5
+ // TODO abstract to a helper file… maybe create a helper function to test in all themes
6
+ const colorThemes = ["theme-dark", "theme-light"];
7
+ const baseThemes = ["", "theme-highcontrast"];
8
+
9
+ const avatarStyles = {
10
+ sizes: ["24", "32", "48", "64", "96", "128"],
11
+ children: ["image", "letter"],
12
+ };
13
+
14
+ const makeTest = ({ testid, theme, classes, child = "" }) => {
15
+ it(`a11y: ${testid} should be accessible`, async () => {
16
+ await fixture(html`<a
17
+ href="#"
18
+ class="s-avatar${classes}"
19
+ data-testid="${testid}"
20
+ >
21
+ <div
22
+ class="${child === "letter" ? "s-avatar--letter" : "d-none"}"
23
+ aria-hidden="true"
24
+ >
25
+ S
26
+ </div>
27
+ <img
28
+ class="${child === "image" ? "s-avatar--image" : "d-none"}"
29
+ src="https://picsum.photos/48"
30
+ alt="team logo"
31
+ />
32
+ <span class="v-visible-sr">Stack Overflow</span>
33
+ </a>`);
34
+
35
+ document.body.className = "";
36
+ document.body.classList.add(...theme);
37
+ const avatar = screen.getByTestId(testid);
38
+ // TODO add conditional option for high contrast mode to test against AAA
39
+ await expect(avatar).to.be.accessible();
40
+ });
41
+ };
42
+
43
+ // TODO move to test utils
44
+ const buildTestid = (arr) => arr.filter(Boolean).join("-");
45
+
46
+ describe("s-avatar", () => {
47
+ // Test default, high contrast themes
48
+ baseThemes.forEach((baseTheme) => {
49
+ // Test light, dark theme
50
+ colorThemes.forEach((colorTheme) => {
51
+ const theme = [baseTheme, colorTheme].filter(Boolean);
52
+ const testidBase = buildTestid(["s-avatar", ...theme]);
53
+
54
+ // Test each size
55
+ ["", ...avatarStyles.sizes].forEach((size) => {
56
+ const sizeClasses = size ? ` s-avatar__${size}` : "";
57
+ const classesSize = ` ${sizeClasses}`;
58
+ const testidSize = buildTestid([testidBase, size]);
59
+
60
+ // Test each size with each child
61
+ ["", ...avatarStyles.children].forEach((child) => {
62
+ const testidChildren = buildTestid([
63
+ testidSize,
64
+ `with-${child}`,
65
+ ]);
66
+
67
+ makeTest({
68
+ child,
69
+ classes: classesSize,
70
+ testid: testidChildren,
71
+ theme,
72
+ });
73
+ });
74
+ });
75
+ });
76
+ });
77
+ });