Skip to content

Commit ba5c93f

Browse files
authored
[CL-427] Add skeleton loading components to the CL (#16728)
1 parent 96d40ae commit ba5c93f

14 files changed

+427
-0
lines changed

.storybook/format-args-for-code-snippet.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export const formatArgsForCodeSnippet = <ComponentType extends Record<string, an
2525
const formattedArray = value.map((v) => `'${v}'`).join(", ");
2626
return `[${key}]="[${formattedArray}]"`;
2727
}
28+
29+
if (typeof value === "number") {
30+
return `[${key}]="${value}"`;
31+
}
32+
2833
return `${key}="${value}"`;
2934
})
3035
.join(" ");

apps/browser/src/platform/popup/layout/popup-layout.stories.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import {
2929
SearchModule,
3030
SectionComponent,
3131
ScrollLayoutDirective,
32+
SkeletonComponent,
33+
SkeletonTextComponent,
34+
SkeletonGroupComponent,
3235
} from "@bitwarden/components";
3336

3437
import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service";
@@ -335,6 +338,9 @@ export default {
335338
SectionComponent,
336339
IconButtonModule,
337340
BadgeModule,
341+
SkeletonComponent,
342+
SkeletonTextComponent,
343+
SkeletonGroupComponent,
338344
],
339345
providers: [
340346
{
@@ -594,6 +600,34 @@ export const Loading: Story = {
594600
}),
595601
};
596602

