feat(tab-button): add the shape property and styles for the ionic theme (#30057)
Issue number: internal --------- <!-- 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? Tab button does not have a shape property. ## What is the new behavior? - Adds support for the shape property in tab button. - Adds styles for the "soft", "round" and "rectangular" shapes in the ionic theme - Defaults the shape to "round" for the ionic theme - Adds an e2e test for shape with screenshots of all shapes ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
@ -2213,6 +2213,7 @@ ion-tab-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-star
|
|||||||
ion-tab-button,prop,mode,"ios" | "md",undefined,false,false
|
ion-tab-button,prop,mode,"ios" | "md",undefined,false,false
|
||||||
ion-tab-button,prop,rel,string | undefined,undefined,false,false
|
ion-tab-button,prop,rel,string | undefined,undefined,false,false
|
||||||
ion-tab-button,prop,selected,boolean,false,false,false
|
ion-tab-button,prop,selected,boolean,false,false,false
|
||||||
|
ion-tab-button,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
|
||||||
ion-tab-button,prop,tab,string | undefined,undefined,false,false
|
ion-tab-button,prop,tab,string | undefined,undefined,false,false
|
||||||
ion-tab-button,prop,target,string | undefined,undefined,false,false
|
ion-tab-button,prop,target,string | undefined,undefined,false,false
|
||||||
ion-tab-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
ion-tab-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||||
|
8
core/src/components.d.ts
vendored
@ -3504,6 +3504,10 @@ export namespace Components {
|
|||||||
* The selected tab component
|
* The selected tab component
|
||||||
*/
|
*/
|
||||||
"selected": boolean;
|
"selected": boolean;
|
||||||
|
/**
|
||||||
|
* Set to `"soft"` for a tab-button with slightly rounded corners, `"round"` for a tab-button with fully rounded corners, or `"rectangular"` for a tab-button without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"shape"?: 'soft' | 'round' | 'rectangular';
|
||||||
/**
|
/**
|
||||||
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
|
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
|
||||||
*/
|
*/
|
||||||
@ -8941,6 +8945,10 @@ declare namespace LocalJSX {
|
|||||||
* The selected tab component
|
* The selected tab component
|
||||||
*/
|
*/
|
||||||
"selected"?: boolean;
|
"selected"?: boolean;
|
||||||
|
/**
|
||||||
|
* Set to `"soft"` for a tab-button with slightly rounded corners, `"round"` for a tab-button with fully rounded corners, or `"rectangular"` for a tab-button without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"shape"?: 'soft' | 'round' | 'rectangular';
|
||||||
/**
|
/**
|
||||||
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
|
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
|
||||||
*/
|
*/
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.7 KiB |
@ -8,8 +8,6 @@
|
|||||||
--focus-ring-color: #{globals.$ion-border-focus-default};
|
--focus-ring-color: #{globals.$ion-border-focus-default};
|
||||||
--focus-ring-width: #{globals.$ion-border-radius-025};
|
--focus-ring-width: #{globals.$ion-border-radius-025};
|
||||||
|
|
||||||
@include globals.border-radius(globals.$ion-border-radius-200);
|
|
||||||
|
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
|
||||||
min-height: globals.$ion-scale-1200;
|
min-height: globals.$ion-scale-1200;
|
||||||
@ -73,3 +71,18 @@
|
|||||||
width: globals.$ion-scale-600;
|
width: globals.$ion-scale-600;
|
||||||
height: globals.$ion-scale-600;
|
height: globals.$ion-scale-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tab Button Shapes
|
||||||
|
// -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
:host(.tab-button-shape-soft) {
|
||||||
|
@include globals.border-radius(globals.$ion-border-radius-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.tab-button-shape-round) {
|
||||||
|
@include globals.border-radius(globals.$ion-border-radius-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.tab-button-shape-rectangular) {
|
||||||
|
@include globals.border-radius(globals.$ion-border-radius-0);
|
||||||
|
}
|
||||||
|
@ -68,6 +68,15 @@ export class TabButton implements ComponentInterface, AnchorInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop({ mutable: true }) selected = false;
|
@Prop({ mutable: true }) selected = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `"soft"` for a tab-button with slightly rounded corners,
|
||||||
|
* `"round"` for a tab-button with fully rounded corners, or `"rectangular"`
|
||||||
|
* for a tab-button without rounded corners.
|
||||||
|
*
|
||||||
|
* Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
@Prop() shape?: 'soft' | 'round' | 'rectangular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tab id must be provided for each `ion-tab`. It's used internally to reference
|
* A tab id must be provided for each `ion-tab`. It's used internally to reference
|
||||||
* the selected tab or by the router to switch between them.
|
* the selected tab or by the router to switch between them.
|
||||||
@ -107,6 +116,22 @@ export class TabButton implements ComponentInterface, AnchorInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getShape(): string | undefined {
|
||||||
|
const theme = getIonTheme(this);
|
||||||
|
const { shape } = this;
|
||||||
|
|
||||||
|
// TODO(ROU-11436): Remove theme check when shapes are defined for all themes.
|
||||||
|
if (theme !== 'ionic') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shape === undefined) {
|
||||||
|
return 'round';
|
||||||
|
}
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
private selectTab(ev: Event | KeyboardEvent) {
|
private selectTab(ev: Event | KeyboardEvent) {
|
||||||
if (this.tab !== undefined) {
|
if (this.tab !== undefined) {
|
||||||
if (!this.disabled) {
|
if (!this.disabled) {
|
||||||
@ -141,6 +166,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
|
|||||||
render() {
|
render() {
|
||||||
const { disabled, hasIcon, hasLabel, href, rel, target, layout, selected, tab, inheritedAttributes } = this;
|
const { disabled, hasIcon, hasLabel, href, rel, target, layout, selected, tab, inheritedAttributes } = this;
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
|
const shape = this.getShape();
|
||||||
const attrs = {
|
const attrs = {
|
||||||
download: this.download,
|
download: this.download,
|
||||||
href,
|
href,
|
||||||
@ -164,6 +190,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
|
|||||||
[`tab-layout-${layout}`]: true,
|
[`tab-layout-${layout}`]: true,
|
||||||
'ion-activatable': true,
|
'ion-activatable': true,
|
||||||
'ion-selectable': true,
|
'ion-selectable': true,
|
||||||
|
[`tab-button-shape-${shape}`]: shape !== undefined,
|
||||||
'ion-focusable': true,
|
'ion-focusable': true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
55
core/src/components/tab-button/test/shape/index.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Tab Button - Shape</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="../../../../../css/ionic.bundle.css" />
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Tab Button - Shape</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-text> (Only available for ionic theme) </ion-text>
|
||||||
|
|
||||||
|
<ion-tab-bar>
|
||||||
|
<ion-tab-button>
|
||||||
|
<ion-label>Default Shape</ion-label>
|
||||||
|
<ion-icon aria-hidden="true" name="triangle-outline"></ion-icon>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button id="soft-tab-button" shape="soft">
|
||||||
|
<ion-label>Soft Shape</ion-label>
|
||||||
|
<ion-icon aria-hidden="true" name="triangle-outline"></ion-icon>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button id="round-tab-button" shape="round">
|
||||||
|
<ion-label>Round Shape</ion-label>
|
||||||
|
<ion-icon aria-hidden="true" name="triangle-outline"></ion-icon>
|
||||||
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<ion-tab-button id="rect-tab-button" shape="rectangular">
|
||||||
|
<ion-label>Rectangular Shape</ion-label>
|
||||||
|
<ion-icon aria-hidden="true" name="triangle-outline"></ion-icon>
|
||||||
|
</ion-tab-button>
|
||||||
|
</ion-tab-bar>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ion-tab-bar {
|
||||||
|
background: #d4d4d4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
51
core/src/components/tab-button/test/shape/tab-button.e2e.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
import type { E2EPage, E2EPageOptions, ScreenshotFn } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
class TabButtonFixture {
|
||||||
|
readonly page: E2EPage;
|
||||||
|
|
||||||
|
constructor(page: E2EPage) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto(config: E2EPageOptions) {
|
||||||
|
const { page } = this;
|
||||||
|
await page.goto(`/src/components/tab-button/test/shape`, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async screenshot(screenshotModifier: string, screenshot: ScreenshotFn, buttonId: string) {
|
||||||
|
const { page } = this;
|
||||||
|
|
||||||
|
const screenshotString = screenshot(`tab-button-${screenshotModifier}`);
|
||||||
|
const tabButton = page.locator(buttonId);
|
||||||
|
|
||||||
|
await expect(tabButton).toHaveScreenshot(screenshotString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior does not vary across directions.
|
||||||
|
*/
|
||||||
|
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
|
||||||
|
test.describe(title('tab-button: shape'), () => {
|
||||||
|
let tabButtonFixture: TabButtonFixture;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
tabButtonFixture = new TabButtonFixture(page);
|
||||||
|
await tabButtonFixture.goto(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render a soft tab button', async () => {
|
||||||
|
await tabButtonFixture.screenshot('shape-soft', screenshot, '#soft-tab-button');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render a round tab button', async () => {
|
||||||
|
await tabButtonFixture.screenshot('shape-round', screenshot, '#round-tab-button');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render a rectangular tab button', async () => {
|
||||||
|
await tabButtonFixture.screenshot('shape-rectangular', screenshot, '#rect-tab-button');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
@ -2276,14 +2276,14 @@ export declare interface IonTabBar extends Components.IonTabBar {}
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target', 'theme']
|
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-tab-button',
|
selector: 'ion-tab-button',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target', 'theme'],
|
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme'],
|
||||||
})
|
})
|
||||||
export class IonTabButton {
|
export class IonTabButton {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
|
@ -2081,14 +2081,14 @@ export declare interface IonTabBar extends Components.IonTabBar {}
|
|||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
defineCustomElementFn: defineIonTabButton,
|
defineCustomElementFn: defineIonTabButton,
|
||||||
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target', 'theme']
|
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-tab-button',
|
selector: 'ion-tab-button',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target', 'theme'],
|
inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme'],
|
||||||
standalone: true
|
standalone: true
|
||||||
})
|
})
|
||||||
export class IonTabButton {
|
export class IonTabButton {
|
||||||
|