@stackoverflow/stacks 2.0.0-rc.0 → 2.0.0-rc.2

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 (47) hide show
  1. package/README.md +5 -0
  2. package/dist/css/stacks.css +582 -532
  3. package/dist/css/stacks.min.css +1 -1
  4. package/dist/js/stacks.js +265 -110
  5. package/dist/js/stacks.min.js +1 -1
  6. package/lib/atomic/typography.less +4 -0
  7. package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +1 -1
  8. package/lib/components/activity-indicator/activity-indicator.less +17 -4
  9. package/lib/components/anchor/anchor.visual.test.ts +1 -5
  10. package/lib/components/avatar/avatar.visual.test.ts +1 -4
  11. package/lib/components/badge/badge.a11y.test.ts +2 -2
  12. package/lib/components/badge/badge.less +15 -23
  13. package/lib/components/block-link/block-link.less +5 -4
  14. package/lib/components/button/button.a11y.test.ts +2 -5
  15. package/lib/components/button/button.less +28 -58
  16. package/lib/components/button/button.visual.test.ts +2 -5
  17. package/lib/components/card/card.less +8 -0
  18. package/lib/components/description/description.a11y.test.ts +1 -0
  19. package/lib/components/expandable/expandable.a11y.test.ts +27 -0
  20. package/lib/components/expandable/expandable.visual.test.ts +27 -0
  21. package/lib/components/input-icon/input-icon.less +1 -1
  22. package/lib/components/input-message/input-message.less +4 -3
  23. package/lib/components/label/label.less +3 -13
  24. package/lib/components/link-preview/link-preview.a11y.test.ts +48 -0
  25. package/lib/components/link-preview/link-preview.less +13 -4
  26. package/lib/components/link-preview/link-preview.visual.test.ts +52 -0
  27. package/lib/components/notice/notice.a11y.test.ts +17 -0
  28. package/lib/components/notice/notice.less +44 -81
  29. package/lib/components/notice/notice.visual.test.ts +26 -0
  30. package/lib/components/pagination/pagination.a11y.test.ts +20 -0
  31. package/lib/components/pagination/pagination.visual.test.ts +26 -0
  32. package/lib/components/post-summary/post-summary.less +3 -3
  33. package/lib/components/sidebar-widget/sidebar-widget.less +2 -2
  34. package/lib/components/spinner/spinner.a11y.test.ts +15 -0
  35. package/lib/components/spinner/spinner.visual.test.ts +43 -0
  36. package/lib/components/toast/toast.a11y.test.ts +30 -0
  37. package/lib/components/toast/toast.visual.test.ts +10 -6
  38. package/lib/components/toggle-switch/toggle-switch.less +2 -5
  39. package/lib/components/uploader/uploader.less +19 -13
  40. package/lib/exports/color-sets.less +127 -78
  41. package/lib/exports/theme.less +3 -3
  42. package/lib/input-utils.less +1 -1
  43. package/lib/test/axe-apca/README.md +19 -0
  44. package/lib/test/axe-apca/src/axe-apca.test.ts +77 -1
  45. package/lib/test/axe-apca/src/axe-apca.ts +16 -8
  46. package/lib/test/test-utils.ts +7 -3
  47. package/package.json +12 -12
@@ -1,76 +1,87 @@
1
1
  /* stylelint-disable property-no-unknown */
2
2
 
3
+ // For colors to render properly, you *cannot* have inline comments following a color value
4
+ // The color *and comments* will be passed as the value for the given key and
5
+ // it will fail silently in Stacks' build but might break consumer's builds
6
+ //
7
+ // For example, the value of this:
8
+ // default: hsl(0, 0%, 100%); // test comment
9
+ // Will render as this:
10
+ // hsl(0, 0%, 100%); // test comment
11
+ //
12
+ // tl;dr: don't put inline after values in this file
13
+
3
14
  // White
4
15
  .set-white() {
5
- default: hsl(0, 0%, 100%); // matches .set-black()[050]
16
+ default: hsl(0, 0%, 100%);
6
17
  }
