ADDED Materialize theme

This commit is contained in:
Marek Lesko
2025-10-13 18:18:04 +02:00
parent 9451589a9d
commit 4dda50cbf4
1669 changed files with 146258 additions and 1 deletions

View File

@@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@@ -0,0 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'Angular20'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('Angular20');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('Angular20 app is running!');
});
});

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'Modernize Angular Admin Template';
}

View File

@@ -0,0 +1,66 @@
import {
ApplicationConfig,
provideZoneChangeDetection,
importProvidersFrom,
} from '@angular/core';
import {
HttpClient,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { routes } from './app.routes';
import {
provideRouter,
withComponentInputBinding,
withInMemoryScrolling,
} from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideClientHydration } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// icons
import { TablerIconsModule } from 'angular-tabler-icons';
import * as TablerIcons from 'angular-tabler-icons/icons';
// perfect scrollbar
import { NgScrollbarModule } from 'ngx-scrollbar';
//Import all material modules
import { MaterialModule } from './material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
export function HttpLoaderFactory(http: HttpClient): any {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(
routes,
withInMemoryScrolling({
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
}),
withComponentInputBinding()
),
provideHttpClient(withInterceptorsFromDi()),
provideClientHydration(),
provideAnimationsAsync(),
importProvidersFrom(
FormsModule,
ReactiveFormsModule,
MaterialModule,
TablerIconsModule.pick(TablerIcons),
NgScrollbarModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
})
),
],
};

View File

@@ -0,0 +1,44 @@
import { Routes } from '@angular/router';
import { BlankComponent } from './layouts/blank/blank.component';
import { FullComponent } from './layouts/full/full.component';
export const routes: Routes = [
{
path: '',
component: FullComponent,
children: [
{
path: '',
redirectTo: '/starter',
pathMatch: 'full',
},
{
path: 'starter',
loadChildren: () =>
import('./pages/pages.routes').then((m) => m.PagesRoutes),
},
{
path: 'sample-page',
loadChildren: () =>
import('./pages/pages.routes').then((m) => m.PagesRoutes),
},
],
},
{
path: '',
component: BlankComponent,
children: [
{
path: 'authentication',
loadChildren: () =>
import('./pages/authentication/authentication.routes').then(
(m) => m.AuthenticationRoutes
),
},
],
},
{
path: '**',
redirectTo: 'authentication/error',
},
];

View File

@@ -0,0 +1,25 @@
export interface AppSettings {
dir: 'ltr' | 'rtl';
theme: string;
sidenavOpened: boolean;
sidenavCollapsed: boolean;
boxed: boolean;
horizontal: boolean;
activeTheme: string;
language: string;
cardBorder: boolean;
navPos: 'side' | 'top';
}
export const defaults: AppSettings = {
dir: 'ltr',
theme: 'light',
sidenavOpened: false,
sidenavCollapsed: false,
boxed: true,
horizontal: false,
cardBorder: false,
activeTheme: 'blue_theme',
language: 'en-us',
navPos: 'side',
};

View File

@@ -0,0 +1,11 @@
<!-- ============================================================== -->
<!-- Only router without any element -->
<!-- ============================================================== -->
<mat-sidenav-container
[ngClass]="{
cardBorder: options.cardBorder,
}"
[dir]="options.dir!"
>
<router-outlet></router-outlet>
</mat-sidenav-container>

View File

@@ -0,0 +1,51 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { AppSettings } from 'src/app/config';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-blank',
templateUrl: './blank.component.html',
styleUrls: [],
imports: [RouterOutlet, MaterialModule, CommonModule],
})
export class BlankComponent {
private htmlElement!: HTMLHtmlElement;
options = this.settings.getOptions();
constructor(private settings: CoreService) {
this.htmlElement = document.querySelector('html')!;
// Initialize project theme with options
this.receiveOptions(this.options);
}
receiveOptions(options: AppSettings): void {
this.toggleDarkTheme(options);
this.toggleColorsTheme(options);
}
toggleDarkTheme(options: AppSettings) {
if (options.theme === 'dark') {
this.htmlElement.classList.add('dark-theme');
this.htmlElement.classList.remove('light-theme');
} else {
this.htmlElement.classList.remove('dark-theme');
this.htmlElement.classList.add('light-theme');
}
}
toggleColorsTheme(options: AppSettings) {
// Remove any existing theme class dynamically
this.htmlElement.classList.forEach((className) => {
if (className.endsWith('_theme')) {
this.htmlElement.classList.remove(className);
}
});
// Add the selected theme class
this.htmlElement.classList.add(options.activeTheme);
}
}

View File

@@ -0,0 +1,190 @@
<mat-sidenav-container class="mainWrapper" autosize autoFocus [ngClass]="{
'sidebarNav-mini': options.sidenavCollapsed && options.navPos !== 'top' && !resView,
'sidebarNav-horizontal': options.horizontal,
cardBorder: options.cardBorder,
}" [dir]="options.dir!">
<!-- ============================================================== -->
<!-- Vertical Sidebar -->
<!-- ============================================================== -->
@if (!options.horizontal) {
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="
options.navPos === 'side' && options.sidenavOpened && !isOver && !resView
" (openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
<div class="flex-layout">
<app-sidebar (toggleMobileNav)="sidenav.toggle()" [showToggle]="isOver"></app-sidebar>
<ng-scrollbar class="position-relative" style="height: 100%">
<mat-nav-list class="sidebar-list">
@for(item of navItems; track item) {
<app-nav-item [item]="item" (notify)="sidenav.toggle()" class="top-parent">
</app-nav-item>
}
</mat-nav-list>
</ng-scrollbar>
<div class="p-24 m-t-auto profile-bar">
<div class="bg-light-secondary d-flex align-items-center rounded p-16">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle" width="40" />
<div class="m-l-16">
<h4 class="f-w-600">Mathew</h4>
<span class="f-s-12">Designer</span>
</div>
<div class="m-l-auto">
<a mat-icon-button [routerLink]="['/authentication/login']" class="d-flex justify-content-center">
<i-tabler name="power" class="text-primary icon-18 d-flex"></i-tabler>
</a>
</div>
</div>
</div>
</div>
</mat-sidenav>
}
<!-- ============================================================== -->
<!-- horizontal Sidebar -->
<!-- ============================================================== -->
@if (resView) {
<mat-sidenav #leftsidenav [mode]="'over'" [opened]="options.sidenavOpened && !isTablet"
(openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
<app-sidebar> </app-sidebar>
<ng-scrollbar class="position-relative" style="height: 100%">
<mat-nav-list class="sidebar-list">
@for(item of navItems; track item) {
<app-nav-item [item]="item" (notify)="sidenav.toggle()"> </app-nav-item>
}
</mat-nav-list>
</ng-scrollbar>
<div class="p-24 m-t-auto profile-bar">
<div class="bg-light-secondary d-flex align-items-center rounded p-16">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle" width="40" />
<div class="m-l-16">
<h4 class="f-w-600">Mathew</h4>
<span class="f-s-12">Designer</span>
</div>
<div class="m-l-auto">
<a mat-icon-button [routerLink]="['/authentication/login']" class="d-flex justify-content-center">
<i-tabler name="power" class="text-primary icon-18 d-flex"></i-tabler>
</a>
</div>
</div>
</div>
</mat-sidenav>
}
<!-- ============================================================== -->
<!-- Main Content -->
<!-- ============================================================== -->
<mat-sidenav-content class="contentWrapper" #content>
<!-- ============================================================== -->
<!-- VerticalHeader -->
<!-- ============================================================== -->
@if (!options.horizontal) {
<app-header [showToggle]="!isOver" (toggleCollapsed)="toggleCollapsed()" (toggleMobileNav)="sidenav.toggle()"
(toggleMobileFilterNav)="toggleFilterNav()" (optionsChange)="receiveOptions($event)"></app-header>
} @else {
<!-- horizontal header -->
<app-horizontal-header (toggleMobileNav)="sidenav.toggle()" (toggleMobileFilterNav)="toggleFilterNav()"
(optionsChange)="receiveOptions($event)"></app-horizontal-header>
} @if(options.horizontal) {
<app-horizontal-sidebar></app-horizontal-sidebar>
}
<main class="pageWrapper" [ngClass]="{
maxWidth: options.boxed
}">
<app-breadcrumb></app-breadcrumb>
<!-- ============================================================== -->
<!-- Outlet -->
<!-- ============================================================== -->
<router-outlet></router-outlet>
<div class="customizerBtn">
<button mat-fab class="bg-primary text-white" (click)="customizerRight.toggle()">
<mat-icon>settings</mat-icon>
</button>
</div>
</main>
<!-- ------------------------------------------------------------------
Mobile Apps Sidebar
------------------------------------------------------------------ -->
<div [class.open]="isFilterNavOpen" class="filter-sidebar">
<div>
<div class="d-flex justify-content-between align-items-center">
<div class="branding">
@if(options.theme === 'light') {
<a href="/">
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
</a>
} @else {
<a href="/">
<img src="./assets/images/logos/light-logo.svg" class="align-middle m-2" alt="logo" />
</a>
}
</div>
<button mat-icon-button (click)="isFilterNavOpen = !isFilterNavOpen" class="d-flex justify-content-center">
<i-tabler name="x" class="icon-18 d-flex"></i-tabler>
</button>
</div>
<mat-accordion>
<mat-expansion-panel class="shadow-none">
<mat-expansion-panel-header>
<mat-panel-title class="f-s-16 f-w-500"> Apps </mat-panel-title>
</mat-expansion-panel-header>
<div>
<div class="row">
@for(appdd of apps; track appdd.img) {
<div class="col-12 text-hover-primary">
<a [routerLink]="[appdd.link]" class="d-flex align-items-center text-decoration-none m-b-24 gap-16">
<button mat-mini-fab class="text-primary bg-light-primary shadow-none rounded">
<img [src]="appdd.img" width="20" />
</button>
<div>
<h5 class="f-s-14 f-w-600 m-0 textprimary f-s-14 hover-text">
{{ appdd.title }}
</h5>
<span class="f-s-14 f-s-12">{{ appdd.subtitle }}</span>
</div>
</a>
</div>
}
</div>
<h4 class="f-s-18 f-w-600 m-b-16">Quick Links</h4>
@for(quicklink of quicklinks; track quicklink.title) {
<div class="text-hover-primary">
<a [routerLink]="['quicklink.link']"
class="hover-text text-decoration-none f-s-14 f-w-600 d-block p-y-8">{{ quicklink.title }}</a>
</div>
}
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="p-x-16">
<a [routerLink]="['/']" class="d-flex align-items-center text-decoration-none f-s-14 p-y-16 gap-8">
<span class="f-s-15 f-w-500 m-l-8">Calendar</span>
</a>
<a [routerLink]="['/']" class="d-flex align-items-center text-decoration-none f-s-14 p-y-16">
<span class="f-s-15 f-w-500 m-l-8">Chat</span>
</a>
<a [routerLink]="['/']" class="d-flex align-items-center text-decoration-none f-s-14 p-y-16 gap-8">
<span class="f-s-15 f-w-500 m-l-8">Email</span>
</a>
</div>
</div>
</div>
</mat-sidenav-content>
<mat-sidenav #customizerRight mode="over" position="end" class="customizerNav">
<div class="p-x-16 p-y-20 d-flex align-items-center justify-content-between b-b-1">
<h3 class="f-s-21 f-w-600">Settings</h3>
<a mat-icon-button (click)="customizerRight.toggle()" class="d-flex justify-content-center">
<mat-icon>close</mat-icon>
</a>
</div>
<app-customizer (optionsChange)="receiveOptions($event)"></app-customizer>
</mat-sidenav>
</mat-sidenav-container>

View File

@@ -0,0 +1,283 @@
import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout';
import { Component, OnInit, ViewChild, ViewEncapsulation, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
import { CoreService } from 'src/app/services/core.service';
import { AppSettings } from 'src/app/config';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '@angular/router';
import { navItems } from './vertical/sidebar/sidebar-data';
import { NavService } from '../../services/nav.service';
import { AppNavItemComponent } from './vertical/sidebar/nav-item/nav-item.component';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
import { SidebarComponent } from './vertical/sidebar/sidebar.component';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { TablerIconsModule } from 'angular-tabler-icons';
import { HeaderComponent } from './vertical/header/header.component';
import { AppHorizontalHeaderComponent } from './horizontal/header/header.component';
import { AppHorizontalSidebarComponent } from './horizontal/sidebar/sidebar.component';
import { AppBreadcrumbComponent } from './shared/breadcrumb/breadcrumb.component';
import { CustomizerComponent } from './shared/customizer/customizer.component';
const MOBILE_VIEW = 'screen and (max-width: 768px)';
const TABLET_VIEW = 'screen and (min-width: 769px) and (max-width: 1024px)';
const MONITOR_VIEW = 'screen and (min-width: 1024px)';
const BELOWMONITOR = 'screen and (max-width: 1023px)';
// for mobile app sidebar
interface apps {
id: number;
img: string;
title: string;
subtitle: string;
link: string;
}
interface quicklinks {
id: number;
title: string;
link: string;
}
@Component({
selector: 'app-full',
imports: [
RouterModule,
AppNavItemComponent,
MaterialModule,
CommonModule,
SidebarComponent,
NgScrollbarModule,
TablerIconsModule,
HeaderComponent,
AppHorizontalHeaderComponent,
AppHorizontalSidebarComponent,
AppBreadcrumbComponent,
CustomizerComponent,
],
templateUrl: './full.component.html',
styleUrls: [],
encapsulation: ViewEncapsulation.None
})
export class FullComponent implements OnInit {
navItems = navItems;
@ViewChild('leftsidenav')
public sidenav: MatSidenav;
resView = false;
@ViewChild('content', { static: true }) content!: MatSidenavContent;
//get options from service
options = this.settings.getOptions();
private layoutChangesSubscription = Subscription.EMPTY;
private isMobileScreen = false;
private isContentWidthFixed = true;
private isCollapsedWidthFixed = false;
private htmlElement!: HTMLHtmlElement;
get isOver(): boolean {
return this.isMobileScreen;
}
get isTablet(): boolean {
return this.resView;
}
// for mobile app sidebar
apps: apps[] = [
{
id: 1,
img: '/assets/images/svgs/icon-dd-chat.svg',
title: 'Chat Application',
subtitle: 'Messages & Emails',
link: '/apps/chat',
},
{
id: 2,
img: '/assets/images/svgs/icon-dd-cart.svg',
title: 'eCommerce App',
subtitle: 'Buy a Product',
link: '/apps/email/inbox',
},
{
id: 3,
img: '/assets/images/svgs/icon-dd-invoice.svg',
title: 'Invoice App',
subtitle: 'Get latest invoice',
link: '/apps/invoice',
},
{
id: 4,
img: '/assets/images/svgs/icon-dd-date.svg',
title: 'Calendar App',
subtitle: 'Get Dates',
link: '/apps/calendar',
},
{
id: 5,
img: '/assets/images/svgs/icon-dd-mobile.svg',
title: 'Contact Application',
subtitle: '2 Unsaved Contacts',
link: '/apps/contacts',
},
{
id: 6,
img: '/assets/images/svgs/icon-dd-lifebuoy.svg',
title: 'Tickets App',
subtitle: 'Create new ticket',
link: '/apps/tickets',
},
{
id: 7,
img: '/assets/images/svgs/icon-dd-message-box.svg',
title: 'Email App',
subtitle: 'Get new emails',
link: '/apps/email/inbox',
},
{
id: 8,
img: '/assets/images/svgs/icon-dd-application.svg',
title: 'Courses',
subtitle: 'Create new course',
link: '/apps/courses',
},
];
quicklinks: quicklinks[] = [
{
id: 1,
title: 'Pricing Page',
link: '/theme-pages/pricing',
},
{
id: 2,
title: 'Authentication Design',
link: '/authentication/login',
},
{
id: 3,
title: 'Register Now',
link: '/authentication/side-register',
},
{
id: 4,
title: '404 Error Page',
link: '/authentication/error',
},
{
id: 5,
title: 'Notes App',
link: '/apps/notes',
},
{
id: 6,
title: 'Employee App',
link: '/apps/employee',
},
{
id: 7,
title: 'Todo Application',
link: '/apps/todo',
},
{
id: 8,
title: 'Treeview',
link: '/theme-pages/treeview',
},
];
constructor(
private settings: CoreService,
private mediaMatcher: MediaMatcher,
private router: Router,
private breakpointObserver: BreakpointObserver,
private navService: NavService, private cdr: ChangeDetectorRef
) {
this.htmlElement = document.querySelector('html')!;
this.layoutChangesSubscription = this.breakpointObserver
.observe([MOBILE_VIEW, TABLET_VIEW, MONITOR_VIEW, BELOWMONITOR])
.subscribe((state) => {
// SidenavOpened must be reset true when layout changes
this.options.sidenavOpened = true;
this.isMobileScreen = state.breakpoints[MOBILE_VIEW];
if (this.options.sidenavCollapsed == false) {
this.options.sidenavCollapsed = state.breakpoints[TABLET_VIEW];
}
this.isContentWidthFixed = state.breakpoints[MONITOR_VIEW];
this.resView = state.breakpoints[BELOWMONITOR];
});
// Initialize project theme with options
this.receiveOptions(this.options);
// This is for scroll to top
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((e) => {
this.content.scrollTo({ top: 0 });
});
}
isFilterNavOpen = false;
toggleFilterNav() {
this.isFilterNavOpen = !this.isFilterNavOpen;
console.log('Sidebar open:', this.isFilterNavOpen);
this.cdr.detectChanges(); // Ensures Angular updates the view
}
ngOnInit(): void {}
ngOnDestroy() {
this.layoutChangesSubscription.unsubscribe();
}
toggleCollapsed() {
this.isContentWidthFixed = false;
this.options.sidenavCollapsed = !this.options.sidenavCollapsed;
this.resetCollapsedState();
}
resetCollapsedState(timer = 400) {
setTimeout(() => this.settings.setOptions(this.options), timer);
}
onSidenavClosedStart() {
this.isContentWidthFixed = false;
}
onSidenavOpenedChange(isOpened: boolean) {
this.isCollapsedWidthFixed = !this.isOver;
this.options.sidenavOpened = isOpened;
this.settings.setOptions(this.options);
}
receiveOptions(options: AppSettings): void {
this.toggleDarkTheme(options);
this.toggleColorsTheme(options);
}
toggleDarkTheme(options: AppSettings) {
if (options.theme === 'dark') {
this.htmlElement.classList.add('dark-theme');
this.htmlElement.classList.remove('light-theme');
} else {
this.htmlElement.classList.remove('dark-theme');
this.htmlElement.classList.add('light-theme');
}
}
toggleColorsTheme(options: AppSettings) {
// Remove any existing theme class dynamically
this.htmlElement.classList.forEach((className) => {
if (className.endsWith('_theme')) {
this.htmlElement.classList.remove(className);
}
});
// Add the selected theme class
this.htmlElement.classList.add(options.activeTheme);
}
}

View File

@@ -0,0 +1,296 @@
<mat-toolbar class="topbar horizontal-topbar ">
<div class="container">
<div class="d-none d-sm-flex">
<app-branding></app-branding>
</div>
<!-- Mobile Menu -->
<button
mat-icon-button
(click)="toggleMobileNav.emit()"
class="d-flex align-items-center justify-content-center d-lg-none m-x-4"
>
<i-tabler name="menu-2" class="icon-20 d-flex"></i-tabler>
</button>
<!-- --------------------------------------------------------------- -->
<!-- --------------------------------------------------------------- -->
<!-- Search -->
<!-- --------------------------------------------------------------- -->
<button mat-icon-button (click)="openDialog()" class="d-flex align-items-center justify-content-center m-x-4">
<i-tabler name="search" class="icon-20 d-flex"></i-tabler>
</button>
<div class="d-none d-lg-flex">
<!-- --------------------------------------------------------------- -->
<!-- Links -->
<!-- --------------------------------------------------------------- -->
<button
mat-button
[matMenuTriggerFor]="appsmenu"
aria-label="Notifications"
>
<div class="d-flex align-items-center">
Apps <i-tabler name="chevron-down" class="icon-16 m-l-4"></i-tabler>
</div>
</button>
<mat-menu #appsmenu="matMenu" class="apps-dd cardWithShadow">
<div class="row">
<div class="col-sm-8 b-r-1 p-r-0">
<div class="p-32 p-b-0">
<div class="row">
@for(appdd of apps; track appdd.title) {
<div class="col-sm-6 text-hover-primary">
<a
[routerLink]="[appdd.link]"
class="d-flex align-items-center text-decoration-none m-b-24"
>
<span
class="text-primary bg-light rounded icon-40 d-flex align-items-center justify-content-center"
>
<img [src]="appdd.img" width="20" />
</span>
<div class="m-l-16">
<h5
class="f-s-14 f-w-600 m-0 textprimary hover-text"
>
{{ appdd.title }}
</h5>
<span class=" f-s-12">{{
appdd.subtitle
}}</span>
</div>
</a>
</div>
}
</div>
</div>
<div
class="b-t-1 p-24 d-none d-lg-flex align-items-center justify-content-between"
>
<span
class="d-flex align-items-center f-s-16 f-w-500"
>
<i-tabler name="help" class="icon-20 m-r-8"></i-tabler
>Frequently Asked Questions
</span>
<a
[routerLink]="['/theme-pages/faq']"
mat-flat-button
color="primary"
>Check</a
>
</div>
</div>
<div class="col-sm-4">
<div class="p-x-16 p-y-32">
<h4 class="f-s-18 f-w-600 m-b-16">Quick Links</h4>
@for(quicklink of quicklinks; track quicklink.title) {
<div class="text-hover-primary">
<a
[routerLink]="[quicklink.link]"
class="hover-text text-decoration-none f-w-600 d-block p-y-8"
>{{ quicklink.title }}</a
>
</div>
}
</div>
</div>
</div>
</mat-menu>
<a mat-button [routerLink]="['/']">Chat</a>
<a mat-button [routerLink]="['/']">Calendar</a>
<a mat-button [routerLink]="['/']">Email</a>
</div>
<span class="flex-1-auto"></span>
<!-- Mobile Menu -->
<button
mat-icon-button
(click)="toggleMobileFilterNav.emit()"
class="d-flex d-lg-none justify-content-center m-x-4"
>
<i-tabler name="grid-dots" class="icon-20 d-flex"></i-tabler>
</button>
<!-- --------------------------------------------------------------- -->
<!-- langugage Dropdown -->
<!-- --------------------------------------------------------------- -->
<button [matMenuTriggerFor]="flags" mat-icon-button class="m-x-4">
<img
[src]="selectedLanguage.icon"
class="rounded-circle object-cover icon-20"
/>
</button>
<mat-menu #flags="matMenu" class="cardWithShadow">
@for(lang of languages; track lang.icon) {
<button mat-menu-item (click)="changeLanguage(lang)">
<div class="d-flex align-items-center">
<img
[src]="lang.icon"
class="rounded-circle object-cover m-r-8 icon-20"
/>
<span class=" f-s-14">{{ lang.language }}</span>
</div>
</button>
}
</mat-menu>
@if(options.theme=='light'){
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('dark')" >
<i-tabler class="d-flex icon-22" [name]="'moon'"></i-tabler>
</button>
}@else{
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('light')">
<i-tabler class="d-flex icon-22" [name]="'sun'"></i-tabler>
</button>
}
<!-- --------------------------------------------------------------- -->
<!-- Notification Dropdown -->
<!-- --------------------------------------------------------------- -->
<button
mat-icon-button matBadge="5"
[matMenuTriggerFor]="notificationmenu"
aria-label="Notifications" class="m-x-4 notification-badge"
>
<i-tabler
class="d-flex"
name="bell"
></i-tabler>
</button>
<mat-menu
#notificationmenu="matMenu"
xPosition="before"
class="topbar-dd cardWithShadow"
>
<div class="d-flex align-items-center p-x-32 p-y-16">
<h6 class="f-s-16 f-w-600 m-0 ">Notifications</h6>
<span class="m-l-auto">
<span class="bg-primary text-white p-x-8 p-y-4 f-w-500 rounded f-s-12"
>5 new</span
>
</span>
</div>
@for(notification of notifications; track notification.title) {
<button mat-menu-item class="p-x-32 p-y-16">
<div class="d-flex align-items-center">
<img [src]="notification.img" class="rounded-circle" width="48" />
<div class="m-l-16">
<h5 class="f-s-14 f-w-600 m-0 ">
{{ notification.title }}
</h5>
<span>{{ notification.subtitle }}</span>
</div>
</div>
</button>
}
<div class="p-y-12 p-x-32">
<button mat-stroked-button color="primary" class="w-100">
See all notifications
</button>
</div>
</mat-menu>
<!-- --------------------------------------------------------------- -->
<!-- profile Dropdown -->
<!-- --------------------------------------------------------------- -->
<button
mat-icon-button
[matMenuTriggerFor]="profilemenu"
aria-label="Notifications" class="m-l-4"
>
<img
src="/assets/images/profile/user-1.jpg"
class="rounded-circle object-cover icon-35 profile-dd"
width="35"
/>
</button>
<mat-menu
#profilemenu="matMenu"
xPosition="before"
class="topbar-dd cardWithShadow"
>
<div class="p-x-32 p-y-16">
<h6 class="f-s-16 f-w-600 m-0 ">User Profile</h6>
<div class="d-flex align-items-center p-b-24 b-b-1 m-t-16">
<img
src="/assets/images/profile/user-1.jpg"
class="rounded-circle"
width="95"
/>
<div class="m-l-16">
<h6 class="f-s-14 f-w-600 m-0 ">Mathew Anderson</h6>
<span class="f-s-14 d-block m-b-4">Designer</span>
<span class="d-flex align-items-center">
<i-tabler name="mail" class="icon-15 m-r-4"></i-tabler>
info&#64;modernize.com
</span>
</div>
</div>
</div>
<div class="p-x-32">
@for(profile of profiledd; track profile.title) {
<a
class="p-y-16 text-decoration-none d-block text-hover-primary"
[routerLink]="[profile.link]"
>
<div class="d-flex align-items-center">
<button
mat-mini-fab
class="text-primary bg-light shadow-none rounded"
>
<img [src]="profile.img" width="20" />
</button>
<div class="m-l-16">
<h5
class="f-s-14 f-w-600 m-0 textprimary hover-text"
>
{{ profile.title }}
</h5>
<span class="">{{ profile.subtitle }}</span>
</div>
</div>
</a>
}
<!-- upgrade -->
<div
class="p-24 overflow-hidden bg-light-primary rounded position-relative m-y-16"
>
<div class="d-flex align-items-center">
<div>
<h5 class="f-s-18 m-0 f-w-600 m-b-12 ">
Unlimited <br />
Access
</h5>
<button mat-flat-button color="primary">Upgrade</button>
</div>
<div class="m-l-auto">
<img
src="/assets/images/backgrounds/unlimited-bg.png"
alt="upgrade-bg"
class="upgrade-bg"
/>
</div>
</div>
</div>
</div>
<div class="p-y-12 p-x-32">
<a
[routerLink]="['/authentication/login']"
mat-stroked-button
color="primary"
class="w-100"
>Logout</a
>
</div>
</mat-menu>
</div>
</mat-toolbar>

View File

@@ -0,0 +1,296 @@
import { Component, Output, EventEmitter, Input } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { MatDialog } from '@angular/material/dialog';
import { navItems } from '../../vertical/sidebar/sidebar-data';
import { TranslateService } from '@ngx-translate/core';
import { RouterModule } from '@angular/router';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { BrandingComponent } from '../../vertical/sidebar/branding.component';
import { AppSettings } from 'src/app/config';
import { FormsModule } from '@angular/forms';
interface notifications {
id: number;
img: string;
title: string;
subtitle: string;
}
interface profiledd {
id: number;
img: string;
title: string;
subtitle: string;
link: string;
}
interface apps {
id: number;
img: string;
title: string;
subtitle: string;
link: string;
}
interface quicklinks {
id: number;
title: string;
link: string;
}
@Component({
selector: 'app-horizontal-header',
imports: [RouterModule, TablerIconsModule, MaterialModule, BrandingComponent],
templateUrl: './header.component.html',
})
export class AppHorizontalHeaderComponent {
@Input() showToggle = true;
@Input() toggleChecked = false;
@Output() toggleMobileNav = new EventEmitter<void>();
@Output() toggleMobileFilterNav = new EventEmitter<void>();
@Output() toggleCollapsed = new EventEmitter<void>();
showFiller = false;
@Output() optionsChange = new EventEmitter<AppSettings>();
public selectedLanguage: any = {
language: 'English',
code: 'en',
type: 'US',
icon: '/assets/images/flag/icon-flag-en.svg',
};
public languages: any[] = [
{
language: 'English',
code: 'en',
type: 'US',
icon: '/assets/images/flag/icon-flag-en.svg',
},
{
language: 'Español',
code: 'es',
icon: '/assets/images/flag/icon-flag-es.svg',
},
{
language: 'Français',
code: 'fr',
icon: '/assets/images/flag/icon-flag-fr.svg',
},
{
language: 'German',
code: 'de',
icon: '/assets/images/flag/icon-flag-de.svg',
},
];
constructor(
private settings: CoreService,
private vsidenav: CoreService,
public dialog: MatDialog,
private translate: TranslateService
) {
translate.setDefaultLang('en');
}
openDialog() {
const dialogRef = this.dialog.open(AppHorizontalSearchDialogComponent);
dialogRef.afterClosed().subscribe((result) => {
console.log(`Dialog result: ${result}`);
});
}
changeLanguage(lang: any): void {
this.translate.use(lang.code);
this.selectedLanguage = lang;
}
options = this.settings.getOptions();
private emitOptions() {
this.optionsChange.emit(this.options);
}
setlightDark(theme: string) {
this.options.theme = theme;
this.emitOptions();
}
notifications: notifications[] = [
{
id: 1,
img: '/assets/images/profile/user-1.jpg',
title: 'Roman Joined the Team!',
subtitle: 'Congratulate him',
},
{
id: 2,
img: '/assets/images/profile/user-2.jpg',
title: 'New message received',
subtitle: 'Salma sent you new message',
},
{
id: 3,
img: '/assets/images/profile/user-3.jpg',
title: 'New Payment received',
subtitle: 'Check your earnings',
},
{
id: 4,
img: '/assets/images/profile/user-4.jpg',
title: 'Jolly completed tasks',
subtitle: 'Assign her new tasks',
},
{
id: 5,
img: '/assets/images/profile/user-5.jpg',
title: 'Roman Joined the Team!',
subtitle: 'Congratulate him',
},
];
profiledd: profiledd[] = [
{
id: 1,
img: '/assets/images/svgs/icon-account.svg',
title: 'My Profile',
subtitle: 'Account Settings',
link: '/',
},
{
id: 2,
img: '/assets/images/svgs/icon-inbox.svg',
title: 'My Inbox',
subtitle: 'Messages & Email',
link: '/',
},
{
id: 3,
img: '/assets/images/svgs/icon-tasks.svg',
title: 'My Tasks',
subtitle: 'To-do and Daily Tasks',
link: '/',
},
];
apps: apps[] = [
{
id: 1,
img: '/assets/images/svgs/icon-dd-chat.svg',
title: 'Chat Application',
subtitle: 'Messages & Emails',
link: '/',
},
{
id: 2,
img: '/assets/images/svgs/icon-dd-cart.svg',
title: 'eCommerce App',
subtitle: 'Buy a Product',
link: '/',
},
{
id: 3,
img: '/assets/images/svgs/icon-dd-invoice.svg',
title: 'Invoice App',
subtitle: 'Get latest invoice',
link: '/',
},
{
id: 4,
img: '/assets/images/svgs/icon-dd-date.svg',
title: 'Calendar App',
subtitle: 'Get Dates',
link: '/',
},
{
id: 5,
img: '/assets/images/svgs/icon-dd-mobile.svg',
title: 'Contact Application',
subtitle: '2 Unsaved Contacts',
link: '/',
},
{
id: 6,
img: '/assets/images/svgs/icon-dd-lifebuoy.svg',
title: 'Tickets App',
subtitle: 'Create new ticket',
link: '/',
},
{
id: 7,
img: '/assets/images/svgs/icon-dd-message-box.svg',
title: 'Email App',
subtitle: 'Get new emails',
link: '/',
},
{
id: 8,
img: '/assets/images/svgs/icon-dd-application.svg',
title: 'Courses',
subtitle: 'Create new course',
link: '/',
},
];
quicklinks: quicklinks[] = [
{
id: 1,
title: 'Pricing Page',
link: '/',
},
{
id: 2,
title: 'Authentication Design',
link: '/',
},
{
id: 3,
title: 'Register Now',
link: '/',
},
{
id: 4,
title: '404 Error Page',
link: '/',
},
{
id: 5,
title: 'Notes App',
link: '/',
},
{
id: 6,
title: 'Employee App',
link: '/',
},
{
id: 7,
title: 'Todo Application',
link: '/',
},
{
id: 8,
title: 'Treeview',
link: '/',
},
];
}
@Component({
selector: 'app-search-dialog',
imports: [RouterModule, MaterialModule, TablerIconsModule, FormsModule],
templateUrl: 'search-dialog.component.html',
})
export class AppHorizontalSearchDialogComponent {
searchText: string = '';
navItems = navItems;
navItemsData = navItems.filter((navitem) => navitem.displayName);
// filtered = this.navItemsData.find((obj) => {
// return obj.displayName == this.searchinput;
// });
}

View File

@@ -0,0 +1,39 @@
<div class="p-24 p-b-0">
<div class="row">
<div class="col-10">
<mat-form-field
appearance="outline"
color="primary"
class="hide-hint w-100"
>
<input matInput placeholder="Search here" [(ngModel)]="searchText" />
</mat-form-field>
</div>
<div class="col-2 d-flex justify-content-end">
<button
mat-icon-button
mat-dialog-close
class="d-flex justify-content-center"
>
<i-tabler name="x" class="icon-18 d-flex"></i-tabler>
</button>
</div>
</div>
</div>
<mat-divider></mat-divider>
<mat-dialog-content class="mat-typography search-dialog">
<h4 class="f-s-18 f-w-500 m-b-16">Quick Page Links</h4>
@for(item of navItemsData; track item.route) {
<a
[routerLink]="[item.route]"
mat-dialog-close
class="p-y-12 text-decoration-none d-block"
>
<h5 class="f-s-14 f-w-500 d-block m-0">
{{ item.displayName }}
</h5>
<span class="f-s-12 ">{{ item.route }}</span>
</a>
}
</mat-dialog-content>

View File

@@ -0,0 +1,21 @@
@if(!item.navCap){
<a class="cursor-pointer menuLink" (click)="onItemSelected(item)" [ngClass]="{
activeMenu: item.route ? router.isActive(item.route, true) : false
}">
<i-tabler class="routeIcon icon-18" name="{{ item.iconName }}"></i-tabler>
{{ item.displayName }}
@if(item.children && item.children.length){
<span class="down-icon d-flex m-l-auto">
<mat-icon> expand_more </mat-icon>
</span>
}
</a>
}
@if(item.children){
<div class="childBox">
@for(child of item.children; track child){
<app-horizontal-nav-item [item]="child" class="ddmenu">
</app-horizontal-nav-item>
}
</div>
}

