feat(angular): add standalone tabs (#28093)

Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?

Tabs cannot be used as a standalone component.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Added tabs as a standalone component.
- Added a quick test. I included the event checking from the lazy test
in the HTML as a smoke check, but didn't bring over the Cypress tests
for them since I noticed the other standalone tests have been quick,
stripped down affairs. I'm assuming I would just be duplicating effort.
Let me know if I should bring more tests over from the lazy version, or
even get rid of the event logging from the standalone HTML.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
This commit is contained in:
Amanda Johnston
2023-09-01 13:08:20 -05:00
committed by GitHub
parent e5b7f5ff55
commit 0afa14ed7a
9 changed files with 195 additions and 0 deletions

View File

@@ -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,

View File

@@ -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: `
<ng-content select="[slot=top]"></ng-content>
<div class="tabs-inner" #tabsInner>
<ion-router-outlet #outlet tabs="true" (stackEvents)="onPageSelected($event)"></ion-router-outlet>
</div>
<ng-content></ng-content>
`,
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<IonTabBar>;
}

View File

@@ -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');
});
});

View File

@@ -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) }
]
},
]
},
];

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-tab-one',
template: `
Tab 1
`,
standalone: true,
})
export class TabOneComponent {
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-tab-two',
template: `
Tab 2
`,
standalone: true,
})
export class TabTwoComponent {
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-tab-three',
template: `
Tab 3
`,
standalone: true,
})
export class TabThreeComponent {
}

View File

@@ -0,0 +1,42 @@
<ion-tabs (ionTabsDidChange)="tabChanged($event)" (ionTabsWillChange)="tabsWillChange($event)">
<ion-tab-bar>
<ion-tab-button tab="tab-one">
<ion-label>Tab One</ion-label>
<ion-icon name="add"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-two">
<ion-label>Tab Two</ion-label>
<ion-icon name="logo-ionic"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-three">
<ion-label>Tab Three</ion-label>
<ion-icon name="save"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
<div id="test">
<ul>
<li>
ionTabsWillChange counter: <span id="ionTabsWillChangeCounter">{{ tabsWillChangeCounter }}</span>
</li>
<li>
ionTabsWillChange event: <span id="ionTabsWillChangeEvent">{{ tabsWillChangeEvent }}</span>
</li>
<li>
ionTabsWillChange selectedTab: <span id="ionTabsWillChangeSelectedTab">{{ tabsWillChangeSelectedTab }}</span>
</li>
</ul>
<ul>
<li>
ionTabsDidChange counter: <span id="ionTabsDidChangeCounter">{{ tabsDidChangeCounter }}</span>
</li>
<li>
ionTabsDidChange event: <span id="ionTabsDidChangeEvent">{{ tabsDidChangeEvent }}</span>
</li>
<li>
ionTabsDidChange selectedTab: <span id="ionTabsDidChangeSelectedTab">{{ tabsDidChangeSelectedTab }}</span>
</li>
</ul>
</div>

View File

@@ -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;
}
}