7
18
  .set-white-dark() {
8
- default: hsl(220, 3%, 15%); // matches .set-black-dark()[050]
19
+ default: hsl(210, 3%, 15%);
9
20
  }
10
21
  .set-white-hc() {
11
- default: hsl(0, 0%, 100%); // matches .set-black-hc()[050]
22
+ default: hsl(0, 0%, 100%);
12
23
  }
13
24
  .set-white-hc-dark() {
14
- default: hsl(0, 0%, 0%); // matches .set-black-dark-hc()[050]
25
+ default: hsl(0, 0%, 0%);
15
26
  }
16
27
 
17
28
  // Black
18
29
  .set-black() {
19
- 050: hsl(0, 0%, 100%); // renders as white
30
+ 050: hsl(0, 0%, 100%);
20
31
  100: hsl(210, 8%, 98%);
21
32
  150: hsl(210, 8%, 95%);
22
33
  200: hsl(210, 8%, 90%);
23
34
  225: hsl(210, 8%, 85%);
24
35
  250: hsl(210, 8%, 80%);
25
- 300: hsl(213, 9%, 75%);
26
- 350: hsl(212, 8%, 68%);
27
- 400: hsl(210, 8%, 45%);
36
+ 300: hsl(210, 9%, 75%);
37
+ 350: hsl(210, 8%, 68%);
38
+ 400: hsl(210, 8%, 42%);
28
39
  500: hsl(210, 8%, 25%);
29
40
  600: hsl(210, 8%, 5%);
30
- default: hsl(0, 8%, 5%); // black, no stop // TODO verify the value w/ design
41
+ default: hsl(210, 8%, 5%);
31
42
  }
32
43
  .set-black-dark() {
33
- 050: hsl(220, 3%, 15%);
34
- 100: hsl(220, 3%, 18%);
35
- 150: hsl(230, 4%, 21%);
36
- 200: hsl(233, 4%, 27%);
37
- 225: hsl(233, 4%, 30%);
38
- 250: hsl(235, 5%, 36%);
39
- 300: hsl(233, 4%, 47%);
40
- 350: hsl(219, 10%, 60%);
41
- 400: hsl(220, 10%, 74%);
42
- 500: hsl(220, 10%, 83%);
43
- 600: hsl(180, 11%, 98%);
44
- default: hsl(0, 0%, 100%); // white, no stop // TODO verify the value w/ design
44
+ 050: hsl(210, 3%, 15%);
45
+ 100: hsl(210, 3%, 18%);
46
+ 150: hsl(210, 4%, 21%);
47
+ 200: hsl(210, 4%, 27%);
48
+ 225: hsl(210, 4%, 30%);
49
+ 250: hsl(210, 5%, 36%);
50
+ 300: hsl(210, 4%, 47%);
51
+ 350: hsl(210, 8%, 70%);
52
+ 400: hsl(210, 8%, 80%);
53
+ 500: hsl(210, 8%, 90%);
54
+ 600: hsl(210, 11%, 98%);
55
+ default: hsl(0, 0%, 100%);
45
56
  }
46
57
  .set-black-hc() {
47
- 050: hsl(0, 0%, 100%); // renders as white
58
+ 050: hsl(0, 0%, 100%);
48
59
  100: hsl(210, 8%, 98%);
49
60
  150: hsl(210, 8%, 95%);
50
61
  200: hsl(210, 8%, 90%);
51
62
  225: hsl(210, 8%, 85%);
52
63
  250: hsl(210, 8%, 80%);
53
- 300: hsl(213, 9%, 75%);
64
+ 300: hsl(210, 9%, 75%);
54
65
  350: hsl(210, 8%, 45%);
55
- 400: hsl(210, 8%, 45%);
66
+ 400: hsl(212, 8%, 35%);
56
67
  500: hsl(210, 8%, 25%);
57
68
  600: hsl(210, 8%, 5%);
58
- default: hsl(0, 8%, 5%); // black, no stop // TODO verify the value w/ design
69
+ default: hsl(210, 8%, 5%);
59
70
  }