View File

@@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';
import { NavService } from '../../../../../services/nav.service';
import { TablerIconsModule } from 'angular-tabler-icons';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-horizontal-nav-item',
imports: [TablerIconsModule, CommonModule, MatIconModule],
templateUrl: './nav-item.component.html',
})
export class AppHorizontalNavItemComponent implements OnInit {
@Input() depth: any;
@Input() item: any;
constructor(public navService: NavService, public router: Router) {
if (this.depth === undefined) {
this.depth = 0;
}
}
ngOnInit() {}
onItemSelected(item: any) {
if (!item.children || !item.children.length) {
this.router.navigate([item.route]);
}
}
}

View File

@@ -0,0 +1,57 @@
import { NavItem } from '../../vertical/sidebar/nav-item/nav-item';
export const navItems: NavItem[] = [
{
navCap: 'Home',
},
{
displayName: 'Starter',
iconName: 'home',
route: '/starter',
},
{
displayName: 'Login',
iconName: 'lock',
route: '/authentication/login',
},
{
navCap: 'Other',
},
{
displayName: 'Menu Level',
iconName: 'box-multiple',
route: '/menu-level',
children: [
{
displayName: 'Menu 1',
iconName: 'point',
route: '/menu-1',
children: [
{
displayName: 'Menu 1',
iconName: 'point',
route: '/menu-1',
},
{
displayName: 'Menu 2',
iconName: 'point',
route: '/menu-2',
},
],
},
{
displayName: 'Menu 2',
iconName: 'point',
route: '/menu-2',
},
],
},
{
displayName: 'Disabled',
iconName: 'ban',
route: '/disabled',
disabled: true,
},
];

View File

@@ -0,0 +1,16 @@
@if(mobileQuery.matches) {
<div class="horizontal-sidebar p-y-8 p-y-8 b-b-1">
<div class="container">
<div>
<div class="horizontal-navbar hstack align-items-center">
@for(item of navItems; track item) {
<app-horizontal-nav-item [item]="item" class="parentBox {{ item.ddType }}" [ngClass]="{
pactive: item.route == parentActive ? 'pactive' : ''
}">
</app-horizontal-nav-item>
}
</div>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,48 @@
import {
Component,
OnInit,
Input,
ChangeDetectorRef,
OnChanges,
} from '@angular/core';
import { navItems } from './sidebar-data';
import { Router } from '@angular/router';
import { NavService } from '../../../../services/nav.service';
import { MediaMatcher } from '@angular/cdk/layout';
import { AppHorizontalNavItemComponent } from './nav-item/nav-item.component';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-horizontal-sidebar',
imports: [AppHorizontalNavItemComponent, CommonModule],
templateUrl: './sidebar.component.html',
})
export class AppHorizontalSidebarComponent implements OnInit {
navItems = navItems;
parentActive = '';
mobileQuery: MediaQueryList;
private _mobileQueryListener: () => void;
constructor(
public navService: NavService,
public router: Router,
media: MediaMatcher,
changeDetectorRef: ChangeDetectorRef
) {
this.mobileQuery = media.matchMedia('(min-width: 1100px)');
this._mobileQueryListener = () => changeDetectorRef.detectChanges();
this.mobileQuery.addListener(this._mobileQueryListener);
this.router.events.subscribe(
() => (this.parentActive = this.router.url.split('/')[1])
);
}
ngOnInit(): void {
this.parentActive = this.router.url.split('/')[1];
this.router.events.subscribe(() => {
this.parentActive = this.router.url.split('/')[1];
});
}
}

View File

@@ -0,0 +1,31 @@
@if(pageInfo?.['title'] != 'Analytical' && pageInfo?.['title'] != 'eCommerce'){
<div class="bg-light-primary rounded p-y-30 p-x-24 m-b-30 overflow-hidden">
<div class="row">
<div class="col-sm-8">
<h4 class="page-title m-0 f-s-20 f-w-600 m-b-16">
{{ pageInfo?.['title'] }}
</h4>
<div class="d-flex align-items-center overflow-hidden">
<ul class="breadcrumb">
@for (url of pageInfo?.['urls']; track url.url;let index = $index,last
= $last ) { @if(!last){
<li class="breadcrumb-item" [routerLink]="url.url">
<a [routerLink]="url.url">{{ url.title }}</a>
</li>
}@else {
<li class="breadcrumb-item">
<i-tabler name="circle-filled" class="icon-8"></i-tabler>
</li>
<li class="breadcrumb-item active f-s-14">{{ url.title }}</li>
} }
</ul>
</div>
</div>
<div class="col-sm-4 text-right position-relative">
<div class="breadcrumb-icon">
<img src="/assets/images/breadcrumb/ChatBc.png" alt="breadcrumb" width="165" />
</div>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { Router, NavigationEnd, ActivatedRoute, Data } from '@angular/router';
import { filter, map, mergeMap } from 'rxjs/operators';
import { TablerIconsModule } from 'angular-tabler-icons';
@Component({
selector: 'app-breadcrumb',
imports: [RouterModule, TablerIconsModule],
templateUrl: './breadcrumb.component.html',
styleUrls: [],
})
export class AppBreadcrumbComponent {
// @Input() layout;
pageInfo: Data | any = Object.create(null);
myurl: any = this.router.url.slice(1).split('/');
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private titleService: Title
) {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.pipe(map(() => this.activatedRoute))
.pipe(
map((route) => {
while (route.firstChild) {
route = route.firstChild;
}
return route;
})
)
.pipe(filter((route) => route.outlet === 'primary'))
.pipe(mergeMap((route) => route.data))
// tslint:disable-next-line - Disables all
.subscribe((event) => {
// tslint:disable-next-line - Disables all
this.titleService.setTitle(event['title'] + ' - Angular 20');
this.pageInfo = event;
});
}
}

