feat(avatar): add styles for default (round) shape in ionic theme (#29550)
Issue number: internal --------- ## What is the current behavior? Avatar does not have styles for the `"round"` shape in the ionic theme. ## What is the new behavior? - Adds the styles for the default (round) shape (border radius) - Adds e2e test for the round shape - Updates the screenshots for the avatar `size` due to the new default shape ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-10738-ionic1.vercel.app/src/components/avatar/test/shape?ionic:theme=ionic)
@ -184,6 +184,7 @@ ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
|||||||
|
|
||||||
ion-avatar,shadow
|
ion-avatar,shadow
|
||||||
ion-avatar,prop,mode,"ios" | "md",undefined,false,false
|
ion-avatar,prop,mode,"ios" | "md",undefined,false,false
|
||||||
|
ion-avatar,prop,shape,"round" | undefined,undefined,false,false
|
||||||
ion-avatar,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | undefined,undefined,false,false
|
ion-avatar,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | undefined,undefined,false,false
|
||||||
ion-avatar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
ion-avatar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||||
ion-avatar,css-prop,--border-radius,ionic
|
ion-avatar,css-prop,--border-radius,ionic
|
||||||
|
8
core/src/components.d.ts
vendored
@ -335,6 +335,10 @@ export namespace Components {
|
|||||||
* The mode determines the platform behaviors of the component.
|
* The mode determines the platform behaviors of the component.
|
||||||
*/
|
*/
|
||||||
"mode"?: "ios" | "md";
|
"mode"?: "ios" | "md";
|
||||||
|
/**
|
||||||
|
* Set to `"round"` for an avatar with fully rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"shape"?: 'round';
|
||||||
/**
|
/**
|
||||||
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, `"large"` for a larger size, or `"xlarge"` for the largest dimensions. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, `"large"` for a larger size, or `"xlarge"` for the largest dimensions. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
||||||
*/
|
*/
|
||||||
@ -5567,6 +5571,10 @@ declare namespace LocalJSX {
|
|||||||
* The mode determines the platform behaviors of the component.
|
* The mode determines the platform behaviors of the component.
|
||||||
*/
|
*/
|
||||||
"mode"?: "ios" | "md";
|
"mode"?: "ios" | "md";
|
||||||
|
/**
|
||||||
|
* Set to `"round"` for an avatar with fully rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"shape"?: 'round';
|
||||||
/**
|
/**
|
||||||
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, `"large"` for a larger size, or `"xlarge"` for the largest dimensions. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, `"large"` for a larger size, or `"xlarge"` for the largest dimensions. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
||||||
*/
|
*/
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host(:not(.avatar-image)) {
|
:host(:not(.avatar-image)) {
|
||||||
|
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
|
||||||
|
|
||||||
border: globals.$ionic-border-size-025 solid globals.$ionic-color-neutral-800;
|
border: globals.$ionic-border-size-025 solid globals.$ionic-color-neutral-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +81,14 @@
|
|||||||
width: globals.$ionic-scale-1400;
|
width: globals.$ionic-scale-1400;
|
||||||
height: globals.$ionic-scale-1400;
|
height: globals.$ionic-scale-1400;
|
||||||
|
|
||||||
font-size: globals.$ionic-font-size-550;
|
font-size: globals.$ionic-font-size-500;
|
||||||
|
|
||||||
line-height: globals.$ionic-line-height-700;
|
line-height: globals.$ionic-line-height-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avatar Shapes
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
:host(.avatar-round) {
|
||||||
|
--border-radius: #{globals.$ionic-border-radius-full};
|
||||||
|
}
|
||||||
|
@ -28,6 +28,13 @@ export class Avatar implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() size?: `xsmall` | 'small' | 'medium' | 'large' | 'xlarge';
|
@Prop() size?: `xsmall` | 'small' | 'medium' | 'large' | 'xlarge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `"round"` for an avatar with fully rounded corners.
|
||||||
|
*
|
||||||
|
* Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
@Prop() shape?: 'round';
|
||||||
|
|
||||||
get hasImage() {
|
get hasImage() {
|
||||||
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
|
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
|
||||||
}
|
}
|
||||||
@ -48,15 +55,33 @@ export class Avatar implements ComponentInterface {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getShape(): string | undefined {
|
||||||
|
const theme = getIonTheme(this);
|
||||||
|
const { shape } = this;
|
||||||
|
|
||||||
|
// TODO(ROU-10755): Remove theme check when shapes are defined for all themes.
|
||||||
|
if (theme !== 'ionic') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shape === undefined) {
|
||||||
|
return 'round';
|
||||||
|
}
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
const size = this.getSize();
|
const size = this.getSize();
|
||||||
|
const shape = this.getShape();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
class={{
|
class={{
|
||||||
[theme]: true,
|
[theme]: true,
|
||||||
[`avatar-${size}`]: size !== undefined,
|
[`avatar-${size}`]: size !== undefined,
|
||||||
|
[`avatar-${shape}`]: shape !== undefined,
|
||||||
[`avatar-image`]: this.hasImage,
|
[`avatar-image`]: this.hasImage,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
54
core/src/components/avatar/test/shape/avatar.e2e.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior does not vary across directions.
|
||||||
|
*/
|
||||||
|
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screenshot, title }) => {
|
||||||
|
test.describe(title('avatar: shape'), () => {
|
||||||
|
test.describe('round', () => {
|
||||||
|
test('should not have visual regressions when containing text', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-avatar shape="round">AB</ion-avatar>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const avatar = page.locator('ion-avatar');
|
||||||
|
|
||||||
|
await expect(avatar).toHaveScreenshot(screenshot(`avatar-shape-round-text`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions when containing an icon', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-avatar shape="round">
|
||||||
|
<ion-icon name="person-outline"></ion-icon>
|
||||||
|
</ion-avatar>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const avatar = page.locator('ion-avatar');
|
||||||
|
|
||||||
|
await expect(avatar).toHaveScreenshot(screenshot(`avatar-shape-round-icon`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions when containing an image', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-avatar shape="round">
|
||||||
|
<img src="/src/components/avatar/test/avatar.svg"/>
|
||||||
|
</ion-avatar>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const avatar = page.locator('ion-avatar');
|
||||||
|
|
||||||
|
await expect(avatar).toHaveScreenshot(screenshot(`avatar-shape-round-image`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
55
core/src/components/avatar/test/shape/index.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Avatar - Shape</title>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
|
/>
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Avatar - Shape</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding" id="content" no-bounce>
|
||||||
|
<h2>Default</h2>
|
||||||
|
<div class="container">
|
||||||
|
<ion-avatar>AB</ion-avatar>
|
||||||
|
<ion-avatar>
|
||||||
|
<ion-icon name="person-outline"></ion-icon>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-avatar>
|
||||||
|
<img src="/src/components/avatar/test/avatar.svg" />
|
||||||
|
</ion-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Round</h2>
|
||||||
|
<div class="container">
|
||||||
|
<ion-avatar shape="round" size="xsmall">AB</ion-avatar>
|
||||||
|
<ion-avatar shape="round" size="small">AB</ion-avatar>
|
||||||
|
<ion-avatar shape="round" size="medium">AB</ion-avatar>
|
||||||
|
<ion-avatar shape="round" size="large">AB</ion-avatar>
|
||||||
|
<ion-avatar shape="round" size="xlarge">AB</ion-avatar>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 728 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 798 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 819 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 521 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 460 B After Width: | Height: | Size: 1008 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 501 B After Width: | Height: | Size: 928 B |
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 891 B |
Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 883 B |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 590 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 784 B After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 771 B After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 780 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 870 B |
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 754 B |
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 887 B |
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 676 B |
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 994 B |
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 888 B |
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 968 B |
@ -210,14 +210,14 @@ export declare interface IonApp extends Components.IonApp {}
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['mode', 'size', 'theme']
|
inputs: ['mode', 'shape', 'size', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-avatar',
|
selector: 'ion-avatar',
|
||||||
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: ['mode', 'size', 'theme'],
|
inputs: ['mode', 'shape', 'size', 'theme'],
|
||||||
})
|
})
|
||||||
export class IonAvatar {
|
export class IonAvatar {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
|
@ -290,14 +290,14 @@ export declare interface IonApp extends Components.IonApp {}
|
|||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
defineCustomElementFn: defineIonAvatar,
|
defineCustomElementFn: defineIonAvatar,
|
||||||
inputs: ['mode', 'size', 'theme']
|
inputs: ['mode', 'shape', 'size', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-avatar',
|
selector: 'ion-avatar',
|
||||||
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: ['mode', 'size', 'theme'],
|
inputs: ['mode', 'shape', 'size', 'theme'],
|
||||||
standalone: true
|
standalone: true
|
||||||
})
|
})
|
||||||
export class IonAvatar {
|
export class IonAvatar {
|
||||||
|
@ -103,7 +103,8 @@ export const IonAccordionGroup = /*@__PURE__*/ defineContainer<JSX.IonAccordionG
|
|||||||
|
|
||||||
|
|
||||||
export const IonAvatar = /*@__PURE__*/ defineContainer<JSX.IonAvatar>('ion-avatar', defineIonAvatar, [
|
export const IonAvatar = /*@__PURE__*/ defineContainer<JSX.IonAvatar>('ion-avatar', defineIonAvatar, [
|
||||||
'size'
|
'size',
|
||||||
|
'shape'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|