Skip to content

Commit

Permalink
added modal styling with interactivity and docs example
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhristov14 committed Sep 19, 2024
1 parent 8b1e2a8 commit b4b81ef
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 22 deletions.
10 changes: 10 additions & 0 deletions libs/core/popover/popover-body/popover-body.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ export class PopoverBodyComponent implements AfterViewInit {
}
}

/** Handler for focus when clicking outside */
@HostListener('document:click', ['$event.target'])
onClick(targetElement: HTMLElement): void {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
// Call the focus logic if clicked outside the popover
this._focusFirstTabbableElement();
}
}

/** @hidden */
ngAfterViewInit(): void {
if (this._scrollbar) {
Expand Down
38 changes: 35 additions & 3 deletions libs/core/popover/popover-service/popover.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,18 +247,50 @@ describe('PopoverService', () => {
expect((<any>service)._shouldClose(mouseEvent)).not.toEqual(true);
});

it('shouldn close on escape keydown from popover body', () => {
it("shouldn't close on closeOnOutsideClick from popover body", () => {
service.initialise(componentInstance.triggerRef, componentInstance, componentInstance.getPopoverTemplateData());
service.closeOnOutsideClick = false;

service.open();

fixture.detectChanges();

jest.spyOn(service, 'close');

componentInstance.popoverBody.onClose.next();
document.body.click();

expect(service.close).toHaveBeenCalled();
expect(service.close).not.toHaveBeenCalled();

});

it ("shouldn't close on escape keydown from popover body", () => {
service.initialise(componentInstance.triggerRef, componentInstance, componentInstance.getPopoverTemplateData());
service.closeOnEscapeKey = false;

service.open();

fixture.detectChanges();

jest.spyOn(service, 'close');

const event = new KeyboardEvent('keydown', { key: 'Escape' });
document.dispatchEvent(event);

expect(service.close).not.toHaveBeenCalled();
});

it("should contain the appropriate classes when checkModalBackground is called", () => {
service.initialise(componentInstance.triggerRef, componentInstance, componentInstance.getPopoverTemplateData());
service.closeOnOutsideClick = false;

service.open();

service.checkModalBackground();

fixture.detectChanges();

expect(document.body.classList.contains('fd-overlay-active')).toBe(true);
expect(document.querySelector('.fd-popover__modal')).toBeTruthy();
});

it('should resize overlay body at least, on refresh position', () => {
Expand Down
37 changes: 37 additions & 0 deletions libs/core/popover/popover-service/popover.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ export class PopoverService extends BasePopoverClass {
/** @hidden */
private _ignoreTriggers = false;

/** @hidden */
private _modalBodyClass = 'fd-overlay-active';

/** @hidden */
private _modalTriggerClass = 'fd-popover__modal';

/** @hidden */
private _isModal = false;

/** An RxJS Subject that will kill the data stream upon component’s destruction (for unsubscribing) */
private readonly _destroyRef = inject(DestroyRef);

Expand Down Expand Up @@ -113,6 +122,10 @@ export class PopoverService extends BasePopoverClass {
this._overlayRef.detach();
this._overlayRef.dispose();
}

if (this._isModal) {
this._removeOverlay(this._modalBodyClass, this._modalTriggerClass);
}
});
}

Expand Down Expand Up @@ -165,6 +178,7 @@ export class PopoverService extends BasePopoverClass {
this.isOpenChange.emit(this.isOpen);
}

this.checkModalBackground();
this._focusLastActiveElementBeforeOpen(focusActiveElement);
}

Expand Down Expand Up @@ -230,12 +244,23 @@ export class PopoverService extends BasePopoverClass {
}
}

/** Changes background theming when modal */
/** @hidden */
checkModalBackground(): void {
if ((!this.closeOnOutsideClick || !this.closeOnEscapeKey) && this.isOpen) {
this._addModalOverlay(this._modalBodyClass, this._modalTriggerClass);
} else if ((!this.closeOnOutsideClick || !this.closeOnEscapeKey) && !this.isOpen) {
this._removeOverlay(this._modalBodyClass, this._modalTriggerClass);
}
}

/** Toggles the popover open state */
toggle(openAction = true, closeAction = true): void {
if (this.isOpen) {
closeAction && this.close();
} else {
openAction && this.open();
this.checkModalBackground();
}
}

Expand Down Expand Up @@ -404,6 +429,18 @@ export class PopoverService extends BasePopoverClass {
this._eventRef = [];
}

private _addModalOverlay(bodyClass: string, triggerClass: string): void {
this._renderer.addClass(document.body, bodyClass);
this._renderer.addClass((this._triggerElement as ElementRef).nativeElement, triggerClass);
this._isModal = true;
}

private _removeOverlay(bodyClass: string, triggerClass: string): void {
this._renderer.removeClass(document.body, bodyClass);
this._renderer.removeClass((this._triggerElement as ElementRef).nativeElement, triggerClass);
this._isModal = false;
}

/** Attach template containing popover body to overlay */
private _attachTemplate(): void {
this._passVariablesToBody();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="fd-docs-flex-display-helper">
<fd-popover [closeOnOutsideClick]="false" [focusAutoCapture]="true" [focusTrapped]="true">
<fd-popover-control>
<button fd-button>Prevent Close on Outside Click</button>
</fd-popover-control>
<fd-popover-body [style.min-width.px]="400">
<div fd-popover-body-header id="popover-bar-simple-header-1">
<div fd-bar barDesign="header">
<div fd-bar-left>
<fd-button-bar
id="preventCloseButton"
glyph="navigation-left-arrow"
fdType="transparent"
ariaLabel="Back"
></fd-button-bar>
<fd-bar-element><h5 fd-title>Header</h5></fd-bar-element>
</div>
</div>
</div>
<fd-avatar [circle]="true" [style.margin.rem]="1" size="xl" glyph="group"></fd-avatar>
</fd-popover-body>
</fd-popover>
<fd-popover [closeOnEscapeKey]="false" [focusAutoCapture]="true" [focusTrapped]="true">
<fd-popover-control>
<button fd-button>Prevent Close on Escape Key</button>
</fd-popover-control>
<fd-popover-body [style.min-width.px]="400">
<div fd-popover-body-header id="popover-bar-simple-header-1">
<div fd-bar barDesign="header">
<div fd-bar-left>
<fd-button-bar
glyph="navigation-left-arrow"
fdType="transparent"
ariaLabel="Back"
></fd-button-bar>
<fd-bar-element><h5 fd-title>Header</h5></fd-bar-element>
</div>
</div>
</div>
<fd-avatar [circle]="true" [style.margin.rem]="1" size="xl" glyph="group"></fd-avatar>
</fd-popover-body>
</fd-popover>
<fd-popover
[closeOnEscapeKey]="false"
[closeOnOutsideClick]="false"
[focusAutoCapture]="true"
[focusTrapped]="true"
>
<fd-popover-control>
<button fd-button>Prevent Close on Outside Click & on Escape Key</button>
</fd-popover-control>
<fd-popover-body [style.min-width.px]="400">
<div fd-popover-body-header id="popover-bar-simple-header-1">
<div fd-bar barDesign="header">
<div fd-bar-left>
<fd-button-bar
glyph="navigation-left-arrow"
fdType="transparent"
ariaLabel="Back"
></fd-button-bar>
<fd-bar-element><h5 fd-title>Header</h5></fd-bar-element>
</div>
</div>
</div>
<fd-avatar [circle]="true" [style.margin.rem]="1" size="xl" glyph="group"></fd-avatar>
</fd-popover-body>
</fd-popover>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.fd-docs-flex-display-helper {
display: flex;
align-items: center;
justify-content: space-around;
flex-flow: row wrap;
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { AvatarComponent } from '@fundamental-ngx/core/avatar';
import {
BarComponent,
BarElementDirective,
BarLeftDirective,
BarMiddleDirective,
BarRightDirective,
ButtonBarComponent
} from '@fundamental-ngx/core/bar';
import { ButtonComponent } from '@fundamental-ngx/core/button';
import {
PopoverBodyComponent,
PopoverBodyDirective,
PopoverBodyHeaderDirective,
PopoverComponent,
PopoverControlComponent
} from '@fundamental-ngx/core/popover';

@Component({
selector: 'fd-popover-closing-example',
templateUrl: './popover-closing-example.component.html',
styleUrls: ['./popover-closing-example.component.scss'],
standalone: true,
encapsulation: ViewEncapsulation.None,
imports: [
PopoverComponent,
PopoverControlComponent,
PopoverBodyComponent,
ButtonComponent,
PopoverBodyHeaderDirective,
PopoverBodyDirective,
AvatarComponent,
BarComponent,
ButtonBarComponent,
BarElementDirective,
BarLeftDirective,
BarMiddleDirective,
BarRightDirective
]
})
export class PopoverClosingExampleComponent {}
14 changes: 14 additions & 0 deletions libs/docs/core/popover/popover-docs.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@

<separator></separator>

<fd-docs-section-title id="closing" componentName="popover"> Closing Popovers </fd-docs-section-title>
<description>
Popovers can be closed in a variety of ways. By default, popovers close when the user clicks outside of the popover
or presses the escape key. These behaviors can be controlled by setting the <code>closeOnOutsideClick</code> and
<code>closeOnEscapeKey</code> inputs to <code>false</code>. Popovers can also be closed when the user navigates away
from the page by setting the <code>closeOnNavigation</code> input to <code>true</code>.
</description>
<component-example>
<fd-popover-closing-example></fd-popover-closing-example>
</component-example>
<code-example [exampleFiles]="popoverClosingExample"></code-example>

<separator></separator>

<fd-docs-section-title id="trigger" componentName="popover"> Simple Popover </fd-docs-section-title>
<description>
There is different way to build popover, it can be done, by using simplified markup, with trigger element connected
Expand Down
19 changes: 19 additions & 0 deletions libs/docs/core/popover/popover-docs.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getAssetFromModuleAssets
} from '@fundamental-ngx/docs/shared';
import { PopoverCFillComponent } from './examples/popover-c-fill/popover-c-fill.component';
import { PopoverClosingExampleComponent } from './examples/popover-closing-example/popover-closing-example.component';
import { PopoverComplexExampleComponent } from './examples/popover-complex-example/popover-complex-example.component';
import { PopoverContainerExampleComponent } from './examples/popover-container-example/popover-container-example.component';
import { PopoverDialogExampleComponent } from './examples/popover-dialog/popover-dialog-example.component';
Expand All @@ -34,6 +35,8 @@ const dropdownPopoverScss = 'popover-dropdown/popover-dropdown.component.scss';

const popoverSrc = 'popover-simple/popover-example.component.html';
const popoverSrcTs = 'popover-simple/popover-example.component.ts';
const popoverClosingSrc = 'popover-closing-example/popover-closing-example.component.html';
const popoverClosingSrcTs = 'popover-closing-example/popover-closing-example.component.ts';
const popoverComplexSrc = 'popover-complex-example/popover-complex-example.component.html';
const popoverComplexSrcTs = 'popover-complex-example/popover-complex-example.component.ts';
const popoverProgrammaticHtmlSrc = 'popover-programmatic/popover-programmatic-open-example.component.html';
Expand Down Expand Up @@ -81,6 +84,7 @@ const dynamicContainerHeightTsSrc =
DescriptionComponent,
ComponentExampleComponent,
PopoverExampleComponent,
PopoverClosingExampleComponent,
CodeExampleComponent,
SeparatorComponent,
PopoverTriggerExampleComponent,
Expand Down Expand Up @@ -114,6 +118,21 @@ export class PopoverDocsComponent {
}
];

popoverClosingExample: ExampleFile[] = [
{
language: 'html',
code: getAssetFromModuleAssets(popoverClosingSrc),
fileName: 'popover-closing-example'
},
{
language: 'typescript',
code: getAssetFromModuleAssets(popoverTriggerSrcTs),
fileName: 'popover-closing-exampl',
typescriptFileCode: getAssetFromModuleAssets(popoverClosingSrcTs),
component: 'PopoverClosingExampleComponent'
}
];

popoverComplex: ExampleFile[] = [
{
language: 'html',
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@angular/platform-browser": "18.0.3",
"@angular/platform-browser-dynamic": "18.0.3",
"@angular/router": "18.0.3",
"@fundamental-styles/cx": "0.37.7",
"@fundamental-styles/cx": "0.37.8",
"@nx/angular": "19.2.3",
"@sap-theming/theming-base-content": "11.18.0",
"@stackblitz/sdk": "1.9.0",
Expand All @@ -58,7 +58,7 @@
"fast-deep-equal": "3.1.3",
"focus-trap": "7.1.0",
"focus-visible": "5.2.1",
"fundamental-styles": "0.37.7",
"fundamental-styles": "0.37.8",
"fuse.js": "7.0.0",
"highlight.js": "11.7.0",
"intl": "1.2.5",
Expand Down Expand Up @@ -92,7 +92,7 @@
"@nx/js": "19.2.3",
"@nx/plugin": "19.2.3",
"@nx/workspace": "19.2.3",
"@sap-ui/common-css": "0.37.7",
"@sap-ui/common-css": "0.37.8",
"@schematics/angular": "18.0.4",
"@swc-node/register": "1.9.2",
"@swc/cli": "0.3.12",
Expand Down
Loading

0 comments on commit b4b81ef

Please sign in to comment.