View File

@@ -0,0 +1,173 @@
<div class="customizerNav">
<ng-scrollbar class="position-relative" style="height: calc(100vh - 85px)">
<div class="p-24">
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16">Theme Option</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.theme" (change)="setDark($event.value)"
class="customizer-button-group gap-16" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="'light'">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="sun-high" class="d-flex m-r-8 fill-icon"></i-tabler>
Light
</div>
</mat-button-toggle>
<mat-button-toggle [value]="'dark'">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="moon" class="d-flex m-r-8 fill-icon"></i-tabler>
Dark
</div>
</mat-button-toggle>
</mat-button-toggle-group>
<div>
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24 p-t-16">
Theme Colors
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.activeTheme"
(change)="setColor($event.value)" class="customizer-button-group theme-colors two-row"
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="'blue_theme'">
<span class="theme-circle blue_theme" matTooltip="blue_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
<mat-button-toggle [value]="'aqua_theme'">
<span class="theme-circle aqua_theme" matTooltip="aqua_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
<mat-button-toggle [value]="'purple_theme'">
<span class="theme-circle purple_theme" matTooltip="purple_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
<mat-button-toggle [value]="'green_theme'">
<span class="theme-circle green_theme" matTooltip="green_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
<mat-button-toggle [value]="'cyan_theme'">
<span class="theme-circle cyan_theme" matTooltip="cyan_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
<mat-button-toggle [value]="'orange_theme'">
<span class="theme-circle orange_theme" matTooltip="orange_theme" matTooltipClass="text-uppercase"
matTooltipPosition="above">
<i-tabler name="check" class="icon-16 theme-icon"></i-tabler>
</span>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24">
Theme Direction
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.dir" (change)="setDir($event.value)"
class="customizer-button-group gap-16" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="'ltr'">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="text-direction-ltr" class="d-flex m-r-8 fill-icon"></i-tabler>
LTR
</div>
</mat-button-toggle>
<mat-button-toggle [value]="'rtl'">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="text-direction-rtl" class="d-flex m-r-8 fill-icon"></i-tabler>
RTL
</div>
</mat-button-toggle>
</mat-button-toggle-group>
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24 p-t-16">
Layout type
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.horizontal"
class="customizer-button-group gap-16" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="false">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-distribute-vertical" class="d-flex m-r-8 fill-icon"></i-tabler>
Vertical
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-distribute-horizontal" class="d-flex m-r-8 fill-icon"></i-tabler>
Horizontal
</div>
</mat-button-toggle>
</mat-button-toggle-group>
@if(!options.horizontal){
<div>
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24 p-t-16">
Sidebar type
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.sidenavCollapsed"
(change)="setSidebar($event.value)" class="customizer-button-group gap-16"
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="false">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-sidebar-right" class="d-flex m-r-8 fill-icon"></i-tabler>
Full
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-sidebar" class="d-flex m-r-8 fill-icon"></i-tabler>
Minisidebar
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
}
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24 p-t-16">
Card with
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.cardBorder"
class="customizer-button-group gap-16" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="false">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="shadow" class="d-flex m-r-8 fill-icon"></i-tabler>
Shadow
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="shadow-off" class="d-flex m-r-8 fill-icon"></i-tabler>
Border
</div>
</mat-button-toggle>
</mat-button-toggle-group>
<h6 class="f-s-16 f-w-600 f-s-16 m-b-16 m-t-24 p-t-16">
Container Option
</h6>
<mat-button-toggle-group #group="matButtonToggleGroup" [(ngModel)]="options.boxed"
class="customizer-button-group gap-16" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator()">
<mat-button-toggle [value]="false">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-navbar" class="d-flex m-r-8 fill-icon"></i-tabler>
Full
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="d-flex align-items-center f-w-500">
<i-tabler name="layout-sidebar" class="d-flex m-r-8 fill-icon"></i-tabler>
Boxed
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
</ng-scrollbar>
</div>

View File

