diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index b2e8af14fd..8bd5de65c8 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -3,6 +3,7 @@ export { IonModal } from './overlays/modal'; export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; +export { IonTabs } from './navigation/tabs'; export { provideIonicAngular } from './providers/ionic-angular'; export { ActionSheetController, diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts new file mode 100644 index 0000000000..b54be17934 --- /dev/null +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -0,0 +1,52 @@ +import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; +import { IonTabs as IonTabsBase } from '@ionic/angular/common'; + +import { IonTabBar } from '../directives/proxies'; + +import { IonRouterOutlet } from './router-outlet'; + +@Component({ + selector: 'ion-tabs', + template: ` + +
+ +
+ + `, + standalone: true, + styles: [ + ` + :host { + display: flex; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + flex-direction: column; + + width: 100%; + height: 100%; + + contain: layout size style; + } + .tabs-inner { + position: relative; + + flex: 1; + + contain: layout size style; + } + `, + ], + imports: [IonRouterOutlet], +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class IonTabs extends IonTabsBase { + @ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet; + + @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; + @ContentChildren(IonTabBar) tabBars: QueryList; +} diff --git a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts new file mode 100644 index 0000000000..a0558f299f --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts @@ -0,0 +1,16 @@ +describe('Tabs', () => { + beforeEach(() => { + cy.visit('/standalone/tabs'); + }); + + it('should redirect to the default tab', () => { + cy.get('app-tab-one').should('be.visible'); + cy.contains('Tab 1'); + }); + + it('should render new content when switching tabs', () => { + cy.get('#tab-button-tab-two').click(); + cy.get('app-tab-two').should('be.visible'); + cy.contains('Tab 2'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 2367c61772..d97d34c66b 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -16,6 +16,16 @@ export const routes: Routes = [ { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) }, { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) }, { path: 'icon', loadComponent: () => import('../icon/icon.component').then(c => c.IconComponent) }, + { path: 'tabs', redirectTo: '/standalone/tabs/tab-one', pathMatch: 'full' }, + { + path: 'tabs', + loadComponent: () => import('../tabs/tabs.component').then(c => c.TabsComponent), + children: [ + { path: 'tab-one', loadComponent: () => import('../tabs/tab1.component').then(c => c.TabOneComponent) }, + { path: 'tab-two', loadComponent: () => import('../tabs/tab2.component').then(c => c.TabTwoComponent) }, + { path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) } + ] + }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts new file mode 100644 index 0000000000..8b6e8976ec --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-one', + template: ` + Tab 1 + `, + standalone: true, +}) +export class TabOneComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts new file mode 100644 index 0000000000..b27b301fdd --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-two', + template: ` + Tab 2 + `, + standalone: true, +}) +export class TabTwoComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts new file mode 100644 index 0000000000..b2af4e6550 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-three', + template: ` + Tab 3 + `, + standalone: true, +}) +export class TabThreeComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html new file mode 100644 index 0000000000..02105307ce --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html @@ -0,0 +1,42 @@ + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + +
+ + +
\ No newline at end of file diff --git a/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts new file mode 100644 index 0000000000..d4b889e0dd --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts @@ -0,0 +1,38 @@ +import { Component, ViewChild } from '@angular/core'; +import { IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { add, logoIonic, save } from 'ionicons/icons'; + +addIcons({ add, logoIonic, save }); + +@Component({ + selector: 'app-tabs', + templateUrl: './tabs.component.html', + standalone: true, + imports: [IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs] +}) +export class TabsComponent { + tabsDidChangeCounter = 0; + tabsDidChangeEvent = ''; + tabsDidChangeSelectedTab? = ''; + + tabsWillChangeCounter = 0; + tabsWillChangeEvent = ''; + tabsWillChangeSelectedTab? = ''; + + @ViewChild(IonTabBar) tabBar!: IonTabBar; + + tabChanged(ev: { tab: string }) { + console.log('ionTabsDidChange', this.tabBar.selectedTab); + this.tabsDidChangeCounter++; + this.tabsDidChangeEvent = ev.tab; + this.tabsDidChangeSelectedTab = this.tabBar.selectedTab; + } + + tabsWillChange(ev: { tab: string }) { + console.log('ionTabsWillChange', this.tabBar.selectedTab); + this.tabsWillChangeCounter++; + this.tabsWillChangeEvent = ev.tab; + this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; + } +}