Skip to content
Snippets Groups Projects
Commit dadaa48d authored by OZGCloud's avatar OZGCloud
Browse files

OZG-6101 Add tab navigation logic

* Rename popup-layer to popup
* Add unit tests
parent 41a763f5
No related branches found
No related tags found
No related merge requests found
...@@ -23,6 +23,6 @@ export * from './lib/icons/send-icon/send-icon.component'; ...@@ -23,6 +23,6 @@ export * from './lib/icons/send-icon/send-icon.component';
export * from './lib/icons/spinner-icon/spinner-icon.component'; export * from './lib/icons/spinner-icon/spinner-icon.component';
export * from './lib/icons/stamp-icon/stamp-icon.component'; export * from './lib/icons/stamp-icon/stamp-icon.component';
export * from './lib/instant-search/instant-search/instant-search.component'; export * from './lib/instant-search/instant-search/instant-search.component';
export * from './lib/popup/popup-layer/popup-layer.component';
export * from './lib/popup/popup-list-item/popup-list-item.component'; export * from './lib/popup/popup-list-item/popup-list-item.component';
export * from './lib/popup/popup/popup.component';
export * from './lib/testbtn/testbtn.component'; export * from './lib/testbtn/testbtn.component';
import { getElementFromFixture } from '@alfa-client/test-utils';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
import { PopupLayerComponent } from './popup-layer.component';
describe('PopupLayerComponent', () => {
let component: PopupLayerComponent;
let fixture: ComponentFixture<PopupLayerComponent>;
const popupButton: string = getDataTestIdOf('popup-button');
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PopupLayerComponent],
}).compileComponents();
fixture = TestBed.createComponent(PopupLayerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('toggleShowPopupList', () => {
it('should change false to true', () => {
component.showPopupList = false;
component.toggleShowPopupList();
expect(component.showPopupList).toBe(true);
});
it('should change true to false', () => {
component.showPopupList = true;
component.toggleShowPopupList();
expect(component.showPopupList).toBe(false);
});
});
describe('aria-expanded', () => {
it('should be true if popup is opened', () => {
component.showPopupList = true;
fixture.detectChanges();
const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
expect(buttonElement.getAttribute('aria-expanded')).toBe('true');
});
it('should be false if popup is closed', () => {
component.showPopupList = false;
fixture.detectChanges();
const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
expect(buttonElement.getAttribute('aria-expanded')).toBe('false');
});
});
describe('toggleClicked', () => {
beforeEach(() => {
component.popupListRef = {
nativeElement: {
focus: jest.fn(),
},
};
});
it('should toggle popup list', () => {
component.toggleShowPopupList = jest.fn();
component.toggleClicked();
expect(component.toggleShowPopupList).toHaveBeenCalled();
});
it('should focus popup list if it is shown', fakeAsync(() => {
component.toggleClicked();
tick();
expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled();
}));
it('should focus popup list if it is hidden', fakeAsync(() => {
component.showPopupList = true;
component.toggleClicked();
tick();
expect(component.popupListRef.nativeElement.focus).not.toHaveBeenCalled();
}));
});
});
import { CommonModule } from '@angular/common';
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
@Component({
selector: 'ods-popup-layer',
standalone: true,
imports: [CommonModule],
template: `<div class="relative w-fit">
<button
class="w-fit"
(click)="toggleClicked()"
[attr.aria-expanded]="showPopupList"
aria-haspopup="true"
[attr.aria-label]="label"
data-test-id="popup-button"
>
<ng-content select="[button]" />
</button>
<ul
*ngIf="showPopupList"
class="animate-fadeIn absolute min-w-44 max-w-80 rounded shadow-lg shadow-grayborder focus:outline-none"
[ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'"
role="dialog"
aria-modal="true"
tabindex="0"
#popupList
>
<ng-content />
</ul>
</div>`,
})
export class PopupLayerComponent {
@Input() alignTo: 'left' | 'right' = 'left';
@Input() label: string = '';
@ViewChild('popupList') popupListRef: ElementRef;
showPopupList: boolean = false;
toggleShowPopupList(): void {
this.showPopupList = !this.showPopupList;
}
toggleClicked(): void {
this.toggleShowPopupList();
if (this.showPopupList) {
setTimeout(() => this.popupListRef.nativeElement.focus());
}
}
}
...@@ -21,7 +21,7 @@ describe('PopupListItemComponent', () => { ...@@ -21,7 +21,7 @@ describe('PopupListItemComponent', () => {
}); });
describe('itemClicked emitter', () => { describe('itemClicked emitter', () => {
it('should emit clickItem', () => { it('should emit itemClicked', () => {
component.itemClicked.emit = jest.fn(); component.itemClicked.emit = jest.fn();
dispatchEventFromFixture(fixture, 'button', 'click'); dispatchEventFromFixture(fixture, 'button', 'click');
......
import { getElementFromFixture } from '@alfa-client/test-utils';
import { ElementRef } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
import { PopupComponent } from './popup.component';
describe('PopupComponent', () => {
let component: PopupComponent;
let fixture: ComponentFixture<PopupComponent>;
const popupButton: string = getDataTestIdOf('popup-button');
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PopupComponent],
}).compileComponents();
fixture = TestBed.createComponent(PopupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('toggleShowPopupList', () => {
it('should change false to true', () => {
component.showPopupList = false;
component.toggleShowPopupList();
expect(component.showPopupList).toBe(true);
});
it('should change true to false', () => {
component.showPopupList = true;
component.toggleShowPopupList();
expect(component.showPopupList).toBe(false);
});
});
describe('aria-expanded', () => {
it('should be true if popup is opened', () => {
component.showPopupList = true;
fixture.detectChanges();
const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
expect(buttonElement.getAttribute('aria-expanded')).toBe('true');
});
it('should be false if popup is closed', () => {
component.showPopupList = false;
fixture.detectChanges();
const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
expect(buttonElement.getAttribute('aria-expanded')).toBe('false');
});
});
describe('toggleClicked', () => {
beforeEach(async () => {
component.showPopupList = true;
fixture.detectChanges();
await fixture.whenStable();
jest.spyOn(component.popupListRef.nativeElement, 'focus');
});
it('should toggle popup list', () => {
component.toggleShowPopupList = jest.fn();
component.toggleClicked();
expect(component.toggleShowPopupList).toHaveBeenCalled();
});
it('should focus popup list if it is shown', fakeAsync(() => {
component.showPopupList = false;
component.toggleClicked();
tick();
expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled();
}));
it('should not focus popup list if it is hidden', fakeAsync(() => {
component.showPopupList = true;
component.toggleClicked();
tick();
expect(component.popupListRef.nativeElement.focus).not.toHaveBeenCalled();
}));
});
describe('isTabKey', () => {
it('should return true', () => {
const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' };
const result: boolean = component.isTabKey(tabKeyEvent);
expect(result).toBe(true);
});
it('should return false', () => {
const keyEvent: KeyboardEvent = new KeyboardEvent('whatever');
const result: boolean = component.isTabKey(keyEvent);
expect(result).toBe(false);
});
});
describe('isShiftTabKey', () => {
it('should return true', () => {
const shiftTabKeyEvent: KeyboardEvent = {
...new KeyboardEvent('tab'),
key: 'Tab',
shiftKey: true,
};
const result: boolean = component.isShiftTabKey(shiftTabKeyEvent);
expect(result).toBe(true);
});
it('should return false', () => {
const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' };
const result: boolean = component.isShiftTabKey(tabKeyEvent);
expect(result).toBe(false);
});
});
describe('isEscapeKey', () => {
it('should return true', () => {
const escapeKeyEvent: KeyboardEvent = {
...new KeyboardEvent('esc'),
key: 'Escape',
};
const result: boolean = component.isEscapeKey(escapeKeyEvent);
expect(result).toBe(true);
});
it('should return false', () => {
const keyEvent: KeyboardEvent = new KeyboardEvent('whatever');
const result: boolean = component.isEscapeKey(keyEvent);
expect(result).toBe(false);
});
});
describe('hidePopupListAndFocusButton', () => {
beforeEach(() => {
jest.spyOn(component.popupButtonRef.nativeElement, 'focus');
});
it('should hide popup list', () => {
component.showPopupList = true;
component.hidePopupListAndFocusButton();
expect(component.showPopupList).toBe(false);
});
it('should focus button', () => {
component.showPopupList = true;
component.hidePopupListAndFocusButton();
expect(component.popupButtonRef.nativeElement.focus).toHaveBeenCalled();
});
});
describe('isPopupListHidden', () => {
it('should return true', () => {
component.showPopupList = false;
const result: boolean = component.isPopupListHidden();
expect(result).toBe(true);
});
it('should return false', () => {
component.showPopupList = true;
const result: boolean = component.isPopupListHidden();
expect(result).toBe(false);
});
});
describe('isFirstItemFocused', () => {
const document: Document = new Document();
const firstElement: HTMLElement = document.createElement('a');
const lastElement: HTMLElement = document.createElement('b');
const element: HTMLElement = document.createElement('div');
element.appendChild(firstElement);
element.appendChild(lastElement);
const popupList: ElementRef<HTMLElement> = {
nativeElement: element,
};
it('should return true', () => {
const documentWithFirstFocused = { ...document, activeElement: firstElement };
const result: boolean = component.isFirstItemFocused(popupList, documentWithFirstFocused);
expect(result).toBe(true);
});
it('should return false if popup list is undefined', () => {
const documentWithNoFocused: Document = new Document();
const result: boolean = component.isFirstItemFocused(undefined, documentWithNoFocused);
expect(result).toBe(false);
});
it('should return false if first element is not focused', () => {
const documentWithLastFocused = { ...document, activeElement: lastElement };
const result: boolean = component.isFirstItemFocused(popupList, documentWithLastFocused);
expect(result).toBe(false);
});
});
describe('isLastItemFocused', () => {
const document: Document = new Document();
const firstElement: HTMLElement = document.createElement('a');
const lastElement: HTMLElement = document.createElement('b');
const element: HTMLElement = document.createElement('div');
element.appendChild(firstElement);
element.appendChild(lastElement);
const popupList: ElementRef<HTMLElement> = {
nativeElement: element,
};
it('should return true', () => {
const documentWithLastFocused = { ...document, activeElement: lastElement };
const result: boolean = component.isLastItemFocused(popupList, documentWithLastFocused);
expect(result).toBe(true);
});
it('should return false if popup list is undefined', () => {
const documentWithNoFocused: Document = new Document();
const result: boolean = component.isLastItemFocused(undefined, documentWithNoFocused);
expect(result).toBe(false);
});
it('should return false if last element is not focused', () => {
const documentWithFirstFocused = { ...document, activeElement: firstElement };
const result: boolean = component.isLastItemFocused(popupList, documentWithFirstFocused);
expect(result).toBe(false);
});
});
describe('isPopupListFocused', () => {
let document: Document = new Document();
const element: HTMLElement = document.createElement('div');
const popupList: ElementRef<HTMLElement> = {
nativeElement: element,
};
it('should return true', () => {
const documentWithListFocused = { ...document, activeElement: element };
const result: boolean = component.isPopupListFocused(popupList, documentWithListFocused);
expect(result).toBe(true);
});
it('should return false if popup list is undefined', () => {
const documentWithNoFocused: Document = new Document();
const result: boolean = component.isPopupListFocused(undefined, documentWithNoFocused);
expect(result).toBe(false);
});
it('should return false if popup list is not focused', () => {
const documentWithNoFocused: Document = new Document();
const result: boolean = component.isPopupListFocused(popupList, documentWithNoFocused);
expect(result).toBe(false);
});
});
describe('focusFirstItem', () => {
const document: Document = new Document();
const listElement: HTMLElement = document.createElement('ul');
const firstElement: HTMLElement = document.createElement('a');
const lastElement: HTMLElement = document.createElement('b');
const buttonElement: HTMLElement = document.createElement('button');
buttonElement.focus = jest.fn();
firstElement.appendChild(buttonElement);
listElement.appendChild(firstElement);
listElement.appendChild(lastElement);
const popupList: ElementRef<HTMLElement> = {
nativeElement: listElement,
};
it('should get focusable element', () => {
component.getFocusableElement = jest.fn();
component.focusFirstItem(popupList);
expect(component.getFocusableElement).toHaveBeenCalled();
});
it('should focus first child', fakeAsync(() => {
component.focusFirstItem(popupList);
tick();
expect(buttonElement.focus).toHaveBeenCalled();
}));
});
describe('focusLastItem', () => {
const document: Document = new Document();
const listElement: HTMLElement = document.createElement('ul');
const firstElement: HTMLElement = document.createElement('a');
const lastElement: HTMLElement = document.createElement('b');
const buttonElement: HTMLElement = document.createElement('button');
buttonElement.focus = jest.fn();
lastElement.appendChild(buttonElement);
listElement.appendChild(firstElement);
listElement.appendChild(lastElement);
const popupList: ElementRef<HTMLElement> = {
nativeElement: listElement,
};
it('should get focusable element', () => {
component.getFocusableElement = jest.fn();
component.focusLastItem(popupList);
expect(component.getFocusableElement).toHaveBeenCalled();
});
it('should focus first child', fakeAsync(() => {
component.focusLastItem(popupList);
tick();
expect(buttonElement.focus).toHaveBeenCalled();
}));
});
describe('shouldFocusFirstItem', () => {
const keyboardEvent = new KeyboardEvent('e');
it('should return true if is tab key and last item is focused', () => {
component.isTabKey = jest.fn().mockReturnValue(true);
component.isLastItemFocused = jest.fn().mockReturnValue(true);
const result: boolean = component.shouldFocusFirstItem(keyboardEvent);
expect(result).toBe(true);
});
it('should return false', () => {
const result: boolean = component.shouldFocusFirstItem(keyboardEvent);
expect(result).toBe(false);
});
});
describe('shouldFocusLastItem', () => {
const keyboardEvent = new KeyboardEvent('e');
it('should return true if is shift tab key and first item is focused', () => {
component.isShiftTabKey = jest.fn().mockReturnValue(true);
component.isFirstItemFocused = jest.fn().mockReturnValue(true);
const result: boolean = component.shouldFocusLastItem(keyboardEvent);
expect(result).toBe(true);
});
it('should return true if is shift tab key and popup list is focused', () => {
component.isShiftTabKey = jest.fn().mockReturnValue(true);
component.isPopupListFocused = jest.fn().mockReturnValue(true);
const result: boolean = component.shouldFocusLastItem(keyboardEvent);
expect(result).toBe(true);
});
it('should return false', () => {
const result: boolean = component.shouldFocusLastItem(keyboardEvent);
expect(result).toBe(false);
});
});
describe('getFocusableElement', () => {
const document: Document = new Document();
const element: HTMLElement = document.createElement('div');
const buttonElement = document.createElement('button');
element.appendChild(buttonElement);
it('should return focusable element', () => {
const result: HTMLElement = component.getFocusableElement(element);
expect(result).toBe(buttonElement);
});
});
describe('onClickHandler', () => {
const e: MouseEvent = new MouseEvent('test');
beforeEach(() => {
component.hidePopupListAndFocusButton = jest.fn();
});
it('should not handle click if popup is hidden', () => {
component.isPopupListHidden = jest.fn().mockReturnValue(true);
component.onClickHandler(e);
expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
});
it('should hide popup and focus button if clicked outside of button', () => {
component.isPopupListHidden = jest.fn().mockReturnValue(false);
component.onClickHandler(e);
expect(component.hidePopupListAndFocusButton).toHaveBeenCalled();
});
it('should not handle click if clicked inside of button', () => {
component.isPopupListHidden = jest.fn().mockReturnValue(false);
component.popupButtonRef.nativeElement.contains = jest.fn().mockReturnValue(true);
component.onClickHandler(e);
expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
});
});
describe('onKeydownHandler', () => {
const keyboardEvent: KeyboardEvent = new KeyboardEvent('e');
beforeEach(() => {
component.isPopupListHidden = jest.fn();
component.isTabKey = jest.fn();
component.isShiftTabKey = jest.fn();
component.isEscapeKey = jest.fn();
component.isFirstItemFocused = jest.fn();
component.isLastItemFocused = jest.fn();
component.focusFirstItem = jest.fn();
component.focusLastItem = jest.fn();
component.isPopupListFocused = jest.fn();
component.shouldFocusFirstItem = jest.fn();
component.shouldFocusLastItem = jest.fn();
component.hidePopupListAndFocusButton = jest.fn();
});
it('should check for hidden popup list', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.isPopupListHidden).toHaveBeenCalled();
});
describe('popup list is hidden', () => {
beforeEach(() => {
component.isPopupListHidden = jest.fn().mockReturnValue(true);
});
it('should not focus first item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.focusFirstItem).not.toHaveBeenCalled();
});
it('should not focus last item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.focusLastItem).not.toHaveBeenCalled();
});
it('should ignore escape key handling', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
});
});
describe('popup list is visible', () => {
beforeEach(() => {
component.isPopupListHidden = jest.fn().mockReturnValue(false);
});
it('should check if should focus first item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.shouldFocusFirstItem).toHaveBeenCalled();
});
it('should focus first item', () => {
component.shouldFocusFirstItem = jest.fn().mockReturnValue(true);
component.onKeydownHandler(keyboardEvent);
expect(component.focusFirstItem).toHaveBeenCalled();
});
it('should not focus first item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.focusFirstItem).not.toHaveBeenCalled();
});
describe('should not focus first item', () => {
beforeEach(() => {
component.shouldFocusFirstItem = jest.fn().mockReturnValue(false);
});
it('should check if should focus last item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.shouldFocusLastItem).toHaveBeenCalled();
});
it('should focus last item', () => {
component.shouldFocusLastItem = jest.fn().mockReturnValue(true);
component.onKeydownHandler(keyboardEvent);
expect(component.focusLastItem).toHaveBeenCalled();
});
it('should not focus last item', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.focusLastItem).not.toHaveBeenCalled();
});
describe('should not focus last item', () => {
beforeEach(() => {
component.shouldFocusFirstItem = jest.fn().mockReturnValue(false);
component.shouldFocusLastItem = jest.fn().mockReturnValue(false);
});
it('should check for escape key', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.isEscapeKey).toHaveBeenCalled();
});
it('should hide popup list', () => {
component.isEscapeKey = jest.fn().mockReturnValue(true);
component.onKeydownHandler(keyboardEvent);
expect(component.hidePopupListAndFocusButton).toHaveBeenCalled();
});
it('should not hide popup list', () => {
component.onKeydownHandler(keyboardEvent);
expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
});
});
});
});
});
});
import { CommonModule } from '@angular/common';
import { Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';
import { first, last } from 'lodash-es';
import { twMerge } from 'tailwind-merge';
@Component({
selector: 'ods-popup',
standalone: true,
imports: [CommonModule],
template: `<div class="relative w-fit">
<button
class="w-fit outline-2 outline-offset-2 outline-focus"
[ngClass]="[twMerge('w-fit outline-2 outline-offset-2 outline-focus', buttonClass)]"
(click)="toggleClicked()"
[attr.aria-expanded]="showPopupList"
aria-haspopup="true"
[attr.aria-label]="label"
data-test-id="popup-button"
#popupButton
>
<ng-content select="[button]" />
</button>
<ul
*ngIf="showPopupList"
class="absolute max-h-120 min-w-44 max-w-80 animate-fadeIn overflow-y-auto rounded shadow-lg shadow-grayborder focus:outline-none"
[ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'"
role="dialog"
aria-modal="true"
tabindex="0"
#popupList
>
<ng-content />
</ul>
</div>`,
})
export class PopupComponent {
@Input() alignTo: 'left' | 'right' = 'left';
@Input() label: string = '';
@Input() buttonClass: string = '';
constructor(public ref: ElementRef) {}
showPopupList: boolean = false;
twMerge = twMerge;
@ViewChild('popupList') popupListRef: ElementRef<HTMLUListElement>;
@ViewChild('popupButton') popupButtonRef: ElementRef<HTMLButtonElement>;
@HostListener('document:keydown', ['$event'])
onKeydownHandler(e: KeyboardEvent): void {
if (this.isPopupListHidden()) return;
if (this.shouldFocusFirstItem(e)) this.focusFirstItem(this.popupListRef);
if (this.shouldFocusLastItem(e)) this.focusLastItem(this.popupListRef);
if (this.isEscapeKey(e)) this.hidePopupListAndFocusButton();
}
@HostListener('document:click', ['$event'])
onClickHandler(e: MouseEvent): void {
if (this.isPopupListHidden()) return;
if (!this.popupButtonRef.nativeElement.contains(e.target as HTMLElement)) {
this.hidePopupListAndFocusButton();
}
}
toggleShowPopupList(): void {
this.showPopupList = !this.showPopupList;
}
hidePopupListAndFocusButton(): void {
this.showPopupList = false;
this.popupButtonRef.nativeElement.focus();
}
toggleClicked(): void {
this.toggleShowPopupList();
if (this.showPopupList) {
setTimeout(() => this.popupListRef.nativeElement.focus());
}
}
isTabKey(e: KeyboardEvent): boolean {
return e.key === 'Tab' && !e.shiftKey;
}
isShiftTabKey(e: KeyboardEvent): boolean {
return e.key === 'Tab' && Boolean(e.shiftKey);
}
isEscapeKey(e: KeyboardEvent): boolean {
return e.key === 'Escape';
}
isFirstItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
if (!popupList) return false;
return popupList.nativeElement.firstChild.contains(document.activeElement);
}
isPopupListFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
if (!popupList) return false;
return popupList.nativeElement.isSameNode(document.activeElement);
}
isLastItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
if (!popupList) return false;
return popupList.nativeElement.lastChild.contains(document.activeElement);
}
isPopupListHidden(): boolean {
return !this.showPopupList;
}
focusFirstItem(popupList: ElementRef<HTMLElement>): void {
const firstItem: HTMLElement = this.getFocusableElement(
first(popupList.nativeElement.children),
);
setTimeout(() => firstItem.focus());
}
focusLastItem(popupList: ElementRef<HTMLElement>): void {
const lastItem: HTMLElement = this.getFocusableElement(last(popupList.nativeElement.children));
setTimeout(() => lastItem.focus());
}
shouldFocusFirstItem(e: KeyboardEvent): boolean {
return this.isTabKey(e) && this.isLastItemFocused(this.popupListRef, document);
}
shouldFocusLastItem(e: KeyboardEvent): boolean {
return (
this.isShiftTabKey(e) &&
(this.isFirstItemFocused(this.popupListRef, document) ||
this.isPopupListFocused(this.popupListRef, document))
);
}
getFocusableElement(element: Element): HTMLElement {
return first(element.querySelectorAll('a[href], button, [tabindex]')) as HTMLElement;
}
}
...@@ -6,27 +6,29 @@ import { ...@@ -6,27 +6,29 @@ import {
type StoryObj, type StoryObj,
} from '@storybook/angular'; } from '@storybook/angular';
import { SaveIconComponent } from '../../icons/save-icon/save-icon.component';
import { UserIconComponent } from '../../icons/user-icon/user-icon.component';
import { PopupListItemComponent } from '../popup-list-item/popup-list-item.component'; import { PopupListItemComponent } from '../popup-list-item/popup-list-item.component';
import { PopupLayerComponent } from './popup-layer.component'; import { PopupComponent } from './popup.component';
const meta: Meta<PopupLayerComponent> = { const meta: Meta<PopupComponent> = {
title: 'Popup/Popup layer', title: 'Popup/Popup',
component: PopupLayerComponent, component: PopupComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [PopupLayerComponent, PopupListItemComponent], imports: [PopupComponent, PopupListItemComponent, SaveIconComponent, UserIconComponent],
}), }),
componentWrapperDecorator((story) => `<div class="flex justify-center mb-20">${story}</div>`), componentWrapperDecorator((story) => `<div class="flex justify-center mb-32">${story}</div>`),
], ],
excludeStories: /.*Data$/, excludeStories: /.*Data$/,
tags: ['autodocs'], tags: ['autodocs'],
}; };
export default meta; export default meta;
type Story = StoryObj<PopupLayerComponent>; type Story = StoryObj<PopupComponent>;
export const Default: Story = { export const Default: Story = {
args: { alignTo: 'left' }, args: { alignTo: 'left', label: '', buttonClass: '' },
argTypes: { argTypes: {
alignTo: { alignTo: {
control: 'select', control: 'select',
...@@ -35,24 +37,42 @@ export const Default: Story = { ...@@ -35,24 +37,42 @@ export const Default: Story = {
defaultValue: { summary: 'left' }, defaultValue: { summary: 'left' },
}, },
}, },
buttonClass: { description: 'Tailwind class for button' },
label: { description: 'Aria-label for button' },
}, },
render: (args) => ({ render: (args) => ({
props: args, props: args,
template: `<ods-popup-layer ${argsToTemplate(args)}> template: `<ods-popup ${argsToTemplate(args)}>
<p button>Trigger popup</p> <ods-user-icon button />
<ods-popup-list-item caption="Lorem" /> <ods-popup-list-item caption="Lorem" />
<ods-popup-list-item caption="Ipsum" /> <ods-popup-list-item caption="Ipsum" />
</ods-popup-layer>`, <ods-popup-list-item caption="Dolor" />
</ods-popup>`,
}), }),
}; };
export const LongText: Story = { export const LongText: Story = {
render: (args) => ({ render: (args) => ({
props: args, props: args,
template: `<ods-popup-layer ${argsToTemplate(args)}> template: `<ods-popup ${argsToTemplate(args)}>
<p button>Trigger popup</p> <p button>Trigger popup</p>
<ods-popup-list-item caption="Lorem" /> <ods-popup-list-item caption="Lorem" />
<ods-popup-list-item caption="Lorem ipsum dolor sit amet" /> <ods-popup-list-item caption="Lorem ipsum dolor sit amet" />
</ods-popup-layer>`, </ods-popup>`,
}),
};
export const ItemsWithIcons: Story = {
render: (args) => ({
props: args,
template: `<ods-popup ${argsToTemplate(args)}>
<p button>Trigger popup</p>
<ods-popup-list-item caption="Lorem">
<ods-save-icon icon size="small" />
</ods-popup-list-item>
<ods-popup-list-item caption="Lorem ipsum dolor sit amet">
<ods-save-icon icon size="small" />
</ods-popup-list-item>
</ods-popup>`,
}), }),
}; };
...@@ -45,6 +45,9 @@ module.exports = { ...@@ -45,6 +45,9 @@ module.exports = {
borderWidth: { borderWidth: {
3: '3px', 3: '3px',
}, },
maxHeight: {
120: '480px',
},
colors: { colors: {
ozgblue: { ozgblue: {
50: 'hsl(200, 100%, 96%)', 50: 'hsl(200, 100%, 96%)',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment