feat(avatar): add styles for default (medium) size in ionic theme (#29538)
Issue number: internal --------- ## What is the current behavior? Avatar does not have any styles in the ionic theme. ## What is the new behavior? - Adds background, border and font styles for avatar - Adds the styles for the medium size and defaults the size to medium - Adds e2e test for avatar sizes ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-10735-ionic1.vercel.app/src/components/avatar/test/size?ionic:theme=ionic)
@ -184,6 +184,7 @@ ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||
|
||||
ion-avatar,shadow
|
||||
ion-avatar,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-avatar,prop,size,"medium" | undefined,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,ios
|
||||
|
8
core/src/components.d.ts
vendored
@ -335,6 +335,10 @@ export namespace Components {
|
||||
* The mode determines the platform behaviors of the component.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Set to `"medium"` for the default height and width. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
||||
*/
|
||||
"size"?: 'medium';
|
||||
/**
|
||||
* The theme determines the visual appearance of the component.
|
||||
*/
|
||||
@ -5563,6 +5567,10 @@ declare namespace LocalJSX {
|
||||
* The mode determines the platform behaviors of the component.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Set to `"medium"` for the default height and width. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
||||
*/
|
||||
"size"?: 'medium';
|
||||
/**
|
||||
* The theme determines the visual appearance of the component.
|
||||
*/
|
||||
|
38
core/src/components/avatar/avatar.ionic.scss
Normal file
@ -0,0 +1,38 @@
|
||||
@use "../../themes/ionic/ionic.globals.scss" as globals;
|
||||
@import "./avatar";
|
||||
|
||||
// Ionic Avatar
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: globals.$ionic-color-neutral-100;
|
||||
color: globals.$ionic-color-neutral-800;
|
||||
|
||||
font-weight: globals.$ionic-font-weight-regular;
|
||||
|
||||
line-height: globals.$ionic-line-height-600;
|
||||
}
|
||||
|
||||
:host(:not(.avatar-image)) {
|
||||
border: globals.$ionic-border-size-025 solid globals.$ionic-color-neutral-800;
|
||||
}
|
||||
|
||||
// Avatar Sizes
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.avatar-medium) {
|
||||
--padding-top: #{globals.$ionic-space-0};
|
||||
--padding-end: #{globals.$ionic-space-200};
|
||||
--padding-bottom: #{globals.$ionic-space-0};
|
||||
--padding-start: #{globals.$ionic-space-200};
|
||||
|
||||
width: globals.$ionic-scale-1000;
|
||||
height: globals.$ionic-scale-1000;
|
||||
|
||||
font-size: globals.$ionic-font-size-400;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Host, h } from '@stencil/core';
|
||||
import { Component, Element, Host, Prop, h } from '@stencil/core';
|
||||
|
||||
import { getIonTheme } from '../../global/ionic-global';
|
||||
|
||||
@ -12,17 +12,50 @@ import { getIonTheme } from '../../global/ionic-global';
|
||||
styleUrls: {
|
||||
ios: 'avatar.ios.scss',
|
||||
md: 'avatar.md.scss',
|
||||
ionic: 'avatar.md.scss',
|
||||
ionic: 'avatar.ionic.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class Avatar implements ComponentInterface {
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
/**
|
||||
* Set to `"medium"` for the default height and width.
|
||||
*
|
||||
* Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
|
||||
*/
|
||||
@Prop() size?: 'medium';
|
||||
|
||||
get hasImage() {
|
||||
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
|
||||
}
|
||||
|
||||
private getSize(): string | undefined {
|
||||
const theme = getIonTheme(this);
|
||||
const { size } = this;
|
||||
|
||||
// TODO(ROU-10752): Remove theme check when sizes are defined for all themes.
|
||||
if (theme !== 'ionic') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (size === undefined) {
|
||||
return 'medium';
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = getIonTheme(this);
|
||||
const size = this.getSize();
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
[theme]: true,
|
||||
[`avatar-${size}`]: size !== undefined,
|
||||
[`avatar-image`]: this.hasImage,
|
||||
}}
|
||||
>
|
||||
<slot></slot>
|
||||
|
54
core/src/components/avatar/test/size/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: size'), () => {
|
||||
test.describe('medium', () => {
|
||||
test('should not have visual regressions when containing text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-avatar size="medium">AB</ion-avatar>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const avatar = page.locator('ion-avatar');
|
||||
|
||||
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-text`));
|
||||
});
|
||||
|
||||
test('should not have visual regressions when containing an icon', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-avatar size="medium">
|
||||
<ion-icon name="person-outline"></ion-icon>
|
||||
</ion-avatar>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const avatar = page.locator('ion-avatar');
|
||||
|
||||
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-icon`));
|
||||
});
|
||||
|
||||
test('should not have visual regressions when containing an image', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-avatar size="medium">
|
||||
<img src="/src/components/avatar/test/avatar.svg"/>
|
||||
</ion-avatar>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const avatar = page.locator('ion-avatar');
|
||||
|
||||
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-image`));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 521 B |
After Width: | Height: | Size: 500 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 585 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 695 B |
After Width: | Height: | Size: 717 B |
After Width: | Height: | Size: 637 B |
61
core/src/components/avatar/test/size/index.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Avatar - Size</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 - Size</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding" id="content" no-bounce>
|
||||
<h2>Text</h2>
|
||||
<div class="container">
|
||||
<ion-avatar>AB</ion-avatar>
|
||||
<ion-avatar size="medium">AB</ion-avatar>
|
||||
</div>
|
||||
|
||||
<h2>Icons</h2>
|
||||
<div class="container">
|
||||
<ion-avatar>
|
||||
<ion-icon name="person-outline"></ion-icon>
|
||||
</ion-avatar>
|
||||
<ion-avatar size="medium">
|
||||
<ion-icon name="person-outline"></ion-icon>
|
||||
</ion-avatar>
|
||||
</div>
|
||||
|
||||
<h2>Images</h2>
|
||||
<div class="container">
|
||||
<ion-avatar>
|
||||
<img src="/src/components/avatar/test/avatar.svg" />
|
||||
</ion-avatar>
|
||||
<ion-avatar size="medium">
|
||||
<img src="/src/components/avatar/test/avatar.svg" />
|
||||
</ion-avatar>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|