Skip to content

Commit 279502e

Browse files
authored
feat(skeleton): add skeleton component (#1925)
* feat(skeleton): add skeleton component * Minor cleanup, a11y enhancements, a11y test * Revert line endings changes * Fix more line endings * Add changeset * Formatting
1 parent 1f93bd3 commit 279502e

32 files changed

+259
-0
lines changed

.changeset/silly-doors-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stackoverflow/stacks": minor
3+
---
4+
5+
Add skeleton component

docs/_data/site-navigation.json

100755100644
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@
299299
"title": "Sidebar widgets",
300300
"url": "/product/components/sidebar-widgets/"
301301
},
302+
{
303+
"title": "Skeleton",
304+
"url": "/product/components/skeleton/"
305+
},
302306
{
303307
"title": "Spinner",
304308
"url": "/product/components/spinner/"

docs/_data/skeleton.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"skeleton": [
3+
{
4+
"class": ".s-skeleton",
5+
"applies": "N/A",
6+
"description": "Base monochrome style"
7+
},
8+
{
9+
"class": ".s-skeleton__ai",
10+
"applies": ".s-skeleton",
11+
"description": "Colorful skeleton style for AI elements"
12+
}
13+
]
14+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
layout: page
3+
title: Skeleton
4+
description: A skeleton component is used to indicate that a large block of content, such as an AI responses or post summary, is loading.
5+
---
6+
7+
<section class="stacks-section">
8+
{% header "h2", "Classes" %}
9+
<div class="overflow-x-auto mb32" tabindex="0">
10+
<table class="wmn4 s-table s-table__bx-simple">
11+
<thead>
12+
<tr>
13+
<th scope="col">Class</th>
14+
<th scope="col">Applied to</th>
15+
<th scope="col">Description</th>
16+
</tr>
17+
</thead>
18+
<tbody class="fs-caption">
19+
{% for item in skeleton.skeleton %}
20+
<tr>
21+
<td><code class="stacks-code ws-nowrap">{{ item.class }}</code></td>
22+
<td>{% if item.applies == "N/A" %}<em class="fc-black-350">{{ item.applies }}</em>{% else %}<code class="stacks-code">{{ item.applies }}</code>{% endif %}</td>
23+
<td>{{ item.description }}</td>
24+
</tr>
25+
{% endfor %}
26+
</tbody>
27+
</table>
28+
</div>
29+
</section>
30+
31+
<section class="stacks-section">
32+
{% header "h2", "Examples" %}
33+
{% header "h3", "Base style" %}
34+
<p class="stacks-copy">The default skeleton should be used when loading large blocks will render multiple rows of content.</p>
35+
<div class="stacks-preview">
36+
{% highlight html %}
37+
<div class="s-skeleton">
38+
<div class="v-visible-sr">Loading…</div>
39+
</div>
40+
{% endhighlight %}
41+
<div class="stacks-preview--example">
42+
<div class="s-skeleton">
43+
<div class="v-visible-sr">Loading…</div>
44+
</div>
45+
</div>
46+
</div>
47+
48+
{% header "h3", "AI" %}
49+
<p class="stacks-copy">The <code class="stacks-code">ai</code> variant of the skeleton should be used when loading AI responses.</p>
50+
<div class="stacks-preview">
51+
{% highlight html %}
52+
<div class="s-skeleton s-skeleton__ai">
53+
<div class="v-visible-sr">Loading…</div>
54+
</div>
55+
{% endhighlight %}
56+
<div class="stacks-preview--example">
57+
<div class="s-skeleton s-skeleton__ai">
58+
<div class="v-visible-sr">Loading…</div>
59+
</div>
60+
</div>
61+
</div>
62+
</section>
63+
64+
<section class="stacks-section">
65+
{% header "h2", "Accessibility" %}
66+
<p class="stacks-copy">For accessibility, it’s important to add fallback loading text that is visible to screen readers. Additionally, you should add <code class="stacks-code">aria-busy="true"</code> to the component that triggered the loading state while the skeleton is shown.</p>
67+
</section>
68+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { runA11yTests } from "../../test/a11y-test-utils";
2+
import "../../index";
3+
4+
describe("skeleton", () => {
5+
runA11yTests({
6+
baseClass: "s-skeleton",
7+
variants: ["ai"],
8+
});
9+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
.s-skeleton {
2+
// BASE COMPONENT-SPECIFIC CUSTOM PROPERTIES
3+
// TODO verify colors with design
4+
--_sk-bg-1: var(--black-400);
5+
--_sk-bg-2: var(--black-350);
6+
--_sk-bg-3: var(--black-300);
7+
--_sk-o: 0.25;
8+
9+
// CONTEXTUAL STYLES
10+
.highcontrast-mode({
11+
--_sk-o: 0.4;
12+
});
13+
14+
@keyframes flow {
15+
0% {
16+
background-position: 400% 50%;
17+
}
18+
100% {
19+
background-position: 0% 50%;
20+
}
21+
}
22+
23+
// VARIANTS
24+
&&__ai {
25+
--_sk-bg-1: #ac76f0;
26+
--_sk-bg-2: #297fff;
27+
--_sk-bg-3: #6abcf8;
28+
}
29+
30+
&,
31+
&:after,
32+
&:before {
33+
@media (prefers-reduced-motion: no-preference) {
34+
animation: flow 8s linear infinite;
35+
}
36+
37+
background-image: linear-gradient(
38+
to right,
39+
var(--_sk-bg-1) 8%,
40+
var(--_sk-bg-2) 16%,
41+
var(--_sk-bg-3) 25%,
42+
var(--_sk-bg-1) 42%,
43+
var(--_sk-bg-2) 58%,
44+
var(--_sk-bg-3) 75%,
45+
var(--_sk-bg-1) 83%
46+
);
47+
background-size: 400% 100%;
48+
border-radius: var(--br-lg);
49+
display: block;
50+
height: var(--su16);
51+
position: relative;
52+
}
53+
54+
&:after,
55+
&:before {
56+
content: '';
57+
position: relative;
58+
}
59+
60+
&:after {
61+
top: calc(var(--su4));
62+
width: calc(2/3 * 100%);
63+
}
64+
65+
&:before {
66+
top: calc(var(--su32) + var(--su8));
67+
width: calc(1/3 * 100%);
68+
}
69+
70+
margin-bottom: var(--su48);
71+
opacity: var(--_sk-o);
72+
width: 100%;
73+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { html } from "@open-wc/testing";
2+
import { runVisualTests } from "../../test/visual-test-utils";
3+
import "../../index";
4+
5+
describe("skeleton", () => {
6+
runVisualTests({
7+
baseClass: "s-skeleton",
8+
variants: ["ai"],
9+
template: ({ component, testid }) => html`
10+
<div class="ws3 p4" data-testid="${testid}">${component}</div>
11+
`,
12+
});
13+
});

lib/stacks-static.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
@import "components/prose/prose.less";
4444
@import "components/select/select.less";
4545
@import "components/sidebar-widget/sidebar-widget.less";
46+
@import "components/skeleton/skeleton.less";
4647
@import "components/spinner/spinner.less";
4748
@import "components/table/table.less";
4849
@import "components/table-container/table-container.less";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:4b44f88d67edbe1906d1d0e118e59c09c3480153271645f43492625b1f1f6479
3+
size 2374
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:fa34bb0a4e86ea761d177826394946b4828568d3262de4ef306327d3bfabd16a
3+
size 2625

0 commit comments

Comments
 (0)