@@ -0,0 +1,56 @@
import {
Component,
Output,
EventEmitter,
ViewEncapsulation,
signal,
} from '@angular/core';
import { AppSettings } from 'src/app/config';
import { CoreService } from 'src/app/services/core.service';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { FormsModule } from '@angular/forms';
import { NgScrollbarModule } from 'ngx-scrollbar';
@Component({
selector: 'app-customizer',
imports: [TablerIconsModule, MaterialModule, FormsModule, NgScrollbarModule],
templateUrl: './customizer.component.html',
styleUrls: ['./customizer.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class CustomizerComponent {
options = this.settings.getOptions();
@Output() optionsChange = new EventEmitter<AppSettings>();
hideSingleSelectionIndicator = signal(true);
constructor(private settings: CoreService) {
}
setDark(theme: string) {
this.settings.setOptions({ theme: theme });
this.emitOptions();
}
setColor(color: string) {
this.settings.setOptions({ activeTheme: color });
this.emitOptions();
}
setDir(dir: 'ltr' | 'rtl') {
this.settings.setOptions({ dir: dir });
this.emitOptions();
}
setSidebar(sidenavOpened: boolean) {
this.settings.setOptions({ sidenavOpened: sidenavOpened });
this.emitOptions();
}
private emitOptions() {
this.optionsChange.emit(this.options);
}
}

View File

@@ -0,0 +1,215 @@
<mat-toolbar class="topbar gap-10">
<!-- --------------------------------------------------------------- -->
<!-- Desktop Menu -->
@if(showToggle) {
<button mat-icon-button (click)="toggleCollapsed.emit()" class="d-flex justify-content-center">
<i-tabler name="menu-2" class="icon-20 d-flex"></i-tabler>
</button>
}
<!-- Mobile Menu -->
@if(!showToggle) {
<button mat-icon-button (click)="toggleMobileNav.emit()" class="d-flex justify-content-center">
<i-tabler name="menu-2" class="icon-20 d-flex"></i-tabler>
</button>
}
<!-- --------------------------------------------------------------- -->
<!-- --------------------------------------------------------------- -->
<!-- Search -->
<!-- --------------------------------------------------------------- -->
<button mat-icon-button (click)="openDialog()" class="d-flex justify-content-center">
<i-tabler name="search" class="icon-20 d-flex"></i-tabler>
</button>
<div class="d-none d-lg-flex">
<!-- --------------------------------------------------------------- -->
<!-- Links -->
<!-- --------------------------------------------------------------- -->
<button mat-button [matMenuTriggerFor]="appsmenu" aria-label="Notifications">
<div class="d-flex align-items-center">
Apps <i-tabler name="chevron-down" class="icon-16 m-l-4"></i-tabler>
</div>
</button>
<mat-menu #appsmenu="matMenu" class="apps-dd cardWithShadow">
<div class="row">
<div class="col-sm-8 b-r-1 p-r-0">
<div class="p-32 p-b-0">
<div class="row">
@for(appdd of apps; track appdd.title) {
<div class="col-sm-6 text-hover-primary">
<a [routerLink]="[appdd.link]" class="d-flex align-items-center text-decoration-none m-b-24">
<span class="bg-light rounded icon-40 d-flex align-items-center justify-content-center">
<img [src]="appdd.img" width="20" />
</span>
<div class="m-l-16">
<h5 class="f-s-14 f-w-600 m-0 hover-text">
{{ appdd.title }}
</h5>
<span class=" f-s-12 text-body">{{ appdd.subtitle }}</span>
</div>
</a>
</div>
}
</div>
</div>
<div class="b-t-1 p-24 d-none d-lg-flex align-items-center justify-content-between">
<span class="d-flex align-items-center f-s-16 f-w-500">
<i-tabler name="help" class="icon-20 m-r-8"></i-tabler>Frequently
Asked Questions
</span>
<a [routerLink]="['/theme-pages/faq']" mat-flat-button color="primary">Check</a>
</div>
</div>
<div class="col-sm-4">
<div class="p-x-16 p-y-32">
<h4 class="f-s-18 f-w-600 m-b-16">Quick Links</h4>
@for(quicklink of quicklinks; track quicklink.title) {
<div class="text-hover-primary">
<a [routerLink]="[quicklink.link]" class="hover-text text-decoration-none f-w-600 d-block p-y-8">{{
quicklink.title }}</a>
</div>
}
</div>
</div>
</div>
</mat-menu>
<a mat-button [routerLink]="['/apps/chat']">Chat</a>
<a mat-button [routerLink]="['/apps/calendar']">Calendar</a>
<a mat-button [routerLink]="['/apps/email/inbox']">Email</a>
</div>
<span class="flex-1-auto"></span>
<!-- Mobile Menu -->
<button mat-icon-button (click)="toggleMobileFilterNav.emit()" class="d-flex d-lg-none justify-content-center">
<i-tabler name="grid-dots" class="icon-20 d-flex"></i-tabler>
</button>
<!-- --------------------------------------------------------------- -->
<!-- langugage Dropdown -->
<!-- --------------------------------------------------------------- -->
<button [matMenuTriggerFor]="flags" mat-icon-button class="m-r-5">
<img [src]="selectedLanguage.icon" class="rounded-circle object-cover icon-20" />
</button>
<mat-menu #flags="matMenu" class="cardWithShadow">
@for(lang of languages; track lang.icon) {
<button mat-menu-item (click)="changeLanguage(lang)">
<div class="d-flex align-items-center">
<img [src]="lang.icon" class="rounded-circle object-cover m-r-8 icon-20" />
<span class=" f-s-14">{{ lang.language }}</span>
</div>
</button>
}
</mat-menu>
@if(options.theme=='light'){
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('dark')">
<i-tabler class="d-flex icon-22" [name]="'moon'"></i-tabler>
</button>
}@else{
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('light')">
<i-tabler class="d-flex icon-22" [name]="'sun'"></i-tabler>
</button>
}
<!-- --------------------------------------------------------------- -->
<!-- Notification Dropdown -->
<!-- --------------------------------------------------------------- -->
<button class="notification-badge" mat-icon-button [matMenuTriggerFor]="notificationmenu" aria-label="Notifications" matBadge="5">
<i-tabler class="d-flex demo-section" name="bell"></i-tabler>
</button>
<mat-menu #notificationmenu="matMenu" class="topbar-dd cardWithShadow">
<div class="d-flex align-items-center p-x-32 p-y-16">
<h6 class="f-s-16 f-w-600 m-0 ">Notifications</h6>
<span class="m-l-auto">
<span class="bg-primary text-white p-x-8 p-y-4 f-w-500 rounded f-s-12">5 new</span>
</span>
</div>
@for(notification of notifications; track notification.title) {
<button mat-menu-item class="p-x-32 p-y-16">
<div class="d-flex align-items-center">
<img [src]="notification.img" class="rounded-circle" width="48" />
<div class="m-l-16">
<h5 class="f-s-14 f-w-600 m-0 ">
{{ notification.title }}
</h5>
<span>{{ notification.subtitle }}</span>
</div>
</div>
</button>
}
<div class="p-y-12 p-x-32">
<button mat-stroked-button color="primary" class="w-100">
See all notifications
</button>
</div>
</mat-menu>
<!-- --------------------------------------------------------------- -->
<!-- profile Dropdown -->
<!-- --------------------------------------------------------------- -->
<button mat-icon-button [matMenuTriggerFor]="profilemenu" aria-label="Notifications">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle object-cover icon-35 profile-dd" width="35" />
</button>
<mat-menu #profilemenu="matMenu" class="topbar-dd cardWithShadow">
<ng-scrollbar class="position-relative" style="height: 647px">
<div class="p-x-32 p-y-16">
<h6 class="f-s-16 f-w-600 m-0 ">User Profile</h6>
<div class="d-flex align-items-center p-b-24 b-b-1 m-t-16">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle" width="95" />
<div class="m-l-16">
<h6 class="f-s-14 f-w-600 m-0 ">Mathew Anderson</h6>
<span class="f-s-14 d-block m-b-4">Designer</span>
<span class="d-flex align-items-center">
<i-tabler name="mail" class="icon-15 m-r-4"></i-tabler>
info&#64;modernize.com
</span>
</div>
</div>
</div>
<div class="p-x-32">
@for(profile of profiledd; track profile.title) {
<a class="p-y-16 text-decoration-none d-block text-hover-primary" [routerLink]="[profile.link]">
<div class="d-flex align-items-center">
<button mat-mini-fab class="text-primary bg-light shadow-none rounded">
<img [src]="profile.img" width="20" />
</button>
<div class="m-l-16">
<h5 class="f-s-14 f-w-600 m-0 textprimary hover-text">
{{ profile.title }}
</h5>
<span class="text-body">{{ profile.subtitle }}</span>
</div>
</div>
</a>
}
<!-- upgrade -->
<div class="p-24 overflow-hidden bg-light rounded position-relative m-y-16">
<div class="d-flex align-items-center">
<div>
<h5 class="f-s-18 m-0 f-w-600 m-b-12 ">
Unlimited <br />
Access
</h5>
<button mat-flat-button color="primary">Upgrade</button>
</div>
<div class="m-l-auto">
<img src="/assets/images/backgrounds/unlimited-bg.png" alt="upgrade-bg" class="upgrade-bg" />
</div>
</div>
</div>
</div>
<div class="p-y-12 p-x-32">
<a [routerLink]="['/authentication/login']" mat-stroked-button color="primary" class="w-100">Logout</a>
</div>
</ng-scrollbar>
</mat-menu>
</mat-toolbar>

View File

@@ -0,0 +1,314 @@
import {
Component,
Output,
EventEmitter,
Input,
signal,
ViewEncapsulation,
} from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { MatDialog } from '@angular/material/dialog';
import { navItems } from '../sidebar/sidebar-data';
import { TranslateService } from '@ngx-translate/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { AppSettings } from 'src/app/config';
interface notifications {
id: number;
img: string;
title: string;
subtitle: string;
}
interface profiledd {
id: number;
img: string;
title: string;
subtitle: string;
link: string;
}
interface apps {
id: number;
img: string;
title: string;
subtitle: string;
link: string;
}
interface quicklinks {
id: number;
title: string;
link: string;
}
@Component({
selector: 'app-header',
imports: [
RouterModule,
NgScrollbarModule,
TablerIconsModule,
MaterialModule
],
templateUrl: './header.component.html',
encapsulation: ViewEncapsulation.None,
})
export class HeaderComponent {
@Input() showToggle = true;
@Input() toggleChecked = false;
@Output() toggleMobileNav = new EventEmitter<void>();
@Output() toggleMobileFilterNav = new EventEmitter<void>();
@Output() toggleCollapsed = new EventEmitter<void>();
showFiller = false;
public selectedLanguage: any = {
language: 'English',
code: 'en',
type: 'US',
icon: '/assets/images/flag/icon-flag-en.svg',
};
public languages: any[] = [
{
language: 'English',
code: 'en',
type: 'US',
icon: '/assets/images/flag/icon-flag-en.svg',
},
{
language: 'Español',
code: 'es',
icon: '/assets/images/flag/icon-flag-es.svg',
},
{
language: 'Français',
code: 'fr',
icon: '/assets/images/flag/icon-flag-fr.svg',
},
{
language: 'German',
code: 'de',
icon: '/assets/images/flag/icon-flag-de.svg',
},
];
@Output() optionsChange = new EventEmitter<AppSettings>();
constructor(
private settings: CoreService,
private vsidenav: CoreService,
public dialog: MatDialog,
private translate: TranslateService
) {
translate.setDefaultLang('en');
}
options = this.settings.getOptions();
openDialog() {
const dialogRef = this.dialog.open(AppSearchDialogComponent);
dialogRef.afterClosed().subscribe((result) => {
console.log(`Dialog result: ${result}`);
});
}
changeLanguage(lang: any): void {
this.translate.use(lang.code);
this.selectedLanguage = lang;
}
setlightDark(theme: string) {
this.options.theme =theme;
this.emitOptions();
}
private emitOptions() {
this.optionsChange.emit(this.options);
}
notifications: notifications[] = [
{
id: 1,
img: '/assets/images/profile/user-1.jpg',
title: 'Roman Joined the Team!',
subtitle: 'Congratulate him sf',
},
{
id: 2,
img: '/assets/images/profile/user-2.jpg',
title: 'New message received',
subtitle: 'Salma sent you new message',
},
{
id: 3,
img: '/assets/images/profile/user-3.jpg',
title: 'New Payment received',
subtitle: 'Check your earnings',
},
{
id: 4,
img: '/assets/images/profile/user-4.jpg',
title: 'Jolly completed tasks',
subtitle: 'Assign her new tasks',
},
{
id: 5,
img: '/assets/images/profile/user-5.jpg',
title: 'Roman Joined thed Team!',
subtitle: 'Congratulate him',
},
];
profiledd: profiledd[] = [
{
id: 1,
img: '/assets/images/svgs/icon-account.svg',
title: 'My Profile',
subtitle: 'Account Settings',
link: '/',
},
{
id: 2,
img: '/assets/images/svgs/icon-inbox.svg',
title: 'My Inbox',
subtitle: 'Messages & Email',
link: '/',
},
{
id: 3,
img: '/assets/images/svgs/icon-tasks.svg',
title: 'My Tasks',
subtitle: 'To-do and Daily Tasks',
link: '/',
},
];
apps: apps[] = [
{
id: 1,
img: '/assets/images/svgs/icon-dd-chat.svg',
title: 'Chat Application',
subtitle: 'Messages & Emails',
link: '/',
},
{
id: 2,
img: '/assets/images/svgs/icon-dd-cart.svg',
title: 'eCommerce App',
subtitle: 'Buy a Product',
link: '/',
},
{
id: 3,
img: '/assets/images/svgs/icon-dd-invoice.svg',
title: 'Invoice App',
subtitle: 'Get latest invoice',
link: '/',
},
{
id: 4,
img: '/assets/images/svgs/icon-dd-date.svg',
title: 'Calendar App',
subtitle: 'Get Dates',
link: '/',
},
{
id: 5,
img: '/assets/images/svgs/icon-dd-mobile.svg',
title: 'Contact Application',
subtitle: '2 Unsaved Contacts',
link: '/',
},
{
id: 6,
img: '/assets/images/svgs/icon-dd-lifebuoy.svg',
title: 'Tickets App',
subtitle: 'Create new ticket',
link: '/',
},
{
id: 7,
img: '/assets/images/svgs/icon-dd-message-box.svg',
title: 'Email App',
subtitle: 'Get new emails',
link: '/',
},
{
id: 8,
img: '/assets/images/svgs/icon-dd-application.svg',
title: 'Courses',
subtitle: 'Create new course',
link: '/',
},
];
quicklinks: quicklinks[] = [
{
id: 1,
title: 'Pricing Page',
link: '/t',
},
{
id: 2,
title: 'Authentication Design',
link: '/',
},
{
id: 3,
title: 'Register Now',
link: '/',
},
{
id: 4,
title: '404 Error Page',
link: '/',
},
{
id: 5,
title: 'Notes App',
link: '/',
},
{
id: 6,
title: 'Employee App',
link: '/',
},
{
id: 7,
title: 'Todo Application',
link: '/',
},
{
id: 8,
title: 'Treeview',
link: '/',
},
];
}
@Component({
selector: 'search-dialog',
imports: [RouterModule, MaterialModule, TablerIconsModule, FormsModule],
templateUrl: 'search-dialog.component.html',
})
export class AppSearchDialogComponent {
searchText: string = '';
navItems = navItems;
navItemsData = navItems.filter((navitem) => navitem.displayName);
// filtered = this.navItemsData.find((obj) => {
// return obj.displayName == this.searchinput;
// });
}

View File

@@ -0,0 +1,39 @@
<div class="p-24 p-b-0">
<div class="row">
<div class="col-10">
<mat-form-field
appearance="outline"
color="primary"
class="hide-hint w-100"
>
<input matInput placeholder="Search here" [(ngModel)]="searchText" />
</mat-form-field>
</div>
<div class="col-2 d-flex justify-content-end">
<button
mat-icon-button
mat-dialog-close
class="d-flex justify-content-center"
>
<i-tabler name="x" class="icon-18 d-flex"></i-tabler>
</button>
</div>
</div>
</div>
<mat-divider></mat-divider>
<mat-dialog-content class="mat-typography search-dialog">
<h4 class="f-s-18 f-w-600 m-b-16">Quick Page Links</h4>
@for(item of navItemsData; track item.displayName) {
<a
[routerLink]="[item.route]"
mat-dialog-close
class="p-y-12 text-decoration-none d-block"
>
<h5 class="f-s-14 f-w-500 d-block m-0">
{{ item.displayName }}
</h5>
<span class="f-s-12 ">{{ item.route }}</span>
</a>
}
</mat-dialog-content>

View File

@@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
@Component({
selector: 'app-branding',
imports: [],
template: `
<a href="/" class="logodark">
<img
src="./assets/images/logos/dark-logo.svg"
class="align-middle m-2"
alt="logo"
/>
</a>
<a href="/" class="logolight">
<img
src="./assets/images/logos/light-logo.svg"
class="align-middle m-2"
alt="logo"
/>
</a>
`,
})
export class BrandingComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService) {}
}

