feat(export): Added export deltagare. (TV-872)

Squashed commit of the following:

commit 9c06b7f1d44a4b48d7f31a22b6bfbd233e09d2f7
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 15:07:15 2021 +0100

    Updated api endpoint

commit f9bfbecda6d03b70d87febbada920d0eca798b1f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 15:01:13 2021 +0100

    Updated libs

commit 9edb413d537288fa0708b8a04eb54f801ebc23a0
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:58:13 2021 +0100

    Added @types/file-saver

commit 624affac55ce771fd58e2770a0d4a98233a8e9ba
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:42:29 2021 +0100

    Updated libs

commit 65dae1d906bbcec474581692b2aced9e47d2484c
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:36:42 2021 +0100

    Added utils lib config

commit 223bd59724663523bdbaf87b5502396156ddb9eb
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 14:06:13 2021 +0100

    Added validation

commit 166dfcf0448155ac21c0eaa904b4ce1271f73193
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 13:25:35 2021 +0100

    Changed styling and removed some fake data

commit 3906f2793dd52b626b95c13e115495451332c894
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 13:18:52 2021 +0100

    Added digi-ng datepicker

commit de0d51434d15cac5476303d4b417c591da16fd8f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Nov 2 12:31:48 2021 +0100

    Added checkbox
This commit is contained in:
Erik Tiekstra
2021-11-03 09:18:25 +01:00
parent e8791556cb
commit c11b0c1463
59 changed files with 1000 additions and 1159 deletions

View File

@@ -0,0 +1,18 @@
<div class="ui-checkbox" [ngClass]="{'ui-checkbox--invalid': uiInvalid && uiValidationMessage}">
<digi-form-checkbox
[afId]="uiId"
[afIndeterminate]="uiIndeterminate"
[afName]="uiName"
[afRequired]="uiRequired"
[afValidation]="uiInvalid ? 'error' : 'neutral'"
[afVariation]="uiSecondary ? 'secondary' : 'primary'"
[afLabel]="labelText"
[afChecked]="currentValue"
(afOnChange)="checkForChange($event.detail.target.checked)"
></digi-form-checkbox>
<div aria-atomic="true" role="alert">
<digi-form-validation-message *ngIf="uiInvalid && uiValidationMessage" af-variation="error"
>{{uiValidationMessage}}</digi-form-validation-message
>
</div>
</div>

View File

@@ -0,0 +1,8 @@
.ui-checkbox {
display: flex;
flex-direction: column;
&--invalid {
gap: var(--digi--layout--gutter--xs);
}
}

View File

