From 1db0a7121f2e0e2ffb27d0af3d1f9ccf17af1b29 Mon Sep 17 00:00:00 2001 From: "INFRAGISTICS\\IPetrov" Date: Tue, 2 Jun 2026 16:16:37 +0300 Subject: [PATCH 01/38] feat(igx-templates): add side-nav-mini project template with collapsible mini nav --- .../side-nav-mini/files/src/app/app.config.ts | 29 ++++++ .../side-nav-mini/files/src/app/app.html | 30 +++++++ .../side-nav-mini/files/src/app/app.routes.ts | 11 +++ .../side-nav-mini/files/src/app/app.scss | 55 ++++++++++++ .../side-nav-mini/files/src/app/app.spec.ts | 20 +++++ .../side-nav-mini/files/src/app/app.ts | 88 +++++++++++++++++++ .../igx-ts/projects/side-nav-mini/index.ts | 19 ++++ 7 files changed, 252 insertions(+) create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.config.ts create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.html create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.routes.ts create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.scss create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.spec.ts create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.ts create mode 100644 packages/igx-templates/igx-ts/projects/side-nav-mini/index.ts diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.config.ts b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.config.ts new file mode 100644 index 000000000..3cd4ad001 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.config.ts @@ -0,0 +1,29 @@ +import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; +import { BrowserModule, HammerModule } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; +import { + IgxNavigationDrawerModule, + IgxNavbarModule, + IgxIconModule, + IgxRippleModule +} from '<%=igxPackage%>'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom( + BrowserModule, + HammerModule, + IgxNavigationDrawerModule, + IgxNavbarModule, + IgxIconModule, + IgxRippleModule + ), + provideAnimations() + ] +}; diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.html b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.html new file mode 100644 index 000000000..aa6e17e54 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.html @@ -0,0 +1,30 @@ + + + + + +
+ + + Views + @for (link of topNavLinks; track link) { + + {{link.icon}} + {{link.name}} + + } + + + @for (link of topNavLinks; track link) { + + {{link.icon}} + + } + + +
+ +
+
diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.routes.ts b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.routes.ts new file mode 100644 index 000000000..d86b75332 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.routes.ts @@ -0,0 +1,11 @@ +import { Routes } from '@angular/router'; +import { Home } from './home/home'; +import { NotFound } from './error-routing/not-found/not-found'; +import { UncaughtError } from './error-routing/error/uncaught-error'; + +export const routes: Routes = [ + { path: '', redirectTo: '/home', pathMatch: 'full'}, + { path: 'home', component: Home, data: { text: 'Home', icon: 'home' }}, + { path: 'error', component: UncaughtError }, + { path: '**', component: NotFound } // must always be last +]; diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.scss b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.scss new file mode 100644 index 000000000..9a8437b9b --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.scss @@ -0,0 +1,55 @@ +// Root element fills the viewport and stacks navbar above content +app-root { + height: 100%; + display: flex; + flex-direction: column; +} + +// Navbar spans full width; height is determined by igx-navbar itself +.app-navbar { + flex-shrink: 0; + width: 100%; +} + +// Row container holds the nav drawer and the page content side by side +.row-layout { + display: flex; + flex-direction: row; + flex: 1 1 auto; + min-height: 0; // allow flex children to shrink below their content height +} + +// Page content fills remaining horizontal space and scrolls independently +.view-container { + flex: 1 1 auto; + min-width: 0; + overflow: auto; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: flex-start; + + > *:not(router-outlet) { + padding: 0 24px; + width: 100%; + } + + p { + margin: 15px; + } +} + +// On small screens: hide the burger button — the nav is always in mini mode +@media only screen and (max-width: 1024px) { + .nav-toggle-btn { + display: none; + } + + .view-container { + justify-content: flex-start; + + > *:not(router-outlet) { + padding: 0 15px; + } + } +} diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.spec.ts b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.spec.ts new file mode 100644 index 000000000..a93a46d34 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.spec.ts @@ -0,0 +1,20 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterModule } from '@angular/router'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterModule.forRoot([]), + App + ] + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); +}); diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.ts b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.ts new file mode 100644 index 000000000..118bc6b15 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/files/src/app/app.ts @@ -0,0 +1,88 @@ +import { Component, HostListener, OnInit, AfterViewInit, viewChild, ViewEncapsulation, inject } from '@angular/core'; +import { NavigationStart, Router, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router'; +import { + IgxNavigationDrawerComponent, + IgxNavDrawerTemplateDirective, + IgxNavDrawerMiniTemplateDirective, + IgxNavDrawerItemDirective, + IgxRippleDirective, + IgxNavbarComponent, + IgxNavbarActionDirective, + IgxIconComponent, + IgxIconButtonDirective, +} from 'igniteui-angular'; +import { filter } from 'rxjs/operators'; +import { routes } from './app.routes'; + +const MINI_BREAKPOINT = 1024; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.scss', + encapsulation: ViewEncapsulation.None, + imports: [ + IgxNavigationDrawerComponent, + IgxNavDrawerTemplateDirective, + IgxNavDrawerMiniTemplateDirective, + IgxNavDrawerItemDirective, + IgxRippleDirective, + RouterLinkActive, + RouterLink, + IgxNavbarComponent, + IgxNavbarActionDirective, + IgxIconComponent, + IgxIconButtonDirective, + RouterOutlet + ] +}) +export class App implements OnInit, AfterViewInit { + // Start open on wide screens, collapsed (mini) on narrow screens + public readonly initiallyOpen = window.innerWidth > MINI_BREAKPOINT; + public topNavLinks: Array<{ path: string; name: string; icon: string }> = []; + + public navdrawer = viewChild.required(IgxNavigationDrawerComponent); + + private router = inject(Router); + + constructor() { + for (const route of routes) { + if (route.path && route.data && route.path.indexOf('*') === -1) { + this.topNavLinks.push({ + name: route.data['text'], + icon: route.data['icon'] ?? 'radio_button_unchecked', + path: '/' + route.path + }); + } + } + } + + public ngOnInit(): void { + this.router.events.pipe( + filter((x): x is NavigationStart => x instanceof NavigationStart) + ).subscribe(() => { + // On small screens the nav stays in mini mode — nothing to close + if (window.innerWidth <= MINI_BREAKPOINT) { + return; + } + }); + } + + public ngAfterViewInit(): void { + this.updateDrawerState(); + } + + /** Burger menu toggle — only active on large screens */ + public toggleNav(): void { + this.navdrawer().toggle(); + } + + @HostListener('window:resize') + public updateDrawerState(): void { + const isWide = window.innerWidth > MINI_BREAKPOINT; + if (!isWide && this.navdrawer().isOpen) { + // Collapse to mini on small screens + this.navdrawer().close(); + } + } +} diff --git a/packages/igx-templates/igx-ts/projects/side-nav-mini/index.ts b/packages/igx-templates/igx-ts/projects/side-nav-mini/index.ts new file mode 100644 index 000000000..992810c78 --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/side-nav-mini/index.ts @@ -0,0 +1,19 @@ +import { ProjectTemplate } from "@igniteui/cli-core"; +import * as path from "path"; +import { SideNavProject } from "../side-nav"; + +export class SideNavMiniProject extends SideNavProject implements ProjectTemplate { + public id: string = "side-nav-mini"; + public name = "Side navigation + collapsible mini nav"; + public description = "Side navigation with a collapsible mini (icons-only) variant and responsive breakpoints"; + public dependencies: string[] = []; + public framework: string = "angular"; + public projectType: string = "igx-ts"; + public hasExtraConfiguration = false; + + public get templatePaths() { + return [...super.templatePaths, path.join(__dirname, "files")]; + } +} + +export default new SideNavMiniProject(); From d3019de763614866fb30036cf5510f57292d7561 Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Wed, 3 Jun 2026 13:25:35 +0300 Subject: [PATCH 02/38] fix(cli:templates): align angular side-nav template layout --- .../files/src/app/app.routes.ts | 2 +- .../files/src/app/home/home.html | 46 ++++- .../files/src/app/home/home.scss | 109 +++++++++-- .../files/src/app/home/home.ts | 2 + .../files/src/assets/indigo-design.svg | 183 ++++++++++++++++++ .../projects/side-nav/files/src/app/app.html | 28 +-- .../projects/side-nav/files/src/app/app.scss | 28 +-- .../projects/side-nav/files/src/app/app.ts | 8 +- .../responsive.gif | Bin 9 files changed, 345 insertions(+), 61 deletions(-) create mode 100644 packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/indigo-design.svg rename packages/igx-templates/{igx-ts/projects/_base_with_home/files/src/assets => reference-assets}/responsive.gif (100%) diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/app.routes.ts b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/app.routes.ts index c829ed7fc..d86b75332 100644 --- a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/app.routes.ts +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/app.routes.ts @@ -5,7 +5,7 @@ import { UncaughtError } from './error-routing/error/uncaught-error'; export const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full'}, - { path: 'home', component: Home, data: { text: 'Home' }}, + { path: 'home', component: Home, data: { text: 'Home', icon: 'home' }}, { path: 'error', component: UncaughtError }, { path: '**', component: NotFound } // must always be last ]; diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html index 9189b1228..567319af8 100644 --- a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html @@ -1,9 +1,39 @@ - -

{{title()}}

-Ignite UI CLI +
+
+

{{title()}}

+

A routed application shell with a responsive side navigation drawer and curated Ignite UI resources.

+
- + Ignite UI for Angular + + +
diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.scss b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.scss index ae3afa812..2e64687e7 100644 --- a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.scss +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.scss @@ -1,35 +1,108 @@ -a { - color: #731963; - text-decoration: none; +:host { + display: flex; + width: 100%; + min-height: 100%; +} + +.home { + width: 100%; + max-width: 920px; + margin: auto; + padding: 48px 24px; +} + +.home__intro { + max-width: 720px; + margin: 0 auto 24px; + text-align: center; } h1 { + margin: 0 0 12px; + color: #09f; font-size: 3rem; font-weight: 600; - color: rgba(0, 0, 0, .74); + line-height: 1.2; } -h3 { - font-size: 1.75rem; - font-weight: 600; +.home__description { + margin: 0; + color: #000; + font-size: 1.125rem; + line-height: 1.5; } -.links{ - text-align: center; - padding: 0 35px; +.home__image { + display: block; + width: 500px; + height: 350px; + margin: 0 auto 28px; + max-width: 100%; + object-fit: contain; } -#linksContainer { - flex-flow: row wrap; - display: flex; +.home__resources { + display: grid; + grid-template-columns: repeat(2, 300px); justify-content: center; + margin: 0 auto; + gap: 12px 64px; } -:host{ - width: 100%; - text-align:center; +.home__resource { + display: flex; + align-items: center; + min-height: 64px; + padding: 6px 0; + color: rgba(0, 0, 0, .74); + text-align: left; + text-decoration: none; +} + +.home__resource:hover strong { + text-decoration: underline; +} + +.home__resource igx-icon { + flex: 0 0 28px; + margin-right: 16px; + color: #09f; + font-size: 28px; +} + +.home__resource strong, +.home__resource small { + display: block; } -img { - max-width:100%; +.home__resource strong { + margin-bottom: 2px; + color: #731963; + font-size: 1rem; +} + +.home__resource small { + color: #000; + font-size: 0.875rem; + line-height: 1.4; +} + +@media only screen and (max-width: 768px) { + .home { + padding: 32px 16px; + } + + h1 { + font-size: 2.25rem; + } + + .home__image { + width: 100%; + height: 240px; + } + + .home__resources { + grid-template-columns: 1fr; + gap: 4px; + } } diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.ts b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.ts index 4bd1b324a..3001902ed 100644 --- a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.ts +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.ts @@ -1,7 +1,9 @@ import { Component, signal } from '@angular/core'; +import { IgxIconComponent } from 'igniteui-angular'; @Component({ selector: 'app-home', + imports: [IgxIconComponent], templateUrl: './home.html', styleUrl: './home.scss' }) diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/indigo-design.svg b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/indigo-design.svg new file mode 100644 index 000000000..d1651e7be --- /dev/null +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/indigo-design.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + import { IgxGridModule } + from 'igniteui-angular'; + @NgModule({ + imports: [ + ... + IgxGridModule + ... + ] + }) + export class AppModule {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.html b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.html index 3e6ad582c..371bb8999 100644 --- a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.html +++ b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.html @@ -1,16 +1,18 @@ -
- - - Views - @for (route of topNavLinks; track route) { - {{route.name}} - } - - -
- -
+
+ +
+ + + @for (route of topNavLinks; track route) { + + {{route.icon}} + {{route.name}} + + } + + +
-
+
diff --git a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.scss b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.scss index 6f341092a..98ee77920 100644 --- a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.scss +++ b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.scss @@ -1,30 +1,20 @@ .outer-wrapper { + display: flex; + flex-flow: column nowrap; height: 100%; + overflow: hidden; +} + +.app-body { + flex: 1 1 auto; + min-height: 0; } .content { - width: 100%; + flex: 1 1 auto; display: flex; flex-flow: row nowrap; justify-content: center; align-items: stretch; overflow: auto; - - > *:not(router-outlet) { - padding: 0 24px; - } - - p { - margin: 15px; - } -} - -@media only screen and (max-width: 1024px) { - .content { - justify-content: flex-start; - - > *:not(router-outlet) { - padding: 0 15px; - } - } } diff --git a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.ts b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.ts index 417ea95e3..9056d136a 100644 --- a/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.ts +++ b/packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.ts @@ -8,6 +8,7 @@ import { IgxRippleDirective, IgxFlexDirective, IgxNavbarComponent, + IgxIconComponent, } from 'igniteui-angular'; import { filter } from 'rxjs/operators'; import { routes } from './app.routes'; @@ -27,13 +28,15 @@ import { routes } from './app.routes'; RouterLink, IgxFlexDirective, IgxNavbarComponent, + IgxIconComponent, RouterOutlet ] }) export class App implements OnInit { public topNavLinks: { path: string, - name: string + name: string, + icon: string }[] = []; public navdrawer = viewChild.required(IgxNavigationDrawerComponent); @@ -45,7 +48,8 @@ export class App implements OnInit { if (route.path && route.data && route.path.indexOf('*') === -1) { this.topNavLinks.push({ name: route.data['text'], - path: '/' + route.path + path: '/' + route.path, + icon: route.data['icon'] || 'home' }); } } diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/responsive.gif b/packages/igx-templates/reference-assets/responsive.gif similarity index 100% rename from packages/igx-templates/igx-ts/projects/_base_with_home/files/src/assets/responsive.gif rename to packages/igx-templates/reference-assets/responsive.gif From 0cc556b7fb36290b692ab7f6ac55af661f0ac407 Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Wed, 3 Jun 2026 13:29:25 +0300 Subject: [PATCH 03/38] fix(cli:templates): align web components side-nav template layout --- .../side-nav/files/src/app/app-routing.ts | 9 + .../projects/side-nav/files/src/app/app.ts | 209 ++++++++++++++++-- .../side-nav/files/src/app/home/home.ts | 195 ++++++++++++++++ 3 files changed, 391 insertions(+), 22 deletions(-) create mode 100644 packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts create mode 100644 packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts diff --git a/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts new file mode 100644 index 000000000..6106a4a45 --- /dev/null +++ b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts @@ -0,0 +1,9 @@ +import { type Route } from '@vaadin/router'; +import './home/home.js'; +import './not-found/not-found.js'; + +export const routes: Array = [ + { path: '/', component: 'app-home', name: 'Home', icon: 'home' }, + // The fallback route should always be after other alternatives. + { path: '(.*)', component: 'app-not-found' }, +]; diff --git a/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts index 713b5157c..03335fa6a 100644 --- a/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts +++ b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts @@ -1,58 +1,223 @@ -import { html, css, LitElement } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { Router } from '@vaadin/router'; +import { css, html, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; import { defineComponents, + IgcIconComponent, IgcNavDrawerComponent, - IgcRippleComponent, + IgcNavDrawerItemComponent, + registerIconFromText, } from 'igniteui-webcomponents'; -import { Router } from '@vaadin/router'; import { routes } from './app-routing.js'; +type AppRoute = { + path: string; + name?: string; + icon?: string; +}; + +const icons = [ + { + name: 'home', + value: '', + }, + { + name: 'menu', + value: '', + }, +]; + defineComponents( + IgcIconComponent, IgcNavDrawerComponent, - IgcRippleComponent, + IgcNavDrawerItemComponent, ); +icons.forEach((icon) => registerIconFromText(icon.name, icon.value, 'material')); + @customElement('app-root') export default class App extends LitElement { + @state() + private drawerOpen = true; + + @state() + private drawerPosition: 'relative' | 'start' = 'relative'; + + @state() + private currentPath = window.location.pathname; + + private mediaQuery?: MediaQueryList; + static styles = css` :host { display: flex; height: 100%; } - router-outlet { - height: 100%; - width: 100%; + .app { display: flex; - text-align: center; flex-flow: column nowrap; - justify-content: stretch; + width: 100%; + height: 100%; + overflow: hidden; + } + + .app__navbar { + display: flex; align-items: center; - padding: 1rem; + flex: 0 0 auto; + height: 56px; + padding: 0 16px; + background: #239ef0; + box-shadow: 0 2px 4px rgba(0, 0, 0, .24); box-sizing: border-box; } + + .app__menu-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + padding: 0; + color: #000; + border: 0; + background: transparent; + cursor: pointer; + } + + .app__menu-button igc-icon { + font-size: 24px; + } + + .app__title { + margin: 0 0 0 16px; + color: #000; + font-size: 1.25rem; + font-weight: 600; + line-height: 1; + } + + .app__body { + display: flex; + flex: 1 1 auto; + min-height: 0; + } + + .app__drawer { + flex: 0 0 auto; + height: 100%; + --ig-nav-drawer-size: 280px; + --ig-navdrawer-item-active-background: #e0f2ff; + --ig-navdrawer-item-active-text-color: #0075d2; + --ig-navdrawer-item-active-icon-color: #0075d2; + } + + igc-nav-drawer-item[active]::part(base) { + background: #e0f2ff; + color: #0075d2; + } + + igc-nav-drawer-item[active] igc-icon { + color: #0075d2; + } + + router-outlet { + flex: 1 1 auto; + display: flex; + align-items: stretch; + justify-content: center; + min-width: 0; + overflow: auto; + } `; render() { + const visibleRoutes = (routes as AppRoute[]).filter((route) => route.name); + return html` - - Ignite UI CLI - ${routes.filter(route => route.name).map(({path, name}) => html` - - - ${name} - - - `)} - - +
+
+ +

$(name)

+
+
+ + ${visibleRoutes.map((route) => html` + this.navigate(route.path)} + > + + ${route.name} + + `)} + + +
+
`; } + connectedCallback() { + super.connectedCallback(); + + this.mediaQuery = window.matchMedia('(min-width: 1025px)'); + this.updateDrawerState(); + this.mediaQuery.addEventListener('change', this.updateDrawerState); + window.addEventListener('popstate', this.updateCurrentPath); + } + + disconnectedCallback() { + this.mediaQuery?.removeEventListener('change', this.updateDrawerState); + window.removeEventListener('popstate', this.updateCurrentPath); + + super.disconnectedCallback(); + } + firstUpdated() { const outlet = this.shadowRoot?.querySelector('router-outlet'); const router = new Router(outlet); router.setRoutes(routes); } + + private toggleDrawer = () => { + this.drawerOpen = !this.drawerOpen; + }; + + private navigate(path: string) { + this.currentPath = path; + Router.go(path); + + if (!this.mediaQuery?.matches) { + this.drawerOpen = false; + } + } + + private updateDrawerState = () => { + const pinned = Boolean(this.mediaQuery?.matches); + + this.drawerOpen = pinned; + this.drawerPosition = pinned ? 'relative' : 'start'; + }; + + private updateCurrentPath = () => { + this.currentPath = window.location.pathname; + }; } diff --git a/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts new file mode 100644 index 000000000..724255183 --- /dev/null +++ b/packages/cli/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts @@ -0,0 +1,195 @@ +import { css, html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { + defineComponents, + IgcIconComponent, + registerIconFromText, +} from 'igniteui-webcomponents'; + +const icons = [ + { + name: 'apps', + value: '', + }, + { + name: 'build', + value: '', + }, + { + name: 'code', + value: '', + }, + { + name: 'palette', + value: '', + }, +]; + +defineComponents(IgcIconComponent); +icons.forEach((icon) => registerIconFromText(icon.name, icon.value, 'material')); + +@customElement('app-home') +export class HomeComponent extends LitElement { + static styles = css` + :host { + display: flex; + width: 100%; + min-height: 100%; + } + + .home { + width: 100%; + max-width: 920px; + margin: auto; + padding: 48px 24px; + box-sizing: border-box; + font-family: "Titillium Web", "Segoe UI", Arial, sans-serif; + } + + .home__intro { + max-width: 720px; + margin: 0 auto 24px; + text-align: center; + } + + h1 { + margin: 0 0 12px; + color: #09f; + font-size: 3rem; + font-weight: 600; + line-height: 1.2; + } + + .home__description { + margin: 0; + color: #000; + font-size: 1.125rem; + line-height: 1.5; + } + + .home__image { + display: block; + width: 500px; + height: 350px; + margin: 0 auto 28px; + max-width: 100%; + object-fit: contain; + } + + .home__resources { + display: grid; + grid-template-columns: repeat(2, 300px); + justify-content: center; + margin: 0 auto; + gap: 12px 64px; + } + + .home__resource { + display: flex; + align-items: center; + min-height: 64px; + padding: 6px 0; + color: rgba(0, 0, 0, .74); + text-align: left; + text-decoration: none; + } + + .home__resource:hover strong { + text-decoration: underline; + } + + .home__resource igc-icon { + flex: 0 0 28px; + margin-right: 16px; + color: #09f; + font-size: 28px; + } + + .home__resource strong, + .home__resource small { + display: block; + } + + .home__resource strong { + margin-bottom: 2px; + color: #731963; + font-size: 1rem; + } + + .home__resource small { + color: #000; + font-size: 0.875rem; + line-height: 1.4; + } + + @media (max-width: 768px) { + .home { + padding: 32px 16px; + } + + h1 { + font-size: 2.25rem; + } + + .home__image { + width: 100%; + height: 240px; + } + + .home__resources { + grid-template-columns: 1fr; + gap: 4px; + } + } + `; + + render() { + return html` +
+
+

Welcome to Ignite UI for Web Components!

+

+ A routed application shell with a responsive side navigation drawer and curated Ignite UI resources. +

+
+ + Ignite UI for Web Components + + +
+ `; + } +} From b5d44a4ddecfb9f09916448d1b867f8d7366249d Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Wed, 3 Jun 2026 13:30:27 +0300 Subject: [PATCH 04/38] fix(cli:templates): align react side-nav template layout --- .../projects/_base/files/src/setupTests.ts | 12 ++ .../files/src/app/home/style.module.css | 4 + .../top-nav/files/src/app/app-routes.tsx | 5 + .../projects/top-nav/files/src/app/app.css | 96 ++++++++------- .../projects/top-nav/files/src/app/app.tsx | 113 ++++++++++++++++-- .../top-nav/files/src/app/home/home.tsx | 69 +++++++++++ .../files/src/app/home/style.module.css | 104 ++++++++++++++++ .../components/navigation-header/index.tsx | 19 --- .../react/igr-ts/projects/top-nav/index.ts | 10 +- 9 files changed, 360 insertions(+), 72 deletions(-) create mode 100644 packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app-routes.tsx create mode 100644 packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/home.tsx create mode 100644 packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/style.module.css delete mode 100644 packages/cli/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx diff --git a/packages/cli/templates/react/igr-ts/projects/_base/files/src/setupTests.ts b/packages/cli/templates/react/igr-ts/projects/_base/files/src/setupTests.ts index 40f45a8b6..dc32f56c8 100644 --- a/packages/cli/templates/react/igr-ts/projects/_base/files/src/setupTests.ts +++ b/packages/cli/templates/react/igr-ts/projects/_base/files/src/setupTests.ts @@ -4,6 +4,18 @@ import ResizeObserver from 'resize-observer-polyfill'; export function setupTestMocks() { globalThis.ResizeObserver = ResizeObserver; + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: true, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); + HTMLElement.prototype.scrollIntoView = vi.fn(); HTMLElement.prototype.hidePopover = vi.fn(); HTMLElement.prototype.showPopover = vi.fn(); diff --git a/packages/cli/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css b/packages/cli/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css index 9281c0040..7dfcab6f4 100644 --- a/packages/cli/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css +++ b/packages/cli/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css @@ -44,6 +44,10 @@ margin: 16px 0 8px; } +:local(.content) p:nth-last-child(2) { + padding-top: 16px; +} + :local(.githubLinks) { display: flex; flex-flow: row wrap; diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app-routes.tsx b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app-routes.tsx new file mode 100644 index 000000000..e65531d40 --- /dev/null +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app-routes.tsx @@ -0,0 +1,5 @@ +import Home from './home/home'; + +export const routes = [ + { path: '/', element: , text: 'Home', icon: 'home' } +]; diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.css b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.css index 41a9dc40f..2e1f56aa9 100644 --- a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.css +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.css @@ -1,62 +1,76 @@ .app { - text-align: center; display: flex; - flex-flow: column; - min-height: 100vh; + flex-flow: column nowrap; + height: 100%; + overflow: hidden; } -.content { - flex: 1 0 auto; - padding: 1em; -} - -/* quick nav menubar */ -nav, -.app__name { - background-color: rgb(0, 153, 255); +.app__navbar { + display: flex; + align-items: center; + flex: 0 0 auto; + height: 56px; + padding: 0 16px; + background: #239ef0; + box-shadow: 0 2px 4px rgba(0, 0, 0, .24); box-sizing: border-box; } -.app__name { + +.app__title { + margin: 0 0 0 16px; + font-size: 1.25rem; font-weight: 600; - font-size: 24px; - line-height: 32px; - padding-left: 24px; - text-align: left; - color: #fff; - padding-bottom: 8px; + line-height: 1; + color: #000; } -nav ul { - list-style: none; - display: flex; - justify-content: flex-start; - flex-wrap: wrap; - margin: 0; +.app__menu-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; padding: 0; + color: #000; + border: 0; + background: transparent; + cursor: pointer; } -nav ul a { - color: #fff; - line-height: 2; - text-decoration: none; - margin: 0 5px; +.app__menu-button igr-icon { + font-size: 24px; } -nav ul a.active { - color: #fff; - font-weight: 600; +.app__body { + display: flex; + flex: 1 1 auto; + min-height: 0; } -nav ul li { - margin: 0px 16px; - box-sizing: border-box; - border-bottom: 1px solid transparent; +.app__drawer { + flex: 0 0 auto; + height: 100%; + --ig-nav-drawer-size: 280px; + --ig-navdrawer-item-active-background: #e0f2ff; + --ig-navdrawer-item-active-text-color: #0075d2; + --ig-navdrawer-item-active-icon-color: #0075d2; } -nav ul li.active { - border-bottom: 2px solid #fff; +igc-nav-drawer-item[active]::part(base) { + background: #e0f2ff; + color: #0075d2; } -nav ul li:not(.active):hover { - border-bottom: 1px solid #fff; +igc-nav-drawer-item[active] igr-icon { + color: #0075d2; +} + +.content { + flex: 1 1 auto; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: stretch; + min-width: 0; + overflow: auto; } diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx index c50c56f51..232e5a62a 100644 --- a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx @@ -1,17 +1,116 @@ -import { Outlet } from "react-router-dom"; -import { routes } from './app-routes'; -import NavigationHeader from "../components/navigation-header"; +import { useEffect, useMemo, useState } from "react"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { + IgrIcon, + IgrNavDrawer, + IgrNavDrawerItem, + registerIconFromText, +} from "igniteui-react"; +import { routes } from "./app-routes"; +import "igniteui-webcomponents/themes/light/bootstrap.css"; import "./app.css"; +const icons = [ + { + name: "apps", + value: '', + }, + { + name: "build", + value: '', + }, + { + name: "code", + value: '', + }, + { + name: "home", + value: '', + }, + { + name: "menu", + value: '', + }, + { + name: "palette", + value: '', + }, +]; + +icons.forEach((icon) => registerIconFromText(icon.name, icon.value, "material")); + export default function App() { const name = "$(name)"; + const location = useLocation(); + const navigate = useNavigate(); + const [drawerOpen, setDrawerOpen] = useState(true); + const [drawerPosition, setDrawerPosition] = useState<"relative" | "start">("relative"); + + const visibleRoutes = useMemo(() => { + return routes.filter((route) => route.path && route.text); + }, []); + + useEffect(() => { + const mediaQuery = window.matchMedia("(min-width: 1025px)"); + const updateDrawerState = () => { + setDrawerOpen(mediaQuery.matches); + setDrawerPosition(mediaQuery.matches ? "relative" : "start"); + }; + + updateDrawerState(); + mediaQuery.addEventListener("change", updateDrawerState); + + return () => mediaQuery.removeEventListener("change", updateDrawerState); + }, []); + + const handleRouteClick = (path: string) => { + navigate(path); + + if (window.matchMedia("(max-width: 1024px)").matches) { + setDrawerOpen(false); + } + }; return (
-
{name}
- -
- +
+ +

{name}

+
+
+ + {visibleRoutes.map((route) => ( + handleRouteClick(route.path)} + > + + {route.text} + + ))} + +
+ +
); diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/home.tsx b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/home.tsx new file mode 100644 index 000000000..0ca0dc4ae --- /dev/null +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/home.tsx @@ -0,0 +1,69 @@ +import logo from "/logo.svg"; +import { IgrIcon } from "igniteui-react"; +import styles from "./style.module.css"; + +export default function App() { + return ( +
+
+

Welcome to Ignite UI for React!

+

+ A routed application shell with a responsive side navigation drawer and curated Ignite UI resources. +

+
+ + Ignite UI for React + + +
+ ); +} diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/style.module.css b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/style.module.css new file mode 100644 index 000000000..62a34e9d0 --- /dev/null +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/app/home/style.module.css @@ -0,0 +1,104 @@ +:local(.home) { + width: 100%; + max-width: 920px; + margin: auto; + padding: 48px 24px; + box-sizing: border-box; + font-family: "Titillium Web", "Segoe UI", Arial, sans-serif; +} + +:local(.intro) { + max-width: 720px; + margin: 0 auto 24px; + text-align: center; +} + +:local(.header) { + margin: 0 0 12px; + color: #09f; + font-size: 3rem; + font-weight: 600; + line-height: 1.2; +} + +:local(.description) { + margin: 0; + color: #000; + font-size: 1.125rem; + line-height: 1.5; +} + +:local(.logo) { + display: block; + width: 500px; + height: 350px; + margin: 0 auto 28px; + max-width: 100%; + object-fit: contain; +} + +:local(.resources) { + display: grid; + grid-template-columns: repeat(2, 300px); + justify-content: center; + margin: 0 auto; + gap: 4px 64px; +} + +:local(.resource) { + display: flex; + align-items: center; + min-height: 64px; + padding: 6px 0; + color: rgba(0, 0, 0, .74); + text-align: left; + text-decoration: none; +} + +:local(.resource):hover strong { + text-decoration: underline; +} + +:local(.resourceIcon) { + flex: 0 0 28px; + margin-right: 16px; + color: #09f; + font-size: 28px; +} + +:local(.resource) strong, +:local(.resource) small { + display: block; +} + +:local(.resource) strong { + margin-bottom: 2px; + color: #731963; + font-size: 1rem; +} + +:local(.resource) small { + color: #000; + font-size: 0.875rem; + line-height: 1.4; +} + +@media (max-width: 768px) { + :local(.home) { + padding: 32px 16px; + } + + :local(.header) { + font-size: 2.25rem; + } + + :local(.logo) { + width: 100%; + height: 240px; + } + + :local(.resources) { + grid-template-columns: 1fr; + gap: 4px; + } +} diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx b/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx deleted file mode 100644 index 99565a4cc..000000000 --- a/packages/cli/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useMemo } from 'react'; -import { Link, useLocation } from 'react-router-dom'; - -export default function NavigationHeader({ routes }: any) { - const location = useLocation(); - const activeItem = useMemo(() => { - return routes.findIndex((route: { path: string; }) => route.path === location.pathname); - }, [routes, location.pathname]); - - return ( - - ) -} diff --git a/packages/cli/templates/react/igr-ts/projects/top-nav/index.ts b/packages/cli/templates/react/igr-ts/projects/top-nav/index.ts index 14a967b85..28914f0cf 100644 --- a/packages/cli/templates/react/igr-ts/projects/top-nav/index.ts +++ b/packages/cli/templates/react/igr-ts/projects/top-nav/index.ts @@ -2,10 +2,10 @@ import { ProjectTemplate } from "@igniteui/cli-core"; import * as path from "path"; import { BaseWithHomeIgrTsProject } from "../_base_with_home"; -export class TopNavIgrTsProject extends BaseWithHomeIgrTsProject implements ProjectTemplate { - public id: string = "top-nav"; - public name = "Default top navigation"; - public description = "Project structure with top navigation menu"; +export class SideNavIgrTsProject extends BaseWithHomeIgrTsProject implements ProjectTemplate { + public id: string = "side-nav"; + public name = "Default side navigation"; + public description = "Project structure with side navigation drawer"; public dependencies: string[] = []; public framework: string = "react"; public projectType: string = "igr-ts"; @@ -16,4 +16,4 @@ export class TopNavIgrTsProject extends BaseWithHomeIgrTsProject implements Proj return [...super.templatePaths, path.join(__dirname, "files")]; } } -export default new TopNavIgrTsProject(); +export default new SideNavIgrTsProject(); From 074c4cf17270d21b93450f660d87d85cf82d5679 Mon Sep 17 00:00:00 2001 From: Georgi Anastasov <48180072+georgianastasov@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:33:22 +0300 Subject: [PATCH 05/38] fix(templates): add navigation role to resources container Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../projects/_base_with_home/files/src/app/home/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html index 567319af8..e1f5c89d1 100644 --- a/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html +++ b/packages/igx-templates/igx-ts/projects/_base_with_home/files/src/app/home/home.html @@ -6,7 +6,7 @@

{{title()}}

Ignite UI for Angular -
+