60
71
 
61
72
  .set-black-hc-dark() {
62
- 050: hsl(220, 3%, 15%);
63
- 100: hsl(220, 3%, 18%);
64
- 150: hsl(230, 4%, 21%);
65
- 200: hsl(233, 4%, 27%);
66
- 225: hsl(233, 4%, 30%);
67
- 250: hsl(235, 5%, 36%);
68
- 300: hsl(233, 4%, 47%);
69
- 350: hsl(220, 10%, 74%);
70
- 400: hsl(220, 10%, 74%);
71
- 500: hsl(220, 10%, 83%);
72
- 600: hsl(180, 11%, 98%);
73
- default: hsl(0, 0%, 100%); // white, no stop // TODO verify the value w/ design
73
+ 050: hsl(210, 3%, 15%);
74
+ 100: hsl(210, 3%, 18%);
75
+ 150: hsl(210, 4%, 21%);
76
+ 200: hsl(210, 4%, 27%);
77
+ 225: hsl(210, 4%, 30%);
78
+ 250: hsl(210, 5%, 36%);
79
+ 300: hsl(210, 4%, 47%);
80
+ 350: hsl(210, 10%, 74%);
81
+ 400: hsl(210, 8%, 80%);
82
+ 500: hsl(210, 8%, 90%);
83
+ 600: hsl(210, 11%, 98%);
84
+ default: hsl(0, 0%, 100%);
74
85
  }
75
86
 
76
87
  // Orange
@@ -78,33 +89,33 @@
78
89
  100: hsl(23, 85%, 97%);
79
90
  200: hsl(27, 85%, 87%);
80
91
  300: hsl(27, 85%, 72%);
81
- 400: hsl(27, 90%, 55%);
82
- 500: hsl(27, 88%, 43%);
92
+ 400: hsl(27, 80%, 52%);
93
+ 500: hsl(27, 80%, 43%);
83
94
  600: hsl(27, 80%, 29%);
84
95
  }
85
96
  .set-orange-dark() {
86
97
  100: hsl(27, 29%, 19%);
87
98
  200: hsl(27, 43%, 31%);
88
99
  300: hsl(27, 43%, 47%);
89
- 400: hsl(27, 80%, 64%);
90
- 500: hsl(27, 80%, 78%);
91
- 600: hsl(27, 80%, 89%);
100
+ 400: hsl(27, 80%, 80%);
101
+ 500: hsl(27, 80%, 88%);
102
+ 600: hsl(27, 80%, 93%);
92
103
  }
93
104
  .set-orange-hc() {
94
105
  100: hsl(22, 85%, 97%);
95
106
  200: hsl(22, 85%, 97%);
96
107
  300: hsl(27, 90%, 55%);
97
108
  400: hsl(27, 90%, 55%);
98
- 500: hsl(27, 80%, 29%);
99
- 600: hsl(27, 80%, 29%);
109
+ 500: hsl(27, 80%, 28%);
110
+ 600: hsl(27, 80%, 28%);
100
111
  }
101
112
  .set-orange-hc-dark() {
102
113
  100: hsl(27, 29%, 19%);
103
114
  200: hsl(27, 29%, 19%);
104
115
  300: hsl(27, 80%, 64%);
105
- 400: hsl(27, 80%, 64%);
116
+ 400: hsl(27, 80%, 80%);
106
117
  500: hsl(27, 79%, 89%);
107
- 600: hsl(27, 79%, 89%);
118
+ 600: hsl(27, 80%, 93%);
108
119
  }
109
120
 
110
121
  // Blue
@@ -112,23 +123,23 @@
112
123
  100: hsl(210, 80%, 96%);
113
124
  200: hsl(210, 80%, 91%);
114
125
  300: hsl(210, 78%, 76%);
115
- 400: hsl(210, 70%, 48%);
116
- 500: hsl(210, 75%, 36%);
126
+ 400: hsl(210, 77%, 46%);
127
+ 500: hsl(210, 77%, 36%);
117
128
  600: hsl(210, 80%, 23%);
