-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dropdown): opening and closing with trigger
- Loading branch information
1 parent
4979851
commit 7857b1c
Showing
15 changed files
with
311 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
1 change: 1 addition & 0 deletions
1
projects/ng-tw/src/modules/dropdown/dropdown-item.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<ng-content></ng-content> |
82 changes: 82 additions & 0 deletions
82
projects/ng-tw/src/modules/dropdown/dropdown-item.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { Component, ChangeDetectionStrategy, Input, ElementRef } from '@angular/core'; | ||
|
||
/** | ||
* IDs need to be unique across components, so this counter exists outside of | ||
* the component definition. | ||
*/ | ||
let _uniqueIdCounter = 0; | ||
|
||
@Component({ | ||
selector: 'tw-dropdown-item, [tw-dropdown-item]', | ||
templateUrl: './dropdown-item.component.html', | ||
styleUrls: ['./dropdown-item.component.css'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
host: { | ||
'[attr.id]': 'id', | ||
'[attr.tabindex]': '_getTabIndex()', | ||
'[attr.aria-disabled]': 'disabled.toString()', | ||
'[attr.disabled]': 'disabled || null', | ||
'[attr.role]': 'menuitem', | ||
'[class]': 'itemClass + " " + (selected === true ? activeClass : inactiveClass)', | ||
}, | ||
}) | ||
export class DropdownItemComponent { | ||
@Input() public disabled: boolean = false; | ||
@Input() public id: string = `tw-dropdown-item-${_uniqueIdCounter++}`; | ||
|
||
public active: boolean = false; | ||
public selected: boolean = false; | ||
|
||
public itemClass: string = 'block px-4 py-2 text-sm text-gray-700 active:bg-gray-100 active:text-gray-900'; | ||
public activeClass: string = ''; //'bg-gray-100 text-gray-900'; | ||
public inactiveClass: string = ''; //'text-gray-700'; | ||
|
||
constructor(private readonly element: ElementRef<HTMLElement>) {} | ||
|
||
/** | ||
* This method sets display styles on the option to make it appear | ||
* active. This is used by the ActiveDescendantKeyManager so key | ||
* events will display the proper options as active on arrow key events. | ||
*/ | ||
setActiveStyles(): void { | ||
console.log('here'); | ||
if (!this.active) { | ||
this.active = true; | ||
this.scrollIntoView(); | ||
} | ||
} | ||
|
||
/** | ||
* This method removes display styles on the option that made it appear | ||
* active. This is used by the ActiveDescendantKeyManager so key | ||
* events will display the proper options as active on arrow key events. | ||
*/ | ||
setInactiveStyles(): void { | ||
if (this.active) { | ||
this.active = false; | ||
} | ||
} | ||
|
||
/** Gets the label to be used when determining whether the option should be focused. */ | ||
getLabel(): string { | ||
return this.element.nativeElement.textContent ? this.element.nativeElement.textContent : ''; | ||
} | ||
|
||
scrollIntoView() { | ||
if (typeof this.element.nativeElement.scrollIntoView !== 'undefined') this.element.nativeElement.scrollIntoView(); | ||
} | ||
|
||
select(): void { | ||
console.log('select'); | ||
// | ||
// Validate disabled | ||
if (this.disabled === true) return; | ||
// set selected | ||
this.selected = true; | ||
} | ||
|
||
/** Used to set the `tabindex`. */ | ||
_getTabIndex(): string { | ||
return this.disabled ? '-1' : '0'; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
projects/ng-tw/src/modules/dropdown/dropdown-panel.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { EventEmitter, QueryList, TemplateRef } from '@angular/core'; | ||
import { DropdownItemComponent } from './dropdown-item.component'; | ||
|
||
export interface TwDropdownPanel { | ||
templateRef: TemplateRef<any>; | ||
items: QueryList<DropdownItemComponent>; | ||
readonly closed: EventEmitter<void>; | ||
readonly yPosition: 'top' | 'bottom'; | ||
readonly xPosition: 'start' | 'end'; | ||
readonly id: string; | ||
} |
95 changes: 95 additions & 0 deletions
95
projects/ng-tw/src/modules/dropdown/dropdown-trigger-for.directive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { OverlayRef, Overlay } from '@angular/cdk/overlay'; | ||
import { TemplatePortal } from '@angular/cdk/portal'; | ||
import { Directive, OnDestroy, Input, ElementRef, ViewContainerRef, AfterContentInit, ContentChildren, QueryList } from '@angular/core'; | ||
import { Subscription, Observable, merge } from 'rxjs'; | ||
import { TwDropdownPanel } from './dropdown-panel.interface'; | ||
|
||
@Directive({ | ||
selector: '[twDropdownTriggerFor]', | ||
host: { | ||
'(click)': 'toggleDropdown()', | ||
// '(keydown)': 'handleKeydown($event)', | ||
}, | ||
}) | ||
export class TwDropdownTriggerFor implements OnDestroy, AfterContentInit { | ||
@Input('twDropdownTriggerFor') public dropdownPanel!: TwDropdownPanel; | ||
|
||
private isDropdownOpen = false; | ||
private overlayRef!: OverlayRef; | ||
private dropdownClosingActionsSub = Subscription.EMPTY; | ||
|
||
constructor(private overlay: Overlay, private elementRef: ElementRef<HTMLElement>, private viewContainerRef: ViewContainerRef) {} | ||
|
||
ngAfterContentInit(): void {} | ||
|
||
toggleDropdown(): void { | ||
this.isDropdownOpen ? this.destroyDropdown() : this.openDropdown(); | ||
} | ||
|
||
openDropdown(): void { | ||
// | ||
// Set position | ||
const position: any = { | ||
originX: 'start', | ||
originY: 'bottom', | ||
overlayX: 'start', | ||
overlayY: 'top', | ||
offsetY: 0, | ||
}; | ||
|
||
const xPosition: any = this.dropdownPanel.xPosition === 'end' ? ['end', 'end'] : ['start', 'start']; | ||
const yPosition: any = this.dropdownPanel.yPosition === 'top' ? ['top', 'bottom'] : ['bottom', 'top']; | ||
|
||
// | ||
// Set user position | ||
position.originX = xPosition[0]; | ||
position.originY = yPosition[0]; | ||
position.overlayX = xPosition[1]; | ||
position.overlayY = yPosition[1]; | ||
|
||
// | ||
// Set dropdown open | ||
this.isDropdownOpen = true; | ||
// create overlay | ||
this.overlayRef = this.overlay.create({ | ||
hasBackdrop: true, | ||
backdropClass: 'cdk-overlay-transparent-backdrop', | ||
scrollStrategy: this.overlay.scrollStrategies.close(), | ||
positionStrategy: this.overlay | ||
.position() | ||
.flexibleConnectedTo(this.elementRef) | ||
.withLockedPosition() | ||
.withGrowAfterOpen() | ||
.withPositions([position]), | ||
}); | ||
|
||
const templatePortal = new TemplatePortal(this.dropdownPanel.templateRef, this.viewContainerRef); | ||
this.overlayRef.attach(templatePortal); | ||
|
||
this.dropdownClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.destroyDropdown()); | ||
} | ||
|
||
private dropdownClosingActions(): Observable<MouseEvent | void> { | ||
const backdropClick$ = this.overlayRef.backdropClick(); | ||
const detachment$ = this.overlayRef.detachments(); | ||
const dropdownClosed = this.dropdownPanel.closed; | ||
|
||
return merge(backdropClick$, detachment$, dropdownClosed); | ||
} | ||
|
||
private destroyDropdown(): void { | ||
if (!this.overlayRef || !this.isDropdownOpen) { | ||
return; | ||
} | ||
|
||
this.dropdownClosingActionsSub.unsubscribe(); | ||
this.isDropdownOpen = false; | ||
this.overlayRef.detach(); | ||
} | ||
|
||
ngOnDestroy(): void { | ||
if (this.overlayRef) { | ||
this.overlayRef.dispose(); | ||
} | ||
} | ||
} |
Empty file.
14 changes: 14 additions & 0 deletions
14
projects/ng-tw/src/modules/dropdown/dropdown.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<ng-template> | ||
<div | ||
(click)="closed.emit()" | ||
class="w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" | ||
role="menu" | ||
aria-orientation="vertical" | ||
aria-labelledby="menu-button" | ||
tabindex="-1" | ||
> | ||
<div class="py-1"> | ||
<ng-content></ng-content> | ||
</div> | ||
</div> | ||
</ng-template> |
25 changes: 25 additions & 0 deletions
25
projects/ng-tw/src/modules/dropdown/dropdown.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { DropdownComponent } from './dropdown.component'; | ||
|
||
describe('DropdownComponent', () => { | ||
let component: DropdownComponent; | ||
let fixture: ComponentFixture<DropdownComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
declarations: [ DropdownComponent ] | ||
}) | ||
.compileComponents(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(DropdownComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { | ||
Component, | ||
ChangeDetectionStrategy, | ||
ViewChild, | ||
TemplateRef, | ||
Output, | ||
EventEmitter, | ||
Input, | ||
ContentChildren, | ||
QueryList, | ||
} from '@angular/core'; | ||
import { DropdownItemComponent } from './dropdown-item.component'; | ||
import { TwDropdownPanel } from './dropdown-panel.interface'; | ||
|
||
/** | ||
* IDs need to be unique across components, so this counter exists outside of | ||
* the component definition. | ||
*/ | ||
let _uniqueIdCounter = 0; | ||
|
||
@Component({ | ||
selector: 'tw-dropdown', | ||
templateUrl: './dropdown.component.html', | ||
styleUrls: ['./dropdown.component.css'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
host: { | ||
'[attr.id]': 'id', | ||
}, | ||
}) | ||
export class DropdownComponent implements TwDropdownPanel { | ||
@ViewChild(TemplateRef) public templateRef!: TemplateRef<any>; | ||
@ContentChildren(DropdownItemComponent, { descendants: true }) public items!: QueryList<DropdownItemComponent>; | ||
|
||
@Output() public closed = new EventEmitter<void>(); | ||
|
||
@Input() public yPosition: 'top' | 'bottom' = 'bottom'; | ||
@Input() public xPosition: 'start' | 'end' = 'start'; | ||
|
||
@Input() public id: string = `tw-dropdown-${_uniqueIdCounter++}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { DropdownComponent } from './dropdown.component'; | ||
import { OverlayModule } from '@angular/cdk/overlay'; | ||
import { TwDropdownTriggerFor } from './dropdown-trigger-for.directive'; | ||
import { DropdownItemComponent } from './dropdown-item.component'; | ||
|
||
@NgModule({ | ||
declarations: [DropdownComponent, TwDropdownTriggerFor, DropdownItemComponent], | ||
imports: [CommonModule, OverlayModule], | ||
exports: [DropdownComponent, TwDropdownTriggerFor, DropdownItemComponent], | ||
}) | ||
export class TwDropdownModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters