Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,19 +469,19 @@ export namespace Components {
*/
"color"?: Color;
/**
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Only applies to the `ionic` theme.
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
"hue"?: 'bold' | 'subtle';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"rectangular"` for non-rounded corners. Set to `"soft"` for slightly rounded corners. Set to `"round"` for fully rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
* Set to `"crisp"` for a badge with even slightly rounded corners, `"soft"` for a badge with slightly rounded corners, `"round"` for a badge with fully rounded corners, or `"rectangular"` for a badge without rounded corners. Defaults to `"soft"` if both the shape property and theme config are unset.
*/
"shape"?: 'soft' | 'round | rectangular';
"shape"?: 'crisp' | 'soft' | 'round' | 'rectangular';
/**
* Set to `"small"` for a small badge. Set to `"medium"` for a medium badge. Set to `"large"` for a large badge, when it is empty (no text or icon). Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
* Set to `"small"` for a smaller size. Set to `"medium"` for a medium size. Set to `"large"` for a larger size. Defaults to `"small"` if both the size property and theme config are unset.
*/
"size"?: 'small' | 'medium' | 'large';
/**
Expand Down Expand Up @@ -898,7 +898,7 @@ export namespace Components {
*/
"shape"?: IonChipShape;
/**
* Set to `"small"` for a chip with less height and padding. Defaults to `"large"` if both the size property and theme config are unset.
* Set to `"small"` for a chip with less height and padding, or `"large"` for a chip with more height and padding. Defaults to `"large"` if both the size property and theme config are unset.
*/
"size"?: IonChipSize;
}
Expand Down Expand Up @@ -6400,19 +6400,19 @@ declare namespace LocalJSX {
*/
"color"?: Color;
/**
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Only applies to the `ionic` theme.
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
"hue"?: 'bold' | 'subtle';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"rectangular"` for non-rounded corners. Set to `"soft"` for slightly rounded corners. Set to `"round"` for fully rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
* Set to `"crisp"` for a badge with even slightly rounded corners, `"soft"` for a badge with slightly rounded corners, `"round"` for a badge with fully rounded corners, or `"rectangular"` for a badge without rounded corners. Defaults to `"soft"` if both the shape property and theme config are unset.
*/
"shape"?: 'soft' | 'round | rectangular';
"shape"?: 'crisp' | 'soft' | 'round' | 'rectangular';
/**
* Set to `"small"` for a small badge. Set to `"medium"` for a medium badge. Set to `"large"` for a large badge, when it is empty (no text or icon). Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
* Set to `"small"` for a smaller size. Set to `"medium"` for a medium size. Set to `"large"` for a larger size. Defaults to `"small"` if both the size property and theme config are unset.
*/
"size"?: 'small' | 'medium' | 'large';
/**
Expand Down Expand Up @@ -6864,7 +6864,7 @@ declare namespace LocalJSX {
*/
"shape"?: IonChipShape;
/**
* Set to `"small"` for a chip with less height and padding. Defaults to `"large"` if both the size property and theme config are unset.
* Set to `"small"` for a chip with less height and padding, or `"large"` for a chip with more height and padding. Defaults to `"large"` if both the size property and theme config are unset.
*/
"size"?: IonChipSize;
}
Expand Down
6 changes: 3 additions & 3 deletions core/src/components/avatar/avatar.common.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import "../../themes/mixins.scss";
@use "../../themes/mixins" as mixins;

// Avatar
// --------------------------------------------------
Expand All @@ -7,7 +7,7 @@
/**
* @prop --border-radius: Border radius of the avatar and inner image
*/
@include border-radius(var(--border-radius));
@include mixins.border-radius(var(--border-radius));

display: block;

Expand All @@ -16,7 +16,7 @@

::slotted(ion-img),
::slotted(img) {
@include border-radius(var(--border-radius));
@include mixins.border-radius(var(--border-radius));

width: 100%;
height: 100%;
Expand Down
73 changes: 0 additions & 73 deletions core/src/components/avatar/avatar.ionic.scss
Original file line number Diff line number Diff line change
Expand Up @@ -169,79 +169,6 @@
height: globals.$ion-scale-800;
}

// Avatar Badge Empty (hint)
// --------------------------------------------------

:host ::slotted(ion-badge.badge-vertical-top:empty) {
@include globals.transform(translate(globals.$ion-scale-050, calc(globals.$ion-scale-050 * -1)));
}

:host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-top:empty) {
@include globals.transform(translate(globals.$ion-scale-100, calc(globals.$ion-scale-100 * -1)));
}

:host ::slotted(ion-badge.badge-vertical-bottom:empty) {
@include globals.transform(translate(0, globals.$ion-scale-100));
}

:host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-bottom:empty) {
@include globals.transform(translate(globals.$ion-scale-100, globals.$ion-scale-100));
}

// Avatar Badge Bottom (hint)
// --------------------------------------------------

:host ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.transform(translate(50%, 50%));
}

:host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.position(null, globals.$ion-scale-100, globals.$ion-scale-100, null);
@include globals.transform(translate(100%, 100%));
}

:host(.avatar-xsmall) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.position(null, calc(globals.$ion-scale-050 * -1), calc(globals.$ion-scale-050 * -1), null);
}

:host(.avatar-small) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)),
:host(.avatar-medium) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)),
:host(.avatar-large) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.position(null, globals.$ion-scale-050, globals.$ion-scale-050, null);
}

:host(.avatar-xlarge) ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.position(null, globals.$ion-scale-150, globals.$ion-scale-150, null);
}

// Avatar Badge Top (hint)
// --------------------------------------------------

:host ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.transform(translate(50%, -50%));
}

:host(.avatar-xxsmall) ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.position(globals.$ion-scale-050, 0, null, null);
}

:host(.avatar-xsmall) ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.position(globals.$ion-scale-100, calc(globals.$ion-scale-050 * -1), null, null);
}

:host(.avatar-small) ::slotted(ion-badge.badge-vertical-top:not(:empty)),
:host(.avatar-medium) ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.position(globals.$ion-scale-150, 0, null, null);
}

:host(.avatar-large) ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.position(globals.$ion-scale-150, globals.$ion-scale-050, null, null);
}

:host(.avatar-xlarge) ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.position(globals.$ion-scale-150, globals.$ion-scale-150, null, null);
}

// Avatar Disabled
// --------------------------------------------------
:host(.avatar-disabled)::after {
Expand Down
19 changes: 0 additions & 19 deletions core/src/components/avatar/avatar.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,3 @@
width: $avatar-md-width;
height: $avatar-md-height;
}

// Avatar Empty Badge (hint)
// --------------------------------------------------

::slotted(ion-badge.badge-vertical-top:empty) {
@include globals.transform(translate(-50%, 50%));
}

::slotted(ion-badge.badge-vertical-bottom:empty) {
@include globals.transform(translateX(-100%));
}

:host ::slotted(ion-badge.badge-vertical-top:not(:empty)) {
@include globals.transform(translate(0, 100%));
}

:host ::slotted(ion-badge.badge-vertical-bottom:not(:empty)) {
@include globals.transform(translate(0, -100%));
}
62 changes: 55 additions & 7 deletions core/src/components/avatar/avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, h } from '@stencil/core';
import type { BadgeObserver } from '@utils/helpers';
import { createBadgeObserver } from '@utils/helpers';

import { getIonTheme } from '../../global/ionic-global';