118
129
  }
119
130
  .set-blue-dark() {
120
131
  100: hsl(209, 29%, 19%);
121
132
  200: hsl(210, 29%, 35%);
122
133
  300: hsl(210, 29%, 50%);
123
- 400: hsl(210, 81%, 72%);
124
- 500: hsl(210, 80%, 82%);
125
- 600: hsl(210, 80%, 90%);
134
+ 400: hsl(210, 80%, 80%);
135
+ 500: hsl(210, 80%, 88%);
136
+ 600: hsl(210, 80%, 93%);
126
137
  }
127
138
  .set-blue-hc() {
128
139
  100: hsl(210, 80%, 96%);
129
140
  200: hsl(210, 80%, 96%);
130
141
  300: hsl(210, 70%, 48%);
131
- 400: hsl(210, 70%, 48%);
142
+ 400: hsl(210, 77%, 34%);
132
143
  500: hsl(210, 80%, 23%);
133
144
  600: hsl(210, 80%, 23%);
134
145
  }
@@ -136,9 +147,9 @@
136
147
  100: hsl(209, 29%, 19%);
137
148
  200: hsl(209, 29%, 19%);
138
149
  300: hsl(210, 80%, 72%);
139
- 400: hsl(210, 80%, 72%);
150
+ 400: hsl(210, 80%, 80%);
140
151
  500: hsl(209, 79%, 87%);
141
- 600: hsl(209, 79%, 87%);
152
+ 600: hsl(210, 80%, 93%);
142
153
  }
143
154
 
144
155
  // Green
@@ -154,15 +165,15 @@
154
165
  100: hsl(148, 29%, 19%);
155
166
  200: hsl(148, 29%, 27%);
156
167
  300: hsl(148, 25%, 40%);
157
- 400: hsl(148, 40%, 62%);
158
- 500: hsl(148, 40%, 75%);
159
- 600: hsl(148, 40%, 87%);
168
+ 400: hsl(148, 40%, 75%);
169
+ 500: hsl(148, 40%, 85%);
170
+ 600: hsl(148, 40%, 93%);
160
171
  }
161
172
  .set-green-hc() {
162
173
  100: hsl(147, 36%, 95%);
163
174
  200: hsl(147, 36%, 95%);
164
175
  300: hsl(148, 70%, 31%);
165
- 400: hsl(148, 70%, 31%);
176
+ 400: hsl(148, 75%, 22%);
166
177
  500: hsl(147, 74%, 15%);
167
178
  600: hsl(147, 74%, 15%);
168
179
  }
@@ -170,9 +181,9 @@
170
181
  100: hsl(147, 29%, 19%);
171
182
  200: hsl(147, 29%, 19%);
172
183
  300: hsl(148, 40%, 62%);
173
- 400: hsl(148, 40%, 62%);
184
+ 400: hsl(148, 40%, 75%);
174
185
  500: hsl(148, 39%, 87%);
175
- 600: hsl(148, 39%, 87%);
186
+ 600: hsl(148, 40%, 93%);
176
187
  }
177
188
 
178
189
  // Red
@@ -188,15 +199,15 @@
188
199
  100: hsl(358, 29%, 19%);
189
200
  200: hsl(0, 29%, 37%);
190
201
  300: hsl(0, 34%, 54%);
191
- 400: hsl(0, 75%, 77%);
192
- 500: hsl(0, 69%, 85%);
193
- 600: hsl(0, 69%, 93%);
202
+ 400: hsl(0, 73%, 85%);
203
+ 500: hsl(0, 73%, 91%);
204
+ 600: hsl(0, 73%, 95%);
194
205
  }
195
206
  .set-red-hc() {
196
207
  100: hsl(0, 79%, 96%);
197
208
  200: hsl(0, 79%, 96%);
198
209
  300: hsl(0, 60%, 49%);
199
- 400: hsl(0, 60%, 49%);
210
+ 400: hsl(0, 65%, 37%);
200
211
  500: hsl(0, 66%, 24%);
201
212
  600: hsl(0, 66%, 24%);
202
213
  }
