mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
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:
@@ -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,
|
||||
|
||||
52
packages/angular/standalone/src/navigation/tabs.ts
Normal file
52
packages/angular/standalone/src/navigation/tabs.ts
Normal 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>;
|
||||
}
|
||||
16
packages/angular/test/base/e2e/src/standalone/tabs.spec.ts
Normal file
16
packages/angular/test/base/e2e/src/standalone/tabs.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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) }
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab-one',
|
||||
template: `
|
||||
Tab 1
|
||||
`,
|
||||
standalone: true,
|
||||
})
|
||||
export class TabOneComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab-two',
|
||||
template: `
|
||||
Tab 2
|
||||
`,
|
||||
standalone: true,
|
||||
})
|
||||
export class TabTwoComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab-three',
|
||||
template: `
|
||||
Tab 3
|
||||
`,
|
||||
standalone: true,
|
||||
})
|
||||
export class TabThreeComponent {
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user