@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
@@ -0,0 +1,435 @@
1
+ import { html } from "@open-wc/testing";
2
+ import {
3
+ IconArchiveSm,
4
+ IconCheckmarkSm,
5
+ IconEllipsisVertical,
6
+ IconEyeSm,
7
+ IconNotInterested,
8
+ IconPencilSm,
9
+ IconTackSm,
10
+ IconTrashSm,
11
+ } from "@stackoverflow/stacks-icons/icons";
12
+ import type { TestVariationArgs } from "../../test/test-utils";
13
+ import "../../index";
14
+
15
+ type BadgeType =
16
+ | "danger"
17
+ | "danger-filled"
18
+ | "info"
19
+ | "muted"
20
+ | "muted-filled"
21
+ | "warning";
22
+
23
+ type Stats = {
24
+ votes: number;
25
+ views: number;
26
+ answers: number;
27
+ accepted?: boolean;
28
+ bounty?: number;
29
+ badge?: BadgeType;
30
+ };
31
+
32
+ type Tags = { text: string; type?: "required" | "moderator" }[];
33
+
34
+ type TruncationSizes = "sm" | "md" | "lg" | "";
35
+
36
+ const formatNumber = (num: number) => {
37
+ switch (true) {
38
+ case num > 10000090:
39
+ return (num / 1000000).toFixed(0) + "m";
40
+ case num > 1000000:
41
+ return (num / 1000000).toFixed(1) + "m";
42
+ case num > 10000:
43
+ return (num / 1000).toFixed(0) + "k";
44
+ case num > 1000:
45
+ return (num / 1000).toFixed(1) + "k";
46
+ default:
47
+ return num.toString();
48
+ }
49
+ };
50
+
51
+ const getBadge = (type: BadgeType) => {
52
+ const badgeClasses = type
53
+ .split("-")
54
+ .map((modifier) => `s-badge__${modifier}`)
55
+ .join(" ");
56
+ const getIcon = () => {
57
+ switch (type) {
58
+ case "danger":
59
+ return IconNotInterested;
60
+ case "danger-filled":
61
+ return IconTrashSm;
62
+ case "info":
63
+ return IconPencilSm;
64
+ case "muted":
65
+ return IconArchiveSm;
66
+ case "muted-filled":
67
+ return IconTackSm;
68
+ case "warning":
69
+ return IconEyeSm;
70
+ default:
71
+ return "";
72
+ }
73
+ };
74
+
75
+ const getText = () => {
76
+ switch (type) {
77
+ case "danger":
78
+ return "Closed";
79
+ case "danger-filled":
80
+ return "Deleted";
81
+ case "info":
82
+ return "Draft";
83
+ case "muted":
84
+ return "Archived";
85
+ case "muted-filled":
86
+ return "Pinned";
87
+ case "warning":
88
+ return "Review";
89
+ default:
90
+ return "";
91
+ }
92
+ };
93
+
94
+ return `
95
+ <div
96
+ class="s-post-summary--stats-item s-badge s-badge__icon ${badgeClasses}"
97
+ >
98
+ ${getIcon()}
99
+ ${getText()}
100
+ </div>`;
101
+ };
102
+
103
+ const getDescription = (truncation?: TruncationSizes, text?: string) => `
104
+ <p class="s-post-summary--content-excerpt
105
+ ${truncation ? `s-post-summary--content-excerpt__${truncation}` : ""}
106
+ ">
107
+ ${text ? text : "In the spirit of type safety associated with the CriteriaQuery JPA 2.0 also has an API to support Metamodel representation of entities. Is anyone aware of a fully functional implementation of this API (to generate the Metamodel as opposed to creating the metamodel classes manually)? It would be awesome if someone also knows the steps for setting this up in Eclipse (I assume it's as simple as setting up an annotation processor, but you never know)."}
108
+ </p>
109
+ `;
110
+
111
+ const getHotnessClass = (num: number) => {
112
+ switch (true) {
113
+ case num > 100000:
114
+ return "is-supernova";
115
+ case num > 10000:
116
+ return "is-hot";
117
+ case num > 1000:
118
+ return "is-warm";
119
+ default:
120
+ return "";
121
+ }
122
+ };
123
+
124
+ const getStats = ({
125
+ votes,
126
+ views,
127
+ answers,
128
+ accepted,
129
+ bounty,
130
+ badge,
131
+ }: Stats) => `
132
+ <div class="s-post-summary--stats">
133
+ ${badge ? getBadge(badge) : ""}
134
+ <div class="s-post-summary--stats-item s-post-summary--stats-item__emphasized">
135
+ <span class="s-post-summary--stats-item-number">${formatNumber(votes)}</span>
136
+ <span class="s-post-summary--stats-item-unit">${votes === 1 ? "vote" : "votes"}</span>
137
+ </div>
138
+ <div class="s-post-summary--stats-item ${accepted ? "has-accepted-answer" : ""} ${answers > 0 ? "has-answers" : ""}">
139
+ ${accepted ? IconCheckmarkSm : ""}
140
+ <span class="s-post-summary--stats-item-number">${formatNumber(answers)}</span>
141
+ <span class="s-post-summary--stats-item-unit">${answers === 1 ? "answer" : "answers"}</span>
142
+ </div>
143
+ <div class="s-post-summary--stats-item ${getHotnessClass(views)}">
144
+ <span class="s-post-summary--stats-item-number">${formatNumber(views)}</span>
145
+ <span class="s-post-summary--stats-item-unit">${views === 1 ? "view" : "views"}</span>
146
+ </div>
147
+ ${
148
+ bounty
149
+ ? `
150
+ <div class="s-post-summary--stats-item s-badge s-badge__bounty">
151
+ +${bounty}
152
+ </div>
153
+ `
154
+ : ""
155
+ }
156
+ </div>
157
+ `;
158
+
159
+ const getTags = (tags?: Tags) => {
160
+ const tagsArr = tags ?? [
161
+ {
162
+ text: "feature-request",
163
+ type: "required",
164
+ },
165
+ {
166
+ text: "status-complete",
167
+ type: "moderator",
168
+ },
169
+ {
170
+ text: "design",
171
+ },
172
+ ];
173
+
174
+ const tagsHTML = tagsArr
175
+ .map(
176
+ ({ text, type }) => `
177
+ <a class="s-tag ${type ? `s-tag__${type}` : ""}" href="/">${text}</a>
178
+ `
179
+ )
180
+ .join("");
181
+
182
+ return `<div class="s-post-summary--meta-tags">${tagsHTML}</div>`;
183
+ };
184
+
185
+ const getUser = () => `
186
+ <div class="s-user-card s-user-card__minimal">
187
+ <a href="#" class="s-avatar s-user-card--avatar">
188
+ <img class="s-avatar--image" src="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" alt="placeholder avatar">
189
+ <span class="v-visible-sr">Tracy Smith</span>
190
+ </a>
191
+ <a href="#" class="s-user-card--link">Tracy Smith</a>
192
+ <ul class="s-user-card--awards">
193
+ <li class="s-user-card--rep">1350</li>
194
+ </ul>
195
+ <time class="s-user-card--time">asked just now</time>
196
+ </div>
197
+ `;
198
+
199
+ const getChildren = ({
200
+ show = {
201
+ description: false,
202
+ menuBtn: false,
203
+ stats: false,
204
+ tags: false,
205
+ title: false,
206
+ user: false,
207
+ },
208
+ description = {
209
+ truncation: "",
210
+ text: "",
211
+ },
212
+ stats,
213
+ tags,
214
+ title,
215
+ }: {
216
+ show?: {
217
+ description?: boolean;
218
+ menuBtn?: boolean;
219
+ stats?: boolean;
220
+ tags?: boolean;
221
+ title?: boolean;
222
+ user?: boolean;
223
+ };
224
+ description?: {
225
+ truncation?: TruncationSizes;
226
+ text?: string;
227
+ };
228
+ stats?: Stats;
229
+ tags?: Tags;
230
+ title?: string;
231
+ }) => {
232
+ const titleEl =
233
+ show.title || title
234
+ ? `
235
+ <h3 class="s-post-summary--content-title">
236
+ ${title ? title : "How to generate the JPA entity Metamodel?"}
237
+ </h3>
238
+ `
239
+ : "";
240
+ const descriptionEl =
241
+ show.description || description.truncation || description.text
242
+ ? description
243
+ ? getDescription(description.truncation, description.text)
244
+ : getDescription()
245
+ : "";
246
+ const tagsEl = show.tags || tags ? getTags(tags) : "";
247
+ const userEl = show.user ? getUser() : "";
248
+ const menuBtnEl = show.menuBtn
249
+ ? `
250
+ <a href="#" class="s-btn s-btn__muted s-post-summary--content-menu-button">
251
+ ${IconEllipsisVertical}
252
+ <span class="v-visible-sr">menu</span>
253
+ </a>
254
+ `
255
+ : "";
256
+
257
+ return `
258
+ ${
259
+ show.stats || stats
260
+ ? getStats(
261
+ stats
262
+ ? stats
263
+ : {
264
+ votes: 95,
265
+ views: 104123,
266
+ answers: 5,
267
+ accepted: true,
268
+ bounty: 50,
269
+ }
270
+ )
271
+ : ""
272
+ }
273
+ ${
274
+ titleEl || descriptionEl || tagsEl || userEl || menuBtnEl
275
+ ? `
276
+ <div class="s-post-summary--content">
277
+ ${titleEl}
278
+ ${descriptionEl}
279
+ <div class="s-post-summary--meta">
280
+ ${tagsEl}
281
+ ${userEl}
282
+ </div>
283
+ ${menuBtnEl}
284
+ </div>`
285
+ : ""
286
+ }
287
+ `;
288
+ };
289
+
290
+ const getBadgeChildren = (badge: BadgeType) => {
291
+ return getChildren({
292
+ show: {
293
+ stats: true,
294
+ },
295
+ stats: {
296
+ badge: badge as BadgeType,
297
+ answers: 0,
298
+ votes: 1,
299
+ views: 20,
300
+ },
301
+ });
302
+ };
303
+
304
+ const getSizeChildren = (size: string) => {
305
+ return getChildren({
306
+ show: {
307
+ description: true,
308
+ menuBtn: true,
309
+ stats: true,
310
+ tags: true,
311
+ title: true,
312
+ user: true,
313
+ },
314
+ description: {
315
+ truncation: size as TruncationSizes,
316
+ },
317
+ });
318
+ };
319
+
320
+ const getStatsChildren = ({
321
+ accepted = false,
322
+ answers = 1,
323
+ views = 20,
324
+ }: {
325
+ accepted?: boolean;
326
+ answers?: number;
327
+ views?: number;
328
+ }) =>
329
+ getChildren({
330
+ show: {
331
+ stats: true,
332
+ },
333
+ stats: {
334
+ votes: 1,
335
+ answers,
336
+ accepted,
337
+ views,
338
+ },
339
+ });
340
+
341
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
342
+ const template = ({ component, testid }: any) => html`
343
+ <div class="d-flex ai-center jc-center p8 ws6" data-testid="${testid}">
344
+ ${component}
345
+ </div>
346
+ `;
347
+
348
+ const testArgs: {
349
+ [key: string]: TestVariationArgs;
350
+ } = {
351
+ base: {
352
+ baseClass: "s-post-summary",
353
+ modifiers: {
354
+ primary: ["deleted", "ignored", "watched"], // variants described as modifiers to test colliding modifiers
355
+ secondary: ["minimal"],
356
+ global: ["w100"],
357
+ },
358
+ children: {
359
+ default: getChildren({
360
+ show: {
361
+ description: true,
362
+ menuBtn: true,
363
+ stats: true,
364
+ tags: true,
365
+ title: true,
366
+ user: true,
367
+ },
368
+ }),
369
+ sparce: getChildren({
370
+ show: {
371
+ stats: true,
372
+ tags: true,
373
+ title: true,
374
+ user: true,
375
+ },
376
+ tags: [
377
+ {
378
+ text: "featured-request",
379
+ type: "required",
380
+ },
381
+ ],
382
+ title: "Short title",
383
+ }),
384
+ },
385
+ options: {
386
+ includeNullModifier: false,
387
+ },
388
+ template,
389
+ },
390
+ badges: {
391
+ baseClass: "s-post-summary",
392
+ children: {
393
+ "badge-danger": getBadgeChildren("danger"),
394
+ "badge-danger-filled": getBadgeChildren("danger-filled"),
395
+ "badge-info": getBadgeChildren("info"),
396
+ "badge-muted": getBadgeChildren("muted"),
397
+ "badge-muted-filled": getBadgeChildren("muted-filled"),
398
+ "badge-warning": getBadgeChildren("warning"),
399
+ },
400
+ template,
401
+ },
402
+ sizes: {
403
+ baseClass: "s-post-summary",
404
+ modifiers: {
405
+ global: ["w100"],
406
+ },
407
+ children: {
408
+ "description-sm": getSizeChildren("sm"),
409
+ "description-md": getSizeChildren("md"),
410
+ "description-lg": getSizeChildren("lg"),
411
+ },
412
+ options: {
413
+ includeNullModifier: false,
414
+ },
415
+ template,
416
+ },
417
+ stats: {
418
+ baseClass: "s-post-summary",
419
+ children: {
420
+ "stats-unanswered": getStatsChildren({ answers: 0 }),
421
+ "stats-answered": getStatsChildren({ answers: 1 }),
422
+ "stats-answered-accepted": getStatsChildren({
423
+ answers: 10,
424
+ accepted: true,
425
+ }),
426
+ "stats-views": getStatsChildren({ views: 1 }),
427
+ "stats-views-warm": getStatsChildren({ views: 1001 }),
428
+ "stats-views-hot": getStatsChildren({ views: 10001 }),
429
+ "stats-views-supernova": getStatsChildren({ views: 100001 }),
430
+ },
431
+ template,
432
+ },
433
+ };
434
+
435
+ export default testArgs;
@@ -0,0 +1,17 @@
1
+ import { runVisualTests } from "../../test/visual-test-utils";
2
+ import testArgs from "./post-summary.test.setup";
3
+ import "../../index";
4
+
5
+ describe("post-summary", () => {
6
+ // Base, sparce
7
+ runVisualTests(testArgs.base);
8
+
9
+ // Truncated description sizes
10
+ runVisualTests(testArgs.sizes);
11
+
12
+ // Stats - answers, view hotness
13
+ runVisualTests(testArgs.stats);
14
+
15
+ // Badges
16
+ runVisualTests(testArgs.badges);
17
+ });
@@ -100,10 +100,14 @@
100
100
 
101
101
  // INTERACTION
102
102
  &:focus {
103
- color: var(--black);
104
103
  .focus-styles();
105
104
  }
106
105
 
106
+ &:focus,
107
+ &.focus {
108
+ color: var(--black);
109
+ }
110
+
107
111
  background-color: var(--_se-select-bg);
108
112
  border: var(--su-static1) solid var(--_se-select-bc);
109
113
  border-radius: var(--_se-select-br);