View File

@@ -0,0 +1,43 @@
@if(item.navCap){
<div mat-subheader class="nav-caption">
{{ item.navCap }}
</div>
} @if(!item.navCap && !item.external && !item.twoLines) {
<a mat-list-item (click)="onItemSelected(item)" [ngClass]="{
'activeMenu': item.route
? router.isActive(item.route, true)
: false,
expanded: expanded, activemenu: isChildActive(item),
disabled: item.disabled
}" class="menu-list-item">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName | translate }}</span>
@if(item.children && item.children.length) {
<span class="arrow-icon" fxFlex>
<span fxFlex></span>
<mat-icon [@indicatorRotate]="expanded ? 'expanded' : 'collapsed'">
expand_more
</mat-icon>
</span>
} @if(item.chip) {
<span>
<span class="{{ item.chipClass }} p-x-8 p-y-4 item-chip f-w-500 rounded-pill ">{{ item.chipContent }}</span>
</span>
}
</a>
}
<!-- external Link -->
@if(!item.navCap && item.external) {
<mat-list-item onClick="window.open('//google.com')" class="menu-list-item" target="_blank">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName | translate }}</span>
</mat-list-item>
}
<!-- children -->
@if(expanded) { @for(child of item.children; track child) {
<app-nav-item [item]="child" (click)="onSubItemSelected(child)" [depth]="depth + 1">
</app-nav-item>
} }

View File

@@ -0,0 +1,104 @@
import {
Component,
HostBinding,
Input,
OnInit,
OnChanges,
Output,
EventEmitter,
} from '@angular/core';
import { NavItem } from './nav-item';
import { Router } from '@angular/router';
import { NavService } from '../../../../../services/nav.service';
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { TranslateModule } from '@ngx-translate/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-nav-item',
imports: [TranslateModule, TablerIconsModule, MaterialModule, CommonModule],
templateUrl: './nav-item.component.html',
styleUrls: [],
animations: [
trigger('indicatorRotate', [
state('collapsed', style({ transform: 'rotate(0deg)' })),
state('expanded', style({ transform: 'rotate(180deg)' })),
transition(
'expanded <=> collapsed',
animate('225ms cubic-bezier(0.4,0.0,0.2,1)')
),
]),
],
})
export class AppNavItemComponent implements OnChanges {
@Output() toggleMobileLink: any = new EventEmitter<void>();
@Output() notify: EventEmitter<boolean> = new EventEmitter<boolean>();
expanded: any = false;
disabled: any = false;
twoLines: any = false;
@HostBinding('attr.aria-expanded') ariaExpanded = this.expanded;
@Input() item: NavItem | any;
@Input() depth: any;
constructor(public navService: NavService, public router: Router) {
if (this.depth === undefined) {
this.depth = 0;
}
}
ngOnChanges() {
const url = this.navService.currentUrl();
if (this.item.route && url) {
this.expanded = url.indexOf(`/${this.item.route}`) === 0;
this.ariaExpanded = this.expanded;
}
}
onItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
this.router.navigate([item.route]);
}
if (item.children && item.children.length) {
this.expanded = !this.expanded;
}
//scroll
window.scroll({
top: 0,
left: 0,
behavior: 'smooth',
});
if (!this.expanded) {
if (window.innerWidth < 1024) {
this.notify.emit();
}
}
}
onSubItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
if (this.expanded && window.innerWidth < 1024) {
this.notify.emit();
}
}
}
isDirectlyActive(item: NavItem): boolean {
return !!item.route && this.router.isActive(item.route, true);
}
isChildActive(item: NavItem): boolean {
if (!item.children) return false;
return item.children.some(
(child) => this.isDirectlyActive(child) || this.isChildActive(child)
);
}
}

View File

@@ -0,0 +1,15 @@
export interface NavItem {
displayName?: string;
disabled?: boolean;
external?: boolean;
twoLines?: boolean;
chip?: boolean;
iconName?: string;
navCap?: string;
chipContent?: string;
chipClass?: string;
subtext?: string;
route?: string;
children?: NavItem[];
ddType?: string;
}

View File

@@ -0,0 +1,84 @@
import { NavItem } from './nav-item/nav-item';
export const navItems: NavItem[] = [
{
navCap: 'Home',
},
{
displayName: 'Starter',
iconName: 'home',
route: '/starter',
},
{
displayName: 'Login',
iconName: 'lock',
route: '/authentication/login',
},
{
displayName: 'Register',
iconName: 'user-edit',
route: '/authentication/register',
},
{
navCap: 'Other',
},
{
displayName: 'Menu Level',
iconName: 'box-multiple',
route: '/menu-level',
children: [
{
displayName: 'Menu 1',
iconName: 'point',
route: '/menu-1',
children: [
{
displayName: 'Menu 1',
iconName: 'point',
route: '/menu-1',
},
{
displayName: 'Menu 2',
iconName: 'point',
route: '/menu-2',
},
],
},
{
displayName: 'Menu 2',
iconName: 'point',
route: '/menu-2',
},
],
},
{
displayName: 'Disabled',
iconName: 'ban',
route: '/disabled',
disabled: true,
},
{
displayName: 'Chip',
iconName: 'mood-smile',
route: '/',
chip: true,
chipClass: 'bg-primary text-white',
chipContent: '9',
},
{
displayName: 'Outlined',
iconName: 'mood-smile',
route: '/',
chip: true,
chipClass: 'b-1 border-primary text-primary',
chipContent: 'outlined',
},
{
displayName: 'External Link',
iconName: 'star',
route: 'https://www.google.com/',
external: true,
},
];

View File

@@ -0,0 +1,13 @@
<div class="d-flex align-items-center justify-content-between">
<div class="branding"><app-branding></app-branding></div>
@if(showToggle) {
<a
href="javascript:void(0)"
(click)="toggleMobileNav.emit()"
class="d-flex justify-content-center ng-star-inserted icon-40 align-items-center"
>
<i-tabler name="x" class="icon-20 d-flex"></i-tabler>
</a>
}
</div>

View File

@@ -0,0 +1,25 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { BrandingComponent } from './branding.component';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-sidebar',
imports: [BrandingComponent, TablerIconsModule, MaterialModule],
templateUrl: './sidebar.component.html',
})
export class SidebarComponent implements OnInit {
constructor() {}
@Input() showToggle = true;
@Output() toggleMobileNav = new EventEmitter<void>();
@Output() toggleCollapsed = new EventEmitter<void>();
ngOnInit(): void {}
}

View File

@@ -0,0 +1,85 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// Material Form Controls
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
// Material Navigation
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
// Material Layout
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatListModule } from '@angular/material/list';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTreeModule } from '@angular/material/tree';
// Material Buttons & Indicators
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatBadgeModule } from '@angular/material/badge';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRippleModule } from '@angular/material/core';
// Material Popups & Modals
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
// Material Data tables
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
@NgModule({
declarations: [],
exports: [
MatAutocompleteModule,
MatCheckboxModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatRadioModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatCardModule,
MatDividerModule,
MatExpansionModule,
MatGridListModule,
MatListModule,
MatStepperModule,
MatTabsModule,
MatTreeModule,
MatButtonModule,
MatButtonToggleModule,
MatBadgeModule,
MatChipsModule,
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatRippleModule,
MatBottomSheetModule,
MatDialogModule,
MatSnackBarModule,
MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatTableModule,
],
})
export class MaterialModule {}

View File

@@ -0,0 +1,26 @@
import { Routes } from '@angular/router';
import { AppErrorComponent } from './error/error.component';
import { AppSideLoginComponent } from './side-login/side-login.component';
import { AppSideRegisterComponent } from './side-register/side-register.component';
export const AuthenticationRoutes: Routes = [
{
path: '',
children: [
{
path: 'error',
component: AppErrorComponent,
},
{
path: 'login',
component: AppSideLoginComponent,
},
{
path: 'register',
component: AppSideRegisterComponent,
},
],
},
];

View File

@@ -0,0 +1,10 @@
<div class="blank-layout-container justify-content-center">
<div class="row">
<div class="col-12 text-center">
<img src="/assets/images/backgrounds/errorimg.svg" alt="error-bg" />
<h2 class="auth-title f-w-600">Opps!!!</h2>
<h6 class="f-s-20 f-w-600 m-b-30">This page you are looking for could not be found.</h6>
<a mat-flat-button color="primary" [routerLink]="['/starter']">Go back to Home</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-error',
imports: [RouterModule],
templateUrl: './error.component.html'
})
export class AppErrorComponent {}

View File