@@ -204,9 +215,9 @@
204
215
  100: hsl(358, 29%, 19%);
205
216
  200: hsl(358, 29%, 19%);
206
217
  300: hsl(0, 75%, 77%);
207
- 400: hsl(0, 75%, 77%);
218
+ 400: hsl(0, 73%, 85%);
208
219
  500: hsl(0, 70%, 92%);
209
- 600: hsl(0, 70%, 92%);
220
+ 600: hsl(0, 73%, 95%);
210
221
  }
211
222
 
212
223
  // Yellow
@@ -222,9 +233,9 @@
222
233
  100: hsl(43, 29%, 17%);
223
234
  200: hsl(43, 29%, 25%);
224
235
  300: hsl(43, 29%, 40%);
225
- 400: hsl(43, 59%, 64%);
226
- 500: hsl(43, 65%, 70%);
227
- 600: hsl(43, 65%, 85%);
236
+ 400: hsl(43, 75%, 75%);
237
+ 500: hsl(43, 75%, 85%);
238
+ 600: hsl(43, 75%, 91%);
228
239
  }
229
240
  .set-yellow-hc() {
230
241
  100: hsl(41, 80%, 96%);
@@ -238,9 +249,43 @@
238
249
  100: hsl(43, 29%, 17%);
239
250
  200: hsl(43, 29%, 17%);
240
251
  300: hsl(43, 59%, 64%);
241
- 400: hsl(43, 59%, 64%);
252
+ 400: hsl(43, 75%, 75%);
242
253
  500: hsl(48, 74%, 91%);
243
- 600: hsl(48, 74%, 91%);
254
+ 600: hsl(43, 75%, 91%);
255
+ }
256
+
257
+ // Purple
258
+ .set-purple() {
259
+ 100: hsl(237, 80%, 96%);
260
+ 200: hsl(237, 77%, 92%);
261
+ 300: hsl(237, 60%, 83%);
262
+ 400: hsl(237, 55%, 57%);
263
+ 500: hsl(237, 50%, 45%);
264
+ 600: hsl(237, 50%, 32%);
265
+ }
266
+ .set-purple-dark() {
267
+ 100: hsl(237, 25%, 24%);
268
+ 200: hsl(237, 27%, 38%);
269
+ 300: hsl(237, 32%, 56%);
270
+ 400: hsl(237, 58%, 86%);
271
+ 500: hsl(237, 60%, 91%);
272
+ 600: hsl(237, 65%, 96%);
273
+ }
274
+ .set-purple-hc() {
275
+ 100: hsl(237, 80%, 96%);
276
+ 200: hsl(237, 80%, 96%);
277
+ 300: hsl(237, 55%, 57%);
278
+ 400: hsl(237, 55%, 57%);
279
+ 500: hsl(237, 50%, 32%);
280
+ 600: hsl(237, 50%, 32%);
281
+ }
282
+ .set-purple-hc-dark() {
283
+ 100: hsl(237, 25%, 24%);
284
+ 200: hsl(237, 25%, 24%);
285
+ 300: hsl(237, 58%, 86%);
286
+ 400: hsl(237, 58%, 86%);
287
+ 500: hsl(237, 65%, 96%);
288
+ 600: hsl(237, 65%, 96%);
244
289
  }
245
290
 
246
291
  // gold
@@ -284,7 +329,7 @@
284
329
  }
285
330
  .set-silver-hc() {
286
331
  100: hsl(0, 0%, 95%);
287
- 200: hsl(0, 0%, 84%);
332
+ 200: hsl(0, 0%, 95%);
288
333
  300: hsl(210, 5%, 68%);
289
334
  400: hsl(216, 2%, 40%);
290
335
  }
@@ -426,12 +471,12 @@
426
471
  }
