Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(badge): add support for hint feature #30213

Open
wants to merge 15 commits into
base: next
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ ion-backdrop,event,ionBackdropTap,void,true
ion-badge,shadow
ion-badge,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-badge,prop,mode,"ios" | "md",undefined,false,false
ion-badge,prop,position,"bottom-right" | "static" | "top-right",'static',false,false
ion-badge,prop,shape,"round | rectangular" | "soft" | undefined,undefined,false,false
ion-badge,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | "xxsmall" | undefined,undefined,false,false
ion-badge,prop,theme,"ios" | "md" | "ionic",undefined,false,false
Expand Down
8 changes: 8 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"top-right"` to position the badge on top right absolute position of the parent element. Set to `"bottom-right"` to position the badge on bottom right absolute position of the parent element. Defaults to `"static"`.
*/
"position": 'top-right' | 'bottom-right' | 'static';
/**
* 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.
*/
Expand Down Expand Up @@ -5788,6 +5792,10 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"top-right"` to position the badge on top right absolute position of the parent element. Set to `"bottom-right"` to position the badge on bottom right absolute position of the parent element. Defaults to `"static"`.
*/
"position"?: 'top-right' | 'bottom-right' | 'static';
/**
* 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.
*/
Expand Down
16 changes: 14 additions & 2 deletions core/src/components/badge/badge.common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@
color: #{color.current-color(contrast)};
}

:host(:empty) {
display: none;
// Badge Empty (hint)
// --------------------------------------------------

:host(:empty:not(.badge-static)) {
@include position(null, 0, null, null);
position: absolute;
}

:host(:empty.badge-top-right) {
top: 0;
}

:host(:empty.badge-bottom-right) {
bottom: 0;
}
33 changes: 33 additions & 0 deletions core/src/components/badge/badge.ionic.scss
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,36 @@
width: globals.$ion-scale-1000;
height: globals.$ion-scale-1000;
}

// Badge Empty
// --------------------------------------------------

:host(:empty) {
--padding-start: 0;
--padding-end: 0;
}

:host(:empty:not(.badge-static)) {
border: globals.$ion-border-size-025 globals.$ion-border-style-solid globals.$ion-bg-surface-inverse;
}

// Badge Sizes Empty
// --------------------------------------------------

/* sm */
:host(.badge-small:empty) {
min-width: globals.$ion-scale-200;
height: globals.$ion-scale-200;
}

/* md */
:host(.badge-medium:empty) {
min-width: globals.$ion-scale-300;
height: globals.$ion-scale-300;
}

/* lg */
:host(.badge-large:empty) {
min-width: globals.$ion-scale-400;
height: globals.$ion-scale-400;
}
13 changes: 13 additions & 0 deletions core/src/components/badge/badge.native.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@

font-family: $font-family-base;
}