@@ -0,0 +1,97 @@
<div class="blank-layout-container justify-content-center">
<div class="position-relative row w-100 h-100">
<div class="col-lg-7 col-xl-8 bg-gredient p-0">
<div class="p-24 h-100">
<app-branding></app-branding>
<div class="align-items-center justify-content-center img-height d-none d-lg-flex">
<img src="/assets/images/backgrounds/login-bg.svg" alt="login" style="max-width: 500px" />
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 p-0">
<div class="p-32 d-flex align-items-start align-items-lg-center justify-content-center h-100">
<div class="row justify-content-center w-100">
<div class="col-lg-9 max-width-form">
<h4 class="f-w-700 f-s-24 m-0">Welcome to Modernize</h4>
<span class="f-s-14 d-block m-t-8">Your Admin Dashboard</span>
<div class="row m-t-24">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
Google
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Facebook
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign in with</div>
<form class="m-t-30" [formGroup]="form" (ngSubmit)="submit()">
<mat-label class=" f-s-14 f-w-600 m-b-12 d-block">Username</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput formControlName="uname" />
@if(f['uname'].touched && f['uname'].invalid){
<mat-hint class="m-b-16 error-msg">
@if(f['uname'].errors && f['uname'].errors['required']){
<div class="text-error">
Name is required.
</div>
}
@if(f['uname'].errors && f['uname'].errors['minlength']){
<div class="text-error">
Name should be 6 character.
</div>
}
</mat-hint>
}
</mat-form-field>
<!-- password -->
<mat-label class=" f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" formControlName="password" />
@if(f['password'].touched && f['password'].invalid){
<mat-hint class="m-b-16 error-msg">
@if(f['password'].errors && f['password'].errors['required']){
<div class="text-error">
Password is required.
</div>
}
</mat-hint>
}
</mat-form-field>
<div class="d-flex align-items-center m-b-12">
<mat-checkbox color="primary">Remember this Device</mat-checkbox>
<a [routerLink]="['/authentication/login']"
class="text-primary f-w-600 text-decoration-none m-l-auto f-s-14">Forgot Password ?</a>
</div>
<button mat-flat-button color="primary" class="w-100" [disabled]="!form.valid">
Sign In
</button>
<!-- input -->
</form>
<span class="d-block f-w-500 d-block m-t-24">New to Modernize?
<a [routerLink]="['/authentication/register']"
class="text-decoration-none text-primary f-w-500 f-s-14">
Create an account</a>
</span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { BrandingComponent } from 'src/app/layouts/full/vertical/sidebar/branding.component';
@Component({
selector: 'app-side-login',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule, BrandingComponent],
templateUrl: './side-login.component.html',
})
export class AppSideLoginComponent {
constructor( private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/starter']);
}
}

View File

@@ -0,0 +1,103 @@
<div class="blank-layout-container justify-content-center">
<div class="position-relative row w-100 h-100">
<div class="col-lg-7 col-xl-8 bg-gredient p-0">
<div class="p-24 h-100">
<app-branding></app-branding>
<div class="align-items-center justify-content-center img-height d-none d-lg-flex">
<img src="/assets/images/backgrounds/login-bg.svg" alt="login" style="max-width: 500px" />
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 p-0">
<div class="p-32 d-flex align-items-start align-items-lg-center justify-content-center h-100">
<div class="row justify-content-center w-100">
<div class="col-lg-9 max-width-form">
<h4 class="f-w-700 f-s-24 m-0">Welcome to Modernize</h4>
<span class="f-s-14 d-block m-t-8">Your Admin Dashboard</span>
<div class="row m-t-24">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
Google
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Facebook
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign up with</div>
<form class="m-t-30" [formGroup]="form" (ngSubmit)="submit()">
<mat-label class=" f-s-14 f-w-600 m-b-12 d-block">Name</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput formControlName="uname" />
@if(f['uname'].touched && f['uname'].invalid){
<mat-hint class="m-b-16 error-msg">
@if(f['uname'].errors && f['uname'].errors['required']){
<div class="text-error">
Name is required.
</div>
}
@if(f['uname'].errors && f['uname'].errors['minlength']){
<div class="text-error">
Name should be 6 character.
</div>
}
</mat-hint>
}
</mat-form-field>
<!-- email -->
<mat-label class=" f-s-14 f-w-600 m-b-12 d-block">Email Address</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="email" formControlName="email" />
@if(f['email'].touched && f['email'].invalid){
<mat-hint class="m-b-16 error-msg">
@if(f['email'].errors && f['email'].errors['required']){
<div class="text-error">
Email is required.
</div>
}
</mat-hint>
}
</mat-form-field>
<!-- password -->
<mat-label class=" f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" formControlName="password" />
@if(f['password'].touched && f['password'].invalid){
<mat-hint class="m-b-16 error-msg">
@if(f['password'].errors && f['password'].errors['required']){
<div class="text-error">
Password is required.
</div>
}
</mat-hint>
}
</mat-form-field>
<button mat-flat-button color="primary" class="w-100" [disabled]="!form.valid">
Sign Up
</button>
<!-- input -->
</form>
<span class="d-block f-w-500 d-block m-t-24">Already have an Account?
<a [routerLink]="['/authentication/login']" class="text-decoration-none text-primary f-w-500 f-s-14">
Sign In</a>
</span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { BrandingComponent } from 'src/app/layouts/full/vertical/sidebar/branding.component';
@Component({
selector: 'app-side-register',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule, BrandingComponent],
templateUrl: './side-register.component.html',
})
export class AppSideRegisterComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService, private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
email: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/']);
}
}

View File

@@ -0,0 +1,16 @@
import { Routes } from '@angular/router';
import { StarterComponent } from './starter/starter.component';
export const PagesRoutes: Routes = [
{
path: '',
component: StarterComponent,
data: {
title: 'Starter Page',
urls: [
{ title: 'Dashboard', url: '/dashboards/dashboard1' },
{ title: 'Starter Page' },
],
},
},
];

View File

@@ -0,0 +1,155 @@
<!-- ============================================================== -->
<!-- Simple four boxes Row -->
<!-- ============================================================== -->
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Sample Page</mat-card-title>
<mat-card-subtitle class=" m-b-10"
>This is test page</mat-card-subtitle
>
<div class="d-flex flex-wrap gap-10">
<button mat-flat-button>Primary</button>
<button mat-flat-button class="bg-success">Success</button>
<button mat-flat-button class="bg-secondary">Secondary</button>
<button mat-flat-button class="bg-warning">Warning</button>
<button mat-flat-button class="bg-error">Error</button>
<button mat-flat-button class="bg-light-primary text-primary">
Light Primary
</button>
<button mat-flat-button class="bg-light-success text-success">
Light Success
</button>
<button mat-flat-button class="bg-light-secondary text-secondary">
Light Secondary
</button>
<button mat-flat-button class="bg-light-warning text-warning">
Light Warning
</button>
<button mat-flat-button class="bg-light-error text-error">
Light Error
</button>
</div>
<div class="d-flex flex-wrap gap-10 m-t-12">
<button mat-raised-button>Basic</button>
<button mat-stroked-button>Basic</button>
<button mat-button>Basic</button>
<button
mat-icon-button
aria-label="Example icon button with a vertical three dot icon"
>
<mat-icon>more_vert</mat-icon>
</button>
<button mat-fab aria-label="Example icon button with a delete icon">
<mat-icon>delete</mat-icon>
</button>
<button mat-mini-fab aria-label="Example icon button with a menu icon">
<mat-icon>menu</mat-icon>
</button>
<button mat-fab extended>
<mat-icon>favorite</mat-icon>
Basic
</button>
</div>
</mat-card-content>
</mat-card>
<mat-card class="cardWithShadow">
<mat-card-header>
<mat-card-title class="m-b-0">Ordrinary Form</mat-card-title>
</mat-card-header>
<mat-card-content class="b-t-1">
<p>this is test</p>
<form>
<!-- input -->
<mat-label class=" f-w-500 m-b-8 d-block">Email</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput />
<mat-hint> We'll never share your email with anyone else.</mat-hint>
</mat-form-field>
<!-- input -->
<mat-label class=" f-w-500 m-b-8 d-block m-t-12"
>Password</mat-label
>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" />
</mat-form-field>
<!-- input -->
<mat-checkbox color="primary">Check Me Out!</mat-checkbox>
<div class="m-t-12">
<button mat-flat-button color="primary">Submit</button>
</div>
</form>
</mat-card-content>
</mat-card>
<mat-card class="cardWithShadow">
<mat-card-content>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- Position Column -->
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef>No.</th>
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef>Weight</th>
<td mat-cell *matCellDef="let element">{{ element.weight }}</td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef>Symbol</th>
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</mat-card-content>
</mat-card>
<mat-card class="cardWithShadow">
<mat-card-header>
<mat-card-title class="m-b-0">Chip</mat-card-title>
</mat-card-header>
<mat-card-content class="b-t-1">
<mat-chip-set aria-label="Dog selection">
<mat-chip>
<img
matChipAvatar
src="https://material.angular.io/assets/img/examples/shiba1.jpg"
alt="Photo of a Shiba Inu"
/>
Dog one
</mat-chip>
<mat-chip>
<img
matChipAvatar
src="https://material.angular.io/assets/img/examples/shiba1.jpg"
alt="Photo of a Shiba Inu"
/>
Dog two
</mat-chip>
<mat-chip>
<img
matChipAvatar
src="https://material.angular.io/assets/img/examples/shiba1.jpg"
alt="Photo of a Shiba Inu"
/>
Dog three
</mat-chip>
</mat-chip-set>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,38 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { MaterialModule } from 'src/app/material.module';
export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}
const ELEMENT_DATA: PeriodicElement[] = [
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
];
@Component({
selector: 'app-starter',
templateUrl: './starter.component.html',
imports: [MaterialModule],
styleUrls: ['./starter.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class StarterComponent {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource = ELEMENT_DATA;
}

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'appFilter', standalone: true, pure: true })
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) {
return [];
}
if (!searchText) {
return items;
}
searchText = searchText.toLocaleLowerCase();
return items.filter((it) => {
return it.displayName.toLocaleLowerCase().includes(searchText);
});
}
}

View File

@@ -0,0 +1,42 @@
import { Injectable, signal } from '@angular/core';
import { AppSettings, defaults } from '../config';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class CoreService {
private optionsSignal = signal<AppSettings>(defaults);
private notify$ = new BehaviorSubject<Record<string, any>>({});
constructor() {
this.notify$.next(this.optionsSignal());
}
// Observable for notification updates
get notify(): Observable<Record<string, any>> {
return this.notify$.asObservable();
}
// Get the current options
getOptions(): AppSettings {
return this.optionsSignal();
}
setOptions(options: Partial<AppSettings>) {
this.optionsSignal.update((current) => ({
...current,
...options,
}));
this.notify$.next(this.optionsSignal);
}
setLanguage(lang: string) {
this.setOptions({ language: lang });
}
getLanguage() {
return this.getOptions().language;
}
}

View File

@@ -0,0 +1,17 @@
import { Injectable, signal } from '@angular/core';
import { Event, NavigationEnd, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class NavService {
showClass: any = false;
public currentUrl = signal<string | undefined>(undefined);
constructor(private router: Router) {
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.currentUrl.set(event.urlAfterRedirects);
}
});
}
}