feat(spinner): add size for ionic theme (#29699)
Co-authored-by: Brandy Carney <brandyscarney@users.noreply.github.com>
@ -2118,8 +2118,11 @@ ion-spinner,prop,duration,number | undefined,undefined,false,false
|
|||||||
ion-spinner,prop,mode,"ios" | "md",undefined,false,false
|
ion-spinner,prop,mode,"ios" | "md",undefined,false,false
|
||||||
ion-spinner,prop,name,"bubbles" | "circles" | "circular" | "crescent" | "dots" | "lines" | "lines-sharp" | "lines-sharp-small" | "lines-small" | undefined,undefined,false,false
|
ion-spinner,prop,name,"bubbles" | "circles" | "circular" | "crescent" | "dots" | "lines" | "lines-sharp" | "lines-sharp-small" | "lines-small" | undefined,undefined,false,false
|
||||||
ion-spinner,prop,paused,boolean,false,false,false
|
ion-spinner,prop,paused,boolean,false,false,false
|
||||||
|
ion-spinner,prop,size,"large" | "medium" | "small" | "xlarge" | "xsmall" | undefined,undefined,false,false
|
||||||
ion-spinner,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
ion-spinner,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||||
ion-spinner,css-prop,--color
|
ion-spinner,css-prop,--color,ionic
|
||||||
|
ion-spinner,css-prop,--color,ios
|
||||||
|
ion-spinner,css-prop,--color,md
|
||||||
|
|
||||||
ion-split-pane,shadow
|
ion-split-pane,shadow
|
||||||
ion-split-pane,prop,contentId,string | undefined,undefined,false,true
|
ion-split-pane,prop,contentId,string | undefined,undefined,false,true
|
||||||
|
8
core/src/components.d.ts
vendored
@ -3346,6 +3346,10 @@ export namespace Components {
|
|||||||
* If `true`, the spinner's animation will be paused.
|
* If `true`, the spinner's animation will be paused.
|
||||||
*/
|
*/
|
||||||
"paused": boolean;
|
"paused": boolean;
|
||||||
|
/**
|
||||||
|
* Set to `"xsmall"` for the smallest size. Set to `"small"` for a smaller size. Set to `"medium"` for a medium size. Set to `"large"` for a large size. Set to `"xlarge"` for the largest size. Defaults to `"xsmall"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"size"?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
||||||
/**
|
/**
|
||||||
* The theme determines the visual appearance of the component.
|
* The theme determines the visual appearance of the component.
|
||||||
*/
|
*/
|
||||||
@ -8689,6 +8693,10 @@ declare namespace LocalJSX {
|
|||||||
* If `true`, the spinner's animation will be paused.
|
* If `true`, the spinner's animation will be paused.
|
||||||
*/
|
*/
|
||||||
"paused"?: boolean;
|
"paused"?: boolean;
|
||||||
|
/**
|
||||||
|
* Set to `"xsmall"` for the smallest size. Set to `"small"` for a smaller size. Set to `"medium"` for a medium size. Set to `"large"` for a large size. Set to `"xlarge"` for the largest size. Defaults to `"xsmall"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
"size"?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
||||||
/**
|
/**
|
||||||
* The theme determines the visual appearance of the component.
|
* The theme determines the visual appearance of the component.
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
@import "../../themes/native/native.globals";
|
@import "../../themes/functions.string";
|
||||||
|
@import "../../themes/functions.color";
|
||||||
|
@import "../../themes/mixins";
|
||||||
|
|
||||||
// Spinners
|
// Spinners
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
38
core/src/components/spinner/spinner.ionic.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@use "../../themes/ionic/ionic.globals.scss" as globals;
|
||||||
|
@use "./spinner.common";
|
||||||
|
|
||||||
|
// Ionic Spinner
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
/* Extra Small */
|
||||||
|
:host(.spinner-xsmall) {
|
||||||
|
width: globals.$ionic-scale-600;
|
||||||
|
height: globals.$ionic-scale-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small */
|
||||||
|
:host(.spinner-small) {
|
||||||
|
width: globals.$ionic-scale-800;
|
||||||
|
height: globals.$ionic-scale-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medium */
|
||||||
|
:host(.spinner-medium) {
|
||||||
|
width: globals.$ionic-scale-1000;
|
||||||
|
height: globals.$ionic-scale-1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large */
|
||||||
|
:host(.spinner-large) {
|
||||||
|
width: globals.$ionic-scale-1200;
|
||||||
|
height: globals.$ionic-scale-1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra Large */
|
||||||
|
:host(.spinner-xlarge) {
|
||||||
|
width: globals.$ionic-scale-1400;
|
||||||
|
height: globals.$ionic-scale-1400;
|
||||||
|
}
|
@ -16,7 +16,11 @@ import type { SpinnerConfig } from './spinner-interface';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-spinner',
|
tag: 'ion-spinner',
|
||||||
styleUrl: 'spinner.scss',
|
styleUrls: {
|
||||||
|
ios: 'spinner.common.scss',
|
||||||
|
md: 'spinner.common.scss',
|
||||||
|
ionic: 'spinner.ionic.scss',
|
||||||
|
},
|
||||||
shadow: true,
|
shadow: true,
|
||||||
})
|
})
|
||||||
export class Spinner implements ComponentInterface {
|
export class Spinner implements ComponentInterface {
|
||||||
@ -43,6 +47,18 @@ export class Spinner implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() paused = false;
|
@Prop() paused = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `"xsmall"` for the smallest size.
|
||||||
|
* Set to `"small"` for a smaller size.
|
||||||
|
* Set to `"medium"` for a medium size.
|
||||||
|
* Set to `"large"` for a large size.
|
||||||
|
* Set to `"xlarge"` for the largest size.
|
||||||
|
*
|
||||||
|
* Defaults to `"xsmall"` for the `ionic` theme, undefined for all other themes.
|
||||||
|
*/
|
||||||
|
@Prop() size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
||||||
|
|
||||||
|
// TODO(ROU-10920): Switch `theme` to `mode`.
|
||||||
private getName(): SpinnerTypes {
|
private getName(): SpinnerTypes {
|
||||||
const spinnerName = this.name || config.get('spinner');
|
const spinnerName = this.name || config.get('spinner');
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
@ -52,10 +68,27 @@ export class Spinner implements ComponentInterface {
|
|||||||
return theme === 'ios' ? 'lines' : 'circular';
|
return theme === 'ios' ? 'lines' : 'circular';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSize(): string | undefined {
|
||||||
|
const theme = getIonTheme(this);
|
||||||
|
const { size } = this;
|
||||||
|
|
||||||
|
// TODO(ROU-10912): Remove theme check when sizes are defined for all themes.
|
||||||
|
if (theme !== 'ionic') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size === undefined) {
|
||||||
|
return 'xsmall';
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const theme = getIonTheme(self);
|
const theme = getIonTheme(self);
|
||||||
const spinnerName = self.getName();
|
const spinnerName = self.getName();
|
||||||
|
const size = this.getSize();
|
||||||
const spinner = SPINNERS[spinnerName] ?? SPINNERS['lines'];
|
const spinner = SPINNERS[spinnerName] ?? SPINNERS['lines'];
|
||||||
const duration = typeof self.duration === 'number' && self.duration > 10 ? self.duration : spinner.dur;
|
const duration = typeof self.duration === 'number' && self.duration > 10 ? self.duration : spinner.dur;
|
||||||
const svgs: SVGElement[] = [];
|
const svgs: SVGElement[] = [];
|
||||||
@ -76,6 +109,7 @@ export class Spinner implements ComponentInterface {
|
|||||||
[theme]: true,
|
[theme]: true,
|
||||||
[`spinner-${spinnerName}`]: true,
|
[`spinner-${spinnerName}`]: true,
|
||||||
'spinner-paused': self.paused || config.getBoolean('_testing'),
|
'spinner-paused': self.paused || config.getBoolean('_testing'),
|
||||||
|
[`spinner-${size}`]: size !== undefined,
|
||||||
})}
|
})}
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
style={spinner.elmDuration ? { animationDuration: duration + 'ms' } : {}}
|
style={spinner.elmDuration ? { animationDuration: duration + 'ms' } : {}}
|
||||||
|
55
core/src/components/spinner/test/size/index.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Spinner - 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>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Spinner - Size</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content id="content">
|
||||||
|
<ion-list>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start"></ion-spinner>
|
||||||
|
<p>Default</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start" size="xsmall"></ion-spinner>
|
||||||
|
<p>xsmall</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start" size="small"></ion-spinner>
|
||||||
|
<p>small</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start" size="medium"></ion-spinner>
|
||||||
|
<p>medium</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start" size="large"></ion-spinner>
|
||||||
|
<p>large</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-spinner slot="start" size="xlarge"></ion-spinner>
|
||||||
|
<p>xlarge</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
74
core/src/components/spinner/test/size/spinner.e2e.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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('spinner: size'), () => {
|
||||||
|
test('should render xsmall spinner', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-spinner size="xsmall"></ion-spinner>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = page.locator('ion-spinner');
|
||||||
|
|
||||||
|
await expect(spinner).toHaveScreenshot(screenshot(`spinner-size-xsmall`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render small spinner', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-spinner size="small"></ion-spinner>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = page.locator('ion-spinner');
|
||||||
|
|
||||||
|
await expect(spinner).toHaveScreenshot(screenshot(`spinner-size-small`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render medium spinner', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-spinner size="medium"></ion-spinner>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = page.locator('ion-spinner');
|
||||||
|
|
||||||
|
await expect(spinner).toHaveScreenshot(screenshot(`spinner-size-medium`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render large spinner', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-spinner size="large"></ion-spinner>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = page.locator('ion-spinner');
|
||||||
|
|
||||||
|
await expect(spinner).toHaveScreenshot(screenshot(`spinner-size-large`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render xlarge spinner', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-spinner size="xlarge"></ion-spinner>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = page.locator('ion-spinner');
|
||||||
|
|
||||||
|
await expect(spinner).toHaveScreenshot(screenshot(`spinner-size-xlarge`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 820 B |
After Width: | Height: | Size: 996 B |
After Width: | Height: | Size: 794 B |
After Width: | Height: | Size: 700 B |
After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 682 B |
After Width: | Height: | Size: 530 B |
After Width: | Height: | Size: 667 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 906 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 490 B |
After Width: | Height: | Size: 449 B |
After Width: | Height: | Size: 471 B |
@ -2105,14 +2105,14 @@ export declare interface IonSkeletonText extends Components.IonSkeletonText {}
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'theme']
|
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-spinner',
|
selector: 'ion-spinner',
|
||||||
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: ['color', 'duration', 'mode', 'name', 'paused', 'theme'],
|
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme'],
|
||||||
})
|
})
|
||||||
export class IonSpinner {
|
export class IonSpinner {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
|
@ -1892,14 +1892,14 @@ export declare interface IonSkeletonText extends Components.IonSkeletonText {}
|
|||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
defineCustomElementFn: defineIonSpinner,
|
defineCustomElementFn: defineIonSpinner,
|
||||||
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'theme']
|
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-spinner',
|
selector: 'ion-spinner',
|
||||||
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: ['color', 'duration', 'mode', 'name', 'paused', 'theme'],
|
inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme'],
|
||||||
standalone: true
|
standalone: true
|
||||||
})
|
})
|
||||||
export class IonSpinner {
|
export class IonSpinner {
|
||||||
|
@ -811,7 +811,8 @@ export const IonSpinner = /*@__PURE__*/ defineContainer<JSX.IonSpinner>('ion-spi
|
|||||||
'color',
|
'color',
|
||||||
'duration',
|
'duration',
|
||||||
'name',
|
'name',
|
||||||
'paused'
|
'paused',
|
||||||
|
'size'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|