1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-27711] Loading Skeleton page (#17224)

* convert popup page component to use inputs

* disable overflow on popup page to allow content to naturally overflow

* migrate popup-page to OnPush

* add vault-loading-skeleton component

* remove internal loading text

* hide entire skeleton from screen readers
This commit is contained in:
Nick Krantz
2025-11-05 14:05:15 -06:00
committed by GitHub
parent edae5e69c7
commit 8cd39690ed
5 changed files with 62 additions and 44 deletions

View File

@@ -29,11 +29,9 @@ import {
SearchModule,
SectionComponent,
ScrollLayoutDirective,
SkeletonComponent,
SkeletonTextComponent,
SkeletonGroupComponent,
} from "@bitwarden/components";
import { VaultLoadingSkeletonComponent } from "../../../vault/popup/components/vault-loading-skeleton/vault-loading-skeleton.component";
import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service";
import { PopupFooterComponent } from "./popup-footer.component";
@@ -366,9 +364,7 @@ export default {
SectionComponent,
IconButtonModule,
BadgeModule,
SkeletonComponent,
SkeletonTextComponent,
SkeletonGroupComponent,
VaultLoadingSkeletonComponent,
],
providers: [
{
@@ -634,21 +630,9 @@ export const SkeletonLoading: Story = {
template: /* HTML */ `
<extension-container>
<popup-tab-navigation>
<popup-page>
<popup-page hideOverflow>
<popup-header slot="header" pageTitle="Page Header"></popup-header>
<div>
<div class="tw-sr-only" role="status">Loading...</div>
<div class="tw-flex tw-flex-col tw-gap-4">
<bit-skeleton-text class="tw-w-1/3"></bit-skeleton-text>
@for (num of data; track $index) {
<bit-skeleton-group>
<bit-skeleton class="tw-size-8" slot="start"></bit-skeleton>
<bit-skeleton-text [lines]="2" class="tw-w-1/2"></bit-skeleton-text>
</bit-skeleton-group>
<bit-skeleton class="tw-w-full tw-h-[1px]"></bit-skeleton>
}
</div>
</div>
<vault-loading-skeleton></vault-loading-skeleton>
</popup-page>
</popup-tab-navigation>
</extension-container>

View File

@@ -1,7 +1,7 @@
<ng-content select="[slot=header]"></ng-content>
<main class="tw-flex-1 tw-overflow-hidden tw-flex tw-flex-col tw-relative tw-bg-background-alt">
<ng-content select="[slot=full-width-notice]"></ng-content>
<!--
<!--
x padding on this container is designed to always be a minimum of 0.75rem (equivalent to tailwind's tw-px-3), or 0.5rem (equivalent
to tailwind's tw-px-2) in compact mode, but stretch to fill the remainder of the container when the content reaches a maximum of
640px in width (equivalent to tailwind's `sm` breakpoint)
@@ -10,26 +10,28 @@
#nonScrollable
class="tw-transition-colors tw-duration-200 tw-border-0 tw-border-b tw-border-solid tw-py-3 bit-compact:tw-py-2 tw-px-[max(0.75rem,calc((100%-(var(--tw-sm-breakpoint)))/2))] bit-compact:tw-px-[max(0.5rem,calc((100%-(var(--tw-sm-breakpoint)))/2))]"
[ngClass]="{
'tw-invisible !tw-p-0 !tw-border-none': loading || nonScrollable.childElementCount === 0,
'tw-invisible !tw-p-0 !tw-border-none': loading() || nonScrollable.childElementCount === 0,
'tw-border-secondary-300': scrolled(),
'tw-border-transparent': !scrolled(),
}"
>
<ng-content select="[slot=above-scroll-area]"></ng-content>
</div>
<!--
<!--
x padding on this container is designed to always be a minimum of 0.75rem (equivalent to tailwind's tw-px-3), or 0.5rem (equivalent
to tailwind's tw-px-2) in compact mode, but stretch to fill the remainder of the container when the content reaches a maximum of
640px in width (equivalent to tailwind's `sm` breakpoint)
-->
<div
class="tw-overflow-y-auto tw-size-full tw-styled-scrollbar"
class="tw-size-full tw-styled-scrollbar"
data-testid="popup-layout-scroll-region"
(scroll)="handleScroll($event)"
[ngClass]="{
'tw-invisible': loading,
'tw-overflow-hidden': hideOverflow(),
'tw-overflow-y-auto': !hideOverflow(),
'tw-invisible': loading(),
'tw-py-3 bit-compact:tw-py-2 tw-px-[max(0.75rem,calc((100%-(var(--tw-sm-breakpoint)))/2))] bit-compact:tw-px-[max(0.5rem,calc((100%-(var(--tw-sm-breakpoint)))/2))]':
!disablePadding,
!disablePadding(),
}"
bitScrollLayoutHost
>
@@ -37,9 +39,9 @@
</div>
<span
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center tw-text-main"
[ngClass]="{ 'tw-invisible': !loading }"
[ngClass]="{ 'tw-invisible': !loading() }"
>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [attr.aria-label]="loadingText"></i>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [attr.aria-label]="loadingText()"></i>
</span>
</main>
<ng-content select="[slot=footer]"></ng-content>

View File

@@ -1,11 +1,16 @@
import { CommonModule } from "@angular/common";
import { booleanAttribute, Component, inject, Input, signal } from "@angular/core";
import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
inject,
input,
signal,
} from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ScrollLayoutHostDirective } from "@bitwarden/components";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-page",
templateUrl: "popup-page.component.html",
@@ -13,28 +18,23 @@ import { ScrollLayoutHostDirective } from "@bitwarden/components";
class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden",
},
imports: [CommonModule, ScrollLayoutHostDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PopupPageComponent {
protected i18nService = inject(I18nService);
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() loading = false;
readonly loading = input<boolean>(false);
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ transform: booleanAttribute })
disablePadding = false;
readonly disablePadding = input(false, { transform: booleanAttribute });
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
protected scrolled = signal(false);
/** Hides any overflow within the page content */
readonly hideOverflow = input(false, { transform: booleanAttribute });
protected readonly scrolled = signal(false);
isScrolled = this.scrolled.asReadonly();
/** Accessible loading label for the spinner. Defaults to "loading" */
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() loadingText?: string = this.i18nService.t("loading");
readonly loadingText = input<string | undefined>(this.i18nService.t("loading"));
handleScroll(event: Event) {
this.scrolled.set((event.currentTarget as HTMLElement).scrollTop !== 0);

View File

@@ -0,0 +1,15 @@
<section aria-hidden="true">
<div class="tw-mt-1.5 tw-flex tw-flex-col tw-gap-4">
<bit-skeleton-text class="tw-w-[8.625rem] tw-max-w-full tw-mb-2.5"></bit-skeleton-text>
@for (num of numberOfItems; track $index) {
<bit-skeleton-group class="tw-mx-2">
<bit-skeleton class="tw-size-6" slot="start"></bit-skeleton>
<div class="tw-flex tw-flex-col tw-gap-1">
<bit-skeleton class="tw-w-40 tw-h-2.5 tw-max-w-full"></bit-skeleton>
<bit-skeleton class="tw-w-24 tw-h-2.5 tw-max-w-full"></bit-skeleton>
</div>
</bit-skeleton-group>
<hr class="tw-h-[1px] -tw-mr-3 tw-bg-secondary-100 tw-border-none" />
}
</div>
</section>

View File

@@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import {
SkeletonComponent,
SkeletonGroupComponent,
SkeletonTextComponent,
} from "@bitwarden/components";
@Component({
selector: "vault-loading-skeleton",
templateUrl: "./vault-loading-skeleton.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkeletonGroupComponent, SkeletonComponent, SkeletonTextComponent],
})
export class VaultLoadingSkeletonComponent {
protected readonly numberOfItems: null[] = new Array(15).fill(null);
}