feat(avatar): add disabled property (#30284)

Issue number: internal

---------

## What is the current behavior?
Avatar does not have a disabled state for the ionic theme.

## What is the new behavior?
- Added styles for ionic theme disabled state
- Added states e2e test & snapshots

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

[Preview](https://ionic-framework-git-rou-11728-ionic1.vercel.app/src/components/avatar/test/states?ionic:theme=ionic)

---------

Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
João Ferreira
2025-03-24 09:57:54 +00:00
committed by GitHub
parent c5fb051be3
commit 02c9d64ca5
12 changed files with 91 additions and 7 deletions

View File

@ -185,6 +185,7 @@ ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-app,method,setFocus,setFocus(elements: HTMLElement[]) => Promise<void>
ion-avatar,shadow
ion-avatar,prop,disabled,boolean,false,false,false
ion-avatar,prop,mode,"ios" | "md",undefined,false,false
ion-avatar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-avatar,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | "xxsmall" | undefined,undefined,false,false

View File

@ -343,6 +343,10 @@ export namespace Components {
"theme"?: "ios" | "md" | "ionic";
}
interface IonAvatar {
/**
* If `true`, the user cannot interact with the avatar.
*/
"disabled": boolean;
/**
* The mode determines the platform behaviors of the component.
*/
@ -5759,6 +5763,10 @@ declare namespace LocalJSX {
"theme"?: "ios" | "md" | "ionic";
}
interface IonAvatar {
/**
* If `true`, the user cannot interact with the avatar.
*/
"disabled"?: boolean;
/**
* The mode determines the platform behaviors of the component.
*/

View File

@ -171,3 +171,9 @@
:host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-bottom:empty) {
transform: translate(globals.$ion-scale-100, calc(globals.$ion-scale-100));
}
// Avatar Disabled
// --------------------------------------------------
:host(.avatar-disabled)::before {
@include globals.disabled-state();
}

View File

@ -40,6 +40,11 @@ export class Avatar implements ComponentInterface {
*/
@Prop() shape?: 'soft' | 'round' | 'rectangular';
/**
* If `true`, the user cannot interact with the avatar.
*/
@Prop() disabled = false;
get hasImage() {
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
}
@ -81,6 +86,7 @@ export class Avatar implements ComponentInterface {
}
render() {
const { hasImage, hasIcon, disabled } = this;
const theme = getIonTheme(this);
const size = this.getSize();
const shape = this.getShape();
@ -91,8 +97,9 @@ export class Avatar implements ComponentInterface {
[theme]: true,
[`avatar-${size}`]: size !== undefined,
[`avatar-${shape}`]: shape !== undefined,
[`avatar-image`]: this.hasImage,
[`avatar-icon`]: this.hasIcon,
[`avatar-image`]: hasImage,
[`avatar-icon`]: hasIcon,
[`avatar-disabled`]: disabled,
}}
>
<slot></slot>

View File

@ -0,0 +1,27 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* Avatar does not test RTL behaviors.
* Usages of Avatar in slots are tested in components that use Avatar.
*/
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('avatar: states'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<div id="container">
<ion-avatar disabled> AV </ion-avatar>
<ion-avatar disabled>
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
</div>
`,
config
);
const container = page.locator('#container');
await expect(container).toHaveScreenshot(screenshot(`avatar-disabled`));
});
});
});

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Avatar - States</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>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Avatar - States</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<h3>Disabled</h3>
<ion-avatar disabled>AV</ion-avatar>
<ion-avatar disabled>
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -211,14 +211,14 @@ export declare interface IonApp extends Components.IonApp {}
@ProxyCmp({
inputs: ['mode', 'shape', 'size', 'theme']
inputs: ['disabled', 'mode', 'shape', 'size', 'theme']
})
@Component({
selector: 'ion-avatar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['mode', 'shape', 'size', 'theme'],
inputs: ['disabled', 'mode', 'shape', 'size', 'theme'],
})
export class IonAvatar {
protected el: HTMLIonAvatarElement;

View File

@ -295,14 +295,14 @@ export declare interface IonApp extends Components.IonApp {}
@ProxyCmp({
defineCustomElementFn: defineIonAvatar,
inputs: ['mode', 'shape', 'size', 'theme']
inputs: ['disabled', 'mode', 'shape', 'size', 'theme']
})
@Component({
selector: 'ion-avatar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['mode', 'shape', 'size', 'theme'],
inputs: ['disabled', 'mode', 'shape', 'size', 'theme'],
standalone: true
})
export class IonAvatar {

View File

@ -112,7 +112,8 @@ export const IonAccordionGroup: StencilVueComponent<JSX.IonAccordionGroup, JSX.I
export const IonAvatar: StencilVueComponent<JSX.IonAvatar> = /*@__PURE__*/ defineContainer<JSX.IonAvatar>('ion-avatar', defineIonAvatar, [
'size',
'shape'
'shape',
'disabled'
]);