From 2256219431a9eb8488bf00f92b19bff5349583f7 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 28 Feb 2025 12:52:28 +0200 Subject: [PATCH 1/2] basic layout --- angular.json | 3 +- src/app/app.component.html | 376 +----------------- src/app/app.component.scss | 42 ++ src/app/app.component.ts | 15 +- src/app/footer/footer.component.html | 1 + src/app/footer/footer.component.scss | 4 + src/app/footer/footer.component.spec.ts | 22 + src/app/footer/footer.component.ts | 10 + src/app/header/header.component.html | 2 + src/app/header/header.component.scss | 7 + src/app/header/header.component.spec.ts | 22 + src/app/header/header.component.ts | 10 + .../main-content/main-content.component.html | 3 + .../main-content/main-content.component.scss | 12 + .../main-content.component.spec.ts | 22 + .../main-content/main-content.component.ts | 10 + src/app/sidenav/sidenav.component.html | 6 + src/app/sidenav/sidenav.component.scss | 7 + src/app/sidenav/sidenav.component.spec.ts | 22 + src/app/sidenav/sidenav.component.ts | 10 + src/app/topnav/topnav.component.html | 1 + src/app/topnav/topnav.component.scss | 5 + src/app/topnav/topnav.component.spec.ts | 22 + src/app/topnav/topnav.component.ts | 10 + src/index.html | 2 +- src/styles.scss | 10 +- 26 files changed, 294 insertions(+), 362 deletions(-) create mode 100644 src/app/footer/footer.component.html create mode 100644 src/app/footer/footer.component.scss create mode 100644 src/app/footer/footer.component.spec.ts create mode 100644 src/app/footer/footer.component.ts create mode 100644 src/app/header/header.component.html create mode 100644 src/app/header/header.component.scss create mode 100644 src/app/header/header.component.spec.ts create mode 100644 src/app/header/header.component.ts create mode 100644 src/app/main-content/main-content.component.html create mode 100644 src/app/main-content/main-content.component.scss create mode 100644 src/app/main-content/main-content.component.spec.ts create mode 100644 src/app/main-content/main-content.component.ts create mode 100644 src/app/sidenav/sidenav.component.html create mode 100644 src/app/sidenav/sidenav.component.scss create mode 100644 src/app/sidenav/sidenav.component.spec.ts create mode 100644 src/app/sidenav/sidenav.component.ts create mode 100644 src/app/topnav/topnav.component.html create mode 100644 src/app/topnav/topnav.component.scss create mode 100644 src/app/topnav/topnav.component.spec.ts create mode 100644 src/app/topnav/topnav.component.ts diff --git a/angular.json b/angular.json index 86b51d27d..869e57f65 100644 --- a/angular.json +++ b/angular.json @@ -111,6 +111,7 @@ "cli": { "schematicCollections": [ "angular-eslint" - ] + ], + "analytics": false } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 5a108b4e6..e426c9274 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,360 +1,22 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for ( - item of [ - { title: "Explore the Docs", link: "https://angular.dev" }, - { - title: "Learn with Tutorials", - link: "https://angular.dev/tutorials", - }, - { title: "CLI Docs", link: "https://angular.dev/tools/cli" }, - { - title: "Angular Language Service", - link: "https://angular.dev/tools/language-service", - }, - { - title: "Angular DevTools", - link: "https://angular.dev/tools/devtools", - }, - ]; - track item.title - ) { - - {{ item.title }} - - - - - } +@if (isDesktop) { +
+ +
+
+ + +
-
+
+} @else { +
+
+
+ + +
-
+
- - - - - - - - - - - +} diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29bb..946ee6b10 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,42 @@ +.layout-desktop { + display: flex; + height: 100vh; + + .content { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + width: 100%; + border: 6px solid #24384a; + border-radius: 12px; + box-sizing: content-box; + + .content-wrapper { + position: relative; + display: flex; + flex-direction: column; + flex: 1; + } + } +} + +.layout-tablet { + display: flex; + height: 100vh; + + .content { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + width: 100%; + + .content-wrapper { + position: relative; + display: flex; + flex-direction: column; + flex: 1; + } + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a3e0702c5..c41aca422 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,9 +1,21 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SidenavComponent } from '@osf/sidenav/sidenav.component'; import { RouterOutlet } from '@angular/router'; +import { HeaderComponent } from '@osf/header/header.component'; +import { MainContentComponent } from '@osf/main-content/main-content.component'; +import { FooterComponent } from '@osf/footer/footer.component'; +import { TopnavComponent } from '@osf/topnav/topnav.component'; @Component({ selector: 'osf-root', - imports: [RouterOutlet], + imports: [ + SidenavComponent, + RouterOutlet, + HeaderComponent, + MainContentComponent, + FooterComponent, + TopnavComponent, + ], templateUrl: './app.component.html', styleUrl: './app.component.scss', standalone: true, @@ -11,4 +23,5 @@ import { RouterOutlet } from '@angular/router'; }) export class AppComponent { title = 'osf'; + isDesktop = window.innerWidth > 1024; } diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html new file mode 100644 index 000000000..28c0d7d79 --- /dev/null +++ b/src/app/footer/footer.component.html @@ -0,0 +1 @@ +

footer works!

diff --git a/src/app/footer/footer.component.scss b/src/app/footer/footer.component.scss new file mode 100644 index 000000000..64a90091f --- /dev/null +++ b/src/app/footer/footer.component.scss @@ -0,0 +1,4 @@ +:host { + height: 50px; + background: aqua; +} diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts new file mode 100644 index 000000000..0070205d8 --- /dev/null +++ b/src/app/footer/footer.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FooterComponent } from './footer.component'; + +describe('FooterComponent', () => { + let component: FooterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FooterComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts new file mode 100644 index 000000000..711bcacdf --- /dev/null +++ b/src/app/footer/footer.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'osf-footer', + imports: [], + templateUrl: './footer.component.html', + styleUrl: './footer.component.scss', +}) +export class FooterComponent {} diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html new file mode 100644 index 000000000..ddbaa8fc6 --- /dev/null +++ b/src/app/header/header.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/header/header.component.scss b/src/app/header/header.component.scss new file mode 100644 index 000000000..49b29c8fa --- /dev/null +++ b/src/app/header/header.component.scss @@ -0,0 +1,7 @@ +:host { + position: absolute; + height: 50px; + top: 0; + right: 0; + background: transparent; +} diff --git a/src/app/header/header.component.spec.ts b/src/app/header/header.component.spec.ts new file mode 100644 index 000000000..cf5779579 --- /dev/null +++ b/src/app/header/header.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeaderComponent } from './header.component'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HeaderComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts new file mode 100644 index 000000000..011749b13 --- /dev/null +++ b/src/app/header/header.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'osf-header', + imports: [], + templateUrl: './header.component.html', + styleUrl: './header.component.scss', +}) +export class HeaderComponent {} diff --git a/src/app/main-content/main-content.component.html b/src/app/main-content/main-content.component.html new file mode 100644 index 000000000..aec502cfa --- /dev/null +++ b/src/app/main-content/main-content.component.html @@ -0,0 +1,3 @@ +
+ main-content works! +
diff --git a/src/app/main-content/main-content.component.scss b/src/app/main-content/main-content.component.scss new file mode 100644 index 000000000..be3b91cdb --- /dev/null +++ b/src/app/main-content/main-content.component.scss @@ -0,0 +1,12 @@ +:host { + min-height: calc(100% - 100px); + padding-top: 50px; + background: antiquewhite; + + .content-body { + flex: 1; + } + .main-container { + height: 100px; + } +} diff --git a/src/app/main-content/main-content.component.spec.ts b/src/app/main-content/main-content.component.spec.ts new file mode 100644 index 000000000..face57c42 --- /dev/null +++ b/src/app/main-content/main-content.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MainContentComponent } from './main-content.component'; + +describe('MainContentComponent', () => { + let component: MainContentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MainContentComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(MainContentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/main-content/main-content.component.ts b/src/app/main-content/main-content.component.ts new file mode 100644 index 000000000..b8dbf3e05 --- /dev/null +++ b/src/app/main-content/main-content.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'osf-main-content', + imports: [], + templateUrl: './main-content.component.html', + styleUrl: './main-content.component.scss', +}) +export class MainContentComponent {} diff --git a/src/app/sidenav/sidenav.component.html b/src/app/sidenav/sidenav.component.html new file mode 100644 index 000000000..3442f3ab8 --- /dev/null +++ b/src/app/sidenav/sidenav.component.html @@ -0,0 +1,6 @@ +

OSF

+
    +
  • Home
  • +
  • Search OSF
  • +
  • Support
  • +
diff --git a/src/app/sidenav/sidenav.component.scss b/src/app/sidenav/sidenav.component.scss new file mode 100644 index 000000000..bfd821be9 --- /dev/null +++ b/src/app/sidenav/sidenav.component.scss @@ -0,0 +1,7 @@ +:host { + display: flex; + flex-direction: column; + width: 250px; + height: 100%; + color: white; +} diff --git a/src/app/sidenav/sidenav.component.spec.ts b/src/app/sidenav/sidenav.component.spec.ts new file mode 100644 index 000000000..bcd47dfda --- /dev/null +++ b/src/app/sidenav/sidenav.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SidenavComponent } from './sidenav.component'; + +describe('SidenavDComponent', () => { + let component: SidenavComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SidenavComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SidenavComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sidenav/sidenav.component.ts b/src/app/sidenav/sidenav.component.ts new file mode 100644 index 000000000..2a69cd824 --- /dev/null +++ b/src/app/sidenav/sidenav.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'osf-sidenav', + imports: [], + templateUrl: './sidenav.component.html', + styleUrl: './sidenav.component.scss', +}) +export class SidenavComponent {} diff --git a/src/app/topnav/topnav.component.html b/src/app/topnav/topnav.component.html new file mode 100644 index 000000000..bda3aad39 --- /dev/null +++ b/src/app/topnav/topnav.component.html @@ -0,0 +1 @@ +

