refactor(project): Renamed all instances of dafa to msfa or mina-sidor-fa. (TV-379)

Squashed commit of the following:

commit d3f52ff6876f6e246c7d3c188e56cc2370289341
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Aug 17 14:10:38 2021 +0200

    Renamed all dafa instances to msfa
This commit is contained in:
Erik Tiekstra
2021-08-18 07:10:28 +02:00
parent 6d29baa533
commit 03a2c7a42f
349 changed files with 720 additions and 734 deletions
@@ -0,0 +1,4 @@
<a class="back-link" [routerLink]="route">
<msfa-icon [icon]="iconType.ARROW_LEFT"></msfa-icon>
<ng-content></ng-content>
</a>
@@ -0,0 +1,11 @@
.back-link {
display: inline-flex;
align-items: center;
text-decoration: none;
font-weight: var(--digi--typography--font-weight--semibold);
gap: var(--digi--layout--gutter--xs);
&:hover {
text-decoration: underline;
}
}
@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BackLinkComponent } from './back-link.component';
describe('BackLinkComponent', () => {
let component: BackLinkComponent;
let fixture: ComponentFixture<BackLinkComponent>;
beforeEach(async(() => {
void TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [BackLinkComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BackLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
@Component({
selector: 'msfa-back-link',
templateUrl: './back-link.component.html',
styleUrls: ['./back-link.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BackLinkComponent {
@Input() route: string[];
iconType = IconType;
}
@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '../icon/icon.module';
import { BackLinkComponent } from './back-link.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [BackLinkComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [BackLinkComponent],
})
export class BackLinkModule {}
@@ -0,0 +1,8 @@
<span class="hide-text">
{{ transformedText }}
<button class="hide-text__button" type="button" [attr.aria-label]="ariaLabel" (click)="toggleText()">
<msfa-icon *ngIf="!hideText" [icon]="iconType.EYESLASH" size="l"></msfa-icon>
<msfa-icon *ngIf="hideText" [icon]="iconType.EYE" size="l"></msfa-icon>
<span class="hide-text__button-text">{{ hideText ? 'Visa' : 'Dölj'}}</span>
</button>
</span>
@@ -0,0 +1,22 @@
.hide-text {
display: flex;
align-items: center;
gap: var(--digi--layout--gutter);
&__button {
background-color: transparent;
color: var(--digi--typography--color--link);
font-size: var(--digi--typography--font-size--desktop);
border-width: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: var(--digi--typography--color--link--active);
}
}
&__button-text {
margin-left: var(--digi--layout--gutter--s);
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HideTextComponent } from './hide-text.component';
describe('HideTextComponent', () => {
let component: HideTextComponent;
let fixture: ComponentFixture<HideTextComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HideTextComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HideTextComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,30 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
@Component({
selector: 'msfa-hide-text',
templateUrl: './hide-text.component.html',
styleUrls: ['./hide-text.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HideTextComponent {
@Input() changingText: string;
@Input() symbols: string;
@Input() ariaLabelType = 'text';
hideText = true;
iconType = IconType;
get transformedText(): string {
return this.hideText ? this.symbols : this.changingText;
}
toggleText(): void {
this.hideText = !this.hideText;
}
get ariaLabel(): string {
const buttonText = this.hideText ? 'Visa' : 'Dölj';
return `${buttonText} ${this.ariaLabelType}`;
}
}
@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { HideTextComponent } from './hide-text.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [HideTextComponent],
imports: [CommonModule, IconModule],
exports: [HideTextComponent],
})
export class HideTextModule {}
@@ -0,0 +1,63 @@
<digi-ng-icon-custom *ngIf="isCustomIcon; else DigiNgIcon" aria-hidden="true" class="icon" [ngClass]="[iconClass]">
<svg *ngIf="icon === iconType.HOME" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="25" height="25">
<path
d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"
fill="currentColor"
></path>
</svg>
<svg
*ngIf="icon === iconType.SETTINGS"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width="25"
height="25"
>
<path
d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"
fill="currentColor"
></path>
</svg>
<svg *ngIf="icon === iconType.PLUS" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="25" height="25">
<path
fill="currentColor"
d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"
></path>
</svg>
<svg
*ngIf="icon === iconType.CLIPBOARD"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="25"
height="25"
>
<path
fill="currentColor"
d="M384 112v352c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h80c0-35.29 28.71-64 64-64s64 28.71 64 64h80c26.51 0 48 21.49 48 48zM192 40c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24m96 114v-20a6 6 0 0 0-6-6H102a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h180a6 6 0 0 0 6-6z"
></path>
</svg>
</digi-ng-icon-custom>
<ng-template #DigiNgIcon>
<ng-container [ngSwitch]="icon">
<digi-icon-user-alt *ngSwitchCase="iconType.USER" [ngClass]="iconClass"></digi-icon-user-alt>
<digi-icon-users-solid *ngSwitchCase="iconType.USERS" [ngClass]="iconClass"></digi-icon-users-solid>
<digi-icon-bell *ngSwitchCase="iconType.BELL" [ngClass]="iconClass"></digi-icon-bell>
<digi-icon-calendar-alt *ngSwitchCase="iconType.CALENDAR" [ngClass]="iconClass"></digi-icon-calendar-alt>
<digi-icon-envelope-filled *ngSwitchCase="iconType.ENVELOPE" [ngClass]="iconClass"></digi-icon-envelope-filled>
<digi-icon-sokkandidat *ngSwitchCase="iconType.SOK_KANDIDAT" [ngClass]="iconClass"></digi-icon-sokkandidat>
<digi-icon-edit *ngSwitchCase="iconType.EDIT" [ngClass]="iconClass"></digi-icon-edit>
<digi-icon-exclamation-circle *ngSwitchCase="iconType.INFO" [ngClass]="iconClass"></digi-icon-exclamation-circle>
<digi-icon-exclamation-triangle
*ngSwitchCase="iconType.WARNING"
[ngClass]="iconClass"
></digi-icon-exclamation-triangle>
<digi-icon-check-circle-reg *ngSwitchCase="iconType.APPROVED" [ngClass]="iconClass"></digi-icon-check-circle-reg>
<digi-icon-x *ngSwitchCase="iconType.X" [ngClass]="iconClass"></digi-icon-x>
<digi-icon-arrow-left *ngSwitchCase="iconType.ARROW_LEFT" [ngClass]="iconClass"></digi-icon-arrow-left>
<digi-icon-arrow-right *ngSwitchCase="iconType.ARROW_RIGHT" [ngClass]="iconClass"></digi-icon-arrow-right>
<digi-icon-eye *ngSwitchCase="iconType.EYE" [ngClass]="iconClass"></digi-icon-eye>
<digi-icon-eye-slash *ngSwitchCase="iconType.EYESLASH" [ngClass]="iconClass"></digi-icon-eye-slash>
</ng-container>
</ng-template>
@@ -0,0 +1,23 @@
@import 'mixins/icon';
:host {
display: inline-flex;
}
.icon {
&--s {
@include msfa__digi-ng-icon(0.875em);
}
&--m {
@include msfa__digi-ng-icon(1em);
}
&--l {
@include msfa__digi-ng-icon(1.25em);
}
&--xl {
@include msfa__digi-ng-icon(1.5em);
}
&--xxl {
@include msfa__digi-ng-icon(2em);
}
}
@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconComponent } from './icon.component';
describe('IconComponent', () => {
let component: IconComponent;
let fixture: ComponentFixture<IconComponent>;
beforeEach(() => {
void TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [IconComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(IconComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,27 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { IconSize } from '@msfa-enums/icon-size.enum';
import { IconType } from '@msfa-enums/icon-type.enum';
const CUSTOM_ICONS: IconType[] = [IconType.HOME, IconType.SETTINGS, IconType.PLUS, IconType.CLIPBOARD];
@Component({
selector: 'msfa-icon',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IconComponent implements OnChanges {
@Input() icon: IconType;
@Input() size: IconSize = IconSize.M;
iconClass: string;
iconType = IconType;
ngOnChanges(): void {
this.iconClass = `icon--${this.size}`;
}
get isCustomIcon(): boolean {
return CUSTOM_ICONS.includes(this.icon);
}
}
@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { IconComponent } from './icon.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [IconComponent],
imports: [CommonModule],
exports: [IconComponent],
})
export class IconModule {}
@@ -0,0 +1,7 @@
<footer class="footer">
<div class="footer__logo-wrapper">
<a class="footer__logo-link" href="/">
<img class="footer__logo" src="/assets/logo/arbetsformedlingen-light.svg" alt="Arbetsförmedlingen" />
</a>
</div>
</footer>
@@ -0,0 +1,15 @@
.footer {
background-color: var(--digi--ui--color--background--profile);
padding: var(--digi--layout--gutter);
&__logo-wrapper {
height: 100%;
display: flex;
align-items: center;
}
&__logo {
height: 2rem;
vertical-align: middle;
}
}
@@ -0,0 +1,28 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [FooterComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'msfa-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent {}
@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FooterComponent } from './footer.component';
@NgModule({
declarations: [FooterComponent],
imports: [CommonModule, RouterModule],
exports: [FooterComponent],
})
export class FooterModule {}
@@ -0,0 +1,7 @@
@media print {
.footer {
border-bottom-width: 0;
padding: var(--digi--layout--gutter) 0;
display: flex;
}
}
@@ -0,0 +1,21 @@
<div class="navigation">
<div class="navigation__logo-wrapper">
<a [routerLink]="['/']" aria-label="Till startsidan för FA Mina sidor">
<digi-logo af-system-name="Mina sidor för fristående aktörer" af-color="secondary"></digi-logo>
</a>
</div>
<ul class="navigation__list msfa__hide-on-print">
<li *ngIf="user" class="navigation__item">
<div class="navigation__user">
<msfa-icon [icon]="iconType.USER" size="l"></msfa-icon>
<span class="navigation__text">{{ user.fullName }}</span>
</div>
</li>
<li class="navigation__item">
<a routerLink="/" class="navigation__link">
<msfa-icon [icon]="iconType.BELL" size="l"></msfa-icon>
<span class="navigation__text">Notiser</span>
</a>
</li>
</ul>
</div>
@@ -0,0 +1,67 @@
@import 'mixins/list';
@import 'variables/breakpoints';
@import 'variables/colors';
@import 'variables/gutters';
@import 'variables/navigation';
.navigation {
background-color: var(--digi--ui--color--background--profile);
border-bottom: 1px solid var(--digi--ui--color--background--off);
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--digi--layout--gutter);
height: $msfa__navigation-height;
@media (min-width: $digi--layout--breakpoint--m) {
height: $msfa__navigation-height-large;
}
&__logo-wrapper {
height: 100%;
display: flex;
align-items: center;
}
&__logo {
height: $msfa__navigation-height / 2.5;
vertical-align: middle;
@media (min-width: $digi--layout--breakpoint--m) {
height: $msfa__navigation-height-large / 2.5;
}
}
&__list {
@include msfa__reset-list;
display: flex;
height: 100%;
gap: $digi--layout--gutter--l;
color: var(--digi--typography--color--text--light);
margin-right: var(--digi--layout--gutter);
}
&__item {
display: flex;
}
&__user,
&__link {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: var(--digi--layout--gutter--s) var(--digi--layout--gutter);
}
&__link {
text-decoration: none;
color: var(--digi--typography--color--text--light);
}
&__text {
font-size: 0.75rem;
margin-top: var(--digi--layout--gutter--s);
}
}
@@ -0,0 +1,28 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { NavigationComponent } from './navigation.component';
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [NavigationComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
import { User } from '@msfa-models/user.model';
@Component({
selector: 'msfa-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavigationComponent {
@Input() user: User;
iconType = IconType;
}
@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { NavigationComponent } from './navigation.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [NavigationComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [NavigationComponent],
})
export class NavigationModule {}
@@ -0,0 +1,7 @@
@media print {
.navigation {
border-bottom-width: 0;
padding: var(--digi--layout--gutter) 0;
display: flex;
}
}
@@ -0,0 +1,45 @@
<nav class="sidebar" aria-label="Sidofält">
<ul class="sidebar__list">
<li class="sidebar__item">
<a
[routerLink]="['/']"
[routerLinkActive]="['sidebar__link--active']"
[routerLinkActiveOptions]="{ exact: true }"
class="sidebar__link"
>
<msfa-icon class="sidebar__icon" [icon]="iconType.HOME" size="xl"></msfa-icon>
Hem
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/avrop']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.CLIPBOARD" size="xl"></msfa-icon>
Avrop
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/deltagare']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.SOK_KANDIDAT" size="xl"></msfa-icon>
Mina deltagare
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/planera-aktiviteter']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.USERS" size="xxl"></msfa-icon>
Planera aktiviteter
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/rapportera']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.EDIT" size="xl"></msfa-icon>
Rapportera
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/administration']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.SETTINGS" size="xl"></msfa-icon>
Administration
</a>
</li>
</ul>
</nav>
@@ -0,0 +1,53 @@
@import 'variables/colors';
@import 'variables/gutters';
@import 'variables/navigation';
@import 'mixins/list';
.sidebar {
display: flex;
flex-direction: column;
position: sticky;
top: $msfa__navigation-height-large;
&__list {
@include msfa__reset-list;
}
&__link {
position: relative;
display: flex;
flex-direction: column;
gap: var(--digi--layout--gutter--xs);
align-items: center;
width: 100%;
padding: $digi--layout--gutter--m var(--digi--layout--gutter);
border-bottom: 1px solid var(--digi--ui--color--background--off);
color: var(--digi--typography--color--text) !important;
text-decoration: none;
font-weight: var(--digi--typography--font-weight);
font-size: var(--digi--typography--font-size--s);
text-align: center;
&:hover {
background-color: var(--digi--ui--color--background--tertiary);
}
&--active {
background-color: var(--digi--ui--color--background--tertiary);
&::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 5px;
background-color: var(--digi--ui--color--primary);
}
}
}
&__icon {
color: var(--digi--typography--color--link);
}
}
@@ -0,0 +1,26 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { SidebarComponent } from './sidebar.component';
describe('SidebarComponent', () => {
let component: SidebarComponent;
let fixture: ComponentFixture<SidebarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SidebarComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SidebarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
@Component({
selector: 'msfa-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SidebarComponent {
iconType = IconType;
}
@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { SidebarComponent } from './sidebar.component';
@NgModule({
declarations: [SidebarComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [SidebarComponent],
})
export class SidebarModule {}
@@ -0,0 +1 @@
<a [attr.href]="skipLinkPath" class="skip-to-content">Gå till sidans innehåll</a>
@@ -0,0 +1,29 @@
@import 'variables/colors';
.skip-to-content {
position: absolute;
top: -1000px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
overflow: hidden;
z-index: 1000;
text-align: center;
padding: var(--digi--layout--gutter--xs) var(--digi--layout--gutter);
display: block;
background-color: var(--digi--ui--color--complementary-alt);
color: var(--digi--typography--color--text--light);
&:focus {
position: fixed;
top: 0;
width: auto;
height: auto;
text-decoration: none;
}
&:hover {
text-decoration: underline;
}
}
@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { SkipToContentComponent } from './skip-to-content.component';
describe('SkipToContentComponent', () => {
let component: SkipToContentComponent;
let fixture: ComponentFixture<SkipToContentComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [SkipToContentComponent],
imports: [RouterTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(SkipToContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,29 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { filter } from 'rxjs/operators';
@Component({
selector: 'msfa-skip-to-content',
templateUrl: './skip-to-content.component.html',
styleUrls: ['./skip-to-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SkipToContentComponent extends UnsubscribeDirective {
@Input() mainContentId: string;
skipLinkPath: string;
constructor(private router: Router, private changeDetectorRef: ChangeDetectorRef) {
super();
super.unsubscribeOnDestroy(
this.router.events.pipe(filter((event: NavigationEnd) => event instanceof NavigationEnd)).subscribe(({ url }) => {
const mainContentId = `#${this.mainContentId}`;
// Check if the current URL already includes the mainContentId.
const existsInUrl: boolean = url.substring(url.length - mainContentId.length, url.length) === mainContentId;
this.skipLinkPath = existsInUrl ? url : `${url}${mainContentId}`;
this.changeDetectorRef.markForCheck();
})
);
}
}
@@ -0,0 +1,10 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SkipToContentComponent } from './skip-to-content.component';
@NgModule({
declarations: [SkipToContentComponent],
imports: [CommonModule],
exports: [SkipToContentComponent],
})
export class SkipToContentModule {}
@@ -0,0 +1,19 @@
<div class="msfa" *ngIf="isLoggedIn$ | async">
<msfa-skip-to-content mainContentId="msfa-main-content"></msfa-skip-to-content>
<header class="msfa__header">
<msfa-navigation [user]="user$ | async"></msfa-navigation>
</header>
<msfa-sidebar class="msfa__sidebar"></msfa-sidebar>
<main id="msfa-main-content" class="msfa__content">
<digi-ng-navigation-breadcrumbs
class="msfa__breadcrumbs"
[afItems]="breadcrumbsItems"
></digi-ng-navigation-breadcrumbs>
<ng-content></ng-content>
</main>
<msfa-footer class="msfa__footer"></msfa-footer>
</div>
@@ -0,0 +1,48 @@
@import 'variables/navigation';
@import 'variables/breakpoints';
@import 'variables/gutters';
.msfa {
display: grid;
height: 100vh;
grid-template-columns: 12rem 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'header header'
'sidebar content'
'footer footer';
// @media (min-width: $digi--layout--breakpoint--m) {
// grid-template-rows: $msfa__navigation-height-large 1fr auto;
// }
&__header {
grid-area: header;
position: sticky;
top: 0;
z-index: 1;
}
&__sidebar {
grid-area: sidebar;
background-color: var(--digi--ui--color--background--secondary);
border-right: 1px solid var(--digi--ui--color--background--off);
}
&__content {
grid-area: content;
max-width: $digi--layout--breakpoint--l;
padding: var(--digi--layout--gutter) $digi--layout--gutter--l $digi--layout--gutter--xxl;
}
&__breadcrumbs {
display: block;
margin-bottom: var(--digi--layout--gutter);
}
&__footer {
grid-area: footer;
background-color: var(--digi--ui--color--primary);
min-height: 10rem;
}
}
@@ -0,0 +1,28 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LayoutComponent } from './Layout.component';
describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [LayoutComponent],
imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,61 @@
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { User } from '@msfa-models/user.model';
import { AuthenticationService } from '@msfa-services/api/authentication.service';
import { UserService } from '@msfa-services/api/user.service';
import { mapPathsToBreadcrumbs } from '@msfa-utils/map-paths-to-breadcrumbs.util';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
@Component({
selector: 'msfa-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LayoutComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start',
routerLink: '/',
};
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]);
isLoggedIn$: Observable<boolean> = this.authService.isLoggedIn$;
user$: Observable<User> = this.isLoggedIn$.pipe(
filter(loggedIn => !!loggedIn),
switchMap(() => this.userService.user$)
);
get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue();
}
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private authService: AuthenticationService,
private userService: UserService
) {
super();
super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
if (urlTree.queryParams.code) {
void this.router.navigate([], {
relativeTo: this.activatedRoute,
queryParams: { code: null },
queryParamsHandling: 'merge',
replaceUrl: true,
});
}
urlTree.queryParams = {};
const paths = urlTree
.toString()
.split('/')
.filter(path => !!path);
this._breadcrumbsItems$.next(mapPathsToBreadcrumbs(paths, this.startBreadcrumb));
})
);
}
}
@@ -0,0 +1,27 @@
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MarkdownModule } from 'ngx-markdown';
import { FooterModule } from './components/footer/footer.module';
import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { LayoutComponent } from './layout.component';
@NgModule({
declarations: [LayoutComponent],
imports: [
CommonModule,
RouterModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule,
],
exports: [LayoutComponent],
})
export class LayoutModule {}