feat(progress-bar): add styling for the ionic theme (#30185)

Issue number: internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
ion-progress-bar does not currently have any custom styling for the
ionic theme.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
According to the design, the following styling changes were made:
- Added a new shape prop which controls the shape of the progress bar
- Changed the color of the unfilled part of the progress bar and the bar
height to design token values
- Adding a new testing page and screenshot tests

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
This commit is contained in:
Pedro Lourenço
2025-02-13 16:06:59 +00:00
committed by GitHub
parent 7be7c08cb0
commit ae78967b3c
22 changed files with 172 additions and 7 deletions

View File

@ -1661,6 +1661,7 @@ ion-progress-bar,prop,buffer,number,1,false,false
ion-progress-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true ion-progress-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-progress-bar,prop,mode,"ios" | "md",undefined,false,false ion-progress-bar,prop,mode,"ios" | "md",undefined,false,false
ion-progress-bar,prop,reversed,boolean,false,false,false ion-progress-bar,prop,reversed,boolean,false,false,false
ion-progress-bar,prop,shape,"rectangular" | "round" | undefined,undefined,false,false
ion-progress-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-progress-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-progress-bar,prop,type,"determinate" | "indeterminate",'determinate',false,false ion-progress-bar,prop,type,"determinate" | "indeterminate",'determinate',false,false
ion-progress-bar,prop,value,number,0,false,false ion-progress-bar,prop,value,number,0,false,false

View File

@ -2609,6 +2609,10 @@ export namespace Components {
* If true, reverse the progress bar direction. * If true, reverse the progress bar direction.
*/ */
"reversed": boolean; "reversed": boolean;
/**
* Set to `"round"` for a progress bar with rounded corners, or `"rectangular"` for a progress bar without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
"shape"?: 'round' | 'rectangular';
/** /**
* The theme determines the visual appearance of the component. * The theme determines the visual appearance of the component.
*/ */
@ -7959,6 +7963,10 @@ declare namespace LocalJSX {
* If true, reverse the progress bar direction. * If true, reverse the progress bar direction.
*/ */
"reversed"?: boolean; "reversed"?: boolean;
/**
* Set to `"round"` for a progress bar with rounded corners, or `"rectangular"` for a progress bar without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
"shape"?: 'round' | 'rectangular';
/** /**
* The theme determines the visual appearance of the component. * The theme determines the visual appearance of the component.
*/ */

View File

@ -0,0 +1,22 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@use "./progress-bar";
// Ionic Progress bar
// --------------------------------------------------
:host {
--background: #{globals.$ion-bg-neutral-subtle-default};
height: globals.$ion-scale-100;
}
// Progress Bar Shapes
// -------------------------------------------------------------------------------
:host(.progress-bar-shape-round) {
@include globals.border-radius(globals.$ion-round-xs);
}
:host(.progress-bar-shape-rectangular) {
@include globals.border-radius(globals.$ion-rectangular-xs);
}

View File

@ -21,7 +21,7 @@ import type { Color } from '../../interface';
styleUrls: { styleUrls: {
ios: 'progress-bar.ios.scss', ios: 'progress-bar.ios.scss',
md: 'progress-bar.md.scss', md: 'progress-bar.md.scss',
ionic: 'progress-bar.md.scss', ionic: 'progress-bar.ionic.scss',
}, },
shadow: true, shadow: true,
}) })
@ -57,10 +57,35 @@ export class ProgressBar implements ComponentInterface {
*/ */
@Prop({ reflect: true }) color?: Color; @Prop({ reflect: true }) color?: Color;
/**
* Set to `"round"` for a progress bar with rounded corners, or `"rectangular"`
* for a progress bar without rounded corners.
*
* Defaults to `"round"` for the `ionic` theme, undefined for all other themes.
*/
@Prop() shape?: 'round' | 'rectangular';
private getShape(): string | undefined {
const theme = getIonTheme(this);
const { shape } = this;
// TODO(ROU-11638): 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 { color, type, reversed, value, buffer } = this; const { color, type, reversed, value, buffer } = this;
const paused = config.getBoolean('_testing'); const paused = config.getBoolean('_testing');
const theme = getIonTheme(this); const theme = getIonTheme(this);
const shape = this.getShape();
// If the progress is displayed as a solid bar. // If the progress is displayed as a solid bar.
const progressSolid = buffer === 1; const progressSolid = buffer === 1;
return ( return (
@ -75,6 +100,7 @@ export class ProgressBar implements ComponentInterface {
'progress-paused': paused, 'progress-paused': paused,
'progress-bar-reversed': document.dir === 'rtl' ? !reversed : reversed, 'progress-bar-reversed': document.dir === 'rtl' ? !reversed : reversed,
'progress-bar-solid': progressSolid, 'progress-bar-solid': progressSolid,
[`progress-bar-shape-${shape}`]: shape !== undefined,
})} })}
> >
{type === 'indeterminate' ? renderIndeterminate() : renderProgress(value, buffer)} {type === 'indeterminate' ? renderIndeterminate() : renderProgress(value, buffer)}