@@ -0,0 +1,23 @@
/* tslint:disable:no-unused-variable */
import { FormCheckboxComponent } from './form-checkbox.component';
export class MockInjector {
get = jest.fn();
}
// tslint:disable-next-line: max-classes-per-file
export class MockChangeDetectorRef {
markForCheck = jest.fn();
detach = jest.fn();
detectChanges = jest.fn();
reattach = jest.fn();
checkNoChanges = jest.fn();
}
describe('FormCheckboxComponent', () => {
let component: FormCheckboxComponent;
it('should create', () => {
component = new FormCheckboxComponent(new MockInjector(), new MockChangeDetectorRef());
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,16 @@
import { ReactiveFormsModule } from '@angular/forms';
import { CheckboxComponent } from './checkbox.component';
import { UiCheckboxModule } from './checkbox.module';
export default { title: 'Checkbox', component: CheckboxComponent };
const componentModule = {
moduleMetadata: {
imports: [ReactiveFormsModule, UiCheckboxModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-checkbox></ui-checkbox>',
});

View File

@@ -0,0 +1,111 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Injector,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { uuid } from '@utils/uuid.util';
/**
* A checkbox input. Implemented with control value accessor
*
* ## Usage
* ``import {UiCheckboxModule} from '@ui/checkbox/checkbox.module';``
*/
@Component({
selector: 'ui-checkbox',
templateUrl: './checkbox.component.html',
styleUrls: ['./checkbox.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CheckboxComponent,
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckboxComponent implements AfterViewInit, ControlValueAccessor, OnChanges {
@Input() uiInvalid: boolean;
@Input() uiValidationMessage: string;
@Input() uiSecondary: boolean;
@Input() uiIndeterminate: boolean = false;
@Input() uiLabel: string = '';
@Input() uiRequired: boolean;
@Input() uiId: string = uuid();
@Input() uiName: string;
@Input() uiAnnounceIfOptional: boolean = false;
@Output() uiOnChange: EventEmitter<any> = new EventEmitter();
name: string | number;
onTouched: () => {};
private onChange: (value: any) => {};
private _value: boolean;
constructor(private injector: Injector, private changeDetectorRef: ChangeDetectorRef) {}
get currentValue(): boolean {
return this._value;
}
get labelText(): string {
return `${this.uiLabel}${this._requiredText}`;
}
private get _requiredText() {
if (this.uiRequired && !this.uiAnnounceIfOptional) {
return ' (obligatoriskt)';
}
if (!this.uiRequired && this.uiAnnounceIfOptional) {
return ' (frivilligt)';
}
return '';
}
ngAfterViewInit(): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = ngControl.name;
}
}
ngOnChanges(changes: SimpleChanges): void {
const ngControl: NgControl = this.injector.get(NgControl, null);
if (ngControl) {
this.name = ngControl.name;
}
}
checkForChange(value: boolean): void {
if (this._value !== !!value) {
if (this.onChange) {
this.onChange(!!value);
}
this._value = !!value;
this.uiOnChange.emit(!!value);
}
}
writeValue(value: any): void {
this._value = value;
this.changeDetectorRef.detectChanges();
}
registerOnChange(fn: (value: string) => {}) {
this.onChange = fn;
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn;
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { CheckboxComponent } from './checkbox.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [CommonModule],
declarations: [CheckboxComponent],
exports: [CheckboxComponent],
})
export class UiCheckboxModule {}

View File

@@ -0,0 +1,5 @@
export enum UiLinkButtonType {
PRIMARY = 'primary',
SECONDARY = 'secondary',
TERTIARY = 'tertiary',
}

View File

@@ -0,0 +1,3 @@
<a [ngClass]="linkButtonClass" [routerLink]="uiRouterLink" [queryParams]="uiQueryParams">
<ng-content></ng-content>
</a>

View File

@@ -0,0 +1,13 @@
@import 'mixins/buttons';
.ui-link-button {
&--primary {
@include msfa__button;
}
&--secondary {
@include msfa__button('secondary');
}
&--tertiary {
@include msfa__button('tertiary');
}
}

View File

@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LinkButtonComponent } from './link-button.component';
describe('LinkButtonComponent', () => {
let component: LinkButtonComponent;
let fixture: ComponentFixture<LinkButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LinkButtonComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LinkButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { LinkButtonComponent } from './link-button.component';
import { UiLinkButtonModule } from './link-button.module';
export default { title: 'LinkButton', component: LinkButtonComponent };
const componentModule = {
moduleMetadata: {
imports: [UiLinkButtonModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-link-button></ui-link-button>',
});

View File

@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Params } from '@angular/router';
import { UiLinkButtonType } from './link-button-type.enum';
@Component({
selector: 'ui-link-button',
templateUrl: './link-button.component.html',
styleUrls: ['./link-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LinkButtonComponent {
private readonly _defaultClass = 'ui-link-button';
@Input() uiType: UiLinkButtonType = UiLinkButtonType.PRIMARY;
@Input() uiSize: 's' | 'm' = 'm';
@Input() uiRouterLink: string | string[];
@Input() uiQueryParams: Params = null;
get linkButtonClass(): string {
if (this.uiType) {
return `${this._defaultClass} ${this._defaultClass}--${this.uiType as string}`;
}
return this._defaultClass;
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LinkButtonComponent } from './link-button.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [LinkButtonComponent],
imports: [CommonModule, RouterModule],
exports: [LinkButtonComponent],
})
export class UiLinkButtonModule {}

View File

@@ -0,0 +1,6 @@
export enum UiLoaderType {
FULL_SCREEN = 'fullscreen',
FULL_BLANK_SCREEN = 'full-blank-screen',
ABSOLUTE = 'absolute',
PADDED = 'padded',
}

View File

@@ -0,0 +1,4 @@
<div [ngClass]="loaderClass">
<digi-icon-spinner [ngClass]="spinnerClass"></digi-icon-spinner>
<span class="ui-loader__text" *ngIf="uiShowLoadingText">{{uiLoadingText}}</span>
</div>

View File

@@ -0,0 +1,46 @@
@import 'mixins/backdrop';
@import 'variables/gutters';
@import 'variables/z-index';
@keyframes spinning {
to {
transform: rotate(360deg);
}
}
.ui-loader {
display: flex;
flex-direction: column;
gap: $digi--layout--gutter--s;
align-items: center;
justify-content: center;
&--padded {
padding: $digi--layout--gutter--l;
}
&--absolute {
@include msfa__backdrop($msfa__z-index-backdrop, false);
}
&--full-screen {
@include msfa__backdrop($msfa__z-index-backdrop);
}
&--full-blank-screen {
@include msfa__backdrop($msfa__z-index-backdrop, true, true);
}
&__spinner {
display: inline-flex;
animation: spinning 1s linear infinite;
&--s {
width: 2.5rem;
}
}
&__text {
font-size: var(--digi--typography--font-size--s);
}
}

View File

@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoaderComponent } from './loader.component';
describe('LoaderComponent', () => {
let component: LoaderComponent;
let fixture: ComponentFixture<LoaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LoaderComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { LoaderComponent } from './loader.component';
import { UiLoaderModule } from './loader.module';
export default { title: 'Loader', component: LoaderComponent };
const componentModule = {
moduleMetadata: {
imports: [UiLoaderModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-loader></ui-loader>',
});

View File

@@ -0,0 +1,27 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { UiLoaderType } from './loader-type.enum';
@Component({
selector: 'ui-loader',
templateUrl: './loader.component.html',
styleUrls: ['./loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoaderComponent {
private readonly _defaultClass = 'ui-loader';
@Input() uiType: UiLoaderType = UiLoaderType.PADDED;
@Input() uiSize: 's' | 'm' = 'm';
@Input() uiShowLoadingText = false;
@Input() uiLoadingText = 'Laddar innehåll';
get loaderClass(): string {
if (this.uiType) {
return `${this._defaultClass} ${this._defaultClass}--${this.uiType as string}`;
}
return this._defaultClass;
}
get spinnerClass(): string {
return `${this._defaultClass}__spinner ${this._defaultClass}__spinner--${this.uiSize}`;
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { LoaderComponent } from './loader.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [LoaderComponent],
imports: [CommonModule],
exports: [LoaderComponent],
})
export class UiLoaderModule {}

View File

@@ -0,0 +1,11 @@
<div class="ui-skeleton" data-digi-ng-component>
<div *ngIf="uiText" class="ui-skeleton__top"></div>
<p *ngIf="uiText" class="ui-skeleton__text">{{ uiText }}</p>
<div class="ui-skeleton__header"></div>
<div *ngFor="let stub of stubs" class="ui-skeleton__lines">
<div class="ui-skeleton__line"></div>
<div class="ui-skeleton__line"></div>
<div class="ui-skeleton__line"></div>
<div class="ui-skeleton__line ui-skeleton__line--last"></div>
</div>
</div>

View File

@@ -0,0 +1,91 @@
@import 'variables/gutters';
@import 'variables/breakpoints';
$typography__margin--text: u(6);
$animation__duration--sweep: 4s;
$ui__color--gray-10: #f5f5f5;
$ui__color--gray-20: #efefef;
$ui__color--gray-30: #d1d1d1;
$ui__color--skeleton-text: linear-gradient(
to left,
$ui__color--gray-20,
$ui__color--gray-20 40%,
$ui__color--gray-10 50%,
$ui__color--gray-20 60%,
$ui__color--gray-20
);
$ui__color--skeleton-head: linear-gradient(
to left,
$ui__color--gray-30,
$ui__color--gray-30 40%,
$ui__color--gray-20 50%,
$ui__color--gray-30 60%,
$ui__color--gray-30
);
$skeleton__top--height: 2.5rem;
$skeleton__top--height-desktop: 4.5rem;
$skeleton__header--height: 1rem + 0.46875rem;
$skeleton__header--height-desktop: 2rem + 0.3125rem;
:host {
.ui-skeleton__top {
animation: ui-skeleton__animation $animation__duration--sweep infinite;
height: $skeleton__top--height;
background: $ui__color--skeleton-text;
background-size: 400%;
width: 25%;
margin-bottom: var(--digi--layout--gutter--m);
@media (min-width: $digi--layout--breakpoint--l) {
height: $skeleton__top--height-desktop;
width: 20%;
margin-bottom: $digi--layout--gutter--l;
}
}
.ui-skeleton__header {
animation: ui-skeleton__animation $animation__duration--sweep infinite;
background: $ui__color--skeleton-head;
background-size: 400%;
width: 33%;
height: $skeleton__header--height;
margin-bottom: var(----digi--layout--gutter--s);
@media (min-width: $digi--layout--breakpoint--l) {
height: $skeleton__header--height-desktop;
margin-bottom: $digi--layout--gutter--l;
width: 90%;
}
}
.ui-skeleton__line {
animation: ui-skeleton__animation $animation__duration--sweep 0.5s infinite;
background: $ui__color--skeleton-text;
background-size: 400%;
margin-bottom: var(--digi--layout--gutter--xs);
height: var(--digi--typography--font-size);
}
.ui-skeleton__line--last {
width: 75%;
margin-bottom: var(--digi--layout--gutter);
}
.ui-skeleton__text {
font-weight: var(--digi--typography--font-weight--semibold);
margin-bottom: $typography__margin--text / 2;
}
}
@keyframes ui-skeleton__animation {
0% {
background-position-x: 100%;
}
100% {
background-position-x: 0%;
}
}

View File

@@ -0,0 +1,40 @@
import { SkeletonComponent } from './skeleton.component';
import { UiSkeletonModule } from './skeleton.module';
export default { title: 'Skeleton', component: SkeletonComponent };
const componentModule = {
moduleMetadata: {
imports: [UiSkeletonModule],
},
};
export const standard = () => ({
...componentModule,
template: '<ui-skeleton></ui-skeleton>',
});
export const limitedMultiLine = () => ({
...componentModule,
template: '<ui-skeleton [uiCount]="2"></ui-skeleton>',
});
export const extensiveMultiLine = () => ({
...componentModule,
template: '<ui-skeleton [uiCount]="5"></ui-skeleton>',
});
export const paragraphless = () => ({
...componentModule,
template: '<ui-skeleton [uiCount]="0"></ui-skeleton>',
});
export const customText = () => ({
...componentModule,
template: '<ui-skeleton uiText="I am a custom text..."></ui-skeleton>',
});
export const noText = () => ({
...componentModule,
template: '<ui-skeleton uiText=""></ui-skeleton>',
});

View File

@@ -0,0 +1,25 @@
import { Component, Input } from '@angular/core';
/**
* Placeholder for content being loaded
*
* ##Usage
* ``import {SkeletonModule} from '@ui/skeleton';``
*/
@Component({
selector: 'ui-skeleton',
templateUrl: './skeleton.component.html',
styleUrls: ['./skeleton.component.scss'],
})
export class SkeletonComponent {
/**
* The amount of skeleton sections
*/
@Input() uiCount: number | undefined;
@Input() uiText = 'Laddar sidan';
get stubs(): string[] {
return Array(this.uiCount);
}
}

View File

@@ -0,0 +1,10 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SkeletonComponent } from './skeleton.component';
@NgModule({
imports: [CommonModule],
declarations: [SkeletonComponent],
exports: [SkeletonComponent],
})
export class UiSkeletonModule {}

View File

@@ -0,0 +1 @@
import 'jest-preset-angular';