feature(digi-migrering): ui popover flyttat till vår kod. Påverkar info-rutorna på deltagareflikarna (TV-852)
Merge in TEA/mina-sidor-fa-web from feature/TV-852-ui-popover-with-angular-cdk to develop Squashed commit of the following: commit 2fc389330b473e53916c1505c15d129c97239e7a Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 13:32:52 2021 +0100 Update ui-popover.component.scss commit d88c5e8cbb2d517f6463ed2ca22a4dfe4c21e696 Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 13:31:41 2021 +0100 refactoir commit 400eae3e2732252dae1be8576f9231418007c9e7 Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 13:30:09 2021 +0100 refactor commit d9c76abd40edd8071324f6c51dab2ecdc5759883 Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 13:27:12 2021 +0100 inline button commit 0acabc39c42556936300b862c976328479084752 Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 09:47:32 2021 +0100 wip commit b7c51ba386c1b3dd771bdebe1d5a4219c0702dc6 Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 08:14:48 2021 +0100 Update ui-popover.component.ts commit 9e63b116b694c94daa4cb3b9a3dd898891d00c2b Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se> Date: Fri Dec 3 07:49:21 2021 +0100 first version
This commit is contained in:
@@ -40,19 +40,19 @@
|
|||||||
<dt>Behov av tolk:</dt>
|
<dt>Behov av tolk:</dt>
|
||||||
<dd class="deltagare-tab-personal-information__info">
|
<dd class="deltagare-tab-personal-information__info">
|
||||||
{{avropInformation.tolkbehov || 'Inget tolkbehov'}}
|
{{avropInformation.tolkbehov || 'Inget tolkbehov'}}
|
||||||
<digi-ng-popover class="deltagare-tab-personal-information__popover" [afRelativeIconSize]="true">
|
<ui-popover>
|
||||||
<p>
|
<p>
|
||||||
Tolk är en typ av språkstöd som kan behöva anlitas vid behov. Ibland står Arbetsförmedlingen för
|
Tolk är en typ av språkstöd som kan behöva anlitas vid behov. Ibland står Arbetsförmedlingen för
|
||||||
tolkkostnaden och ibland står leverantören för tolkkostnaden. Det finns skillnader mellan vilket språkstöd
|
tolkkostnaden och ibland står leverantören för tolkkostnaden. Det finns skillnader mellan vilket språkstöd
|
||||||
som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om språkstöd och
|
som ingår i upphandlingen av olika tjänster och utbildningar. Du hittar mer information om språkstöd och
|
||||||
tolk i förfrågningsunderlaget för specifik upphandling.
|
tolk i förfrågningsunderlaget för specifik upphandling.
|
||||||
</p>
|
</p>
|
||||||
</digi-ng-popover>
|
</ui-popover>
|
||||||
</dd>
|
</dd>
|
||||||
<dt>Behov av språkstöd:</dt>
|
<dt>Behov av språkstöd:</dt>
|
||||||
<dd class="deltagare-tab-personal-information__info">
|
<dd class="deltagare-tab-personal-information__info">
|
||||||
{{avropInformation.sprakstod || 'Inget språkstöd'}}
|
{{avropInformation.sprakstod || 'Inget språkstöd'}}
|
||||||
<digi-ng-popover class="deltagare-tab-personal-information__popover" [afRelativeIconSize]="true">
|
<ui-popover>
|
||||||
<p>
|
<p>
|
||||||
Det finns flera olika typer av språkstöd. Till exempel användande av flerspråkig personal, tillgång till
|
Det finns flera olika typer av språkstöd. Till exempel användande av flerspråkig personal, tillgång till
|
||||||
material på lätt svenska eller olika språk, anlitande av tolk med mera. Dessa kan erbjudas och kombineras
|
material på lätt svenska eller olika språk, anlitande av tolk med mera. Dessa kan erbjudas och kombineras
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
tjänster och utbildningar. Du hittar mer information om språkstöd i förfrågningsunderlaget för specifik
|
tjänster och utbildningar. Du hittar mer information om språkstöd i förfrågningsunderlaget för specifik
|
||||||
upphandling.
|
upphandling.
|
||||||
</p>
|
</p>
|
||||||
</digi-ng-popover>
|
</ui-popover>
|
||||||
</dd>
|
</dd>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</dl>
|
</dl>
|
||||||
@@ -108,12 +108,12 @@
|
|||||||
class="deltagare-tab-personal-information__info"
|
class="deltagare-tab-personal-information__info"
|
||||||
>
|
>
|
||||||
{{ avropInformation.genomforandeReferens }}
|
{{ avropInformation.genomforandeReferens }}
|
||||||
<digi-ng-popover class="deltagare-tab-personal-information__popover" [afRelativeIconSize]="true">
|
<ui-popover [uiPopoverPosition]="UiPopoverPosition.Bottom">
|
||||||
<p>
|
<p>
|
||||||
Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen. Du
|
Genomförandereferens är det referensnummer du ska använda dig av i kontakten med Arbetsförmedlingen. Du
|
||||||
kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen.
|
kan också använda genomförandereferensen till att leta fram en order i leverantörsportalen.
|
||||||
</p>
|
</p>
|
||||||
</digi-ng-popover>
|
</ui-popover>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,13 +22,4 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__popover {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: var(--digi--layout--gutter--s);
|
|
||||||
|
|
||||||
::ng-deep .digi-ng-popover__container {
|
|
||||||
z-index: $msfa__z-index-popover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { distinctUntilChanged, filter, map, startWith, switchMap } from 'rxjs/op
|
|||||||
import { DeltagareCardService } from '../../deltagare-card.service';
|
import { DeltagareCardService } from '../../deltagare-card.service';
|
||||||
import { UiIconType } from '@ui/icon/icon-type.enum';
|
import { UiIconType } from '@ui/icon/icon-type.enum';
|
||||||
import { UiIconSize } from '@ui/icon/icon-size.enum';
|
import { UiIconSize } from '@ui/icon/icon-size.enum';
|
||||||
|
import { UiPopoverPosition } from '@ui/popover/ui-popover.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-deltagare-tab-personal-information',
|
selector: 'msfa-deltagare-tab-personal-information',
|
||||||
@@ -21,6 +22,7 @@ export class DeltagareTabPersonalInformationComponent {
|
|||||||
@Input() handledarePickerVisible: boolean;
|
@Input() handledarePickerVisible: boolean;
|
||||||
IconType = UiIconType;
|
IconType = UiIconType;
|
||||||
IconSize = UiIconSize;
|
IconSize = UiIconSize;
|
||||||
|
UiPopoverPosition = UiPopoverPosition;
|
||||||
|
|
||||||
avropInformation$: Observable<DeltagareAvrop> = this.deltagareCardService.avropInformation$;
|
avropInformation$: Observable<DeltagareAvrop> = this.deltagareCardService.avropInformation$;
|
||||||
contactInformation$: Observable<ContactInformation> = this.deltagareCardService.contactInformation$;
|
contactInformation$: Observable<ContactInformation> = this.deltagareCardService.contactInformation$;
|
||||||
|
|||||||
@@ -6,12 +6,9 @@
|
|||||||
<ng-container *ngFor="let disability of disabilities; let index = index">
|
<ng-container *ngFor="let disability of disabilities; let index = index">
|
||||||
<dt>Funktionsnedsättning {{index + 1}}</dt>
|
<dt>Funktionsnedsättning {{index + 1}}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span>{{ disability.title }}</span>
|
{{ disability.title }}
|
||||||
<digi-ng-popover
|
<ui-popover *ngIf="disability.description" class="deltagare-tab-sensitive-information__popover"
|
||||||
*ngIf="disability.description"
|
>{{ disability.description }}</ui-popover
|
||||||
class="deltagare-tab-sensitive-information__popover"
|
|
||||||
[afRelativeIconSize]="true"
|
|
||||||
>{{ disability.description }}</digi-ng-popover
|
|
||||||
>
|
>
|
||||||
</dd>
|
</dd>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -15,9 +15,5 @@
|
|||||||
&__popover {
|
&__popover {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: var(--digi--layout--gutter--s);
|
margin-left: var(--digi--layout--gutter--s);
|
||||||
|
|
||||||
::ng-deep .digi-ng-popover__container {
|
|
||||||
z-index: $msfa__z-index-popover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { DeltagareTabReportsComponent } from './components/deltagare-tab-reports
|
|||||||
import { DeltagareTabSensitiveInformationComponent } from './components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component';
|
import { DeltagareTabSensitiveInformationComponent } from './components/deltagare-tab-sensitive-information/deltagare-tab-sensitive-information.component';
|
||||||
import { DeltagareCardComponent } from './deltagare-card.component';
|
import { DeltagareCardComponent } from './deltagare-card.component';
|
||||||
import { DeltagareCardService } from './deltagare-card.service';
|
import { DeltagareCardService } from './deltagare-card.service';
|
||||||
|
import { UiPopoverModule } from '@ui/popover/ui-popover.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
@@ -41,6 +42,7 @@ import { DeltagareCardService } from './deltagare-card.service';
|
|||||||
HandledarePickerFormModule,
|
HandledarePickerFormModule,
|
||||||
DigiNgLayoutExpansionPanelModule,
|
DigiNgLayoutExpansionPanelModule,
|
||||||
DigiNgPopoverModule,
|
DigiNgPopoverModule,
|
||||||
|
UiPopoverModule,
|
||||||
UiSkeletonModule,
|
UiSkeletonModule,
|
||||||
UiIconModule,
|
UiIconModule,
|
||||||
UiLinkButtonModule,
|
UiLinkButtonModule,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export enum UiIconType {
|
|||||||
ARROW_RIGHT = 'arrow-right',
|
ARROW_RIGHT = 'arrow-right',
|
||||||
EYE = 'eye',
|
EYE = 'eye',
|
||||||
EYESLASH = 'eyeslash',
|
EYESLASH = 'eyeslash',
|
||||||
|
INFO_CIRCLE_SOLID = 'info-circle-solid',
|
||||||
|
INFO_CIRCLE_OUTLINE = 'info-circle-outline',
|
||||||
ARCHIVE = 'archive',
|
ARCHIVE = 'archive',
|
||||||
PAPERCLIP = 'paperclip'
|
PAPERCLIP = 'paperclip',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,5 +100,13 @@
|
|||||||
<digi-icon-eye-slash *ngSwitchCase="iconType.EYESLASH" [ngClass]="iconClass"></digi-icon-eye-slash>
|
<digi-icon-eye-slash *ngSwitchCase="iconType.EYESLASH" [ngClass]="iconClass"></digi-icon-eye-slash>
|
||||||
<digi-icon-archive *ngSwitchCase="iconType.ARCHIVE" [ngClass]="iconClass"></digi-icon-archive>
|
<digi-icon-archive *ngSwitchCase="iconType.ARCHIVE" [ngClass]="iconClass"></digi-icon-archive>
|
||||||
<digi-icon-paperclip *ngSwitchCase="iconType.PAPERCLIP" [ngClass]="iconClass"></digi-icon-paperclip>
|
<digi-icon-paperclip *ngSwitchCase="iconType.PAPERCLIP" [ngClass]="iconClass"></digi-icon-paperclip>
|
||||||
|
<digi-icon-info-circle-solid
|
||||||
|
*ngSwitchCase="iconType.INFO_CIRCLE_SOLID"
|
||||||
|
[ngClass]="iconClass"
|
||||||
|
></digi-icon-info-circle-solid>
|
||||||
|
<digi-icon-info-circle-reg
|
||||||
|
*ngSwitchCase="iconType.INFO_CIRCLE_OUTLINE"
|
||||||
|
[ngClass]="iconClass"
|
||||||
|
></digi-icon-info-circle-reg>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ interface PropagateTouchedFn {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MultiselectComponent implements AfterViewInit, ControlValueAccessor {
|
export class MultiselectComponent implements AfterViewInit, ControlValueAccessor {
|
||||||
@ViewChild(OverlayTriggerForDirective) dropdownRef: OverlayTriggerForDirective;
|
@ViewChild(OverlayTriggerForDirective) overlayRef: OverlayTriggerForDirective;
|
||||||
panelId = `panel-${uuid()}`;
|
panelId = `panel-${uuid()}`;
|
||||||
@Input() buttonElementId: string = uuid();
|
@Input() buttonElementId: string = uuid();
|
||||||
@Input() isInvalid = false;
|
@Input() isInvalid = false;
|
||||||
@@ -88,7 +88,7 @@ export class MultiselectComponent implements AfterViewInit, ControlValueAccessor
|
|||||||
// Allows Angular to disable the input.
|
// Allows Angular to disable the input.
|
||||||
setDisabledState?(isDisabled: boolean): void {
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
this.renderer.setProperty(this.multiselectButton.nativeElement, 'disabled', isDisabled);
|
this.renderer.setProperty(this.multiselectButton.nativeElement, 'disabled', isDisabled);
|
||||||
this.dropdownRef.closeDropdown();
|
this.overlayRef.closeOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSelectedOptions(selectedOptions: MultiselectFilterOption[]): void {
|
emitSelectedOptions(selectedOptions: MultiselectFilterOption[]): void {
|
||||||
@@ -100,14 +100,14 @@ export class MultiselectComponent implements AfterViewInit, ControlValueAccessor
|
|||||||
this.selectedOptionsChange.emit(selectedOptions);
|
this.selectedOptionsChange.emit(selectedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dropdownRef.closeDropdown();
|
this.overlayRef.closeOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (!this.dropdownRef) {
|
if (!this.overlayRef) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.dropdownRef.onOpen(() => {
|
this.overlayRef.onOpen(() => {
|
||||||
// force refresh of selected options if it's reopened
|
// force refresh of selected options if it's reopened
|
||||||
this.selectedOptions = [...(this.selectedOptions || [])];
|
this.selectedOptions = [...(this.selectedOptions || [])];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { ConnectedPosition, Overlay, OverlayRef } from '@angular/cdk/overlay';
|
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||||
import { TemplatePortal } from '@angular/cdk/portal';
|
import { TemplatePortal } from '@angular/cdk/portal';
|
||||||
import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core';
|
import {
|
||||||
|
Directive,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
Output,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
import { merge, Observable, Subscription } from 'rxjs';
|
import { merge, Observable, Subscription } from 'rxjs';
|
||||||
import { OverlayPanel } from './overlay.model';
|
import { OverlayPanel } from './overlay.model';
|
||||||
|
|
||||||
@@ -9,6 +18,7 @@ import { OverlayPanel } from './overlay.model';
|
|||||||
})
|
})
|
||||||
export class OverlayTriggerForDirective implements OnDestroy {
|
export class OverlayTriggerForDirective implements OnDestroy {
|
||||||
@Input() public uiOverlayTriggerFor: OverlayPanel;
|
@Input() public uiOverlayTriggerFor: OverlayPanel;
|
||||||
|
|
||||||
@Input() public uiOverlayPositions: ConnectedPosition[] = [
|
@Input() public uiOverlayPositions: ConnectedPosition[] = [
|
||||||
{
|
{
|
||||||
originX: 'start',
|
originX: 'start',
|
||||||
@@ -18,6 +28,13 @@ export class OverlayTriggerForDirective implements OnDestroy {
|
|||||||
offsetY: 3,
|
offsetY: 3,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* uiOverlayConfig will override uiOverlayPreferredPositions
|
||||||
|
* */
|
||||||
|
@Input() public uiOverlayConfig: OverlayConfig;
|
||||||
|
|
||||||
|
@Output() public uiOverlayRealPositions = new EventEmitter<ConnectedPosition[]>();
|
||||||
private isOverlayOpen = false;
|
private isOverlayOpen = false;
|
||||||
public overlayRef: OverlayRef;
|
public overlayRef: OverlayRef;
|
||||||
private overlayClosingActionsSub = Subscription.EMPTY;
|
private overlayClosingActionsSub = Subscription.EMPTY;
|
||||||
@@ -30,32 +47,36 @@ export class OverlayTriggerForDirective implements OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@HostListener('click') onClick(): void {
|
@HostListener('click') onClick(): void {
|
||||||
return this.toggleDropdown();
|
return this.toggleOverlay();
|
||||||
}
|
}
|
||||||
@HostListener('document:keyup.escape', ['$event']) onKeydownHandler(): void {
|
@HostListener('document:keyup.escape', ['$event']) onKeydownHandler(): void {
|
||||||
this.closeDropdown();
|
this.closeOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private overlay: Overlay,
|
private overlay: Overlay,
|
||||||
private elementRef: ElementRef<HTMLElement>,
|
private triggerRef: ElementRef<HTMLElement>,
|
||||||
private viewContainerRef: ViewContainerRef
|
private viewContainerRef: ViewContainerRef
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
toggleDropdown(): void {
|
toggleOverlay(): void {
|
||||||
this.isOverlayOpen ? this.closeDropdown() : this.openOverlay();
|
this.isOverlayOpen ? this.closeOverlay() : this.openOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
openOverlay(): void {
|
openOverlay(): void {
|
||||||
this.isOverlayOpen = true;
|
this.isOverlayOpen = true;
|
||||||
|
|
||||||
|
const positionStrategy = this.overlay
|
||||||
|
.position()
|
||||||
|
.flexibleConnectedTo(this.triggerRef)
|
||||||
|
.withPositions(this.uiOverlayPositions);
|
||||||
|
|
||||||
this.overlayRef = this.overlay.create({
|
this.overlayRef = this.overlay.create({
|
||||||
hasBackdrop: true,
|
hasBackdrop: true,
|
||||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||||
scrollStrategy: this.overlay.scrollStrategies.close(),
|
scrollStrategy: this.overlay.scrollStrategies.close(),
|
||||||
positionStrategy: this.overlay
|
positionStrategy,
|
||||||
.position()
|
...(this.uiOverlayConfig ?? {}),
|
||||||
.flexibleConnectedTo(this.elementRef)
|
|
||||||
.withPositions(this.uiOverlayPositions),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const templatePortal = new TemplatePortal(this.uiOverlayTriggerFor.templateRef, this.viewContainerRef);
|
const templatePortal = new TemplatePortal(this.uiOverlayTriggerFor.templateRef, this.viewContainerRef);
|
||||||
@@ -63,7 +84,7 @@ export class OverlayTriggerForDirective implements OnDestroy {
|
|||||||
this.overlayRef.attach(templatePortal);
|
this.overlayRef.attach(templatePortal);
|
||||||
this.onOpenCallback();
|
this.onOpenCallback();
|
||||||
|
|
||||||
this.overlayClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeDropdown());
|
this.overlayClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeOverlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
private dropdownClosingActions(): Observable<MouseEvent | void> {
|
private dropdownClosingActions(): Observable<MouseEvent | void> {
|
||||||
@@ -74,7 +95,7 @@ export class OverlayTriggerForDirective implements OnDestroy {
|
|||||||
return merge(backdropClick$, detachment$, overlayClosed);
|
return merge(backdropClick$, detachment$, overlayClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDropdown(): void {
|
closeOverlay(): void {
|
||||||
if (!this.overlayRef || !this.isOverlayOpen) {
|
if (!this.overlayRef || !this.isOverlayOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
27
libs/ui/src/popover/ui-popover.component.html
Normal file
27
libs/ui/src/popover/ui-popover.component.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<div class="popover">
|
||||||
|
<button
|
||||||
|
class="popover__button"
|
||||||
|
type="button"
|
||||||
|
#triggerButton
|
||||||
|
[attr.aria-label]="uiAriaLabel"
|
||||||
|
[uiOverlayTriggerFor]="overlay"
|
||||||
|
[uiOverlayPositions]="overlayPositions"
|
||||||
|
[attr.aria-controls]="panelId"
|
||||||
|
>
|
||||||
|
<ui-icon [uiType]="UiIconType.INFO_CIRCLE_SOLID" [uiSize]="UiIconSize.L"></ui-icon>
|
||||||
|
<span class="popover__button-text">{{uiButtonText}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ui-overlay #overlay>
|
||||||
|
<div class="popover__overlay-content" [attr.id]="panelId" #content cdkTrapFocus cdkTrapFocusAutoCapture>
|
||||||
|
<digi-typography>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</digi-typography>
|
||||||
|
</div>
|
||||||
|
<button (click)="closePopover()" class="popover__close-button" type="button">
|
||||||
|
<span class="popover__close-button-text">Stäng </span>
|
||||||
|
<ui-icon [uiType]="UiIconType.X" [uiSize]="UiIconSize.L"></ui-icon>
|
||||||
|
</button>
|
||||||
|
<div class="popover__container-arrow" #arrow></div>
|
||||||
|
</ui-overlay>
|
||||||
70
libs/ui/src/popover/ui-popover.component.scss
Normal file
70
libs/ui/src/popover/ui-popover.component.scss
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
@import 'libs/styles/src/variables/colors';
|
||||||
|
@import 'libs/styles/src/mixins/list';
|
||||||
|
@import 'libs/styles/src/variables/gutters';
|
||||||
|
@import 'libs/styles/src/variables/shadows';
|
||||||
|
@import 'libs/styles/src/variables/z-index';
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
margin-left: var(--digi--layout--gutter);
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&__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);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__overlay-content {
|
||||||
|
max-width: var(--digi--typography--text--max-width);
|
||||||
|
padding: var(--digi--layout--padding--50) var(--digi--layout--padding--20) var(--digi--layout--padding--30);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--digi--layout--gutter);
|
||||||
|
right: var(--digi--layout--gutter--s);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-button-text {
|
||||||
|
font-size: var(--digi--typography--font-size--s);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container-arrow {
|
||||||
|
width: u(6);
|
||||||
|
height: u(6);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
right: 50%;
|
||||||
|
z-index: $msfa__z-index-popover;
|
||||||
|
overflow: hidden;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background: #fff;
|
||||||
|
border-width: u(3);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent #fff #fff;
|
||||||
|
right: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
box-shadow: 0 0 u(0.7) u(0.7) rgb(0 0 0 / 30%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
libs/ui/src/popover/ui-popover.component.spec.ts
Normal file
27
libs/ui/src/popover/ui-popover.component.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { UiPopoverComponent } from './ui-popover.component';
|
||||||
|
import { DropdownModule } from '@ui/dropdown/dropdown.module';
|
||||||
|
|
||||||
|
describe('popoverComponent', () => {
|
||||||
|
let component: UiPopoverComponent;
|
||||||
|
let fixture: ComponentFixture<UiPopoverComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DropdownModule],
|
||||||
|
declarations: [UiPopoverComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UiPopoverComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
96
libs/ui/src/popover/ui-popover.component.ts
Normal file
96
libs/ui/src/popover/ui-popover.component.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
Renderer2,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { uuid } from '@utils/uuid.util';
|
||||||
|
import { OverlayTriggerForDirective } from '@ui/overlay/overlay-trigger-for.directive';
|
||||||
|
import { ConnectedPosition } from '@angular/cdk/overlay';
|
||||||
|
import { UiIconType } from '@ui/icon/icon-type.enum';
|
||||||
|
import { UiIconSize } from '@ui/icon/icon-size.enum';
|
||||||
|
|
||||||
|
export enum UiPopoverPosition {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ui-popover',
|
||||||
|
templateUrl: './ui-popover.component.html',
|
||||||
|
styleUrls: ['./ui-popover.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class UiPopoverComponent implements AfterViewInit {
|
||||||
|
UiIconType = UiIconType;
|
||||||
|
UiIconSize = UiIconSize;
|
||||||
|
@ViewChild(OverlayTriggerForDirective) overlayRef: OverlayTriggerForDirective;
|
||||||
|
panelId = `panel-${uuid()}`;
|
||||||
|
@Input() uiAriaLabel: string;
|
||||||
|
@Input() uiButtonText?: string = 'Info';
|
||||||
|
@Input() buttonElementId: string = uuid();
|
||||||
|
@Input() uiPopoverPosition: UiPopoverPosition = UiPopoverPosition.Top;
|
||||||
|
@ViewChild('triggerButton') triggerButton: ElementRef;
|
||||||
|
@ViewChild('content') content: ElementRef;
|
||||||
|
@ViewChild('arrow') arrow: ElementRef;
|
||||||
|
readonly offsetPixels = -30;
|
||||||
|
|
||||||
|
@ViewChild('popoverButton') popoverButton: ElementRef;
|
||||||
|
|
||||||
|
get overlayPositions(): ConnectedPosition[] {
|
||||||
|
switch (this.uiPopoverPosition) {
|
||||||
|
case UiPopoverPosition.Top:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
originX: 'center',
|
||||||
|
originY: 'top',
|
||||||
|
overlayX: 'center',
|
||||||
|
overlayY: 'bottom',
|
||||||
|
offsetY: this.offsetPixels,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
case UiPopoverPosition.Bottom:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
originX: 'center',
|
||||||
|
originY: 'bottom',
|
||||||
|
overlayX: 'center',
|
||||||
|
overlayY: 'top',
|
||||||
|
offsetY: -this.offsetPixels,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private renderer: Renderer2) {}
|
||||||
|
|
||||||
|
closePopover(): void {
|
||||||
|
this.overlayRef.closeOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
switch (this.uiPopoverPosition) {
|
||||||
|
case UiPopoverPosition.Top:
|
||||||
|
this.moveArrowBottom();
|
||||||
|
break;
|
||||||
|
case UiPopoverPosition.Bottom:
|
||||||
|
this.moveArrowTop();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('uiPopoverPosition is mandatory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveArrowBottom() {
|
||||||
|
this.renderer.setStyle(this.arrow.nativeElement, 'transform', 'rotate(90deg)');
|
||||||
|
this.renderer.setStyle(this.arrow.nativeElement, 'bottom', `${this.offsetPixels + 6}px `);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveArrowTop() {
|
||||||
|
this.renderer.setStyle(this.arrow.nativeElement, 'transform', 'rotate(-90deg)');
|
||||||
|
this.renderer.setStyle(this.arrow.nativeElement, 'top', `${this.offsetPixels + 7}px `);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
libs/ui/src/popover/ui-popover.module.ts
Normal file
16
libs/ui/src/popover/ui-popover.module.ts
Normal file
@@ -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 { UiOverlayModule } from '@ui/overlay/overlay.module';
|
||||||
|
import { UiPopoverComponent } from '@ui/popover/ui-popover.component';
|
||||||
|
import { UiIconModule } from '@ui/icon/icon.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [UiPopoverComponent],
|
||||||
|
imports: [CommonModule, A11yModule, DigiNgFormCheckboxModule, FormsModule, UiOverlayModule, UiIconModule],
|
||||||
|
exports: [UiPopoverComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
})
|
||||||
|
export class UiPopoverModule {}
|
||||||
Reference in New Issue
Block a user