@lukfel/ng-scaffold 20.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.eslintrc.json +37 -0
  2. package/README.md +382 -0
  3. package/ng-package.json +8 -0
  4. package/package.json +28 -0
  5. package/src/lib/components/bottom-bar/bottom-bar.component.html +29 -0
  6. package/src/lib/components/bottom-bar/bottom-bar.component.scss +33 -0
  7. package/src/lib/components/bottom-bar/bottom-bar.component.spec.ts +24 -0
  8. package/src/lib/components/bottom-bar/bottom-bar.component.ts +31 -0
  9. package/src/lib/components/content-title-card/content-title-card.component.html +25 -0
  10. package/src/lib/components/content-title-card/content-title-card.component.scss +17 -0
  11. package/src/lib/components/content-title-card/content-title-card.component.spec.ts +108 -0
  12. package/src/lib/components/content-title-card/content-title-card.component.ts +24 -0
  13. package/src/lib/components/drawer/drawer.component.html +33 -0
  14. package/src/lib/components/drawer/drawer.component.scss +10 -0
  15. package/src/lib/components/drawer/drawer.component.spec.ts +52 -0
  16. package/src/lib/components/drawer/drawer.component.ts +30 -0
  17. package/src/lib/components/floating-button/floating-button.component.html +32 -0
  18. package/src/lib/components/floating-button/floating-button.component.scss +20 -0
  19. package/src/lib/components/floating-button/floating-button.component.spec.ts +84 -0
  20. package/src/lib/components/floating-button/floating-button.component.ts +57 -0
  21. package/src/lib/components/footer/footer.component.html +38 -0
  22. package/src/lib/components/footer/footer.component.scss +39 -0
  23. package/src/lib/components/footer/footer.component.spec.ts +118 -0
  24. package/src/lib/components/footer/footer.component.ts +14 -0
  25. package/src/lib/components/header/header.component.html +170 -0
  26. package/src/lib/components/header/header.component.scss +102 -0
  27. package/src/lib/components/header/header.component.spec.ts +134 -0
  28. package/src/lib/components/header/header.component.ts +53 -0
  29. package/src/lib/components/loading-overlay/loading-overlay.component.html +3 -0
  30. package/src/lib/components/loading-overlay/loading-overlay.component.scss +16 -0
  31. package/src/lib/components/loading-overlay/loading-overlay.component.spec.ts +24 -0
  32. package/src/lib/components/loading-overlay/loading-overlay.component.ts +10 -0
  33. package/src/lib/components/navbar/navbar.component.html +43 -0
  34. package/src/lib/components/navbar/navbar.component.scss +71 -0
  35. package/src/lib/components/navbar/navbar.component.spec.ts +43 -0
  36. package/src/lib/components/navbar/navbar.component.ts +35 -0
  37. package/src/lib/components/scaffold/scaffold.component.html +74 -0
  38. package/src/lib/components/scaffold/scaffold.component.scss +48 -0
  39. package/src/lib/components/scaffold/scaffold.component.spec.ts +119 -0
  40. package/src/lib/components/scaffold/scaffold.component.ts +191 -0
  41. package/src/lib/interceptors/loading.interceptor.ts +51 -0
  42. package/src/lib/models/bottom-bar-config.model.ts +8 -0
  43. package/src/lib/models/confirm-dialog-config.model.ts +6 -0
  44. package/src/lib/models/content-title-card-config.model.ts +6 -0
  45. package/src/lib/models/drawer-config.model.ts +6 -0
  46. package/src/lib/models/floating-button-config.model.ts +13 -0
  47. package/src/lib/models/footer-config.model.ts +10 -0
  48. package/src/lib/models/header-config.model.ts +26 -0
  49. package/src/lib/models/index.ts +15 -0
  50. package/src/lib/models/library-config.model.ts +4 -0
  51. package/src/lib/models/menu-button.model.ts +10 -0
  52. package/src/lib/models/navbar-config.model.ts +8 -0
  53. package/src/lib/models/navigation-link.model.ts +6 -0
  54. package/src/lib/models/placeholder-config.model.ts +7 -0
  55. package/src/lib/models/scaffold-config.model.ts +21 -0
  56. package/src/lib/models/seo-config.model.ts +6 -0
  57. package/src/lib/scaffold.module.ts +54 -0
  58. package/src/lib/services/breakpoint.service.spec.ts +15 -0
  59. package/src/lib/services/breakpoint.service.ts +16 -0
  60. package/src/lib/services/dialog.service.spec.ts +18 -0
  61. package/src/lib/services/dialog.service.ts +58 -0
  62. package/src/lib/services/index.ts +9 -0
  63. package/src/lib/services/local-storage.service.spec.ts +15 -0
  64. package/src/lib/services/local-storage.service.ts +125 -0
  65. package/src/lib/services/logger.service.spec.ts +15 -0
  66. package/src/lib/services/logger.service.ts +46 -0
  67. package/src/lib/services/router.service.spec.ts +15 -0
  68. package/src/lib/services/router.service.ts +91 -0
  69. package/src/lib/services/scaffold.service.spec.ts +15 -0
  70. package/src/lib/services/scaffold.service.ts +77 -0
  71. package/src/lib/services/seo.service.spec.ts +15 -0
  72. package/src/lib/services/seo.service.ts +75 -0
  73. package/src/lib/services/snackbar.service.spec.ts +18 -0
  74. package/src/lib/services/snackbar.service.ts +38 -0
  75. package/src/lib/services/theme.service.spec.ts +20 -0
  76. package/src/lib/services/theme.service.ts +71 -0
  77. package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.html +24 -0
  78. package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.scss +0 -0
  79. package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.spec.ts +85 -0
  80. package/src/lib/shared/components/dialogs/confirm-dialog/confirm-dialog.component.ts +14 -0
  81. package/src/lib/shared/components/file-upload/file-upload.component.html +21 -0
  82. package/src/lib/shared/components/file-upload/file-upload.component.scss +5 -0
  83. package/src/lib/shared/components/file-upload/file-upload.component.spec.ts +25 -0
  84. package/src/lib/shared/components/file-upload/file-upload.component.ts +43 -0
  85. package/src/lib/shared/components/icon/icon.component.html +17 -0
  86. package/src/lib/shared/components/icon/icon.component.scss +9 -0
  87. package/src/lib/shared/components/icon/icon.component.spec.ts +22 -0
  88. package/src/lib/shared/components/icon/icon.component.ts +17 -0
  89. package/src/lib/shared/components/input/input.component.html +38 -0
  90. package/src/lib/shared/components/input/input.component.scss +31 -0
  91. package/src/lib/shared/components/input/input.component.spec.ts +62 -0
  92. package/src/lib/shared/components/input/input.component.ts +72 -0
  93. package/src/lib/shared/components/placeholder/placeholder.component.html +21 -0
  94. package/src/lib/shared/components/placeholder/placeholder.component.scss +30 -0
  95. package/src/lib/shared/components/placeholder/placeholder.component.spec.ts +24 -0
  96. package/src/lib/shared/components/placeholder/placeholder.component.ts +16 -0
  97. package/src/lib/shared/modules/material.module.ts +77 -0
  98. package/src/lib/shared/shared.module.ts +18 -0
  99. package/src/public-api.ts +16 -0
  100. package/styles/_classes.scss +34 -0
  101. package/styles/_theme.scss +97 -0
  102. package/styles/_variables.scss +50 -0
  103. package/styles/fonts/icons/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 +0 -0
  104. package/styles/fonts/icons/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2 +0 -0
  105. package/styles/fonts/material-icons.scss +48 -0
  106. package/styles/fonts/roboto/roboto-v30-latin-100.woff +0 -0
  107. package/styles/fonts/roboto/roboto-v30-latin-100.woff2 +0 -0
  108. package/styles/fonts/roboto/roboto-v30-latin-100italic.woff +0 -0
  109. package/styles/fonts/roboto/roboto-v30-latin-100italic.woff2 +0 -0
  110. package/styles/fonts/roboto/roboto-v30-latin-300.woff +0 -0
  111. package/styles/fonts/roboto/roboto-v30-latin-300.woff2 +0 -0
  112. package/styles/fonts/roboto/roboto-v30-latin-300italic.woff +0 -0
  113. package/styles/fonts/roboto/roboto-v30-latin-300italic.woff2 +0 -0
  114. package/styles/fonts/roboto/roboto-v30-latin-500.woff +0 -0
  115. package/styles/fonts/roboto/roboto-v30-latin-500.woff2 +0 -0
  116. package/styles/fonts/roboto/roboto-v30-latin-500italic.woff +0 -0
  117. package/styles/fonts/roboto/roboto-v30-latin-500italic.woff2 +0 -0
  118. package/styles/fonts/roboto/roboto-v30-latin-700.woff +0 -0
  119. package/styles/fonts/roboto/roboto-v30-latin-700.woff2 +0 -0
  120. package/styles/fonts/roboto/roboto-v30-latin-700italic.woff +0 -0
  121. package/styles/fonts/roboto/roboto-v30-latin-700italic.woff2 +0 -0
  122. package/styles/fonts/roboto/roboto-v30-latin-italic.woff +0 -0
  123. package/styles/fonts/roboto/roboto-v30-latin-italic.woff2 +0 -0
  124. package/styles/fonts/roboto/roboto-v30-latin-regular.woff +0 -0
  125. package/styles/fonts/roboto/roboto-v30-latin-regular.woff2 +0 -0
  126. package/styles/fonts/roboto-font.scss +109 -0
  127. package/styles/style.scss +54 -0
  128. package/tsconfig.lib.json +14 -0
  129. package/tsconfig.lib.prod.json +10 -0
  130. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,35 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { NavbarConfig } from '../../models';