427
472
  .set-highlight-dark() {
428
473
  addition: var(--green-500);
429
- attribute: hsl(207, 41.5%, 67%);
474
+ attribute: var(--blue-400);
430
475
  bg: hsl(0, 2%, 11%);
431
476
  color: var(--black);
432
477
  comment: hsl(0, 0%, 60%);
433
478
  deletion: var(--red-500);
434
- keyword: hsl(208, 41.5%, 67%);
479
+ keyword:var(--blue-400);
435
480
  literal: hsl(27, 85%, 61.5%);
436
481
  namespace: hsl(27, 85%, 61.5%);
437
482
  punctuation: hsl(0, 0%, 80%);
@@ -491,6 +536,7 @@
491
536
  green: .set-green();
492
537
  red: .set-red();
493
538
  yellow: .set-yellow();
539
+ purple: .set-purple();
494
540
  gold: .set-gold();
495
541
  silver: .set-silver();
496
542
  bronze: .set-bronze();
@@ -504,6 +550,7 @@
504
550
  green: .set-green-dark();
505
551
  red: .set-red-dark();
506
552
  yellow: .set-yellow-dark();
553
+ purple: .set-purple-dark();
507
554
  gold: .set-gold-dark();
508
555
  silver: .set-silver-dark();
509
556
  bronze: .set-bronze-dark();
@@ -517,6 +564,7 @@
517
564
  green: .set-green-hc();
518
565
  red: .set-red-hc();
519
566
  yellow: .set-yellow-hc();
567
+ purple: .set-purple-hc();
520
568
  gold: .set-gold-hc();
521
569
  silver: .set-silver-hc();
522
570
  bronze: .set-bronze-hc();
@@ -530,6 +578,7 @@
530
578
  green: .set-green-hc-dark();
531
579
  red: .set-red-hc-dark();
532
580
  yellow: .set-yellow-hc-dark();
581
+ purple: .set-purple-hc-dark();
533
582
  gold: .set-gold-hc-dark();
534
583
  silver: .set-silver-hc-dark();
535
584
  bronze: .set-bronze-hc-dark();
@@ -610,11 +659,11 @@
610
659
  }
611
660
 
612
661
  .theme-light-default() {
613
- primary: .set-orange()[400]; // orange-400 (light)
614
- secondary: .set-blue()[400]; // blue-400 (light)
662
+ primary: .set-orange()[400];
663
+ secondary: .set-blue()[400];
615
664
  }
616
665
 
617
666
  .theme-dark-default() {
618
- primary: .set-orange-dark()[400]; // orange-400 (dark)
619
- secondary: .set-blue-dark()[400]; // blue-400 (dark)
667
+ primary: .set-orange-dark()[400];
668
+ secondary: .set-blue-dark()[400];
620
669
  }
@@ -9,7 +9,7 @@
9
9
  // Button Default (Secondary)
10
10
  --theme-button-color: var(--theme-secondary-400);
11
11
  --theme-button-background-color: transparent;
12
- --theme-button-hover-color: var(--theme-secondary-400); // TODO was 500, now same as base button color
12
+ --theme-button-hover-color: var(--theme-secondary-500);
13
13
  --theme-button-hover-background-color: var(--theme-secondary-200);
14
14
  --theme-button-active-background-color: var(--theme-secondary-300);
15
15
  --theme-button-selected-color: var(--theme-secondary-600);
@@ -19,8 +19,8 @@
19
19
  --theme-button-primary-color: var(--white);
20
20
  --theme-button-primary-background-color: var(--theme-secondary-400);
21
21
  --theme-button-primary-hover-color: var(--white);
22
- --theme-button-primary-hover-background-color: var(--theme-secondary-400); // TODO was 500, now same as primary button color
23
- --theme-button-primary-active-background-color: var(--theme-secondary-500);
22
+ --theme-button-primary-hover-background-color: var(--theme-secondary-500);
23
+ --theme-button-primary-active-background-color: var(--theme-secondary-600);
24
24
  --theme-button-primary-selected-color: var(--white);
25
25
  --theme-button-primary-selected-background-color: var(--theme-secondary-500);