OSF

diff --git a/src/app/topnav/topnav.component.scss b/src/app/topnav/topnav.component.scss new file mode 100644 index 000000000..f88421c37 --- /dev/null +++ b/src/app/topnav/topnav.component.scss @@ -0,0 +1,5 @@ +:host { + background: #24384a; + height: 96px; + color: white; +} diff --git a/src/app/topnav/topnav.component.spec.ts b/src/app/topnav/topnav.component.spec.ts new file mode 100644 index 000000000..4b7235b54 --- /dev/null +++ b/src/app/topnav/topnav.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TopnavComponent } from './topnav.component'; + +describe('TopnavComponent', () => { + let component: TopnavComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TopnavComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(TopnavComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/topnav/topnav.component.ts b/src/app/topnav/topnav.component.ts new file mode 100644 index 000000000..04e47034d --- /dev/null +++ b/src/app/topnav/topnav.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'osf-topnav', + imports: [], + templateUrl: './topnav.component.html', + styleUrl: './topnav.component.scss', +}) +export class TopnavComponent {} diff --git a/src/index.html b/src/index.html index e3d3fb501..7bb289d81 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,6 @@ - + diff --git a/src/styles.scss b/src/styles.scss index 1d812c2c6..a0951c557 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,6 +1,12 @@ /* You can add global styles to this file, and also import other style files */ -@layer pringe-ng-override { - +@layer pringe-ng-override { } +html, +body { + height: 100%; + margin: 0; + padding: 0; + background: #24384a; +} From 2ff00310035827871f479f50a4dffa2fcc155a20 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 28 Feb 2025 13:52:11 +0200 Subject: [PATCH 2/2] use breakpoints --- package-lock.json | 21 +++++++++++++++++-- package.json | 1 + src/app/app.component.html | 2 +- src/app/app.component.ts | 8 +++++--- src/app/shared/utils/breakpoints.tokens.ts | 24 ++++++++++++++++++++++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 src/app/shared/utils/breakpoints.tokens.ts diff --git a/package-lock.json b/package-lock.json index 55fe37629..b34738f15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "osf", "version": "0.0.0", "dependencies": { + "@angular/cdk": "^19.2.1", "@angular/common": "^19.2.0", "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", @@ -1018,6 +1019,22 @@ "@esbuild/win32-x64": "0.24.2" } }, + "node_modules/@angular/cdk": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.1.tgz", + "integrity": "sha512-j7dg18PJIbyeU4DTko3vIK3M2OuUv3H0ZViNddOaLlGN5X93cq4QCGcNhcGm3x3r5rUr/AaexYu+KHMyN8PwmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.0.tgz", @@ -8137,7 +8154,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12" }, @@ -12379,7 +12396,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, + "devOptional": true, "dependencies": { "entities": "^4.5.0" }, diff --git a/package.json b/package.json index c572531d7..2bb8a0571 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "private": true, "dependencies": { + "@angular/cdk": "^19.2.1", "@angular/common": "^19.2.0", "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", diff --git a/src/app/app.component.html b/src/app/app.component.html index e426c9274..91925a99c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,4 @@ -@if (isDesktop) { +@if (!isPortrait()) {
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c41aca422..e529d68ac 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,12 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { SidenavComponent } from '@osf/sidenav/sidenav.component'; import { RouterOutlet } from '@angular/router'; import { HeaderComponent } from '@osf/header/header.component'; import { MainContentComponent } from '@osf/main-content/main-content.component'; import { FooterComponent } from '@osf/footer/footer.component'; import { TopnavComponent } from '@osf/topnav/topnav.component'; +import { IS_PORTRAIT } from '@shared/utils/breakpoints.tokens'; +import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'osf-root', @@ -22,6 +24,6 @@ import { TopnavComponent } from '@osf/topnav/topnav.component'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { - title = 'osf'; - isDesktop = window.innerWidth > 1024; + private isPortrait$ = inject(IS_PORTRAIT); + isPortrait = toSignal(this.isPortrait$); } diff --git a/src/app/shared/utils/breakpoints.tokens.ts b/src/app/shared/utils/breakpoints.tokens.ts new file mode 100644 index 000000000..b39c89d5c --- /dev/null +++ b/src/app/shared/utils/breakpoints.tokens.ts @@ -0,0 +1,24 @@ +import { inject, InjectionToken } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { Observable, map } from 'rxjs'; + +function createBreakpointToken( + query: string, +): InjectionToken> { + return new InjectionToken>(`Breakpoint ${query}`, { + providedIn: 'root', + factory: () => { + const breakpointObserver = inject(BreakpointObserver); + return breakpointObserver + .observe([query]) + .pipe(map((result) => result.matches)); + }, + }); +} + +export const IS_XSMALL = createBreakpointToken(Breakpoints.XSmall); +export const IS_SMALL = createBreakpointToken(Breakpoints.Small); +export const IS_MEDIUM = createBreakpointToken(Breakpoints.Medium); +export const IS_LARGE = createBreakpointToken(Breakpoints.Large); +export const IS_XLARGE = createBreakpointToken(Breakpoints.XLarge); +export const IS_PORTRAIT = createBreakpointToken('(orientation: portrait)');