diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts index 0b39552..849270d 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts @@ -23,10 +23,10 @@ import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model'; import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service'; import { ValidationErrorLink } from '@msfa-shared/components/error-list/error-list.component'; import { TreeNodesSelectorService } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service'; +import { uuid } from '@msfa-utils/uuid'; import { EmailValidator } from '@msfa-utils/validators/email.validator'; import { EmployeeValidator } from '@msfa-utils/validators/employee.validator'; import { RequiredValidator } from '@msfa-utils/validators/required.validator'; -import { UUID } from 'angular2-uuid'; import { EmployeeFormService } from '../services/employee-form.service'; @Component({ @@ -58,7 +58,7 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { readonly utforandeVerksamheterFormControlName = 'utforandeVerksamheter'; readonly selectAllUtforandeVerksamheterFormControlName = 'allaUtforandeVerksamheter'; readonly utforandeVerksamhetRequiredMessage = 'Välj minst en utförande verksamhet'; - readonly formUuid = UUID.UUID(); + readonly formUuid = uuid(); readonly emailElementId = `email-control-${this.formUuid}`; readonly tjansterElementId = `tjanster-control-${this.formUuid}`; readonly utforandeVerksamhetElementId = `utforande-verksamhet-control-${this.formUuid}`; diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts b/apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts index d0de1b6..8f2e203 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/avrop/avrop.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Avrop, AvropCompactData } from '@msfa-models/avrop.model'; import { Handledare } from '@msfa-models/handledare.model'; -import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option'; +import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option'; import { AvropService } from '@msfa-services/avrop.service'; import { Observable } from 'rxjs'; diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.html b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.html index 28aa86a..4ff7d8d 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.html +++ b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.html @@ -1,29 +1,32 @@ -
- - +
+ - - - +
+
+ - + > +
- - + - + > +


diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.scss b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.scss index 9a4dfac..127a121 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.scss +++ b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.scss @@ -1,6 +1,8 @@ @import 'variables/gutters'; .avrop-filters { + display: flex; + &__tags { display: flex; flex-wrap: wrap; @@ -9,9 +11,13 @@ &__tag { display: inline-block; - margin-right: $digi--layout--gutter--s; + margin-right: var(--digi--layout--gutter--s); } &__loading-spinner { - margin-right: $digi--layout--gutter; + margin-right: var(--digi--layout--gutter); + } + + &__filter-wrapper { + margin-right: var(--digi--layout--gutter--s); } } diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.spec.ts b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.spec.ts index 929f5e3..387cc97 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.spec.ts +++ b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.spec.ts @@ -4,7 +4,6 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from import { By } from '@angular/platform-browser'; import { AvropService } from '@msfa-services/avrop.service'; import { of } from 'rxjs'; -import { TemporaryFilterComponent } from '../temporary-filter/temporary-filter.component'; import { AvropFiltersComponent } from './avrop-filters.component'; describe('AvropFiltersComponent', () => { @@ -15,7 +14,7 @@ describe('AvropFiltersComponent', () => { await TestBed.configureTestingModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], imports: [HttpClientTestingModule], - declarations: [AvropFiltersComponent, TemporaryFilterComponent], + declarations: [AvropFiltersComponent], providers: [AvropService], }).compileComponents(); }); diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.ts b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.ts index 970c57c..dd7755b 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option'; +import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option'; import { AvropService } from '@msfa-services/avrop.service'; import { Observable } from 'rxjs'; diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.module.ts b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.module.ts index 9009134..c76d143 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.module.ts +++ b/apps/mina-sidor-fa/src/app/pages/avrop/components/avrop-filters/avrop-filters.module.ts @@ -1,12 +1,12 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { TemporaryFilterModule } from '../temporary-filter/temporary-filter.module'; import { AvropFiltersComponent } from './avrop-filters.component'; +import { MultiselectModule } from '@msfa-shared/components/multiselect/multiselect.module'; @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [AvropFiltersComponent], - imports: [CommonModule, TemporaryFilterModule], + imports: [CommonModule, MultiselectModule], exports: [AvropFiltersComponent], }) export class AvropFiltersModule {} diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.html b/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.html deleted file mode 100644 index e115483..0000000 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
- {{filterLabel}} - - - - Spara -
diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.scss b/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.ts b/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.ts deleted file mode 100644 index eebdbe5..0000000 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - OnChanges, - OnInit, - Output, - SimpleChanges, -} from '@angular/core'; -import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option'; -import { BehaviorSubject, Observable } from 'rxjs'; - -@Component({ - selector: 'msfa-temporary-filter', - templateUrl: './temporary-filter.component.html', - styleUrls: ['./temporary-filter.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TemporaryFilterComponent implements OnInit, OnChanges { - private _selectedAvropFilterOption$ = new BehaviorSubject(null); - selectedAvropFilterOptionState$: Observable; - - @Input() filterLabel: string; - @Input() filterOptions: MultiselectFilterOption[]; - @Input() selectedOptions: MultiselectFilterOption[]; - @Output() selectedOptionsChange = new EventEmitter(); - - // THIS SHOULD BE REPLACED BY DIGI COMPONENT - ngOnInit(): void { - this._selectedAvropFilterOption$ = new BehaviorSubject(this.selectedOptions); - this.selectedAvropFilterOptionState$ = this._selectedAvropFilterOption$.asObservable(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.selectedOptions?.currentValue) { - this._selectedAvropFilterOption$.next(changes.selectedOptions.currentValue); - } - } - - isSelected(filterOption: MultiselectFilterOption): boolean { - return this.selectedOptions?.includes(filterOption) ?? false; - } - - setOptionState(filterOption: MultiselectFilterOption, isSelected: boolean): void { - if (isSelected) { - return this._selectedAvropFilterOption$.next([...(this._selectedAvropFilterOption$.value ?? []), filterOption]); - } - return this._selectedAvropFilterOption$.next( - this._selectedAvropFilterOption$.value?.filter(item => item != filterOption) ?? [] - ); - } - - emitSelectedOptions(): void { - this.selectedOptionsChange.emit(this._selectedAvropFilterOption$.value); - } -} diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.module.ts b/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.module.ts deleted file mode 100644 index 3f1e70a..0000000 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { TemporaryFilterComponent } from './temporary-filter.component'; - -@NgModule({ - schemas: [CUSTOM_ELEMENTS_SCHEMA], - declarations: [TemporaryFilterComponent], - imports: [CommonModule], - exports: [TemporaryFilterComponent], -}) -export class TemporaryFilterModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-panel.ts b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-panel.ts new file mode 100644 index 0000000..dcbfbb5 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-panel.ts @@ -0,0 +1,6 @@ +import { EventEmitter, TemplateRef } from '@angular/core'; + +export interface DropdownPanel { + templateRef: TemplateRef; + readonly closed: EventEmitter; +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-trigger-for.directive.ts b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-trigger-for.directive.ts new file mode 100644 index 0000000..69edae3 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown-trigger-for.directive.ts @@ -0,0 +1,101 @@ +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core'; +import { merge, Observable, Subscription } from 'rxjs'; +import { DropdownPanel } from './dropdown-panel'; + +@Directive({ + selector: '[msfaDropdownTriggerFor]', +}) +export class DropdownTriggerForDirective implements OnDestroy { + @Input('msfaDropdownTriggerFor') public dropdownPanel: DropdownPanel; + + private isDropdownOpen = false; + public overlayRef: OverlayRef; + private dropdownClosingActionsSub = Subscription.EMPTY; + + private onClosedCallback: () => void = () => { + return; + }; + private onOpenCallback: () => void = () => { + return; + }; + + @HostListener('click') onClick(): void { + return this.toggleDropdown(); + } + @HostListener('document:keyup.escape', ['$event']) onKeydownHandler(): void { + this.closeDropdown(); + } + + constructor( + private overlay: Overlay, + private elementRef: ElementRef, + private viewContainerRef: ViewContainerRef + ) {} + + toggleDropdown(): void { + this.isDropdownOpen ? this.closeDropdown() : this.openDropdown(); + } + + openDropdown(): void { + this.isDropdownOpen = true; + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'cdk-overlay-transparent-backdrop', + scrollStrategy: this.overlay.scrollStrategies.close(), + positionStrategy: this.overlay + .position() + .flexibleConnectedTo(this.elementRef) + .withPositions([ + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + offsetY: 3, + }, + ]), + }); + + const templatePortal = new TemplatePortal(this.dropdownPanel.templateRef, this.viewContainerRef); + + this.overlayRef.attach(templatePortal); + this.onOpenCallback(); + + this.dropdownClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeDropdown()); + } + + private dropdownClosingActions(): Observable { + const backdropClick$ = this.overlayRef.backdropClick(); + const detachment$ = this.overlayRef.detachments(); + const dropdownClosed = this.dropdownPanel.closed; + + return merge(backdropClick$, detachment$, dropdownClosed); + } + + closeDropdown(): void { + if (!this.overlayRef || !this.isDropdownOpen) { + return; + } + + this.onClosedCallback(); + this.dropdownClosingActionsSub.unsubscribe(); + this.isDropdownOpen = false; + this.overlayRef.detach(); + } + + ngOnDestroy(): void { + if (this.overlayRef) { + this.overlayRef.dispose(); + } + } + + onClosed(fn: () => void): void { + this.onClosedCallback = fn; + } + + onOpen(fn: () => void): void { + this.onOpenCallback = fn; + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.html b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.html new file mode 100644 index 0000000..4c65e10 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.html @@ -0,0 +1,5 @@ + + + diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.scss b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.scss new file mode 100644 index 0000000..c0e699d --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.scss @@ -0,0 +1,7 @@ +@import "variables/shadows"; + +.dropdown-content { + background-color: white; + border-radius: var(--digi--ui--border--radius); + box-shadow: $msfa__shadow; +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.spec.ts b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.spec.ts new file mode 100644 index 0000000..8fde5e0 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DropdownComponent } from './dropdown.component'; + +describe('DropdownComponent', () => { + let component: DropdownComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DropdownComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DropdownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.ts b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.ts new file mode 100644 index 0000000..562e4e4 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Output, TemplateRef, ViewChild } from '@angular/core'; +import { DropdownPanel } from './dropdown-panel'; + +@Component({ + selector: 'msfa-dropdown', + templateUrl: './dropdown.component.html', + styleUrls: ['./dropdown.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DropdownComponent implements DropdownPanel { + @ViewChild(TemplateRef) templateRef: TemplateRef; + @Output() closed = new EventEmitter(); +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.module.ts b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.module.ts new file mode 100644 index 0000000..ecd2958 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/dropdown/dropdown.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DropdownTriggerForDirective } from './dropdown-trigger-for.directive'; +import { DropdownComponent } from './dropdown.component'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { A11yModule } from '@angular/cdk/a11y'; + +@NgModule({ + declarations: [DropdownTriggerForDirective, DropdownComponent], + imports: [CommonModule, OverlayModule, A11yModule], + exports: [DropdownTriggerForDirective, DropdownComponent], +}) +export class DropdownModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/models/multiselect-filter-option.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-filter-option.ts similarity index 80% rename from apps/mina-sidor-fa/src/app/shared/models/multiselect-filter-option.ts rename to apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-filter-option.ts index e315282..40e7fea 100644 --- a/apps/mina-sidor-fa/src/app/shared/models/multiselect-filter-option.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-filter-option.ts @@ -2,4 +2,5 @@ export interface MultiselectFilterOption { label?: string; id?: string; count?: number; + secondary?: boolean; } diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.html b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.html new file mode 100644 index 0000000..23becb1 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.html @@ -0,0 +1,60 @@ +
+
+
+
+ {{ multiselectTitle }} +

{{ description }}

+ +
+
    +
  • + +
  • + +
  • + +
  • +
    +
+
+
+
+ + +
+
+ + +
+

+ I kombination med dina nuvarande filterval, finns det inga nya deltagare med {{ multiselectTitlePlural }} att + filtrera på. +

+
+
diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.scss b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.scss new file mode 100644 index 0000000..db6904d --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.scss @@ -0,0 +1,115 @@ +@import 'mixins/form'; + +@import 'mixins/a11y'; +.multiselect-panel { + &__checkboxes { + max-width: var(--digi--typography--text--max-width); + padding: var(--digi--layout--padding--15); + } + + &__fieldset { + @include msfa__fieldset; + } + + &__checkboxes-list { + list-style: none; + margin: 0; + padding: 0; + } + + &__checkboxes-item { + margin-top: var(--digi--layout--gutter--s); + + &:first-child { + margin-top: 0; + } + + &--unavailable { + margin-top: var(--digi--layout--gutter); + } + } + + &__search-wrapper { + position: relative; + margin-bottom: var(--digi--layout--gutter--l); + + &--searched { + margin-bottom: var(--digi--layout--gutter--s); + } + } + + &__select-all { + margin-bottom: var(--digi--layout--gutter--l); + } + + &__search-input ::ng-deep .digi-ng-form-input__label { + @include msfa__a11y-sr-only; + } + + &__input-reset-button { + position: absolute; + right: 0; + top: 1px; + } +} + +.multiselect-panel { + position: relative; + display: flex; + flex-direction: column; + + ::ng-deep &__backdrop { + background-color: rgba(var(--digi--ui--color--background--dark), 40%); + } + + &__fieldset { + margin: 0; + padding: 0; + border-width: 0; + } + + &__legend { + position: relative; + text-align: left; + } + + &__heading, + &__legend { + width: 100%; + height: 45px; + display: flex; + align-items: center; + margin: 0; + font-weight: var(--digi--typography--font-weight--bold); + font-size: var(--digi--typography--font-size); + padding: var(--digi--layout--gutter--s) var(--digi--layout--gutter); + border-bottom: 1px solid var(--digi--ui--color--background--tertiary); + } + + &__content { + position: relative; + flex-grow: 1; + } + + &__inner-content { + width: 100%; + min-width: 300px; + max-width: 800px; + overflow-y: auto; + overflow-x: hidden; + } + + &__footer { + display: flex; + justify-content: center; + align-items: center; + background-color: var(--digi--ui--color--background--tertiary); + height: 70px; + } + + &__no-options-available { + text-align: center; + padding: var(--digi--layout--gutter--s); + max-width: 300px; + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.spec.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.spec.ts new file mode 100644 index 0000000..4149d1f --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.spec.ts @@ -0,0 +1,25 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MultiselectPanelComponent } from './multiselect-panel.component'; + +describe('MultiselectPanelComponent', () => { + let component: MultiselectPanelComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MultiselectPanelComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MultiselectPanelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.ts new file mode 100644 index 0000000..839626f --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect-panel/multiselect-panel.component.ts @@ -0,0 +1,71 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, +} from '@angular/core'; +import { DropdownTriggerForDirective } from '@msfa-shared/components/dropdown/dropdown-trigger-for.directive'; +import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option'; +import { BehaviorSubject } from 'rxjs'; + +@Component({ + selector: 'msfa-multiselect-panel', + templateUrl: './multiselect-panel.component.html', + styleUrls: ['./multiselect-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MultiselectPanelComponent implements OnChanges { + @Input() dropdownRef: DropdownTriggerForDirective; + @Input() confirmButtonText = 'Spara'; + @Input() multiselectTitle: string; + @Input() multiselectTitlePlural: string; + @Input() description: string; + @Input() availableOptions: MultiselectFilterOption[]; + @Input() selectedOptions: MultiselectFilterOption[]; + @Output() selectedOptionsChange = new EventEmitter(); + + private _pendingSelectedOptions$ = new BehaviorSubject([]); + private _selectedUnavailableOptions$ = new BehaviorSubject([]); + + ngOnChanges(changes: SimpleChanges): void { + if (changes.availableOptions || changes.selectedOptions) { + this._setPendingSelectedOptions(); + } + } + get selectedUnavailableOptions(): MultiselectFilterOption[] { + return this._selectedUnavailableOptions$.getValue(); + } + private get _pendingSelectedOptions(): MultiselectFilterOption[] { + return this._pendingSelectedOptions$.getValue(); + } + + private _setPendingSelectedOptions(): void { + this._pendingSelectedOptions$.next(this.selectedOptions || []); + this._selectedUnavailableOptions$.next( + this.selectedOptions?.filter( + selectedOption => !this.availableOptions.some(availableOption => availableOption.id === selectedOption.id) + ) || [] + ); + } + + isSelected(filterOption: MultiselectFilterOption): boolean { + return this._pendingSelectedOptions$.value?.some(selectedOption => selectedOption.id === filterOption.id) ?? false; + } + + emitSelectedOptions(): void { + this.selectedOptionsChange.emit(this._pendingSelectedOptions); + } + + setOptionState(filterOption: MultiselectFilterOption, isSelected: boolean): void { + if (isSelected) { + this._pendingSelectedOptions$.next([...this._pendingSelectedOptions, filterOption]); + } else { + this._pendingSelectedOptions$.next( + this._pendingSelectedOptions.filter(item => item.id !== filterOption.id) ?? [] + ); + } + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.html b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.html new file mode 100644 index 0000000..b2add69 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.html @@ -0,0 +1,30 @@ + + + + + + diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.scss b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.scss new file mode 100644 index 0000000..c94b9b5 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.scss @@ -0,0 +1,64 @@ +@import 'variables/colors'; +@import 'mixins/list'; +@import 'variables/gutters'; + + +.multiselect { + + &__toggle-btn { + margin-right: var(--digi--layout--gutter); + display: flex; + justify-content: space-between; + align-items: center; + appearance: none; + width: 100%; + height: var(--digi--ui--input--height); + padding: var(--digi--ui--input--padding); + border: 1px solid var(--digi--ui--input--border--color); + cursor: pointer; + font-size: var(--digi--typography--font-size--m); + color: var(--digi--ui--color--background--overlay--opaque); + background-color: var(--digi--ui--color--background); + border-color: var(--digi-form-select--border-color); + + &:focus { + border-color: var(--digi--ui--color--focus--light); + box-shadow: 0 0 0.1rem 0.05rem var(--digi--ui--color--focus); + outline: none; + } + + &:disabled { + cursor: default; + background-color: var(--digi--ui--color--background--disabled); + border-color: var(--digi--ui--color--border--disabled); + color: var(--digi--typography--color--text--disabled); + } + + &--invalid { + background-color: var(--digi--ui--color--background--error); + border-color: var(--digi--ui--color--border--error); + box-shadow: 0 0 0 1px var(--digi--ui--color--border--error); + } + + &__text { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + &__toggle-btn__arrow-down { + padding: var(--digi--layout--padding); + } + + &__validation-messages { + @include msfa__reset-list; + display: flex; + justify-content: space-between; + padding: var(--digi--layout--gutter--s) 0; + flex-direction: column; + gap: var(--digi--layout--gutter--s); + } + +} diff --git a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.spec.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.spec.ts similarity index 57% rename from apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.spec.ts rename to apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.spec.ts index 4f3e955..cf12f8e 100644 --- a/apps/mina-sidor-fa/src/app/pages/avrop/components/temporary-filter/temporary-filter.component.spec.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.spec.ts @@ -1,21 +1,20 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MultiselectComponent } from './multiselect.component'; -import { TemporaryFilterComponent } from './temporary-filter.component'; - -describe('TemporaryFilterComponent', () => { - let component: TemporaryFilterComponent; - let fixture: ComponentFixture; +describe('MultiselectComponent', () => { + let component: MultiselectComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [TemporaryFilterComponent], + declarations: [MultiselectComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(TemporaryFilterComponent); + fixture = TestBed.createComponent(MultiselectComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.ts new file mode 100644 index 0000000..31ce949 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.component.ts @@ -0,0 +1,102 @@ +import { + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + forwardRef, + Input, + AfterViewInit, + Output, + Renderer2, + ViewChild, + ChangeDetectorRef, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { DropdownTriggerForDirective } from '@msfa-shared/components/dropdown/dropdown-trigger-for.directive'; +import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option'; +import { uuid } from '@msfa-utils/uuid'; + +interface PropagateChangeFn { + (_: unknown): void; +} + +interface PropagateTouchedFn { + (): void; +} + +@Component({ + selector: 'msfa-multiselect', + templateUrl: './multiselect.component.html', + styleUrls: ['./multiselect.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MultiselectComponent), + multi: true, + }, + ], +}) +export class MultiselectComponent implements AfterViewInit, ControlValueAccessor { + @ViewChild(DropdownTriggerForDirective) dropdownRef: DropdownTriggerForDirective; + panelId = `panel-${uuid()}`; + @Input() buttonElementId: string = uuid(); + @Input() isInvalid = false; + @Input() showValidation = false; + @Input() confirmButtonText = 'Spara'; + @Input() description: string; + @Input() multiselectTitle: string; + @Input() availableOptions: MultiselectFilterOption[]; + @Input() selectedOptions: MultiselectFilterOption[]; + @Output() selectedOptionsChange = new EventEmitter(); + @ViewChild('multiselectButton') multiselectButton: ElementRef; + isDisabled = false; + + private propagateChange: PropagateChangeFn; + private propagateTouched: PropagateTouchedFn; + + constructor(private renderer: Renderer2, private changeDetectionRef: ChangeDetectorRef) {} + + // Allows Angular to update the model. + // Update the model and changes needed for the view here. + writeValue(selectedOptions: MultiselectFilterOption[]): void { + this.selectedOptions = selectedOptions; + } + + // Allows Angular to register a function to call when the model changes. + // Save the function as a property to call later here. + registerOnChange(fn: PropagateChangeFn): void { + this.propagateChange = fn; + } + + // Allows Angular to register a function to call when the input has been touched. + // Save the function as a property to call later here. + registerOnTouched(fn: PropagateTouchedFn): void { + this.propagateTouched = fn; + } + + // Allows Angular to disable the input. + setDisabledState?(isDisabled: boolean): void { + this.renderer.setProperty(this.multiselectButton.nativeElement, 'disabled', isDisabled); + this.dropdownRef.closeDropdown(); + } + + emitSelectedOptions(selectedOptions: MultiselectFilterOption[]): void { + // If used with FormsModule/ReactiveFormsModule + if (this.propagateChange && this.propagateTouched) { + this.propagateChange(selectedOptions); + this.propagateTouched(); + } else { + this.selectedOptionsChange.emit(selectedOptions); + } + + this.dropdownRef.closeDropdown(); + } + + ngAfterViewInit(): void { + this.dropdownRef.onOpen(() => { + // force refresh of selected options if it's reopened + this.selectedOptions = [...(this.selectedOptions || [])]; + }); + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.module.ts b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.module.ts new file mode 100644 index 0000000..df03ea5 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/multiselect/multiselect.module.ts @@ -0,0 +1,16 @@ +import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox'; +import { A11yModule } from '@angular/cdk/a11y'; +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { DropdownModule } from '@msfa-shared/components/dropdown/dropdown.module'; +import { MultiselectComponent } from '@msfa-shared/components/multiselect/multiselect.component'; +import { MultiselectPanelComponent } from './multiselect-panel/multiselect-panel.component'; + +@NgModule({ + declarations: [MultiselectComponent, MultiselectPanelComponent], + imports: [CommonModule, DropdownModule, A11yModule, DigiNgFormCheckboxModule, FormsModule], + exports: [MultiselectComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], +}) +export class MultiselectModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.spec.ts b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.spec.ts index 0940ffb..91449cd 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.spec.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.spec.ts @@ -1,5 +1,5 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; - import { ExpandedTreeNodeComponent } from './expanded-tree-node.component'; describe('ExpandedTreeNodeComponent', () => { @@ -8,9 +8,9 @@ describe('ExpandedTreeNodeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ExpandedTreeNodeComponent ] - }) - .compileComponents(); + declarations: [ExpandedTreeNodeComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }).compileComponents(); }); beforeEach(() => { diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.ts b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.ts index 2470465..87d9d22 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.ts @@ -11,7 +11,7 @@ import { ViewChild, } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { UUID } from 'angular2-uuid'; +import { uuid } from '@msfa-utils/uuid'; import { TreeNode, TreeNodeModel, TreeNodesSelectorService } from '../../services/tree-nodes-selector.service'; interface PropagateChangeFn { @@ -36,7 +36,7 @@ interface PropagateTouchedFn { ], }) export class TreeNodesSelectorComponent implements ControlValueAccessor { - @Input() buttonElementId: string = UUID.UUID(); + @Input() buttonElementId: string = uuid(); @Input() headingText: string; @Input() confirmationButtonText = 'Spara'; @Input() isInvalid = false; @@ -51,7 +51,7 @@ export class TreeNodesSelectorComponent implements ControlValueAccessor { selectionLabel: string | null = null; isDisabled = false; - panelId = `panel-${UUID.UUID()}`; + panelId = `panel-${uuid()}`; private propagateChange: PropagateChangeFn; private propagateTouched: PropagateTouchedFn; diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/services/tree-nodes-selector.service.ts b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/services/tree-nodes-selector.service.ts index 499b918..a093412 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/services/tree-nodes-selector.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/services/tree-nodes-selector.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { UUID } from 'angular2-uuid'; +import { uuid } from '@msfa-utils/uuid'; export interface TreeNode { label: string; @@ -43,7 +43,7 @@ export class TreeNodesSelectorService { return { ...treeNode, - uuid: UUID.UUID(), + uuid: uuid(), hasFocus: false, children, hideTreeNode: false, diff --git a/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.directive.ts b/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.directive.ts new file mode 100644 index 0000000..0cc1932 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.directive.ts @@ -0,0 +1,16 @@ +import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core'; + +@Directive({ + selector: '[msfaAutofocus]', +}) +export class AutofocusDirective implements AfterViewInit { + @Input() msfaAutofocus: boolean; + + constructor(private elementRef: ElementRef) {} + + ngAfterViewInit(): void { + if (this.msfaAutofocus) { + (this.elementRef.nativeElement as HTMLElement).focus(); + } + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.module.ts b/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.module.ts new file mode 100644 index 0000000..82aa0cb --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/directives/autofocus/autofocus.module.ts @@ -0,0 +1,10 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { AutofocusDirective } from './autofocus.directive'; + +@NgModule({ + declarations: [AutofocusDirective], + imports: [CommonModule], + exports: [AutofocusDirective], +}) +export class AutofocusModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/services/avrop.service.ts b/apps/mina-sidor-fa/src/app/shared/services/avrop.service.ts index a8c0887..ba8c669 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/avrop.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/avrop.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { Params } from '@msfa-models/api/params.model'; import { Avrop, AvropCompactData } from '@msfa-models/avrop.model'; import { Handledare } from '@msfa-models/handledare.model'; -import { MultiselectFilterOption } from '@msfa-models/multiselect-filter-option'; import { AvropApiService } from '@msfa-services/api/avrop-api.service'; +import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; @@ -50,24 +50,24 @@ export class AvropService { this._filteredKommuner$, this._page$, this._limit$, - ]).pipe(switchMap(() => this.avropApiService.fetchAvrop$(this._getParams('avrop')))); + ]).pipe(switchMap(() => this.avropApiService.fetchAvrop$(this.filtersForAvrop))); public availableTjanster$: Observable = combineLatest([ this._filteredUtforandeVerksamheter$, this._filteredKommuner$, - ]).pipe(switchMap(() => this.avropApiService.fetchAvailableTjanster$(this._getParams('tjanster')))); + ]).pipe(switchMap(() => this.avropApiService.fetchAvailableTjanster$(this.filtersForTjanster))); public availableUtforandeVerksamheter$: Observable = combineLatest([ this._filteredTjanster$, this._filteredKommuner$, ]).pipe( - switchMap(() => this.avropApiService.fetchAvailableUtforandeVerksamheter$(this._getParams('utforandeVerksamheter'))) + switchMap(() => this.avropApiService.fetchAvailableUtforandeVerksamheter$(this.filtersForUtforandeVerksamheter)) ); public availableKommuner$: Observable = combineLatest([ this._filteredTjanster$, this._filteredUtforandeVerksamheter$, - ]).pipe(switchMap(() => this.avropApiService.fetchAvailableKommuner$(this._getParams('kommuner')))); + ]).pipe(switchMap(() => this.avropApiService.fetchAvailableKommuner$(this.filtersForKommuner))); private _lockedAvrop$: Observable = combineLatest([this.selectedAvrop$, this.avropIsLocked$]).pipe( map(([selectedAvrop, isLocked]) => (isLocked ? selectedAvrop : null)) @@ -102,48 +102,57 @@ export class AvropService { return 1; } - private _getParams(type: 'avrop' | 'kommuner' | 'tjanster' | 'utforandeVerksamheter'): Params { - const tjanstKoder = this._filteredTjanster$.getValue()?.length - ? { tjanstKoder: this._filteredTjanster$.getValue().map(tjanst => tjanst.id) } + private get tjanstKoder(): { tjanstKoder: string[] } | null { + return this._filteredTjanster$.value?.length + ? { tjanstKoder: this._filteredTjanster$.value.map(tjanst => tjanst.id) } : null; - const kommunKoder = this._filteredKommuner$.getValue()?.length - ? { kommunKoder: this._filteredKommuner$.getValue().map(kommun => kommun.id) } + } + + private get kommunKoder(): { kommunKoder: string[] } | null { + return this._filteredKommuner$.value?.length + ? { kommunKoder: this._filteredKommuner$.value.map(kommun => kommun.id) } : null; - const utforandeverksamhetIds = this._filteredUtforandeVerksamheter$.getValue()?.length + } + + private get utforandeverksamhetIds(): { utforandeverksamhetIds: string[] } | null { + return this._filteredUtforandeVerksamheter$.value?.length ? { - utforandeverksamhetIds: this._filteredUtforandeVerksamheter$ - .getValue() - .map(utforandeVerksamhet => utforandeVerksamhet.id), + utforandeverksamhetIds: this._filteredUtforandeVerksamheter$.value.map( + utforandeVerksamhet => utforandeVerksamhet.id + ), } : null; + } - switch (type) { - case 'avrop': - return { - ...tjanstKoder, - ...kommunKoder, - ...utforandeverksamhetIds, - page: this._page$.getValue().toString(), - limit: this._limit$.getValue().toString(), - }; - case 'kommuner': - return { - ...tjanstKoder, - ...utforandeverksamhetIds, - }; - case 'tjanster': - return { - ...kommunKoder, - ...utforandeverksamhetIds, - }; - case 'utforandeVerksamheter': - return { - ...tjanstKoder, - ...kommunKoder, - }; - default: - return {}; - } + private get filtersForAvrop(): Params { + return { + ...this.tjanstKoder, + ...this.kommunKoder, + ...this.utforandeverksamhetIds, + page: this._page$.value.toString(), + limit: this._limit$.value.toString(), + }; + } + + private get filtersForTjanster(): Params { + return { + ...this.kommunKoder, + ...this.utforandeverksamhetIds, + }; + } + + private get filtersForUtforandeVerksamheter(): Params { + return { + ...this.tjanstKoder, + ...this.kommunKoder, + }; + } + + private get filtersForKommuner(): Params { + return { + ...this.tjanstKoder, + ...this.utforandeverksamhetIds, + }; } public setSelectedAvrop(deltagare: Avrop[]): void { diff --git a/apps/mina-sidor-fa/src/app/shared/utils/uuid.ts b/apps/mina-sidor-fa/src/app/shared/utils/uuid.ts new file mode 100644 index 0000000..6fdfda8 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/utils/uuid.ts @@ -0,0 +1,8 @@ +// From https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid +export function uuid(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0, + v = c == 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} diff --git a/apps/mina-sidor-fa/src/styles/mixins/_form.scss b/apps/mina-sidor-fa/src/styles/mixins/_form.scss new file mode 100644 index 0000000..ec2c7f5 --- /dev/null +++ b/apps/mina-sidor-fa/src/styles/mixins/_form.scss @@ -0,0 +1,25 @@ +@import 'variables/colors'; +@import 'variables/gutters'; + + +@mixin msfa__fieldset($legendAsHeading: true) { + margin: 0; + padding: 0; + border-width: 0; + + legend { + position: relative; + text-align: left; + + @if $legendAsHeading { + width: 100%; + display: flex; + align-items: center; + padding-bottom: var(--digi--layout--gutter--xs); + margin: 0; + font-weight: var(--digi--typography--font-weight--bold); + font-size: var(--digi--typography--font-size--s); + border-bottom: 1px solid var(--digi--ui--color--background--off); + } + } +} diff --git a/apps/mina-sidor-fa/src/styles/styles.scss b/apps/mina-sidor-fa/src/styles/styles.scss index b6d15fd..2868a73 100644 --- a/apps/mina-sidor-fa/src/styles/styles.scss +++ b/apps/mina-sidor-fa/src/styles/styles.scss @@ -1,4 +1,5 @@ @import '@digi/core/dist/digi/digi.css'; +@import '~@angular/cdk/overlay-prebuilt.css'; @import 'mixins/a11y'; @import 'mixins/backdrop'; @import 'mixins/icon'; diff --git a/package-lock.json b/package-lock.json index 98dd381..8ec39b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,11 @@ { "name": "mina-sidor-fa-web", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "mina-sidor-fa-web", - "version": "1.5.0", + "version": "1.5.1", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -25,7 +24,6 @@ "@digi/core": "^9.4.0", "@digi/styles": "^6.0.2", "@nrwl/angular": "11.5.1", - "angular2-uuid": "^1.1.1", "date-fns": "^2.22.1", "ngx-markdown": "^11.1.3", "rxjs": "~6.6.3", @@ -4952,12 +4950,6 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, - "node_modules/angular2-uuid": { - "version": "1.1.1", - "resolved": "http://nexus.arbetsformedlingen.se/repository/npm/angular2-uuid/-/angular2-uuid-1.1.1.tgz", - "integrity": "sha1-cvA81TK39AAy6x7PufhFc4S+lW4=", - "license": "MIT" - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "http://nexus.arbetsformedlingen.se/repository/npm/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -36731,11 +36723,6 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, - "angular2-uuid": { - "version": "1.1.1", - "resolved": "http://nexus.arbetsformedlingen.se/repository/npm/angular2-uuid/-/angular2-uuid-1.1.1.tgz", - "integrity": "sha1-cvA81TK39AAy6x7PufhFc4S+lW4=" - }, "ansi-colors": { "version": "4.1.1", "resolved": "http://nexus.arbetsformedlingen.se/repository/npm/ansi-colors/-/ansi-colors-4.1.1.tgz", diff --git a/package.json b/package.json index e6ce3e6..5d46fa2 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@digi/core": "^9.4.0", "@digi/styles": "^6.0.2", "@nrwl/angular": "11.5.1", - "angular2-uuid": "^1.1.1", "date-fns": "^2.22.1", "ngx-markdown": "^11.1.3", "rxjs": "~6.6.3",