feat: Add newProjectConfigureHttpApiModule for API configuration and localization
feat: Implement SampleAppServiceTests for application service testing feat: Create newProjectConfigureApplicationTests project for application layer tests feat: Establish newProjectConfigureDomainTests project for domain layer tests feat: Introduce Entity Framework Core tests for the newProjectConfigure application feat: Develop console test application for HTTP API client demonstration feat: Implement FakeCurrentPrincipalAccessor for testing purposes chore: Set up test base classes and data seed contributors for consistent testing environment
This commit is contained in:
1 parent
7909179fca
commit
b6f2213ccf
279 files changed
+39466
No files matched your search
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
|
||||
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
|
||||
export type ModalVariant = 'default' | 'primary' | 'danger' | 'warning' | 'success' | 'info';
|
||||
export type ModalPosition = 'center' | 'top';
|
||||
|
||||
/**
|
||||
* Professional, reusable modal dialog component.
|
||||
*
|
||||
* Fully integrated with the application theme (supports Light/Dark mode via CSS tokens).
|
||||
* Uses @angular/cdk's A11yModule (cdkTrapFocus) for keyboard accessibility and focus trapping.
|
||||
*
|
||||
* Features:
|
||||
* - Responsive sizing: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'
|
||||
* - Semantic variants: 'default' | 'primary' | 'danger' | 'warning' | 'success' | 'info'
|
||||
* - Title, subtitle, and badge icon support
|
||||
* - Custom content projection via [modal-header], [modal-body], and [modal-footer]
|
||||
* - Smooth backdrop fade and panel scale transitions
|
||||
* - Accessible Esc and backdrop click dismissing
|
||||
* - Automatic background scroll-lock management
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal',
|
||||
imports: [CommonModule, A11yModule],
|
||||
templateUrl: './modal.component.html',
|
||||
styleUrl: './modal.component.scss',
|
||||
})
|
||||
export class ModalComponent implements OnChanges, OnDestroy {
|
||||
@Input() isOpen = false;
|
||||
@Input() title = '';
|
||||
@Input() subtitle?: string;
|
||||
@Input() iconClass?: string;
|
||||
@Input() variant: ModalVariant = 'default';
|
||||
@Input() size: ModalSize = 'md';
|
||||
@Input() position: ModalPosition = 'center';
|
||||
@Input() scrollable = true;
|
||||
@Input() closeOnBackdropClick = true;
|
||||
@Input() closeOnEsc = true;
|
||||
@Input() showCloseButton = true;
|
||||
/** Set false when you don't project any modal-footer content to omit the bottom action bar. */
|
||||
@Input() showFooter = true;
|
||||
|
||||
@Output() closed = new EventEmitter<void>();
|
||||
@Output() opened = new EventEmitter<void>();
|
||||
|
||||
private previouslyFocusedElement: HTMLElement | null = null;
|
||||
private originalBodyOverflow = '';
|
||||
|
||||
constructor(private hostRef: ElementRef<HTMLElement>) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!changes['isOpen']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isOpen) {
|
||||
this.previouslyFocusedElement = document.activeElement as HTMLElement;
|
||||
this.originalBodyOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
this.opened.emit();
|
||||
} else {
|
||||
this.unlockBodyScroll();
|
||||
this.previouslyFocusedElement?.focus?.();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.isOpen) {
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
}
|
||||
|
||||
private unlockBodyScroll(): void {
|
||||
document.body.style.overflow = this.originalBodyOverflow || '';
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.escape')
|
||||
onEscKey(): void {
|
||||
if (this.isOpen && this.closeOnEsc) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
onBackdropClick(): void {
|
||||
if (this.closeOnBackdropClick) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.closed.emit();
|
||||
}
|
||||
|
||||
get sizeClass(): string {
|
||||
switch (this.size) {
|
||||
case 'sm':
|
||||
return 'modal-panel--sm';
|
||||
case 'lg':
|
||||
return 'modal-panel--lg';
|
||||
case 'xl':
|
||||
return 'modal-panel--xl';
|
||||
case '2xl':
|
||||
return 'modal-panel--2xl';
|
||||
case 'full':
|
||||
return 'modal-panel--full';
|
||||
default:
|
||||
return 'modal-panel--md';
|
||||
}
|
||||
}
|
||||
|
||||
get positionClass(): string {
|
||||
return this.position === 'top' ? 'items-start pt-12 md:pt-16' : 'items-center';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user