Expand All @@ -18,6 +20,7 @@ import { getIonTheme } from '../../global/ionic-global';
})
export class Avatar implements ComponentInterface {
@Element() el!: HTMLElement;
private badgeObserver?: BadgeObserver;

/**
* Set to `"xxsmall"` for the smallest size.
Expand Down Expand Up @@ -45,6 +48,14 @@ export class Avatar implements ComponentInterface {
*/
@Prop() disabled = false;

componentDidLoad(): void {
this.setupBadgeObserver();
}

disconnectedCallback() {
this.destroyBadgeObserver();
}

get hasImage() {
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
}
Expand All @@ -53,14 +64,51 @@ export class Avatar implements ComponentInterface {
return !!this.el.querySelector('ion-icon');
}

private getSize(): string | undefined {
const theme = getIonTheme(this);
const { size } = this;
private get hasBadge() {
return !!this.el.querySelector('ion-badge');
}

// TODO(ROU-10752): Remove theme check when sizes are defined for all themes.
if (theme !== 'ionic') {
return undefined;
private onSlotChanged = () => {
/**
* Badges can be added or removed dynamically to mimic use
* cases like notifications. Based on the presence of a
* badge, we need to set up or destroy the badge observer.
*
* If the badge observer is already set up and there is a badge, then we don't need to do anything.
*/
if (this.hasBadge && this.badgeObserver) {
return;
}

if (this.hasBadge) {
this.setupBadgeObserver();
} else {
this.destroyBadgeObserver();
}
};

private setupBadgeObserver() {
this.destroyBadgeObserver();

// Only set up the badge observer if there is a badge and it's anchored
const badge = this.el.querySelector('ion-badge[vertical]') as HTMLElement | null;

if (!badge) {
return;
}

this.badgeObserver = createBadgeObserver({
host: this.el,
badge,
});
}

private destroyBadgeObserver() {
this.badgeObserver?.disconnect();
}

private getSize(): string | undefined {
const { size } = this;

if (size === undefined) {
return 'medium';
Expand Down Expand Up @@ -102,7 +150,7 @@ export class Avatar implements ComponentInterface {
[`avatar-disabled`]: disabled,
}}
>
<slot></slot>
<slot onSlotchange={this.onSlotChanged}></slot>
</Host>
);
}
Expand Down
97 changes: 97 additions & 0 deletions core/src/components/avatar/test/hint/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Avatar - Hint</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>
.row {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 16px;
}

h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
</style>
</head>

<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Avatar - Hint</ion-title>
</ion-toolbar>
</ion-header>

<ion-content id="content"></ion-content>
</ion-app>

<script>
const avatarSizes = ['xxsmall', 'xsmall', 'small', 'medium', 'large', 'xlarge'];
const badgeSizes = ['small', 'medium', 'large'];
const positions = ['top', 'bottom'];
const imgSrc = '/src/components/avatar/test/avatar.svg';

// Badge content variants: empty (dot), text, icon
const badgeContents = [
{ label: 'empty', html: '' },
{ label: 'text', html: '1' },
{ label: 'longText', html: '999+' },
{ label: 'icon', html: '<ion-icon icon="star"></ion-icon>' },
];

const content = document.getElementById('content');

avatarSizes.forEach((avatarSize) => {
positions.forEach((position) => {
const heading = document.createElement('h2');
heading.textContent = `${avatarSize} — ${position}`;
content.appendChild(heading);

const row = document.createElement('div');
row.className = 'row';

badgeSizes.forEach((badgeSize) => {
badgeContents.forEach(({ html }) => {
const avatar = document.createElement('ion-avatar');
avatar.setAttribute('size', avatarSize);

const img = document.createElement('img');
img.setAttribute('src', imgSrc);
avatar.appendChild(img);

const badge = document.createElement('ion-badge');
badge.setAttribute('hue', 'bold');
badge.setAttribute('color', 'danger');
badge.setAttribute('shape', 'round');
badge.setAttribute('size', badgeSize);
badge.setAttribute('vertical', position);
badge.innerHTML = html;

avatar.appendChild(badge);
row.appendChild(avatar);
});
});

content.appendChild(row);
});
});
</script>
</body>
</html>
Loading
Loading