-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #527 from funidata/DS-248-spinner-v1
[Loading Spinner]: New Component
- Loading branch information
Showing
117 changed files
with
902 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
...-fudis/src/lib/components/loading-spinner/examples/loading-spinner-example.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
@let loading = loadingState(); | ||
<div class="fudis-loading-spinner-demo fudis-p-md"> | ||
<div | ||
class="fudis-loading-spinner-demo__backdrop" | ||
[class.fudis-loading-spinner-demo__backdrop--visible]="loading" | ||
[class.fudis-loading-spinner-demo__backdrop--hidden]="!loading" | ||
> | ||
<fudis-loading-spinner [variant]="'lg'" [visible]="loading" | ||
/></div> | ||
<fudis-grid [alignItemsX]="'center'" *ngIf="!loading"> | ||
<fudis-heading #headingRef tabindex="-1" [align]="'center'" [level]="1" | ||
>Welcome to Loading Spinner Demo Page</fudis-heading | ||
> | ||
<fudis-body-text | ||
>Click Button below to turn loading state on for 3 seconds. This will trigger also status | ||
message for screen readers.</fudis-body-text | ||
> | ||
<fudis-body-text | ||
>After 3s, focus handled by this demo component, will move shortly to Heading component. There | ||
is small delay in focus, so that screen readers have some time to read out Loading Spinner's | ||
changed status message. | ||
</fudis-body-text> | ||
<fudis-button [label]="'Set loading on!'" (handleClick)="toggleLoading()" /> | ||
</fudis-grid> | ||
</div> |
36 changes: 36 additions & 0 deletions
36
...-fudis/src/lib/components/loading-spinner/examples/loading-spinner-example.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* stylelint-disable unit-disallowed-list */ | ||
@use '../../../foundations/borders/mixins.scss' as borders; | ||
@use '../../../foundations/colors/mixins.scss' as colors; | ||
|
||
.fudis-loading-spinner-demo { | ||
@include borders.outline('3px', 'solid', 'primary'); | ||
|
||
display: flex; | ||
position: relative; | ||
align-items: center; | ||
justify-content: center; | ||
max-width: 30rem; | ||
min-height: 15rem; | ||
|
||
&__backdrop { | ||
@include colors.bg-color('white'); | ||
@include borders.outline('3px', 'solid', 'green'); | ||
|
||
display: flex; | ||
position: relative; | ||
position: absolute; | ||
align-items: center; | ||
justify-content: center; | ||
transition: 1s; | ||
width: 100%; | ||
height: 100%; | ||
|
||
&--visible { | ||
opacity: 1; | ||
} | ||
|
||
&--hidden { | ||
opacity: 0; | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...gx-fudis/src/lib/components/loading-spinner/examples/loading-spinner-example.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Component, signal, ViewChild } from '@angular/core'; | ||
|
||
import { CommonModule } from '@angular/common'; | ||
import { LoadingSpinnerComponent } from '../loading-spinner.component'; | ||
import { NgxFudisModule } from '../../../ngx-fudis.module'; | ||
import { HeadingComponent } from '../../typography/heading/heading.component'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [CommonModule, LoadingSpinnerComponent, NgxFudisModule], | ||
selector: 'example-loading-spinner-demo', | ||
styleUrl: './loading-spinner-example.component.scss', | ||
templateUrl: './loading-spinner-example.component.html', | ||
}) | ||
export class StorybookExampleLoadingSpinnerComponent { | ||
loadingState = signal<boolean>(false); | ||
|
||
@ViewChild('headingRef') public headingRef: HeadingComponent; | ||
|
||
public toggleLoading(): void { | ||
this.loadingState.set(true); | ||
|
||
setTimeout(() => { | ||
this.loadingState.set(false); | ||
|
||
// Enough delay, so that screen reader has time to announce from Loading Spinner that page load is finished | ||
setTimeout(() => { | ||
this.headingRef.headingRef.nativeElement.focus(); | ||
}, 500); | ||
}, 3000); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...udis/projects/ngx-fudis/src/lib/components/loading-spinner/loading-spinner.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
@let translations = _translationService.getTranslations()(); | ||
|
||
<div class="fudis-loading-spinner"> | ||
<p | ||
*ngIf="variant === 'lg'" | ||
class="fudis-loading-spinner__status fudis-visually-hidden" | ||
role="status" | ||
>{{ | ||
statusMessage || | ||
(visible | ||
? translations.LOADING_SPINNER.PAGE_LOADING | ||
: translations.LOADING_SPINNER.PAGE_LOAD_FINISHED) | ||
}}</p | ||
> | ||
<div *ngIf="visible" class="fudis-loading-spinner__ui-content"> | ||
<svg | ||
class="fudis-loading-spinner__svg fudis-loading-spinner__variant__{{ variant }}" | ||
aria-hidden="true" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
fill-rule="evenodd" | ||
clip-rule="evenodd" | ||
d="M15.2256 0.441641C12.7017 -0.262704 10.0165 -0.121557 7.58041 0.843527C5.14424 1.80861 3.09092 3.54458 1.73407 5.78628C0.377218 8.02798 -0.208579 10.6522 0.066151 13.2581C0.34088 15.864 1.46103 18.3084 3.25553 20.2179C5.05002 22.1274 7.4202 23.397 10.004 23.8328C12.5879 24.2687 15.2434 23.8468 17.565 22.6316L16.1133 19.8581C14.3974 20.7563 12.4346 21.0681 10.5247 20.746C8.61493 20.4239 6.86307 19.4855 5.5367 18.0741C4.21034 16.6628 3.3824 14.856 3.17934 12.9299C2.97627 11.0038 3.40925 9.06416 4.41214 7.40725C5.41504 5.75033 6.93271 4.46723 8.73335 3.75391C10.534 3.04059 12.5186 2.93626 14.3841 3.45686C16.2497 3.97747 17.8935 5.09438 19.0645 6.63702C20.2356 8.17965 20.8696 10.0632 20.8696 12H24C24 9.37963 23.1424 6.83129 21.5579 4.7442C19.9735 2.65711 17.7496 1.14599 15.2256 0.441641Z" | ||
/> | ||
</svg> | ||
|
||
<fudis-body-text | ||
[align]="'center'" | ||
[variant]="variant === 'sm' ? 'md-regular' : 'lg-regular'" | ||
>{{ label || translations.LOADING_SPINNER.VISIBLE_LABEL }}</fudis-body-text | ||
> | ||
</div> | ||
</div> |
49 changes: 49 additions & 0 deletions
49
...udis/projects/ngx-fudis/src/lib/components/loading-spinner/loading-spinner.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
@use '../../foundations/colors/tokens.scss' as colors; | ||
@use '../../foundations/spacing/tokens.scss' as spacing; | ||
|
||
/* stylelint-disable-next-line unit-disallowed-list */ | ||
$lg-size: calc(3rem / var(--fudis-rem-multiplier)); // 48px | ||
/* stylelint-disable-next-line unit-disallowed-list */ | ||
$max-width: calc(16rem / var(--fudis-rem-multiplier)); // 256px | ||
|
||
.fudis-loading-spinner { | ||
display: inline-block; | ||
max-width: $max-width; | ||
|
||
&__ui-content { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
|
||
&__variant { | ||
&__sm { | ||
margin-top: spacing.$spacing-xxs; | ||
margin-bottom: spacing.$spacing-xs; | ||
width: spacing.$spacing-md; | ||
height: spacing.$spacing-md; | ||
} | ||
|
||
&__lg { | ||
margin-bottom: spacing.$spacing-sm; | ||
width: $lg-size; | ||
height: $lg-size; | ||
} | ||
} | ||
|
||
&__svg { | ||
animation: spin 1.5s linear infinite; | ||
fill: colors.$color-primary; | ||
} | ||
} | ||
|
||
@keyframes spin { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
|
||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} |
161 changes: 161 additions & 0 deletions
161
...s/projects/ngx-fudis/src/lib/components/loading-spinner/loading-spinner.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { LoadingSpinnerComponent } from './loading-spinner.component'; | ||
import { getElement } from '../../utilities/tests/utilities'; | ||
import { NgxFudisModule } from '../../ngx-fudis.module'; | ||
|
||
describe('LoadingSpinnerComponent', () => { | ||
let component: LoadingSpinnerComponent; | ||
let fixture: ComponentFixture<LoadingSpinnerComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [LoadingSpinnerComponent, NgxFudisModule], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(LoadingSpinnerComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
const getParagraphLabel = (variant: string): HTMLParagraphElement => { | ||
return getElement( | ||
fixture, | ||
`.fudis-loading-spinner .fudis-loading-spinner__ui-content .fudis-body-text__${variant}-regular`, | ||
) as HTMLParagraphElement; | ||
}; | ||
|
||
const getSvgIcon = (variant: string): HTMLOrSVGElement => { | ||
return getElement( | ||
fixture, | ||
`svg.fudis-loading-spinner__svg.fudis-loading-spinner__variant__${variant}[aria-hidden="true"]`, | ||
) as HTMLOrSVGElement; | ||
}; | ||
|
||
const getStatusMessage = (): string | null => { | ||
return ( | ||
getElement( | ||
fixture, | ||
'.fudis-loading-spinner__status.fudis-visually-hidden[role="status"]', | ||
) as HTMLParagraphElement | ||
)?.textContent; | ||
}; | ||
|
||
describe('visual properties', () => { | ||
const variants = [ | ||
{ | ||
variant: 'sm', | ||
name: 'small (default)', | ||
bodyTextVariant: 'md', | ||
}, | ||
{ | ||
variant: 'lg', | ||
name: 'large', | ||
bodyTextVariant: 'lg', | ||
}, | ||
]; | ||
|
||
variants.forEach((variant) => { | ||
describe(`${variant.name} variant`, () => { | ||
beforeEach(() => { | ||
fixture.componentRef.setInput('variant', variant.variant); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should have default Loading text if label is not provided', () => { | ||
const labelText = getParagraphLabel(variant.bodyTextVariant); | ||
|
||
expect(labelText.textContent).toEqual('Loading'); | ||
}); | ||
|
||
it('should have app provided label', async () => { | ||
const appLabel = 'App provided label'; | ||
|
||
fixture.componentRef.setInput('label', appLabel); | ||
|
||
fixture.detectChanges(); | ||
|
||
const labelText = getParagraphLabel(variant.bodyTextVariant); | ||
|
||
expect(labelText.textContent).toEqual(appLabel); | ||
}); | ||
|
||
it('should have correct svg icon', () => { | ||
const svgElement = getSvgIcon(variant.variant); | ||
|
||
expect(svgElement).toBeTruthy(); | ||
}); | ||
|
||
it('should not have visible elements, if visible is false', () => { | ||
fixture.componentRef.setInput('visible', false); | ||
|
||
fixture.detectChanges(); | ||
|
||
const uiContent = getElement(fixture, '.fudis-loading-spinner__ui-content'); | ||
|
||
expect(uiContent).toBeNull(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('screen reader elements', () => { | ||
beforeEach(() => { | ||
fixture.componentRef.setInput('variant', 'lg'); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should not be present with small variant', () => { | ||
fixture.componentRef.setInput('variant', 'sm'); | ||
fixture.detectChanges(); | ||
|
||
const statusElement = getElement(fixture, '.fudis-loading-spinner__status'); | ||
|
||
expect(statusElement).toBeNull(); | ||
}); | ||
|
||
it('should be present with large variant', () => { | ||
const statusElement = getElement( | ||
fixture, | ||
'.fudis-loading-spinner__status.fudis-visually-hidden[role="status"]', | ||
); | ||
|
||
expect(statusElement).toBeTruthy(); | ||
}); | ||
|
||
it('should have correct DEFAULT status message when visible is TRUE', () => { | ||
expect(getStatusMessage()).toEqual('Page is loading'); | ||
}); | ||
|
||
it('should have correct DEFAULT status message when visible is FALSE', () => { | ||
fixture.componentRef.setInput('visible', false); | ||
fixture.detectChanges(); | ||
expect(getStatusMessage()).toEqual('Page load finished'); | ||
}); | ||
|
||
it('should have correct APP PROVIDED status message when visible is TRUE', async () => { | ||
const appMessage = 'We need more loading!'; | ||
|
||
fixture.componentRef.setInput('statusMessage', appMessage); | ||
fixture.detectChanges(); | ||
|
||
await fixture.whenStable(); | ||
|
||
fixture.detectChanges(); | ||
|
||
expect(getStatusMessage()).toEqual(appMessage); | ||
}); | ||
|
||
it('should have correct APP PROVIDED status message when visible is FALSE', () => { | ||
const appMessage = 'Enough is enough!'; | ||
|
||
fixture.componentRef.setInput('statusMessage', appMessage); | ||
fixture.componentRef.setInput('visible', false); | ||
fixture.detectChanges(); | ||
expect(getStatusMessage()).toEqual(appMessage); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.