603+
export const SkeletonLoading: Story = {
604+
render: (args) => ({
605+
props: { ...args, data: Array(8) },
606+
template: /* HTML */ `
607+
<extension-container>
608+
<popup-tab-navigation>
609+
<popup-page>
610+
<popup-header slot="header" pageTitle="Page Header"></popup-header>
611+
<div>
612+
<div class="tw-sr-only" role="status">Loading...</div>
613+
<div class="tw-flex tw-flex-col tw-gap-4">
614+
<bit-skeleton-text class="tw-w-1/3"></bit-skeleton-text>
615+
@for (num of data; track $index) {
616+
<bit-skeleton-group>
617+
<bit-skeleton class="tw-size-8" slot="start"></bit-skeleton>
618+
<bit-skeleton-text [lines]="2" class="tw-w-1/2"></bit-skeleton-text>
619+
</bit-skeleton-group>
620+
<bit-skeleton class="tw-w-full tw-h-[1px]"></bit-skeleton>
621+
}
622+
</div>
623+
</div>
624+
</popup-page>
625+
</popup-tab-navigation>
626+
</extension-container>
627+
`,
628+
}),
629+
};
630+
597631
export const TransparentHeader: Story = {
598632
render: (args) => ({
599633
props: args,

libs/components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export * from "./search";
3737
export * from "./section";
3838
export * from "./select";
3939
export * from "./shared/compact-mode.service";
40+
export * from "./skeleton";
4041
export * from "./table";
4142
export * from "./tabs";
4243
export * from "./toast";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./skeleton.component";
2+
export * from "./skeleton-text.component";
3+
export * from "./skeleton-group.component";
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div class="tw-flex tw-flex-row tw-justify-between tw-gap-2">
2+
<div class="tw-flex tw-gap-2 tw-w-full">
3+
<ng-content select="[slot=start]"></ng-content>
4+
<ng-content></ng-content>
5+
</div>
6+
<ng-content select="[slot=end]"></ng-content>
7+
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { CommonModule } from "@angular/common";
2+
import { Component } from "@angular/core";
3+
4+
/**
5+
* Arranges skeleton loaders into a pre-arranged group that mimics the table and item components.
6+
*
7+
* Pass skeleton loaders into the start, default, and end content slots. The content within each slot
8+
* is fully customizable.
9+
*/
10+
@Component({
11+
selector: "bit-skeleton-group",
12+
templateUrl: "./skeleton-group.component.html",
13+
imports: [CommonModule],
14+
host: {
15+
class: "tw-block",
16+
},
17+
})
18+
export class SkeletonGroupComponent {}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
2+
3+
import { SharedModule } from "../shared/shared.module";
4+
5+
import { SkeletonGroupComponent } from "./skeleton-group.component";
6+
import { SkeletonTextComponent } from "./skeleton-text.component";
7+
import { SkeletonComponent } from "./skeleton.component";
8+
9+
export default {
10+
title: "Component Library/Skeleton/Skeleton Group",
11+
component: SkeletonGroupComponent,
12+
decorators: [
13+
moduleMetadata({
14+
imports: [SharedModule, SkeletonTextComponent, SkeletonComponent],
15+
}),
16+
],
17+
} as Meta<SkeletonGroupComponent>;
18+
19+
type Story = StoryObj<SkeletonGroupComponent>;
20+
21+
export const Default: Story = {
22+
render: (args) => ({
23+
props: args,
24+
template: /*html*/ `
25+
<bit-skeleton-group>
26+
<bit-skeleton class="tw-size-8" slot="start"></bit-skeleton>
27+
<bit-skeleton-text [lines]="2" class="tw-w-1/2"></bit-skeleton-text>
28+
<bit-skeleton-text [lines]="1" slot="end" class="tw-w-1/4"></bit-skeleton-text>
29+
</bit-skeleton-group>
30+
`,
31+
}),
32+
};
33+
34+
export const NoEndSlot: Story = {
35+
render: (args) => ({
36+
props: args,
37+
template: /*html*/ `
38+
<bit-skeleton-group>
39+
<bit-skeleton class="tw-size-8" slot="start"></bit-skeleton>
40+
<bit-skeleton-text [lines]="2" class="tw-w-1/2"></bit-skeleton-text>
41+
</bit-skeleton-group>
42+
`,
43+
}),
44+
};
45+
46+
export const NoStartSlot: Story = {
47+
render: (args) => ({
48+
props: args,
49+
template: /*html*/ `
50+
<bit-skeleton-group>
51+
<bit-skeleton-text [lines]="2" class="tw-w-1/2"></bit-skeleton-text>
52+
<bit-skeleton-text [lines]="1" slot="end" class="tw-w-1/4"></bit-skeleton-text>
53+
</bit-skeleton-group>
54+
`,
55+
}),
56+
};
57+
58+
export const CustomContent: Story = {
59+
render: (args) => ({
60+
props: args,
61+
template: /*html*/ `
62+
<bit-skeleton-group>
63+
<bit-skeleton class="tw-size-12" slot="start" edgeShape="circle"></bit-skeleton>
64+
<bit-skeleton-text [lines]="3" class="tw-w-full"></bit-skeleton-text>
65+
<div slot="end" class="tw-flex tw-flex-row tw-gap-1">
66+
<bit-skeleton class="tw-size-4" slot="start"></bit-skeleton>
67+
<bit-skeleton class="tw-size-4" slot="start"></bit-skeleton>
68+
<bit-skeleton class="tw-size-4" slot="start"></bit-skeleton>
69+
</div>
70+
</bit-skeleton-group>
71+
`,
72+
}),
73+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div class="tw-w-full tw-flex tw-flex-col tw-gap-2">
2+
@for (line of this.linesArray(); track $index; let last = $last, first = $first) {
3+
<bit-skeleton
4+
class="tw-h-3"
5+
[ngClass]="{
6+
'tw-w-full': first || !last,
7+
'tw-w-1/3': !first && last,
8+
}"
9+
></bit-skeleton>
10+
}
11+
</div>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { CommonModule } from "@angular/common";
2+
import { Component, computed, input } from "@angular/core";
3+
4+
import { SkeletonComponent } from "./skeleton.component";
5+
6+
/**
7+
* Specific skeleton component used to represent lines of text. It uses the `bit-skeleton`
8+
* under the hood.
9+
*
10+
* Customize the number of lines represented with the `lines` input. Customize the width
11+
* by applying a class to the `bit-skeleton-text` element (i.e. `tw-w-1/2`).
12+
*/
13+
@Component({
14+
selector: "bit-skeleton-text",
15+
templateUrl: "./skeleton-text.component.html",
16+
imports: [CommonModule, SkeletonComponent],
17+
host: {
18+
class: "tw-block",
19+
},
20+
})
21+
export class SkeletonTextComponent {
22+
/**
23+
* The number of text lines to display
24+
*/
25+
readonly lines = input<number>(1);
26+
27+
/**
28+
* Array-transformed version of the `lines` to loop over
29+
*/
30+
protected linesArray = computed(() => [...Array(this.lines()).keys()]);
31+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
2+
3+
import { SharedModule } from "../shared/shared.module";
4+
5+
import { SkeletonTextComponent } from "./skeleton-text.component";
6+
7+
import { formatArgsForCodeSnippet } from ".storybook/format-args-for-code-snippet";
8+
9+
export default {
10+
title: "Component Library/Skeleton/Skeleton Text",
11+
component: SkeletonTextComponent,
12+
decorators: [
13+
moduleMetadata({
14+
imports: [SharedModule],
15+
}),
16+
],
17+
args: {
18+
lines: 1,
19+
},
20+
argTypes: {
21+
lines: {
22+
control: { type: "number", min: 1 },
23+
},
24+
},
25+
} as Meta<SkeletonTextComponent>;
26+
27+
type Story = StoryObj<SkeletonTextComponent>;
28+
29+
export const Text: Story = {
30+
render: (args) => ({
31+
props: args,
32+
template: /*html*/ `
33+
<bit-skeleton-text ${formatArgsForCodeSnippet<SkeletonTextComponent>(args)}></bit-skeleton-text>
34+
`,
35+
}),
36+
};
37+
38+
export const TextMultiline: Story = {
39+
render: (args) => ({
40+
props: args,
41+
template: /*html*/ `
42+
<bit-skeleton-text ${formatArgsForCodeSnippet<SkeletonTextComponent>(args)}></bit-skeleton-text>
43+
`,
44+
}),
45+
args: {
46+
lines: 5,
47+
},
48+
};

0 commit comments

Comments
 (0)