26
26
  --theme-button-primary-number-color: var(--theme-secondary-600);
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  .has-warning & {
31
- --_@{prefix}-bc: var(--yellow-600);
31
+ --_@{prefix}-bc: var(--yellow-500);
32
32
  --_@{prefix}-bs-focus: 0 0 0 var(--su-static4) var(--focus-ring-warning);
33
33
  @warning();
34
34
  }
@@ -32,3 +32,22 @@ axe.configure({
32
32
  console.log(results);
33
33
  });
34
34
  ```
35
+
36
+ ### Using custom APCA thresholds
37
+
38
+ To set custom thresholds for APCA checks, follow these steps:
39
+
40
+ 1. Use `custom` as the first argument when calling `registerAxeAPCA`.
41
+ 1. Provide a function as the second argument, optionally accepting `fontSize` and `fontWeight` arguments.
42
+
43
+
44
+ ```js
45
+ const customConformanceThresholdFn = (fontSize, fontWeight) => {
46
+ const size = parseFloat(fontSize);
47
+ const weight = parseFloat(fontWeight);
48
+
49
+ return size >= 32 || weight > 700 ? 45 : 60;
50
+ };
51
+
52
+ registerAxeAPCA('custom', customConformanceThresholdFn);
53
+ ```
@@ -13,6 +13,82 @@ const runAxe = async (el: HTMLElement): Promise<AxeResults> => {
13
13
  };
14
14
 
15
15
  describe("axe-apca", () => {
16
+ describe("custom conformance level", () => {
17
+ beforeEach(() => {
18
+ const customConformanceThresholdFn = (
19
+ fontSize: string
20
+ ): number | null => {
21
+ return parseFloat(fontSize) >= 32 ? 45 : 60;
22
+ };
23
+
24
+ registerAxeAPCA("custom", customConformanceThresholdFn);
25
+ });
26
+
27
+ it("should check for APCA accessibility contrast violations", async () => {
28
+ const el: HTMLElement = await fixture(
29
+ html`<p
30
+ style="background: white; color: black; font-size: 12px; font-weight: 400;"
31
+ >
32
+ Some copy
33
+ </p>`
34
+ );
35
+
36
+ const results = await runAxe(el);
37
+
38
+ const apcaPasses = results.passes.filter((violation) =>
39
+ violation.id.includes("color-contrast-apca-custom")
40
+ );
41
+
42
+ await expect(apcaPasses.length).to.equal(1);
43
+
44
+ const passNode = apcaPasses[0].nodes[0];
45
+ expect(passNode.all[0].message).to.include(
46
+ "Element has sufficient APCA custom level lightness contrast"
47
+ );
48
+ });
49
+
50
+ it("should detect APCA accessibility contrast violations", async () => {
51
+ const el: HTMLElement = await fixture(
52
+ html`<p
53
+ style="background: white; color: lightgray; font-size: 12px; font-weight: 400;"
54
+ >
55
+ Some copy
56
+ </p>`
57
+ );
58
+
59
+ const results = await runAxe(el);
60
+
61
+ const apcaViolations = results.violations.filter((violation) =>
62
+ violation.id.includes("color-contrast-apca-custom")
63
+ );
64
+
65
+ await expect(apcaViolations.length).to.equal(1);
66
+
67
+ const violationNode = apcaViolations[0].nodes[0];
68
+ expect(violationNode.failureSummary).to.include(
69
+ "Element has insufficient APCA custom level contrast"
70
+ );
71
+ });
72
+
73
+ it("should check nested nodes", async () => {
74
+ const el: HTMLElement = await fixture(
75
+ html`<div style="background: black;">
76
+ <h2>Some title</h2>
77
+ <p>Some copy</p>
78
+ <button style="background: black;">Some button</button>
79
+ </div>`
80
+ );
81
+
82
+ const results = await runAxe(el);
83
+
84
+ const apcaViolations = results.violations.filter((violation) =>
85
+ violation.id.includes("color-contrast-apca-custom")
86
+ );
87
+
88
+ await expect(apcaViolations[0].nodes.length).to.equal(3);
89
+ });
90
+ });
91
+
16
92
  describe("bronze conformance level", () => {
17
93
  beforeEach(() => {
18
94
  registerAxeAPCA("bronze");
@@ -44,7 +120,7 @@ describe("axe-apca", () => {
44
120
  it("should detect APCA accessibility contrast violations", async () => {
45
121
  const el: HTMLElement = await fixture(
46
122
  html`<p