// TODO(ROU-10747): Review size styles when sizes are defined for native themes.
:host(:empty) {
--padding-start: 0;
--padding-end: 0;
--padding-bottom: 0;
--padding-top: 0;

@include border-radius(999px);

width: $badge-min-width;
height: $badge-min-width;
}
9 changes: 9 additions & 0 deletions core/src/components/badge/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export class Badge implements ComponentInterface {
*/
@Prop() size?: 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';

/**
* Set to `"top-right"` to position the badge on top right absolute position of the parent element.
* Set to `"bottom-right"` to position the badge on bottom right absolute position of the parent element.
*
* Defaults to `"static"`.
*/
@Prop() position: 'top-right' | 'bottom-right' | 'static' = 'static';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the property name and values. Here are some of the other components for reference:

Component Property Options
ion-infinite-scroll position "bottom" | "top"
ion-label position "fixed" | "floating" | "stacked"
ion-toast position "bottom" | "middle" | "top"
ion-fab horizontal "center" | "end" | "start"
ion-fab vertical "bottom" | "center" | "top"

Do we plan to eventually support a left property? Maybe we should follow the approach used by ion-fab and offer horizontal and vertical options instead? This is how MUI handles it: https://mui.com/material-ui/react-badge/#badge-alignment

Also, we should use end instead of right if it flips in RTL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not planned, but I thought this would scale better if that changes in the future. But I agree with you with the start/end stuff, will change that. Maybe will make it bottom | top, to make it similar to other components.

I think for this horizontal and vertical would be too much just for two positions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would static stand for? Should this be described as well? Any reason we don't include top-left and bottom-left?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original idea for static, was to stand for the actual css property used, position: static, as in no absolute position is being used.

But after @brandyscarney feedback I changed to just top and bottom. As for other positions, according to UX team only the right/end side makes sense for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My vote is to follow what fab does with horizontal and vertical. It would stay consistent with what we have, leading for an easier introduction for the community.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there's also position in ion-infinite-scroll, ion-label and ion-toast. Personally, when I see horizontal or vertical props, I think about alignment, not absolute positioning 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brandyscarney @thetaPC @christian-bromann do you all vote for the ion-fab approach and instead have a vertical prop that accepts top | bottom ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes for me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the change 👍


private getShape(): string | undefined {
const theme = getIonTheme(this);
const { shape } = this;
Expand Down Expand Up @@ -89,6 +97,7 @@ export class Badge implements ComponentInterface {
[theme]: true,
[`badge-${shape}`]: shape !== undefined,
[`badge-${size}`]: size !== undefined,
[`badge-${this.position}`]: true,
})}
>
<slot></slot>
Expand Down
14 changes: 14 additions & 0 deletions core/src/components/badge/test/hint/badge.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';

configs({ modes: ['md', 'ios', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('badge: hint'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/badge/test/hint', config);

const container = page.locator('ion-list');

await expect(container).toHaveScreenshot(screenshot(`badge-hint`));
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions core/src/components/badge/test/hint/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Badge - 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>
</head>

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

<ion-content id="content">
<ion-list>
<ion-list-header>
<ion-label> Badge Empty </ion-label>
</ion-list-header>
<ion-item>
<ion-label>Badge small</ion-label>
<ion-badge color="primary" size="small"></ion-badge>
</ion-item>
<ion-item>
<ion-label>Badge Medium</ion-label>
<ion-badge color="danger" size="medium"></ion-badge>
</ion-item>
<ion-item>
<ion-label>Badge Large</ion-label>
<ion-badge color="warning" size="large"></ion-badge>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,14 @@ export declare interface IonBackdrop extends Components.IonBackdrop {


@ProxyCmp({
inputs: ['color', 'mode', 'shape', 'size', 'theme']
inputs: ['color', 'mode', 'position', 'shape', 'size', 'theme']
})
@Component({
selector: 'ion-badge',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['color', 'mode', 'shape', 'size', 'theme'],
inputs: ['color', 'mode', 'position', 'shape', 'size', 'theme'],
})
export class IonBadge {
protected el: HTMLElement;
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/standalone/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,14 @@ export declare interface IonBackdrop extends Components.IonBackdrop {

@ProxyCmp({
defineCustomElementFn: defineIonBadge,
inputs: ['color', 'mode', 'shape', 'size', 'theme']
inputs: ['color', 'mode', 'position', 'shape', 'size', 'theme']
})
@Component({
selector: 'ion-badge',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['color', 'mode', 'shape', 'size', 'theme'],
inputs: ['color', 'mode', 'position', 'shape', 'size', 'theme'],
standalone: true
})
export class IonBadge {
Expand Down
3 changes: 2 additions & 1 deletion packages/vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export const IonBackdrop = /*@__PURE__*/ defineContainer<JSX.IonBackdrop>('ion-b
export const IonBadge = /*@__PURE__*/ defineContainer<JSX.IonBadge>('ion-badge', defineIonBadge, [
'color',
'shape',
'size'
'size',
'position'
]);


Expand Down
Loading