3
+
4
+ @Component({
5
+ selector: 'lf-navbar',
6
+ templateUrl: './navbar.component.html',
7
+ styleUrls: ['./navbar.component.scss'],
8
+ standalone: false
9
+ })
10
+ export class NavbarComponent {
11
+
12
+ @Input() public navbarConfig: NavbarConfig | null = null;
13
+ @Input() public isMobile: boolean = false;
14
+ @Input() public currentRoute: string;
15
+
16
+ @Output() public navbarButtonClickEvent = new EventEmitter<string>();
17
+
18
+ public buttonClicked(id: string): void {
19
+ if(!id) {
20
+ return;
21
+ }
22
+
23
+ this.navbarButtonClickEvent.emit(id);
24
+ }
25
+
26
+ public isActive(id: string): boolean {
27
+ if(!id || !this.currentRoute) {
28
+ return false;
29
+ }
30
+
31
+ const route: string = this.currentRoute.substring(this.currentRoute.indexOf('/') + 1);
32
+ return route === id;
33
+ }
34
+
35
+ }
@@ -0,0 +1,74 @@
1
+ <!-- header-height: 64px; navbar-size: 80px; floating-button-bottom: 24px -->
2
+
3
+ <div class="lf-scaffold mat-app-background mat-typography" [ngClass]="scaffoldConfig?.class">
4
+ <!-- loading overlay -->
5
+ @if (scaffoldConfig?.loading) {
6
+ <lf-loading-overlay></lf-loading-overlay>
7
+ }
8
+ <!-- header -->
9
+ <lf-header
10
+ [headerConfig]="headerConfig"
11
+ [isMobile]="isMobile"
12
+ [routeLoading]="routeLoading"
13
+ [currentRoute]="currentRoute"
14
+ (headerButtonClickEvent)="headerButtonClicked($event)"
15
+ (headerInputSubmitEvent)="headerInputSubmitted($event)"
16
+ (headerInputChangeEvent)="headerInputChanged($event)"></lf-header>
17
+ <!-- navbar -->
18
+ <lf-navbar
19
+ [navbarConfig]="navbarConfig"
20
+ [isMobile]="isMobile"
21
+ [currentRoute]="currentRoute"
22
+ (navbarButtonClickEvent)="navbarButtonClicked($event)"></lf-navbar>
23
+ <!-- content wrapper -->
24
+ <div
25
+ class="lf-content-wrapper"
26
+ [class.lf-show-navbar]="navbarConfig?.enable && !isMobile"
27
+ [class.lf-show-header]="headerConfig?.enable && !(navbarConfig?.enable && isMobile)"
28
+ [class.lf-show-navbar-mobile]="navbarConfig?.enable && isMobile && !headerConfig?.enable"
29
+ [class.lf-show-header-and-navbar-mobile]="
30
+ headerConfig?.enable && navbarConfig?.enable && isMobile
31
+ "
32
+ #scrollContainer
33
+ cdkScrollable>
34
+ <!-- drawer -->
35
+ <lf-drawer
36
+ [drawerConfig]="drawerConfig"
37
+ [drawerPortal]="drawerPortal"
38
+ [isMobile]="isMobile"
39
+ [fixedOffset]="headerConfig?.enable ? 64 : 0">
40
+ <!-- drawer content -->
41
+ <ng-content select="[drawerContent]" drawerContent></ng-content>
42
+ <!-- content title card -->
43
+ <lf-content-title-card
44
+ [contentTitleCardConfig]="contentTitleCardConfig"
45
+ [isMobile]="isMobile"
46
+ [routeHistory]="routeHistory"
47
+ (backButtonClickEvent)="backButtonClicked()"></lf-content-title-card>
48
+ <!-- main content -->
49
+ <div
50
+ class="lf-content"
51
+ [class.lf-content-mobile]="isMobile"
52
+ [class.lf-show-footer]="footerConfig?.enable"
53
+ #content>
54
+ <ng-content></ng-content>
55
+ </div>
56
+ <!-- footer -->
57
+ <lf-footer [footerConfig]="footerConfig"></lf-footer>
58
+ </lf-drawer>
59
+ </div>
60
+ <!-- to top button -->
61
+ <lf-floating-button
62
+ [floatingButtonConfig]="floatingButtonConfig"
63
+ [onTop]="scrollTopPosition <= 0"
64
+ [isMobile]="isMobile && navbarConfig?.enable!"
65
+ [bottomBarEnabled]="bottomBarConfig?.enable!"
66
+ (floatingButtonClickEvent)="floatingButtonClicked($event)"></lf-floating-button>
67
+ <!-- bottom bar -->
68
+ <lf-bottom-bar
69
+ [bottomBarConfig]="bottomBarConfig"
70
+ [isMobile]="isMobile && navbarConfig?.enable!"
71
+ [navbarEnabled]="navbarConfig?.enable!"
72
+ (bottomBarCloseClickEvent)="bottomBarCloseClicked($event)"
73
+ (bottomBarButtonClickEvent)="bottomBarButtonClicked($event)"></lf-bottom-bar>
74
+ </div>
@@ -0,0 +1,48 @@
1
+ @use '../../../../styles/variables' as *;
2
+
3
+ .lf-scaffold {
4
+ height: 100vh;
5
+
6
+ .lf-content-wrapper {
7
+ transition: height $transition;
8
+ position: absolute;
9
+ top: 0;
10
+ right: 0;
11
+ bottom: 0;
12
+ left: 0;
13
+ overflow-y: auto;
14
+
15
+ &.lf-show-navbar {
16
+ left: calc($navbar-height + (2 * $navbar-padding));
17
+ }
18
+
19
+ &.lf-show-header {
20
+ top: $header-height;
21
+ height: calc(100vh - $header-height);
22
+ }
23
+
24
+ &.lf-show-navbar-mobile {
25
+ height: calc(100vh - $navbar-height-mobile);
26
+ }
27
+
28
+ &.lf-show-header-and-navbar-mobile {
29
+ top: $header-height;
30
+ height: calc(100vh - $header-height - $navbar-height-mobile);
31
+ }
32
+
33
+ .lf-content {
34
+ padding-left: $content-padding;
35
+ padding-right: $content-padding;
36
+
37
+ &.lf-content-mobile {
38
+ padding-left: $content-padding-mobile;
39
+ padding-right: $content-padding-mobile;
40
+ }
41
+
42
+ &.lf-show-footer{
43
+ min-height: 100%;
44
+ }
45
+ }
46
+ }
47
+ }
48
+
@@ -0,0 +1,119 @@
1
+ import { DebugElement } from '@angular/core';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { By } from '@angular/platform-browser';
4
+ import { RouterTestingModule } from '@angular/router/testing';
5
+ import { Logger } from '../../services';
6
+ import { SharedModule } from '../../shared/shared.module';
7
+ import { BottomBarComponent } from '../bottom-bar/bottom-bar.component';
8
+ import { ContentTitleCardComponent } from '../content-title-card/content-title-card.component';
9
+ import { DrawerComponent } from '../drawer/drawer.component';
10
+ import { FloatingButtonComponent } from '../floating-button/floating-button.component';
11
+ import { FooterComponent } from '../footer/footer.component';
12
+ import { HeaderComponent } from '../header/header.component';
13
+ import { LoadingOverlayComponent } from '../loading-overlay/loading-overlay.component';
14
+ import { NavbarComponent } from '../navbar/navbar.component';
15
+ import { ScaffoldComponent } from './scaffold.component';
16
+
17
+ // @Component({
18
+ // selector: 'lf-header',
19
+ // template: ''
20
+ // })
21
+ // class MockHeaderComponent {
22
+ // @Input() public headerConfig: HeaderConfig = {};
23
+ // @Input() public isMobile: boolean = false;
24
+ // @Input() public routeLoading: boolean = false;
25
+ // }
26
+
27
+ class MockLogger { }
28
+
29
+ describe('ScaffoldComponent', () => {
30
+ let component: ScaffoldComponent;
31
+ let fixture: ComponentFixture<ScaffoldComponent>;
32
+
33
+ beforeEach(async () => {
34
+ await TestBed.configureTestingModule({
35
+ declarations: [
36
+ // MockHeaderComponent
37
+ ScaffoldComponent,
38
+ LoadingOverlayComponent,
39
+ HeaderComponent,
40
+ NavbarComponent,
41
+ DrawerComponent,
42
+ ContentTitleCardComponent,
43
+ FooterComponent,
44
+ FloatingButtonComponent,
45
+ BottomBarComponent
46
+ ],
47
+ imports: [
48
+ SharedModule,
49
+ RouterTestingModule
50
+ ],
51
+ providers: [
52
+ { provide: Logger, useClass: MockLogger }
53
+ ]
54
+ }).compileComponents();
55
+
56
+ fixture = TestBed.createComponent(ScaffoldComponent);
57
+ component = fixture.componentInstance;
58
+ fixture.detectChanges();
59
+ });
60
+
61
+ it('should create', () => {
62
+ expect(component).toBeTruthy();
63
+ });
64
+
65
+ it('should render the header component', () => {
66
+ component.headerConfig = { enable: true };
67
+ fixture.detectChanges();
68
+ const headerDebugElement: DebugElement = fixture.debugElement.query(
69
+ By.directive(HeaderComponent)
70
+ );
71
+ expect(headerDebugElement).toBeTruthy();
72
+ });
73
+
74
+
75
+ it('should render the navbar component', () => {
76
+ component.navbarConfig = { enable: true };
77
+ fixture.detectChanges();
78
+ const navbarDebugElement: DebugElement = fixture.debugElement.query(
79
+ By.directive(NavbarComponent)
80
+ );
81
+ expect(navbarDebugElement).toBeTruthy();
82
+ });
83
+
84
+ it('should render the drawer component', () => {
85
+ component.drawerConfig = { enable: true };
86
+ fixture.detectChanges();
87
+ const drawerDebugElement: DebugElement = fixture.debugElement.query(
88
+ By.directive(DrawerComponent)
89
+ );
90
+ expect(drawerDebugElement).toBeTruthy();
91
+ });
92
+
93
+ it('should render the title component', () => {
94
+ component.contentTitleCardConfig = { enable: true };
95
+ fixture.detectChanges();
96
+ const titleDebugElement: DebugElement = fixture.debugElement.query(
97
+ By.directive(ContentTitleCardComponent)
98
+ );
99
+ expect(titleDebugElement).toBeTruthy();
100
+ });
101
+
102
+ it('should render the footer component', () => {
103
+ component.footerConfig = { enable: true };
104
+ fixture.detectChanges();
105
+ const footerDebugElement: DebugElement = fixture.debugElement.query(
106
+ By.directive(FooterComponent)
107
+ );
108
+ expect(footerDebugElement).toBeTruthy();
109
+ });
110
+
111
+ it('should render the button component', () => {
112
+ component.floatingButtonConfig = { enable: true };
113
+ fixture.detectChanges();
114
+ const buttonDebugElement: DebugElement = fixture.debugElement.query(
115
+ By.directive(FloatingButtonComponent)
116
+ );
117
+ expect(buttonDebugElement).toBeTruthy();
118
+ });
119
+ });
@@ -0,0 +1,191 @@
1
+ import { Breakpoints, BreakpointState } from '@angular/cdk/layout';
2
+ import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
3
+
4
+ import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild, DOCUMENT, inject } from '@angular/core';
5
+ import { ActivatedRoute } from '@angular/router';
6
+ import { debounceTime, distinctUntilChanged, fromEvent, Subscription } from 'rxjs';
7
+ import { BottomBarConfig, ContentTitleCardConfig, DrawerConfig, FloatingButtonConfig, FooterConfig, HeaderConfig, ScaffoldLibraryConfig, NavbarConfig, ScaffoldConfig } from '../../models';
8
+ import { CONFIG } from '../../scaffold.module';
9
+ import { BreakpointService, Logger, RouterService, ScaffoldService } from '../../services';
10
+
11
+ @Component({
12
+ selector: 'lf-scaffold',
13
+ templateUrl: './scaffold.component.html',
14
+ styleUrls: ['./scaffold.component.scss'],
15
+ standalone: false
16
+ })
17
+ export class ScaffoldComponent implements OnInit, OnDestroy {
18
+ private scaffoldService = inject(ScaffoldService);
19
+ private breakpointService = inject(BreakpointService);
20
+ private routerService = inject(RouterService);
21
+ private logger = inject(Logger);
22
+ private route = inject(ActivatedRoute);
23
+ private document = inject<Document>(DOCUMENT);
24
+ private config = inject<ScaffoldLibraryConfig>(CONFIG, { optional: true });
25
+
26
+ @ViewChild('scrollContainer', { static: true }) public scrollContainer: ElementRef;
27
+ @ViewChild('content', { static: true }) public content: ElementRef;
28
+
29
+ @Output() public headerButtonClickEvent = new EventEmitter<string>();
30
+ @Output() public headerInputSubmitEvent = new EventEmitter<string>();
31
+ @Output() public headerInputChangeEvent = new EventEmitter<string>();
32
+ @Output() public navbarButtonClickEvent = new EventEmitter<string>();
33
+ @Output() public floatingButtonClickEvent = new EventEmitter<string>();
34
+ @Output() public bottomBarButtonClickEvent = new EventEmitter<string>();
35
+
36
+ public scaffoldConfig: ScaffoldConfig | null = null;
37
+ public headerConfig: HeaderConfig | null = null;
38
+ public navbarConfig: NavbarConfig | null = null;
39
+ public drawerConfig: DrawerConfig | null = null;
40
+ public drawerPortal: ComponentPortal<unknown> | TemplatePortal<unknown> | null;
41
+ public footerConfig: FooterConfig | null = null;
42
+ public contentTitleCardConfig: ContentTitleCardConfig | null = null;
43
+ public floatingButtonConfig: FloatingButtonConfig | null = null;
44
+ public bottomBarConfig: BottomBarConfig | null = null;
45
+
46
+ public routeHistory: string[] = [];
47
+ public currentRoute: string;
48
+ public isMobile: boolean = false;
49
+ public routeLoading: boolean = false;
50
+ public scrollTopPosition: number = 0;
51
+
52
+ private _subscription: Subscription = new Subscription;
53
+
54
+ ngOnInit(): void {
55
+ // Listen for config changes
56
+ this._subscription.add(this.scaffoldService.scaffoldConfig$.subscribe((scaffoldConfig: ScaffoldConfig) => {
57
+ if (this.config?.debugging) this.logger.log('[ScaffoldConfig]', scaffoldConfig);
58
+
59
+ this.scaffoldConfig = scaffoldConfig;
60
+ this.headerConfig = this.scaffoldConfig.headerConfig!;
61
+ this.navbarConfig = this.scaffoldConfig.navbarConfig!;
62
+ this.drawerConfig = this.scaffoldConfig.drawerConfig!;
63
+ this.footerConfig = this.scaffoldConfig.footerConfig!;
64
+ this.contentTitleCardConfig = this.scaffoldConfig.contentTitleCardConfig!;
65
+ this.floatingButtonConfig = this.scaffoldConfig.floatingButtonConfig!;
66
+ this.bottomBarConfig = this.scaffoldConfig.bottomBarConfig!;
67
+ }));
68
+
69
+ // Listen for drawer portal changes
70
+ this._subscription.add(this.scaffoldService.drawerPortal$.subscribe((drawerPortal: ComponentPortal<unknown> | TemplatePortal<unknown> | null) => {
71
+ if (this.config?.debugging) this.logger.log('[DrawerPortal]', drawerPortal);
72
+
73
+ this.drawerPortal = drawerPortal;
74
+ }));
75
+
76
+ // Listen for breakpoint changes
77
+ this._subscription.add(this.breakpointService.breakpoint$.subscribe((breakpointState: BreakpointState) => {
78
+ if (this.config?.debugging) this.logger.log('[BreakpointState]', breakpointState);
79
+
80
+ if (breakpointState.breakpoints[Breakpoints.XSmall]) {
81
+ this.isMobile = true;
82
+ } else if (breakpointState.breakpoints[Breakpoints.Small]) {
83
+ this.isMobile = true;
84
+ } else if (breakpointState.breakpoints[Breakpoints.Medium]) {
85
+ this.isMobile = false;
86
+ } else if (breakpointState.breakpoints[Breakpoints.Large]) {
87
+ this.isMobile = false;
88
+ }
89
+ }));
90
+
91
+ // Listen for route changes
92
+ this._subscription.add(this.routerService.routeHistory$.subscribe((routeHistory: string[]) => {
93
+ if (this.config?.debugging) this.logger.log('[RouteHistory]', routeHistory);
94
+
95
+ if (routeHistory) {
96
+ this.routeHistory = routeHistory;
97
+ }
98
+
99
+ if (this.scrollContainer && this.scaffoldConfig?.scrollPositionRestoration) {
100
+ this.scrollContainer.nativeElement.scrollTop = 0;
101
+ }
102
+ }));
103
+
104
+ // Listen for current route changes
105
+ this._subscription.add(this.routerService.currentRoute$.subscribe((currentRout: string) => {
106
+ this.currentRoute = currentRout
107
+ }));
108
+
109
+ // Listen for route loading
110
+ this._subscription.add(this.routerService.loading$.subscribe((routeLoading: boolean) => {
111
+ this.routeLoading = routeLoading
112
+ }));
113
+
114
+ // Listen to scroll events
115
+ if (this.scrollContainer) {
116
+ const element: HTMLElement = this.scrollContainer.nativeElement;
117
+
118
+ this._subscription.add(fromEvent(element, 'scroll').pipe(
119
+ distinctUntilChanged(),
120
+ debounceTime(100)
121
+ ).subscribe((e: Event) => {
122
+ const target: HTMLElement = e.target as HTMLElement;
123
+ this.scrollTopPosition = target.scrollTop;
124
+ }));
125
+ }
126
+
127
+ // Listen for fragments in the current route
128
+ if (this.scaffoldConfig?.anchorScrolling) {
129
+ this._subscription.add(this.route.fragment.subscribe((fragment: string | null) => {
130
+ if (fragment) {
131
+ if (this.config?.debugging) this.logger.log('[RouteFragment]', fragment);
132
+ setTimeout(() => {
133
+ const element = this.document.querySelector(`#${fragment}`);
134
+ if (element) {
135
+ element.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'nearest' });
136
+ }
137
+ }, 100);
138
+ }
139
+ }));
140
+ }
141
+ }
142
+
143
+ ngOnDestroy(): void {
144
+ if (this._subscription) {
145
+ this._subscription.unsubscribe();
146
+ }
147
+ }
148
+
149
+ public headerButtonClicked(id: string): void {
150
+ this.scaffoldService.buttonClickEventValue = id;
151
+ this.headerButtonClickEvent.emit(id);
152
+ }
153
+
154
+ public headerInputSubmitted(value: string): void {
155
+ this.headerInputSubmitEvent.emit(value);
156
+ }
157
+
158
+ public headerInputChanged(value: string): void {
159
+ this.scaffoldService.headerInputChangeValue = value;
160
+ this.headerInputChangeEvent.emit(value);
161
+ }
162
+
163
+ public navbarButtonClicked(id: string): void {
164
+ this.scaffoldService.buttonClickEventValue = id;
165
+ this.navbarButtonClickEvent.emit(id);
166
+ }
167
+
168
+ public backButtonClicked(): void {
169
+ this.routerService.navigateBack();
170
+ }
171
+
172
+ public floatingButtonClicked(id: string): void {
173
+ if (!id && this.scrollContainer) {
174
+ this.scrollContainer.nativeElement.scrollTop = 0;
175
+ } else {
176
+ this.scaffoldService.buttonClickEventValue = id;
177
+ this.floatingButtonClickEvent.emit(id);
178
+ }
179
+ }
180
+
181
+ public bottomBarCloseClicked(id: string): void {
182
+ this.scaffoldService.buttonClickEventValue = id;
183
+ this.bottomBarButtonClickEvent.emit(id);
184
+ }
185
+
186
+ public bottomBarButtonClicked(id: string): void {
187
+ this.scaffoldService.buttonClickEventValue = id;
188
+ this.bottomBarButtonClickEvent.emit(id);
189
+ }
190
+
191
+ }
@@ -0,0 +1,51 @@
1
+ import {
2
+ HttpEvent,
3
+ HttpHandler,
4
+ HttpInterceptor,
5
+ HttpRequest
6
+ } from '@angular/common/http';
7
+ import { Injectable, inject } from '@angular/core';
8
+ import { Observable, finalize } from 'rxjs';
9
+ import { ScaffoldService } from '../services';
10
+
11
+ @Injectable()
12
+ export class LoadingInterceptor implements HttpInterceptor {
13
+ private scaffoldService = inject(ScaffoldService);
14
+
15
+ private activeRequests = 0;
16
+ private loadingDelay = 100; // milliseconds
17
+ private spinnerTimeout: any;
18
+
19
+ public intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
20
+ this.activeRequests++;
21
+
22
+ // Set a delayed spinner timeout
23
+ if (this.activeRequests === 1) {
24
+ this.spinnerTimeout = setTimeout(() => {
25
+ this.scaffoldService.updateScaffoldProperty('loading', true);
26
+ }, this.loadingDelay);
27
+ }
28
+
29
+ return next.handle(req).pipe(
30
+ // tap({
31
+ // next: (event) => {
32
+ // if (event instanceof HttpResponse) {
33
+ // // Successful response
34
+ // }
35
+ // },
36
+ // error: (error: HttpErrorResponse) => {
37
+ // // Handle error if needed
38
+ // }
39
+ // }),
40
+ finalize(() => {
41
+ this.activeRequests--;
42
+
43
+ if (this.activeRequests === 0) {
44
+ // Clear any pending spinner timeout
45
+ clearTimeout(this.spinnerTimeout);
46
+ this.scaffoldService.updateScaffoldProperty('loading', false);
47
+ }
48
+ })
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,8 @@
1
+ import { MenuButton } from './menu-button.model';
2
+
3
+ export interface BottomBarConfig {
4
+ enable?: boolean;
5
+ class?: string;
6
+ message?: string;
7
+ actions?: MenuButton[];
8
+ }
@@ -0,0 +1,6 @@
1
+ export interface ConfirmDialogConfig {
2
+ title?: string;
3
+ message?: string;
4
+ confirmLabel?: string;
5
+ closeLabel?: string;
6
+ }
@@ -0,0 +1,6 @@
1
+ export interface ContentTitleCardConfig {
2
+ enable?: boolean;
3
+ label?: string;
4
+ showBackButton?: boolean;
5
+ class?: string;
6
+ }
@@ -0,0 +1,6 @@
1
+ export interface DrawerConfig {
2
+ enable?: boolean;
3
+ open?: boolean;
4
+ fixed?: boolean;
5
+ class?: string;
6
+ }
@@ -0,0 +1,13 @@
1
+ export interface FloatingButtonConfig {
2
+ enable?: boolean;
3
+ id?: string;
4
+ label?: string;
5
+ matIcon?: string;
6
+ svgIcon?: string;
7
+ outlineIcon?: boolean;
8
+ tooltip?: string;
9
+ horizontalPosition?: 'left' | 'center' | 'right';
10
+ bottomPositionPx?: number;
11
+ autoHide?: boolean;
12
+ class?: string;
13
+ }
@@ -0,0 +1,10 @@
1
+ import { NavigationLink } from './navigation-link.model';
2
+
3
+ export interface FooterConfig {
4
+ enable?: boolean;
5
+ svgLogo?: string;
6
+ imgLogo?: string;
7
+ copyright?: string;
8
+ links?: NavigationLink[];
9
+ class?: string;
10
+ }
@@ -0,0 +1,26 @@
1
+ import { MenuButton } from './menu-button.model';
2
+
3
+ export interface HeaderConfig {
4
+ enable?: boolean;
5
+ svgLogo?: string;
6
+ imgLogo?: string;
7
+ title?: string;
8
+ subtitle?: string;
9
+ titleRouterLink?: string;
10
+ loading?: boolean;
11
+ showRouteLoading?: boolean;
12
+ leftMenuButton?: MenuButton;
13
+ rightMenuButtons?: MenuButton[];
14
+ inputConfig?: HeaderInputConfig;
15
+ class?: string;
16
+ }
17
+
18
+ export interface HeaderInputConfig {
19
+ enable?: boolean;
20
+ label?: string;
21
+ matIconSubmit?: string;
22
+ // matIconPrefix?: string;
23
+ hint?: string;
24
+ disabled?: boolean;
25
+ autoFocus?: boolean;
26
+ }
@@ -0,0 +1,15 @@
1
+ export { BottomBarConfig } from './bottom-bar-config.model';
2
+ export { ConfirmDialogConfig } from './confirm-dialog-config.model';
3
+ export { ContentTitleCardConfig } from './content-title-card-config.model';
4
+ export { DrawerConfig } from './drawer-config.model';
5
+ export { FloatingButtonConfig } from './floating-button-config.model';
6
+ export { FooterConfig } from './footer-config.model';
7
+ export { HeaderConfig, HeaderInputConfig } from './header-config.model';
8
+ export { ScaffoldLibraryConfig } from './library-config.model';
9
+ export { MenuButton } from './menu-button.model';
10
+ export { NavbarConfig } from './navbar-config.model';
11
+ export { NavigationLink } from './navigation-link.model';
12
+ export { PlaceholderConfig } from './placeholder-config.model';
13
+ export { ScaffoldConfig } from './scaffold-config.model';
14
+ export { SeoConfig } from './seo-config.model';
15
+
@@ -0,0 +1,4 @@
1
+ export interface ScaffoldLibraryConfig {
2
+ production?: boolean;
3
+ debugging?: boolean;
4
+ }
@@ -0,0 +1,10 @@
1
+ export interface MenuButton {
2
+ id: string;
3
+ label?: string;
4
+ matIcon?: string;
5
+ svgIcon?: string;
6
+ outlineIcon?: boolean;
7
+ tooltip?: string;
8
+ class?: string;
9
+ menuButtons?: MenuButton[];
10
+ }
@@ -0,0 +1,8 @@
1
+ import { MenuButton } from './menu-button.model';
2
+
3
+ export interface NavbarConfig {
4
+ enable?: boolean;
5
+ showAllLabels?: boolean;
6
+ menuButtons?: MenuButton[];
7
+ class?: string;
8
+ }
@@ -0,0 +1,6 @@
1
+ export interface NavigationLink {
2
+ label?: string;
3
+ routerLink?: string;
4
+ href?: string;
5
+ externalTab?: boolean;
6
+ }
@@ -0,0 +1,7 @@
1
+ export interface PlaceholderConfig {
2
+ matIcon?: string;
3
+ svgIcon?: string;
4
+ outlineIcon?: boolean;
5
+ heading?: string;
6
+ message?: string;
7
+ }