View File

@ -1,7 +1,7 @@
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright'; import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => { configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('progress-bar: basic'), () => { test.describe(title('progress-bar: basic'), () => {
test('should not have visual regressions', async ({ page }) => { test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/progress-bar/test/basic', config); await page.goto('/src/components/progress-bar/test/basic', config);

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Progress Bar - 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>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Progress Bar - Shape</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list lines="none">
<ion-list-header>
<ion-label> Default </ion-label>
</ion-list-header>
<ion-item>
<ion-progress-bar value="0.50"></ion-progress-bar>
</ion-item>
<ion-list-header>
<ion-label> Round </ion-label>
</ion-list-header>
<ion-item>
<ion-progress-bar value="0.50" shape="round"></ion-progress-bar>
</ion-item>
<ion-list-header>
<ion-label> Rectangular </ion-label>
</ion-list-header>
<ion-item>
<ion-progress-bar value="0.50" shape="rectangular"></ion-progress-bar>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,56 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('progress-bar: shape'), () => {
test('round - should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<style>
:root {
background: #ccc7c7;
}
.container {
padding: 10px;
}
</style>
<div class="container">
<ion-progress-bar value="0.50" shape="round"></ion-progress-bar>
</div>
`,
config
);
const container = page.locator('.container');
await expect(container).toHaveScreenshot(screenshot(`progress-bar-shape-round`));
});
test('rectangular - should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<style>
:root {
background: #ccc7c7;
}
.container {
padding: 10px;
}
</style>
<div class="container">
<ion-progress-bar value="0.50" shape="rectangular"></ion-progress-bar>
</div>
`,
config
);
const container = page.locator('.container');
await expect(container).toHaveScreenshot(screenshot(`progress-bar-shape-rectangular`));
});
});
});

View File

@ -1589,14 +1589,14 @@ Shorthand for ionPickerDidDismiss.
@ProxyCmp({ @ProxyCmp({
inputs: ['buffer', 'color', 'mode', 'reversed', 'theme', 'type', 'value'] inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value']
}) })
@Component({ @Component({
selector: 'ion-progress-bar', selector: 'ion-progress-bar',
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: ['buffer', 'color', 'mode', 'reversed', 'theme', 'type', 'value'], inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value'],
}) })
export class IonProgressBar { export class IonProgressBar {
protected el: HTMLElement; protected el: HTMLElement;

View File

@ -1593,14 +1593,14 @@ Shorthand for ionPickerDidDismiss.
@ProxyCmp({ @ProxyCmp({
defineCustomElementFn: defineIonProgressBar, defineCustomElementFn: defineIonProgressBar,
inputs: ['buffer', 'color', 'mode', 'reversed', 'theme', 'type', 'value'] inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value']
}) })
@Component({ @Component({
selector: 'ion-progress-bar', selector: 'ion-progress-bar',
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: ['buffer', 'color', 'mode', 'reversed', 'theme', 'type', 'value'], inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value'],
standalone: true standalone: true
}) })
export class IonProgressBar { export class IonProgressBar {

View File

@ -619,7 +619,8 @@ export const IonProgressBar = /*@__PURE__*/ defineContainer<JSX.IonProgressBar>(
'reversed', 'reversed',
'value', 'value',
'buffer', 'buffer',
'color' 'color',
'shape'
]); ]);