47
- style="background: white; color: lightgray; font-size: 12px; font-weight: 400;"
123
+ style="background: white; color: gray; font-size: 12px; font-weight: 400;"
48
124
  >
49
125
  Some copy
50
126
  </p>`
@@ -12,6 +12,13 @@ type Color = {
12
12
  toHexString: () => string;
13
13
  };
14
14
 
15
+ type ConformanceLevel = "bronze" | "silver" | "custom";
16
+
17
+ type ConformanceThresholdFn = (
18
+ fontSize: string,
19
+ fontWeight: string
20
+ ) => number | null;
21
+
15
22
  declare module "axe-core" {
16
23
  const commons: {
17
24
  color: {
@@ -89,10 +96,7 @@ const getAPCABronzeThreshold = (fontSize: string): number | null => {
89
96
 
90
97
  const generateColorContrastAPCAConformanceCheck = (
91
98
  conformanceLevel: string,
92
- conformanceThresholdFn: (
93
- fontSize: string,
94
- fontWeight: string
95
- ) => number | null
99
+ conformanceThresholdFn: ConformanceThresholdFn
96
100
  ): Check => ({
97
101
  id: `color-contrast-apca-${conformanceLevel}-conformance`,
98
102
  metadata: {
@@ -188,15 +192,19 @@ const colorContrastAPCABronzeConformanceCheck =
188
192
  const colorContrastAPCASilverRule = generateColorContrastAPCARule("silver");
189
193
  const colorContrastAPCABronzeRule = generateColorContrastAPCARule("bronze");
190
194
 
191
- const registerAxeAPCA = (conformanceLevel: "bronze" | "silver") => {
195
+ const registerAxeAPCA = (
196
+ conformanceLevel: ConformanceLevel,
197
+ customConformanceThresholdFn?: ConformanceThresholdFn
198
+ ) => {
192
199
  axe.configure({
193
200
  rules: [generateColorContrastAPCARule(conformanceLevel)],
194
201
  checks: [
195
202
  generateColorContrastAPCAConformanceCheck(
196
203
  conformanceLevel,
197
- conformanceLevel === "bronze"
198
- ? getAPCABronzeThreshold
199
- : getAPCASilverPlusThreshold
204
+ customConformanceThresholdFn ||
205
+ (conformanceLevel === "silver"
206
+ ? getAPCASilverPlusThreshold
207
+ : getAPCABronzeThreshold)
200
208
  ),
201
209
  ],
202
210
  });
@@ -5,7 +5,11 @@ import type { TemplateResult } from "lit-html";
5
5
  import axe from "axe-core";
6
6
  import registerAxeAPCA from "./axe-apca";
7
7
 
8
- registerAxeAPCA("bronze");
8
+ const customConformanceThresholdFn = (fontSize: string): number | null => {
9
+ return parseFloat(fontSize) >= 32 ? 45 : 60;
10
+ };
11
+
12
+ registerAxeAPCA("custom", customConformanceThresholdFn);
9
13
 
10
14
  const colorThemes = ["dark", "light"];
11
15
  const baseThemes = ["", "highcontrast"];
@@ -325,10 +329,10 @@ const runComponentTest = ({
325
329
  axe.configure({
326
330
  rules: [
327
331
  // for non-high contrast, we disable WCAG 2.1 AA (4.5:1)
328
- // and use APCA bronze level instead
332
+ // and use a Stacks-specific APCA custom level instead
329
333
  { id: "color-contrast", enabled: false },
330
334
  {
331
- id: "color-contrast-apca-bronze",
335
+ id: "color-contrast-apca-custom",
332
336
  enabled: !highcontrast,
333
337
  },
334
338
  // for high contrast, we check against WCAG 2.1 AAA (7:1)