Compare commits
8 Commits
b7f73033f3
...
fa4a4702c8
Author | SHA1 | Date |
---|---|---|
Igor Hrenowitsch Propisnov | fa4a4702c8 | |
Igor Hrenowitsch Propisnov | 09cc4cf0e7 | |
Igor Hrenowitsch Propisnov | ab9fa52653 | |
Igor Hrenowitsch Propisnov | 8a4800748b | |
Igor Hrenowitsch Propisnov | 062344f65d | |
Igor Hrenowitsch Propisnov | 5246b374c4 | |
Igor Hrenowitsch Propisnov | 2548133558 | |
Igor Hrenowitsch Propisnov | 18320ddd11 |
|
@ -1,36 +0,0 @@
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [AppComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create the app', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have the "frontend" title', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
|
|
||||||
expect(app.title).toEqual('frontend');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render title', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
|
||||||
|
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain(
|
|
||||||
'Hello, frontend'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { Component } from '@angular/core';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
import { ThemeService } from './shared/service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
providers: [],
|
providers: [],
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet, CommonModule],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
public constructor(private readonly themeService: ThemeService) {}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
const publicRoutes: Routes = [
|
const simpleLayoutRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
loadComponent: () =>
|
|
||||||
import('./pages/home-root/home-root.component').then(
|
|
||||||
(m) => m.HomeComponent
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'welcome',
|
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./pages/register-root/register-root.component').then(
|
import('./pages/register-root/register-root.component').then(
|
||||||
(m) => m.RegisterRootComponent
|
(m) => m.RegisterRootComponent
|
||||||
|
@ -38,10 +31,27 @@ const protectedRoutes: Routes = [
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./layout/simple-layout/simple-layout.component').then(
|
||||||
|
(m) => m.LayoutSimpleComponent
|
||||||
|
),
|
||||||
|
children: simpleLayoutRoutes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./layout/main-layout/layout.component').then(
|
||||||
|
(m) => m.LayoutComponent
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
...publicRoutes,
|
{
|
||||||
...protectedRoutes,
|
path: '',
|
||||||
{ path: '', redirectTo: '', pathMatch: 'full' },
|
children: [
|
||||||
|
...protectedRoutes,
|
||||||
|
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ path: '**', redirectTo: '' },
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
<div class="flex h-screen overflow-hidden">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div
|
||||||
|
[ngStyle]="navigation"
|
||||||
|
[class]="
|
||||||
|
isCollapsed
|
||||||
|
? 'bg-primary w-0 md:w-20 transition-all duration-300 ease-in-out'
|
||||||
|
: showMobileMenu
|
||||||
|
? 'bg-primary w-64 transition-all duration-300 ease-in-out'
|
||||||
|
: isDesktopCollapsed
|
||||||
|
? 'bg-primary w-48 md:w-16 transition-all duration-300 ease-in-out'
|
||||||
|
: 'bg-primary w-48 md:w-48 transition-all duration-300 ease-in-out'
|
||||||
|
"
|
||||||
|
class="transform h-full z-20 overflow-y-auto fixed md:relative">
|
||||||
|
<div
|
||||||
|
[ngClass]="showMobileMenu ? 'justify-center' : 'justify-between'"
|
||||||
|
[ngStyle]="navigation"
|
||||||
|
class="p-1 w-full h-16 bg-base-100 flex items-center relative">
|
||||||
|
<div class="flex items-center justify-center h-full w-full">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
@if (!isCollapsed && !isDesktopCollapsed) {
|
||||||
|
<div class="text-primary">Logo</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!isCollapsed && !showMobileMenu) {
|
||||||
|
<button
|
||||||
|
(click)="toggleDesktopSidebar()"
|
||||||
|
class="flex items-center justify-center w-10 h-10 rounded-full">
|
||||||
|
@if (isDesktopCollapsed) {
|
||||||
|
<svg
|
||||||
|
class="stroke-current text-primary w-6 h-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="3">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
} @else {
|
||||||
|
<svg
|
||||||
|
class="stroke-current text-primary w-6 h-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="3">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="m-1">
|
||||||
|
<li
|
||||||
|
class="cursor-pointer rounded-btn"
|
||||||
|
[ngClass]="{
|
||||||
|
'bg-base-100 text-primary': item.active,
|
||||||
|
'text-primary-content hover:text-base-content': !item.active
|
||||||
|
}"
|
||||||
|
(click)="setActive(item)"
|
||||||
|
*ngFor="let item of menuItems">
|
||||||
|
<div
|
||||||
|
class="flex justify-center p-2"
|
||||||
|
*ngIf="isDesktopCollapsed && !showMobileMenu">
|
||||||
|
<span class="p-1" [innerHTML]="item.icon"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center rounded-btn justify-between cursor-pointer px-1 py-2"
|
||||||
|
*ngIf="!isDesktopCollapsed || showMobileMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span [innerHTML]="item.icon" class="mx-2"></span>
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col flex-grow">
|
||||||
|
<header
|
||||||
|
[ngStyle]="navigation"
|
||||||
|
class="p-4 bg-primary text-primary-content flex items-center h-16">
|
||||||
|
<div class="w-10 flex items-center justify-center md:hidden">
|
||||||
|
<label class="btn btn-ghost swap swap-rotate">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
(change)="toggleSidebar()"
|
||||||
|
[checked]="!isCollapsed" />
|
||||||
|
<svg
|
||||||
|
class="swap-off fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 512 512">
|
||||||
|
<path
|
||||||
|
d="M64,384H448V341.33H64Zm0-106.67H448V234.67H64ZM64,128v42.67H448V128Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
class="swap-on fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 512 512">
|
||||||
|
<polygon
|
||||||
|
points="400 145.49 366.51 112 256 222.51 145.49 112 112 145.49 222.51 256 112 366.51 145.49 400 256 289.49 366.51 400 400 366.51 289.49 256 400 145.49" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div [ngStyle]="mainContent" class="px-8 py-4 flex-grow text-2xl p-4">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="!isCollapsed"
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 z-10 md:hidden"
|
||||||
|
(click)="toggleSidebar()"></div>
|
||||||
|
</div>
|
|
@ -0,0 +1,139 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
HostListener,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
import { Router, RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
import { BackgroundPatternService, ThemeService } from '../../shared/service';
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
name: string;
|
||||||
|
icon: SafeHtml;
|
||||||
|
route: string;
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-layout',
|
||||||
|
standalone: true,
|
||||||
|
providers: [],
|
||||||
|
imports: [RouterOutlet, CommonModule],
|
||||||
|
templateUrl: './layout.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class LayoutComponent implements OnInit {
|
||||||
|
public isCollapsed: boolean = false;
|
||||||
|
public isDesktopCollapsed: boolean = false;
|
||||||
|
public showMobileMenu: boolean = false;
|
||||||
|
public userHasInteracted: boolean = false;
|
||||||
|
public menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
name: 'Dashboard',
|
||||||
|
route: '/dashboard',
|
||||||
|
icon: this.sanitizer
|
||||||
|
.bypassSecurityTrustHtml(`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
||||||
|
</svg>`),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
public mainContent: { 'background-image': string } | null = null;
|
||||||
|
public navigation: { 'background-image': string } | null = null;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly sanitizer: DomSanitizer,
|
||||||
|
private readonly router: Router,
|
||||||
|
private readonly backgroundPatternService: BackgroundPatternService,
|
||||||
|
private readonly themeService: ThemeService,
|
||||||
|
private readonly el: ElementRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.setActiveItemBasedOnRoute();
|
||||||
|
this.router.events.subscribe(() => {
|
||||||
|
this.setActiveItemBasedOnRoute();
|
||||||
|
});
|
||||||
|
this.setBackground();
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
public onResize(): void {
|
||||||
|
if (window.innerWidth >= 768) {
|
||||||
|
this.showMobileMenu = false;
|
||||||
|
this.isCollapsed = false;
|
||||||
|
} else {
|
||||||
|
this.isDesktopCollapsed = false;
|
||||||
|
this.isCollapsed = true;
|
||||||
|
this.showMobileMenu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setBackground(): void {
|
||||||
|
const theme = this.themeService.getTheme();
|
||||||
|
let opacity: number;
|
||||||
|
|
||||||
|
if (theme === 'dark') {
|
||||||
|
opacity = 0.05;
|
||||||
|
} else {
|
||||||
|
opacity = 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorPrimary = getComputedStyle(
|
||||||
|
this.el.nativeElement
|
||||||
|
).getPropertyValue('--p');
|
||||||
|
|
||||||
|
const colorPrimaryC = getComputedStyle(
|
||||||
|
this.el.nativeElement
|
||||||
|
).getPropertyValue('--pc');
|
||||||
|
|
||||||
|
const svgUrlMainContent = this.backgroundPatternService.getPlusPattern(
|
||||||
|
colorPrimary,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
const svgUrlNavigation = this.backgroundPatternService.getBankNotePattern(
|
||||||
|
colorPrimaryC,
|
||||||
|
opacity
|
||||||
|
);
|
||||||
|
|
||||||
|
this.mainContent = {
|
||||||
|
'background-image': `url("${svgUrlMainContent}")`,
|
||||||
|
};
|
||||||
|
this.navigation = {
|
||||||
|
'background-image': `url("${svgUrlNavigation}")`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleSidebar(): void {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
this.showMobileMenu = !this.showMobileMenu;
|
||||||
|
this.isCollapsed = !this.showMobileMenu;
|
||||||
|
} else {
|
||||||
|
this.isDesktopCollapsed = !this.isDesktopCollapsed;
|
||||||
|
}
|
||||||
|
this.userHasInteracted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleDesktopSidebar(): void {
|
||||||
|
this.isDesktopCollapsed = !this.isDesktopCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setActive(item: MenuItem): void {
|
||||||
|
this.menuItems.forEach((menu: MenuItem) => {
|
||||||
|
menu.active = false;
|
||||||
|
});
|
||||||
|
item.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setActiveItemBasedOnRoute(): void {
|
||||||
|
const url = this.router.url;
|
||||||
|
|
||||||
|
this.menuItems.forEach((item: MenuItem) => {
|
||||||
|
item.active = url.startsWith(item.route);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-simple-layout',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterOutlet],
|
||||||
|
template: `
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class LayoutSimpleComponent {}
|
|
@ -1 +1 @@
|
||||||
<h1>Hello World</h1>
|
<h1>Dashboard Works</h1>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard-root',
|
selector: 'app-dashboard-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [CommonModule],
|
||||||
providers: [],
|
providers: [],
|
||||||
templateUrl: './dashboard-root.component.html',
|
templateUrl: './dashboard-root.component.html',
|
||||||
styleUrl: './dashboard-root.component.scss',
|
styleUrl: './dashboard-root.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DashboardRootComponent {
|
export class DashboardRootComponent {}
|
||||||
public constructor() {}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue