Skip to content

Commit

Permalink
feat(admin-ui): Implement custom dropdown based on CDK Overlay
Browse files Browse the repository at this point in the history
Relates to #95
  • Loading branch information
michaelbromley committed May 14, 2019
1 parent c8539de commit 409bb16
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Directive, HostListener } from '@angular/core';

import { DropdownComponent } from './dropdown.component';

@Directive({
selector: '[vdrDropdownItem]',
// tslint:disable-next-line
host: { '[class.dropdown-item]': 'true' },
})
export class DropdownItemDirective {
constructor(private dropdown: DropdownComponent) {}

@HostListener('click', ['$event'])
onDropdownItemClick(event: any): void {
this.dropdown.toggleOpen();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.clear-backdrop {
background-color: hotpink;
}

.dropdown.open > .dropdown-menu {
position: relative;
top: 0;
}

:host {
opacity: 1;
transition: opacity 0.3s;
}
141 changes: 141 additions & 0 deletions admin-ui/src/app/shared/components/dropdown/dropdown-menu.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
ConnectedPosition,
HorizontalConnectionPos,
Overlay,
OverlayRef,
PositionStrategy,
VerticalConnectionPos,
} from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ElementRef,
Input,
OnDestroy,
OnInit,
TemplateRef,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Subscription } from 'rxjs';

import { DropdownTriggerDirective } from './dropdown-trigger.directive';
import { DropdownComponent } from './dropdown.component';

export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';

/**
* A dropdown menu modelled on the Clarity Dropdown component (https://v1.clarity.design/dropdowns).
*
* This was created because the Clarity implementation (at this time) does not handle edge detection. Instead
* we make use of the Angular CDK's Overlay module to manage the positioning.
*
* The API of this component (and its related Components & Directives) are based on the Clarity version,
* albeit only a subset which is currently used in this application.
*/
@Component({
selector: 'vdr-dropdown-menu',
template: `
<ng-template #menu>
<div class="dropdown open">
<div class="dropdown-menu">
<ng-content></ng-content>
</div>
</div>
</ng-template>
`,
styleUrls: ['./dropdown-menu.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
@Input('vdrPosition') private position: DropdownPosition = 'bottom-left';
@ViewChild('menu') private menuTemplate: TemplateRef<any>;
private menuPortal: TemplatePortal<any>;
private overlayRef: OverlayRef;
private backdropClickSub: Subscription;

constructor(
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private dropdown: DropdownComponent,
) {}

ngOnInit(): void {
this.dropdown.onOpenChange(isOpen => {
if (isOpen) {
this.overlayRef.attach(this.menuPortal);
} else {
this.overlayRef.detach();
}
});
}

ngAfterViewInit() {
this.overlayRef = this.overlay.create({
hasBackdrop: true,
backdropClass: 'clear-backdrop',
positionStrategy: this.getPositionStrategy(),
});
this.menuPortal = new TemplatePortal(this.menuTemplate, this.viewContainerRef);
this.backdropClickSub = this.overlayRef.backdropClick().subscribe(() => {
this.dropdown.toggleOpen();
});
}

ngOnDestroy(): void {
this.overlayRef.dispose();
if (this.backdropClickSub) {
this.backdropClickSub.unsubscribe();
}
}

private getPositionStrategy(): PositionStrategy {
const position: { [K in DropdownPosition]: ConnectedPosition } = {
['top-left']: {
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom',
},
['top-right']: {
originX: 'end',
originY: 'top',
overlayX: 'end',
overlayY: 'bottom',
},
['bottom-left']: {
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
},
['bottom-right']: {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top',
},
};

const pos = position[this.position];

return this.overlay
.position()
.flexibleConnectedTo(this.dropdown.trigger)
.withPositions([pos, this.invertPosition(pos)])
.withViewportMargin(12)
.withPush(true);
}

/** Inverts an overlay position. */
private invertPosition(pos: ConnectedPosition): ConnectedPosition {
const inverted = { ...pos };
inverted.originY = pos.originY === 'top' ? 'bottom' : 'top';
inverted.overlayY = pos.overlayY === 'top' ? 'bottom' : 'top';

return inverted;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Directive, ElementRef, HostListener } from '@angular/core';

import { DropdownComponent } from './dropdown.component';

@Directive({
selector: '[vdrDropdownTrigger]',
})
export class DropdownTriggerDirective {
constructor(private dropdown: DropdownComponent, private elementRef: ElementRef) {
dropdown.setTriggerElement(this.elementRef);
}

@HostListener('click', ['$event'])
onDropdownTriggerClick(event: any): void {
this.dropdown.toggleOpen();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<ng-content></ng-content>
Empty file.
26 changes: 26 additions & 0 deletions admin-ui/src/app/shared/components/dropdown/dropdown.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core';

@Component({
selector: 'vdr-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownComponent {
private isOpen = false;
private onOpenChangeCallbacks: Array<(isOpen: boolean) => void> = [];
public trigger: ElementRef;

toggleOpen() {
this.isOpen = !this.isOpen;
this.onOpenChangeCallbacks.forEach(fn => fn(this.isOpen));
}

onOpenChange(callback: (isOpen: boolean) => void) {
this.onOpenChangeCallbacks.push(callback);
}

setTriggerElement(elementRef: ElementRef) {
this.trigger = elementRef;
}
}
10 changes: 10 additions & 0 deletions admin-ui/src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
Expand All @@ -21,6 +22,10 @@ import { CustomFieldControlComponent } from './components/custom-field-control/c
import { CustomerLabelComponent } from './components/customer-label/customer-label.component';
import { DataTableColumnComponent } from './components/data-table/data-table-column.component';
import { DataTableComponent } from './components/data-table/data-table.component';
import { DropdownItemDirective } from './components/dropdown/dropdown-item.directive';
import { DropdownMenuComponent } from './components/dropdown/dropdown-menu.component';
import { DropdownTriggerDirective } from './components/dropdown/dropdown-trigger.directive';
import { DropdownComponent } from './components/dropdown/dropdown.component';
import { FacetValueChipComponent } from './components/facet-value-chip/facet-value-chip.component';
import { FacetValueSelectorComponent } from './components/facet-value-selector/facet-value-selector.component';
import { FormFieldControlDirective } from './components/form-field/form-field-control.directive';
Expand Down Expand Up @@ -54,6 +59,7 @@ const IMPORTS = [
NgSelectModule,
NgxPaginationModule,
TranslateModule,
OverlayModule,
];

const DECLARATIONS = [
Expand Down Expand Up @@ -90,6 +96,10 @@ const DECLARATIONS = [
SimpleDialogComponent,
TitleInputComponent,
SentenceCasePipe,
DropdownComponent,
DropdownMenuComponent,
DropdownTriggerDirective,
DropdownItemDirective,
];

@NgModule({
Expand Down
1 change: 1 addition & 0 deletions admin-ui/src/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
@import "theme/theme";

@import "~@ng-select/ng-select/themes/default.theme.css";
@import '~@angular/cdk/overlay-prebuilt.css';

0 comments on commit 409bb16

Please sign in to comment.